Repository: chaijs/chai
Pull request #757 (2016-07-26 00:59:37)

Breaking: Rename `.deep.property` to `.nested.property`
- Previously, the `deep` flag had unexpected behavior when used with the
`property` assertion: Instead of switching from strict to deep equality,
it unlocked the ability to use dot and bracket notation when referencing
property names. This behavior now belongs to the new `nested` flag.
- assert.deepProperty renamed to assert.nestedProperty
- assert.notDeepProperty renamed to assert.notNestedProperty
- assert.deepPropertyVal renamed to assert.nestedPropertyVal
- assert.notDeepPropertyVal renamed to assert.notNestedPropertyVal

Note:
- #744 is a prerequisite for this PR. I recommend reviewing #744 first, and then only reviewing the newest commit in this PR.
- This PR is a prerequisite for completing #743 in such a way that `.include` internally leverages `.property` when performing property assertions while providing a consistent, intuitive experience.


Patch:
diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js
index f3bc6d6..f7b5dba 100644
--- a/lib/chai/core/assertions.js
+++ b/lib/chai/core/assertions.js
@@ -70,26 +70,40 @@ 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` assertion.
    *
    *     expect(foo).to.deep.equal({ bar: 'baz' });
+   *
+   * @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: { 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 +841,27 @@ 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 `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');
    *
-   *     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');
-   *
-   * 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 +870,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 +884,23 @@ 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 +912,24 @@ 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')
+      , descriptor = isNested ? 'nested property ' : '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 +938,7 @@ module.exports = function (chai, _) {
 
     if (arguments.length > 1) {
       this.assert(
-          val === value
+          hasProperty && 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 850ac9f..1e29514 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,20 +1020,20 @@ 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])
+   * ### .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 deep
-   * reference.
+   * by `value`. `property` can use dot- and bracket-notation for nested
+   * reference. Uses a strict equality check (===).
    *
-   *     assert.deepPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha');
+   *     assert.nestedPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'matcha');
    *
-   * @name deepPropertyVal
+   * @name nestedPropertyVal
    * @param {Object} object
    * @param {String} property
    * @param {Mixed} value
@@ -1041,20 +1042,21 @@ module.exports = function (chai, util) {
    * @api public
    */
 
-  assert.deepPropertyVal = function (obj, prop, val, msg) {
-    new Assertion(obj, msg).to.have.deep.property(prop, val);
+  assert.nestedPropertyVal = function (obj, prop, val, msg) {
+    new Assertion(obj, msg).to.have.nested.property(prop, val);
   };
 
   /**
-   * ### .deepPropertyNotVal(object, property, value, [message])
+   * ### .notNestedPropertyVal(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`. `property` can use dot- and bracket-notation for
+   * nested reference. Uses a strict equality check (===).
    *
-   *     assert.deepPropertyNotVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha');
+   *     assert.notNestedPropertyVal({ tea: { green: 'matcha' }}, 'tea.green', 'konacha');
+   *     assert.notNestedPropertyVal({ tea: { green: 'matcha' }}, 'coffee.green', 'matcha');
    *
-   * @name deepPropertyNotVal
+   * @name notNestedPropertyVal
    * @param {Object} object
    * @param {String} property
    * @param {Mixed} value
@@ -1063,8 +1065,8 @@ module.exports = function (chai, util) {
    * @api public
    */
 
-  assert.deepPropertyNotVal = function (obj, prop, val, msg) {
-    new Assertion(obj, msg).to.not.have.deep.property(prop, val);
+  assert.notNestedPropertyVal = function (obj, prop, val, msg) {
+    new Assertion(obj, msg).to.not.have.nested.property(prop, val);
   };
 
   /**


Test patch:
diff --git a/test/assert.js b/test/assert.js
index 594bdc1..a9c7464 100644
--- a/test/assert.js
+++ b/test/assert.js
@@ -942,28 +942,31 @@ 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.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 +977,16 @@ 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('throws / throw / Throw', function() {
diff --git a/test/expect.js b/test/expect.js
index 0595076..4f01053 100644
--- a/test/expect.js
+++ b/test/expect.js
@@ -511,50 +511,52 @@ 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);
 
     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 +564,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 +584,27 @@ 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('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');
 
     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('ownProperty(name)', function(){
diff --git a/test/should.js b/test/should.js
index 41cf8b1..e369b29 100644
--- a/test/should.js
+++ b/test/should.js
@@ -438,10 +438,25 @@ 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);
 
     err(function(){
       'asd'.should.have.property('length', 4, 'blah');
@@ -451,15 +466,24 @@ 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('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');
+
+    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('ownProperty(name)', function(){
     'test'.should.have.ownProperty('length');
     'test'.should.haveOwnProperty('length');