diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index f3bc6d6f4..dfafa8bcb 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -70,26 +70,44 @@ module.exports = function (chai, _) { /** * ### .deep * - * Sets the `deep` flag, later used by the `equal` and - * `property` assertions. + * Sets the `deep` flag, later used by the `equal`, `members`, and `property` + * assertions. + * + * const obj = {a: 1}; + * expect(obj).to.deep.equal({a: 1}); + * expect([obj]).to.have.deep.members([{a: 1}]); + * expect({foo: obj}).to.have.deep.property('foo', {a: 1}); + * + * @name deep + * @namespace BDD + * @api public + */ + + Assertion.addProperty('deep', function () { + flag(this, 'deep', true); + }); + + /** + * ### .nested + * + * Sets the `nested` flag, later used by the `property` assertion. * - * expect(foo).to.deep.equal({ bar: 'baz' }); * expect({ foo: { bar: { baz: 'quux' } } }) - * .to.have.deep.property('foo.bar.baz', 'quux'); + * .to.have.nested.property('foo.bar.baz', 'quux'); * - * `.deep.property` special characters can be escaped - * by adding two slashes before the `.` or `[]`. + * `.nested.property` special characters can be escaped by adding two slashes + * before the `.` or `[]`. * * var deepCss = { '.link': { '[target]': 42 }}; - * expect(deepCss).to.have.deep.property('\\.link.\\[target\\]', 42); + * expect(deepCss).to.have.nested.property('\\.link.\\[target\\]', 42); * - * @name deep + * @name nested * @namespace BDD * @api public */ - Assertion.addProperty('deep', function () { - flag(this, 'deep', true); + Assertion.addProperty('nested', function () { + flag(this, 'nested', true); }); /** @@ -827,26 +845,39 @@ module.exports = function (chai, _) { * ### .property(name, [value]) * * Asserts that the target has a property `name`, optionally asserting that - * the value of that property is strictly equal to `value`. - * If the `deep` flag is set, you can use dot- and bracket-notation for deep - * references into objects and arrays. + * the value of that property is strictly equal to `value`. * - * // simple referencing * var obj = { foo: 'bar' }; * expect(obj).to.have.property('foo'); * expect(obj).to.have.property('foo', 'bar'); + * expect(obj).to.not.have.property('baz'); + * expect(obj).to.not.have.property('foo', 'baz'); + * expect(obj).to.not.have.property('baz', 'bar'); + * + * If the `deep` flag is set, asserts that the value of the property is deeply + * equal to `value`. + * + * var obj = { foo: { bar: 'baz' } }; + * expect(obj).to.have.deep.property('foo', { bar: 'baz' }); + * expect(obj).to.not.have.deep.property('foo', { bar: 'quux' }); + * + * If the `nested` flag is set, you can use dot- and bracket-notation for + * nested references into objects and arrays. * - * // deep referencing * var deepObj = { * green: { tea: 'matcha' } * , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] * }; + * expect(deepObj).to.have.nested.property('green.tea', 'matcha'); + * expect(deepObj).to.have.nested.property('teas[1]', 'matcha'); + * expect(deepObj).to.have.nested.property('teas[2].tea', 'konacha'); + * + * The `deep` and `nested` flags can be combined. * - * expect(deepObj).to.have.deep.property('green.tea', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); - * expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); + * expect({ foo: { bar: { baz: 'quux' } } }) + * .to.have.deep.nested.property('foo.bar', { baz: 'quux' }); * - * You can also use an array as the starting point of a `deep.property` + * You can also use an array as the starting point of a `nested.property` * assertion, or traverse nested arrays. * * var arr = [ @@ -855,9 +886,8 @@ module.exports = function (chai, _) { * , { tea: 'matcha' } * , { tea: 'konacha' } ] * ]; - * - * expect(arr).to.have.deep.property('[0][1]', 'matcha'); - * expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); + * expect(arr).to.have.nested.property('[0][1]', 'matcha'); + * expect(arr).to.have.nested.property('[1][2].tea', 'konacha'); * * Furthermore, `property` changes the subject of the assertion * to be the value of that property from the original object. This @@ -870,23 +900,24 @@ module.exports = function (chai, _) { * .that.deep.equals({ tea: 'matcha' }); * expect(deepObj).to.have.property('teas') * .that.is.an('array') - * .with.deep.property('[2]') + * .with.nested.property('[2]') * .that.deep.equals({ tea: 'konacha' }); * * Note that dots and brackets in `name` must be backslash-escaped when - * the `deep` flag is set, while they must NOT be escaped when the `deep` + * the `nested` flag is set, while they must NOT be escaped when the `nested` * flag is not set. * - * // simple referencing + * // without nested referencing * var css = { '.link[target]': 42 }; * expect(css).to.have.property('.link[target]', 42); * - * // deep referencing + * // with nested referencing * var deepCss = { '.link': { '[target]': 42 }}; - * expect(deepCss).to.have.deep.property('\\.link.\\[target\\]', 42); + * expect(deepCss).to.have.nested.property('\\.link.\\[target\\]', 42); * * @name property * @alias deep.property + * @alias nested.property * @param {String} name * @param {Mixed} value (optional) * @param {String} message _optional_ @@ -898,24 +929,27 @@ module.exports = function (chai, _) { Assertion.addMethod('property', function (name, val, msg) { if (msg) flag(this, 'message', msg); - var isDeep = !!flag(this, 'deep') - , descriptor = isDeep ? 'deep property ' : 'property ' + var isNested = !!flag(this, 'nested') + , isDeep = !!flag(this, 'deep') + , descriptor = (isDeep ? 'deep ' : '') + + (isNested ? 'nested ' : '') + + 'property ' , negate = flag(this, 'negate') , obj = flag(this, 'object') - , pathInfo = isDeep ? _.getPathInfo(name, obj) : null - , hasProperty = isDeep + , pathInfo = isNested ? _.getPathInfo(name, obj) : null + , hasProperty = isNested ? pathInfo.exists : _.hasProperty(name, obj) - , value = isDeep + , value = isNested ? pathInfo.value : obj[name]; - if (negate && arguments.length > 1) { - if (undefined === value) { - msg = (msg != null) ? msg + ': ' : ''; - throw new Error(msg + _.inspect(obj) + ' has no ' + descriptor + _.inspect(name)); - } - } else { + // When performing a negated assertion for both name and val, merely having + // a property with the given name isn't enough to cause the assertion to + // fail. It must both have a property with the given name, and the value of + // that property must equal the given val. Therefore, skip this assertion in + // favor of the next. + if (!negate || arguments.length === 1) { this.assert( hasProperty , 'expected #{this} to have a ' + descriptor + _.inspect(name) @@ -924,7 +958,7 @@ module.exports = function (chai, _) { if (arguments.length > 1) { this.assert( - val === value + hasProperty && (isDeep ? _.eql(val, value) : val === value) , 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' , 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}' , val diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js index 850ac9f18..a9d96e6c5 100644 --- a/lib/chai/interface/assert.js +++ b/lib/chai/interface/assert.js @@ -942,14 +942,14 @@ module.exports = function (chai, util) { }; /** - * ### .deepProperty(object, property, [message]) + * ### .nestedProperty(object, property, [message]) * * Asserts that `object` has a property named by `property`, which can be a - * string using dot- and bracket-notation for deep reference. + * string using dot- and bracket-notation for nested reference. * - * assert.deepProperty({ tea: { green: 'matcha' }}, 'tea.green'); + * assert.nestedProperty({ tea: { green: 'matcha' }}, 'tea.green'); * - * @name deepProperty + * @name nestedProperty * @param {Object} object * @param {String} property * @param {String} message @@ -957,19 +957,19 @@ module.exports = function (chai, util) { * @api public */ - assert.deepProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.have.deep.property(prop); + assert.nestedProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.have.nested.property(prop); }; /** - * ### .notDeepProperty(object, property, [message]) + * ### .notNestedProperty(object, property, [message]) * * Asserts that `object` does _not_ have a property named by `property`, which - * can be a string using dot- and bracket-notation for deep reference. + * can be a string using dot- and bracket-notation for nested reference. * - * assert.notDeepProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); + * assert.notNestedProperty({ tea: { green: 'matcha' }}, 'tea.oolong'); * - * @name notDeepProperty + * @name notNestedProperty * @param {Object} object * @param {String} property * @param {String} message @@ -977,15 +977,15 @@ module.exports = function (chai, util) { * @api public */ - assert.notDeepProperty = function (obj, prop, msg) { - new Assertion(obj, msg).to.not.have.deep.property(prop); + assert.notNestedProperty = function (obj, prop, msg) { + new Assertion(obj, msg).to.not.have.nested.property(prop); }; /** * ### .propertyVal(object, property, value, [message]) * - * Asserts that `object` has a property named by `property` with value given - * by `value`. + * Asserts that `object` has a property named by `property` with a value given + * by `value`. Uses a strict equality check (===). * * assert.propertyVal({ tea: 'is good' }, 'tea', 'is good'); * @@ -1003,14 +1003,15 @@ module.exports = function (chai, util) { }; /** - * ### .propertyNotVal(object, property, value, [message]) + * ### .notPropertyVal(object, property, value, [message]) * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. + * Asserts that `object` does _not_ have a property named by `property` with + * value given by `value`. Uses a strict equality check (===). * - * assert.propertyNotVal({ tea: 'is good' }, 'tea', 'is bad'); + * assert.notPropertyVal({ tea: 'is good' }, 'tea', 'is bad'); + * assert.notPropertyVal({ tea: 'is good' }, 'coffee', 'is good'); * - * @name propertyNotVal + * @name notPropertyVal * @param {Object} object * @param {String} property * @param {Mixed} value @@ -1019,18 +1020,17 @@ module.exports = function (chai, util) { * @api public */ - assert.propertyNotVal = function (obj, prop, val, msg) { + assert.notPropertyVal = function (obj, prop, val, msg) { new Assertion(obj, msg).to.not.have.property(prop, val); }; /** * ### .deepPropertyVal(object, property, value, [message]) * - * Asserts that `object` has a property named by `property` with value given - * by `value`. `property` can use dot- and bracket-notation for deep - * reference. + * Asserts that `object` has a property named by `property` with a value given + * by `value`. Uses a deep equality check. * - * assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); + * assert.deepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'matcha' }); * * @name deepPropertyVal * @param {Object} object @@ -1046,15 +1046,16 @@ module.exports = function (chai, util) { }; /** - * ### .deepPropertyNotVal(object, property, value, [message]) + * ### .notDeepPropertyVal(object, property, value, [message]) * - * Asserts that `object` has a property named by `property`, but with a value - * different from that given by `value`. `property` can use dot- and - * bracket-notation for deep reference. + * Asserts that `object` does _not_ have a property named by `property` with + * value given by `value`. Uses a deep equality check. * - * assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); + * assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { black: 'matcha' }); + * assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'oolong' }); + * assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'coffee', { green: 'matcha' }); * - * @name deepPropertyNotVal + * @name notDeepPropertyVal * @param {Object} object * @param {String} property * @param {Mixed} value @@ -1063,10 +1064,101 @@ module.exports = function (chai, util) { * @api public */ - assert.deepPropertyNotVal = function (obj, prop, val, msg) { + assert.notDeepPropertyVal = function (obj, prop, val, msg) { new Assertion(obj, msg).to.not.have.deep.property(prop, val); }; + /** + * ### .nestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with value given + * by `value`. `property` can use dot- and bracket-notation for nested + * reference. Uses a strict equality check (===). + * + * assert.nestedPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha'); + * + * @name nestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.nestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.nested.property(prop, val); + }; + + /** + * ### .notNestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a property named by `property` with + * value given by `value`. `property` can use dot- and bracket-notation for + * nested reference. Uses a strict equality check (===). + * + * assert.notNestedPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha'); + * assert.notNestedPropertyVal({ tea: { green: 'matcha' }}, 'coffee.green', 'matcha'); + * + * @name notNestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notNestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.nested.property(prop, val); + }; + + /** + * ### .deepNestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` has a property named by `property` with a value given + * by `value`. `property` can use dot- and bracket-notation for nested + * reference. Uses a deep equality check. + * + * assert.deepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { matcha: 'yum' }); + * + * @name deepNestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.deepNestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.have.deep.nested.property(prop, val); + }; + + /** + * ### .notDeepNestedPropertyVal(object, property, value, [message]) + * + * Asserts that `object` does _not_ have a property named by `property` with + * value given by `value`. `property` can use dot- and bracket-notation for + * nested reference. Uses a deep equality check. + * + * assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { oolong: 'yum' }); + * assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { matcha: 'yuck' }); + * assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.black', { matcha: 'yum' }); + * + * @name notDeepNestedPropertyVal + * @param {Object} object + * @param {String} property + * @param {Mixed} value + * @param {String} message + * @namespace Assert + * @api public + */ + + assert.notDeepNestedPropertyVal = function (obj, prop, val, msg) { + new Assertion(obj, msg).to.not.have.deep.nested.property(prop, val); + } + /** * ### .lengthOf(object, length, [message]) * diff --git a/test/assert.js b/test/assert.js index 594bdc191..defd4b0b9 100644 --- a/test/assert.js +++ b/test/assert.js @@ -942,28 +942,32 @@ describe('assert', function () { assert.property(obj, 'foo'); assert.property(undefinedKeyObj, 'foo'); assert.propertyVal(undefinedKeyObj, 'foo', undefined); - assert.deepProperty(obj, 'foo.bar'); + assert.nestedProperty(obj, 'foo.bar'); assert.notProperty(obj, 'baz'); assert.notProperty(obj, 'foo.bar'); - assert.notDeepProperty(obj, 'foo.baz'); - assert.deepPropertyVal(obj, 'foo.bar', 'baz'); - assert.deepPropertyNotVal(obj, 'foo.bar', 'flow'); + assert.notPropertyVal(simpleObj, 'foo', 'flow'); + assert.notPropertyVal(simpleObj, 'flow', 'bar'); + assert.notPropertyVal(obj, 'foo', {bar: 'baz'}); + assert.notNestedProperty(obj, 'foo.baz'); + assert.nestedPropertyVal(obj, 'foo.bar', 'baz'); + assert.notNestedPropertyVal(obj, 'foo.bar', 'flow'); + assert.notNestedPropertyVal(obj, 'foo.flow', 'baz'); err(function () { assert.property(obj, 'baz'); }, "expected { foo: { bar: 'baz' } } to have a property 'baz'"); err(function () { - assert.deepProperty(obj, 'foo.baz'); - }, "expected { foo: { bar: 'baz' } } to have a deep property 'foo.baz'"); + assert.nestedProperty(obj, 'foo.baz'); + }, "expected { foo: { bar: 'baz' } } to have a nested property 'foo.baz'"); err(function () { assert.notProperty(obj, 'foo'); }, "expected { foo: { bar: 'baz' } } to not have property 'foo'"); err(function () { - assert.notDeepProperty(obj, 'foo.bar'); - }, "expected { foo: { bar: 'baz' } } to not have deep property 'foo.bar'"); + assert.notNestedProperty(obj, 'foo.bar'); + }, "expected { foo: { bar: 'baz' } } to not have nested property 'foo.bar'"); err(function () { assert.propertyVal(simpleObj, 'foo', 'ball'); @@ -974,16 +978,56 @@ describe('assert', function () { }, "expected { foo: 'bar' } to have a property 'foo' of undefined, but got 'bar'"); err(function () { - assert.deepPropertyVal(obj, 'foo.bar', 'ball'); - }, "expected { foo: { bar: 'baz' } } to have a deep property 'foo.bar' of 'ball', but got 'baz'"); + assert.nestedPropertyVal(obj, 'foo.bar', 'ball'); + }, "expected { foo: { bar: 'baz' } } to have a nested property 'foo.bar' of 'ball', but got 'baz'"); err(function () { - assert.propertyNotVal(simpleObj, 'foo', 'bar'); + assert.notPropertyVal(simpleObj, 'foo', 'bar'); }, "expected { foo: 'bar' } to not have a property 'foo' of 'bar'"); err(function () { - assert.deepPropertyNotVal(obj, 'foo.bar', 'baz'); - }, "expected { foo: { bar: 'baz' } } to not have a deep property 'foo.bar' of 'baz'"); + assert.notNestedPropertyVal(obj, 'foo.bar', 'baz'); + }, "expected { foo: { bar: 'baz' } } to not have a nested property 'foo.bar' of 'baz'"); + }); + + it('deepPropertyVal', function () { + var obj = {a: {b: 1}}; + assert.deepPropertyVal(obj, 'a', {b: 1}); + assert.notDeepPropertyVal(obj, 'a', {b: 7}); + assert.notDeepPropertyVal(obj, 'a', {z: 1}); + assert.notDeepPropertyVal(obj, 'z', {b: 1}); + + err(function () { + assert.deepPropertyVal(obj, 'a', {b: 7}, 'blah'); + }, "blah: expected { a: { b: 1 } } to have a deep property 'a' of { b: 7 }, but got { b: 1 }"); + + err(function () { + assert.deepPropertyVal(obj, 'z', {b: 1}, 'blah'); + }, "blah: expected { a: { b: 1 } } to have a deep property 'z'"); + + err(function () { + assert.notDeepPropertyVal(obj, 'a', {b: 1}, 'blah'); + }, "blah: expected { a: { b: 1 } } to not have a deep property 'a' of { b: 1 }"); + }); + + it('deepNestedPropertyVal', function () { + var obj = {a: {b: {c: 1}}}; + assert.deepNestedPropertyVal(obj, 'a.b', {c: 1}); + assert.notDeepNestedPropertyVal(obj, 'a.b', {c: 7}); + assert.notDeepNestedPropertyVal(obj, 'a.b', {z: 1}); + assert.notDeepNestedPropertyVal(obj, 'a.z', {c: 1}); + + err(function () { + assert.deepNestedPropertyVal(obj, 'a.b', {c: 7}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.b' of { c: 7 }, but got { c: 1 }"); + + err(function () { + assert.deepNestedPropertyVal(obj, 'a.z', {c: 1}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.z'"); + + err(function () { + assert.notDeepNestedPropertyVal(obj, 'a.b', {c: 1}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to not have a deep nested property 'a.b' of { c: 1 }"); }); it('throws / throw / Throw', function() { diff --git a/test/expect.js b/test/expect.js index 05950761a..4298879e1 100644 --- a/test/expect.js +++ b/test/expect.js @@ -511,50 +511,53 @@ describe('expect', function () { }, "expected { foo: { bar: 'baz' } } to have a property 'foo.bar'"); }); - it('deep.property(name)', function(){ + it('nested.property(name)', function(){ expect({ 'foo.bar': 'baz'}) - .to.not.have.deep.property('foo.bar'); + .to.not.have.nested.property('foo.bar'); expect({ foo: { bar: 'baz' } }) - .to.have.deep.property('foo.bar'); + .to.have.nested.property('foo.bar'); expect({ 'foo': [1, 2, 3] }) - .to.have.deep.property('foo[1]'); + .to.have.nested.property('foo[1]'); expect({ 'foo.bar[]': 'baz'}) - .to.have.deep.property('foo\\.bar\\[\\]'); + .to.have.nested.property('foo\\.bar\\[\\]'); err(function(){ expect({ 'foo.bar': 'baz' }) - .to.have.deep.property('foo.bar'); - }, "expected { 'foo.bar': 'baz' } to have a deep property 'foo.bar'"); + .to.have.nested.property('foo.bar'); + }, "expected { 'foo.bar': 'baz' } to have a nested property 'foo.bar'"); }); it('property(name, val)', function(){ expect('test').to.have.property('length', 4); expect('asd').to.have.property('constructor', String); + expect('test').to.not.have.property('length', 3); + expect('test').to.not.have.property('foo', 4); + expect({a: {b: 1}}).to.not.have.property('a', {b: 1}); var deepObj = { green: { tea: 'matcha' } , teas: [ 'chai', 'matcha', { tea: 'konacha' } ] }; - expect(deepObj).to.have.deep.property('green.tea', 'matcha'); - expect(deepObj).to.have.deep.property('teas[1]', 'matcha'); - expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); + expect(deepObj).to.have.nested.property('green.tea', 'matcha'); + expect(deepObj).to.have.nested.property('teas[1]', 'matcha'); + expect(deepObj).to.have.nested.property('teas[2].tea', 'konacha'); expect(deepObj).to.have.property('teas') .that.is.an('array') - .with.deep.property('[2]') + .with.nested.property('[2]') .that.deep.equals({tea: 'konacha'}); err(function(){ - expect(deepObj).to.have.deep.property('teas[3]'); - }, "expected { Object (green, teas) } to have a deep property 'teas[3]'"); + expect(deepObj).to.have.nested.property('teas[3]'); + }, "expected { Object (green, teas) } to have a nested property 'teas[3]'"); err(function(){ - expect(deepObj).to.have.deep.property('teas[3]', 'bar'); - }, "expected { Object (green, teas) } to have a deep property 'teas[3]'"); + expect(deepObj).to.have.nested.property('teas[3]', 'bar'); + }, "expected { Object (green, teas) } to have a nested property 'teas[3]'"); err(function(){ - expect(deepObj).to.have.deep.property('teas[3].tea', 'bar'); - }, "expected { Object (green, teas) } to have a deep property 'teas[3].tea'"); + expect(deepObj).to.have.nested.property('teas[3].tea', 'bar'); + }, "expected { Object (green, teas) } to have a nested property 'teas[3].tea'"); var arr = [ [ 'chai', 'matcha', 'konacha' ] @@ -562,17 +565,17 @@ describe('expect', function () { , { tea: 'matcha' } , { tea: 'konacha' } ] ]; - expect(arr).to.have.deep.property('[0][1]', 'matcha'); - expect(arr).to.have.deep.property('[1][2].tea', 'konacha'); + expect(arr).to.have.nested.property('[0][1]', 'matcha'); + expect(arr).to.have.nested.property('[1][2].tea', 'konacha'); err(function(){ - expect(arr).to.have.deep.property('[2][1]'); - }, "expected [ Array(2) ] to have a deep property '[2][1]'"); + expect(arr).to.have.nested.property('[2][1]'); + }, "expected [ Array(2) ] to have a nested property '[2][1]'"); err(function(){ - expect(arr).to.have.deep.property('[2][1]', 'none'); - }, "expected [ Array(2) ] to have a deep property '[2][1]'"); + expect(arr).to.have.nested.property('[2][1]', 'none'); + }, "expected [ Array(2) ] to have a nested property '[2][1]'"); err(function(){ - expect(arr).to.have.deep.property('[0][3]', 'none'); - }, "expected [ Array(2) ] to have a deep property '[0][3]'"); + expect(arr).to.have.nested.property('[0][3]', 'none'); + }, "expected [ Array(2) ] to have a nested property '[0][3]'"); err(function(){ expect('asd').to.have.property('length', 4, 'blah'); @@ -582,31 +585,68 @@ describe('expect', function () { expect('asd').to.not.have.property('length', 3, 'blah'); }, "blah: expected 'asd' to not have a property 'length' of 3"); - err(function(){ - expect('asd').to.not.have.property('foo', 3, 'blah'); - }, "blah: 'asd' has no property 'foo'"); - err(function(){ expect('asd').to.have.property('constructor', Number, 'blah'); }, "blah: expected 'asd' to have a property 'constructor' of [Function: Number], but got [Function: String]"); }); - it('deep.property(name, val)', function(){ + it('deep.property(name, val)', function () { + var obj = {a: {b: 1}}; + expect(obj).to.have.deep.property('a', {b: 1}); + expect(obj).to.not.have.deep.property('a', {b: 7}); + expect(obj).to.not.have.deep.property('a', {z: 1}); + expect(obj).to.not.have.deep.property('z', {b: 1}); + + err(function () { + expect(obj).to.have.deep.property('a', {b: 7}, 'blah'); + }, "blah: expected { a: { b: 1 } } to have a deep property 'a' of { b: 7 }, but got { b: 1 }"); + + err(function () { + expect(obj).to.have.deep.property('z', {b: 1}, 'blah'); + }, "blah: expected { a: { b: 1 } } to have a deep property 'z'"); + + err(function () { + expect(obj).to.not.have.deep.property('a', {b: 1}, 'blah'); + }, "blah: expected { a: { b: 1 } } to not have a deep property 'a' of { b: 1 }"); + }); + + it('nested.property(name, val)', function(){ + expect({ foo: { bar: 'baz' } }) + .to.have.nested.property('foo.bar', 'baz'); expect({ foo: { bar: 'baz' } }) - .to.have.deep.property('foo.bar', 'baz'); + .to.not.have.nested.property('foo.bar', 'quux'); + expect({ foo: { bar: 'baz' } }) + .to.not.have.nested.property('foo.quux', 'baz'); + expect({a: {b: {c: 1}}}).to.not.have.nested.property('a.b', {c: 1}); err(function(){ expect({ foo: { bar: 'baz' } }) - .to.have.deep.property('foo.bar', 'quux', 'blah'); - }, "blah: expected { foo: { bar: 'baz' } } to have a deep property 'foo.bar' of 'quux', but got 'baz'"); + .to.have.nested.property('foo.bar', 'quux', 'blah'); + }, "blah: expected { foo: { bar: 'baz' } } to have a nested property 'foo.bar' of 'quux', but got 'baz'"); err(function(){ expect({ foo: { bar: 'baz' } }) - .to.not.have.deep.property('foo.bar', 'baz', 'blah'); - }, "blah: expected { foo: { bar: 'baz' } } to not have a deep property 'foo.bar' of 'baz'"); - err(function(){ - expect({ foo: 5 }) - .to.not.have.deep.property('foo.bar', 'baz', 'blah'); - }, "blah: { foo: 5 } has no deep property 'foo.bar'"); + .to.not.have.nested.property('foo.bar', 'baz', 'blah'); + }, "blah: expected { foo: { bar: 'baz' } } to not have a nested property 'foo.bar' of 'baz'"); + }); + + it('deep.nested.property(name, val)', function () { + var obj = {a: {b: {c: 1}}}; + expect(obj).to.have.deep.nested.property('a.b', {c: 1}); + expect(obj).to.not.have.deep.nested.property('a.b', {c: 7}); + expect(obj).to.not.have.deep.nested.property('a.b', {z: 1}); + expect(obj).to.not.have.deep.nested.property('a.z', {c: 1}); + + err(function () { + expect(obj).to.have.deep.nested.property('a.b', {c: 7}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.b' of { c: 7 }, but got { c: 1 }"); + + err(function () { + expect(obj).to.have.deep.nested.property('a.z', {c: 1}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.z'"); + + err(function () { + expect(obj).to.not.have.deep.nested.property('a.b', {c: 1}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to not have a deep nested property 'a.b' of { c: 1 }"); }); it('ownProperty(name)', function(){ diff --git a/test/should.js b/test/should.js index 41cf8b15a..9f7dc4985 100644 --- a/test/should.js +++ b/test/should.js @@ -438,10 +438,26 @@ describe('should', function() { }, "expected 'asd' to have a property 'foo'"); }); + it('nested.property(name)', function(){ + ({ 'foo.bar': 'baz'}).should.not.have.nested.property('foo.bar'); + ({ foo: { bar: 'baz' } }).should.have.nested.property('foo.bar'); + + ({ 'foo': [1, 2, 3] }).should.have.nested.property('foo[1]'); + + ({ 'foo.bar[]': 'baz'}).should.have.nested.property('foo\\.bar\\[\\]'); + + err(function(){ + ({ 'foo.bar': 'baz' }).should.have.nested.property('foo.bar'); + }, "expected { 'foo.bar': 'baz' } to have a nested property 'foo.bar'"); + }); + it('property(name, val)', function(){ 'test'.should.have.property('length', 4); 'asd'.should.have.property('constructor', String); ({ 1: 1 }).should.have.property(1, 1); + 'test'.should.not.have.property('length', 3); + 'test'.should.not.have.property('foo', 4); + ({a: {b: 1}}).should.not.have.property('a', {b: 1}); err(function(){ 'asd'.should.have.property('length', 4, 'blah'); @@ -451,15 +467,65 @@ describe('should', function() { 'asd'.should.not.have.property('length', 3, 'blah'); }, "blah: expected 'asd' to not have a property 'length' of 3"); - err(function(){ - 'asd'.should.not.have.property('foo', 3, 'blah'); - }, "blah: 'asd' has no property 'foo'"); - err(function(){ 'asd'.should.have.property('constructor', Number, 'blah'); }, "blah: expected 'asd' to have a property 'constructor' of [Function: Number], but got [Function: String]"); }); + it('deep.property(name, val)', function () { + var obj = {a: {b: 1}}; + obj.should.have.deep.property('a', {b: 1}); + obj.should.not.have.deep.property('a', {b: 7}); + obj.should.not.have.deep.property('a', {z: 1}); + obj.should.not.have.deep.property('z', {b: 1}); + + err(function () { + obj.should.have.deep.property('a', {b: 7}, 'blah'); + }, "blah: expected { a: { b: 1 } } to have a deep property 'a' of { b: 7 }, but got { b: 1 }"); + + err(function () { + obj.should.have.deep.property('z', {b: 1}, 'blah'); + }, "blah: expected { a: { b: 1 } } to have a deep property 'z'"); + + err(function () { + obj.should.not.have.deep.property('a', {b: 1}, 'blah'); + }, "blah: expected { a: { b: 1 } } to not have a deep property 'a' of { b: 1 }"); + }); + + it('nested.property(name, val)', function(){ + ({ foo: { bar: 'baz' } }).should.have.nested.property('foo.bar', 'baz'); + ({ foo: { bar: 'baz' } }).should.not.have.nested.property('foo.bar', 'quux'); + ({ foo: { bar: 'baz' } }).should.not.have.nested.property('foo.quux', 'baz'); + ({a: {b: {c: 1}}}).should.not.have.nested.property('a.b', {c: 1}); + + err(function(){ + ({ foo: { bar: 'baz' } }).should.have.nested.property('foo.bar', 'quux', 'blah'); + }, "blah: expected { foo: { bar: 'baz' } } to have a nested property 'foo.bar' of 'quux', but got 'baz'"); + err(function(){ + ({ foo: { bar: 'baz' } }).should.not.have.nested.property('foo.bar', 'baz', 'blah'); + }, "blah: expected { foo: { bar: 'baz' } } to not have a nested property 'foo.bar' of 'baz'"); + }); + + it('deep.nested.property(name, val)', function () { + var obj = {a: {b: {c: 1}}}; + obj.should.have.deep.nested.property('a.b', {c: 1}); + obj.should.not.have.deep.nested.property('a.b', {c: 7}); + obj.should.not.have.deep.nested.property('a.b', {z: 1}); + obj.should.not.have.deep.nested.property('a.z', {c: 1}); + + err(function () { + obj.should.have.deep.nested.property('a.b', {c: 7}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.b' of { c: 7 }, but got { c: 1 }"); + + err(function () { + obj.should.have.deep.nested.property('a.z', {c: 1}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.z'"); + + err(function () { + obj.should.not.have.deep.nested.property('a.b', {c: 1}, 'blah'); + }, "blah: expected { a: { b: { c: 1 } } } to not have a deep nested property 'a.b' of { c: 1 }"); + }); + it('ownProperty(name)', function(){ 'test'.should.have.ownProperty('length'); 'test'.should.haveOwnProperty('length');