Repository: chaijs/chai
Pull request #964 (2017-05-04 15:11:06)

assert: add nestedInclude, deepNestedInclude, ownInclude and deepOwnInclude
I added these methods and their negated versions to the assert interface and adapted the expect-style tests to assert-style as discussed in this issue: https://github.com/chaijs/chai/issues/905.

zetamorph: Thanks! I added the custom messages. I will also do the same for the other tests.

Patch:
diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js
index d4a39ca..86f4833 100644
--- a/lib/chai/interface/assert.js
+++ b/lib/chai/interface/assert.js
@@ -1012,6 +1012,197 @@ module.exports = function (chai, util) {
     new Assertion(exp, msg, assert.notDeepInclude, true).not.deep.include(inc);
   };
 
+  /**
+   * ### .nestedInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' includes 'needle'. 
+   * Can be used to assert the inclusion of a subset of properties in an 
+   * object.
+   * Enables the use of dot- and bracket-notation for referencing nested 
+   * properties.
+   * '[]' and '.' in property names can be escaped using double backslashes.
+   * 
+   *     assert.nestedInclude({'.a': {'b': 'x'}}, {'\\.a.[b]': 'x'});
+   *     assert.nestedInclude({'a': {'[b]': 'x'}}, {'a.\\[b\\]': 'x'});
+   * 
+   * @name nestedInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public 
+   */ 
+
+  assert.nestedInclude = function (exp, inc, msg) {
+    new Assertion(exp, msg, assert.nestedInclude, true).nested.include(inc);
+  };
+
+  /**
+   * ### .notNestedInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' does not include 'needle'. 
+   * Can be used to assert the absence of a subset of properties in an 
+   * object.
+   * Enables the use of dot- and bracket-notation for referencing nested 
+   * properties. 
+   * '[]' and '.' in property names can be escaped using double backslashes.
+   * 
+   *     assert.notNestedInclude({'.a': {'b': 'x'}}, {'\\.a.b': 'y'});
+   *     assert.notNestedInclude({'a': {'[b]': 'x'}}, {'a.\\[b\\]': 'y'});
+   * 
+   * @name notNestedInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public 
+   */ 
+
+  assert.notNestedInclude = function (exp, inc, msg) {
+    new Assertion(exp, msg, assert.notNestedInclude, true)
+      .not.nested.include(inc);
+  };
+
+  /**
+   * ### .deepNestedInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' includes 'needle'.
+   * Can be used to assert the inclusion of a subset of properties in an 
+   * object while checking for deep equality.
+   * Enables the use of dot- and bracket-notation for referencing nested 
+   * properties.
+   * '[]' and '.' in property names can be escaped using double backslashes.
+   * 
+   *     assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {x: 1}});
+   *     assert.deepNestedInclude({'.a': {'[b]': {x: 1}}}, {'\\.a.\\[b\\]': {x: 1}});
+   *    
+   * @name deepNestedInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public 
+   */
+
+  assert.deepNestedInclude = function(exp, inc, msg) {
+    new Assertion(exp, msg, assert.deepNestedInclude, true)
+      .deep.nested.include(inc);
+  };
+
+  /**
+   * ### .notDeepNestedInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' does not include 'needle'.
+   * Can be used to assert the absence of a subset of properties in an 
+   * object while checking for deep equality.
+   * Enables the use of dot- and bracket-notation for referencing nested 
+   * properties.
+   * '[]' and '.' in property names can be escaped using double backslashes.
+   * 
+   *     assert.notDeepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {y: 1}})
+   *     assert.notDeepNestedInclude({'.a': {'[b]': {x: 1}}}, {'\\.a.\\[b\\]': {y: 2}});
+   *    
+   * @name notDeepNestedInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public 
+   */
+
+  assert.notDeepNestedInclude = function(exp, inc, msg) {
+    new Assertion(exp, msg, assert.notDeepNestedInclude, true)
+      .not.deep.nested.include(inc);
+  };
+
+  /**
+   * ### .ownInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' includes 'needle'.
+   * Can be used to assert the inclusion of a subset of properties in an 
+   * object while ignoring inherited properties.
+   * 
+   *     assert.ownInclude({ a: 1 }, { a: 1 });
+   * 
+   * @name ownInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.ownInclude = function(exp, inc, msg) {
+    new Assertion(exp, msg, assert.ownInclude, true).own.include(inc);
+  };
+
+  /**
+   * ### .notOwnInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' includes 'needle'.
+   * Can be used to assert the absence of a subset of properties in an 
+   * object while ignoring inherited properties.
+   * 
+   *     Object.prototype.b = 2;
+   * 
+   *     assert.notOwnInclude({ a: 1 }, { b: 2 });
+   * 
+   * @name notOwnInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.notOwnInclude = function(exp, inc, msg) {
+    new Assertion(exp, msg, assert.notOwnInclude, true).not.own.include(inc);
+  };
+
+  /**
+   * ### .deepOwnInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' includes 'needle'.
+   * Can be used to assert the inclusion of a subset of properties in an 
+   * object while ignoring inherited properties and checking for deep equality.
+   * 
+   *      assert.deepOwnInclude({a: {b: 2}}, {a: {b: 2}});
+   *      
+   * @name deepOwnInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.deepOwnInclude = function(exp, inc, msg) {
+    new Assertion(exp, msg, assert.deepOwnInclude, true)
+      .deep.own.include(inc);
+  };
+
+   /**
+   * ### .notDeepOwnInclude(haystack, needle, [message])
+   * 
+   * Asserts that 'haystack' includes 'needle'.
+   * Can be used to assert the absence of a subset of properties in an 
+   * object while ignoring inherited properties and checking for deep equality.
+   * 
+   *      assert.notDeepOwnInclude({a: {b: 2}}, {a: {c: 3}});
+   *      
+   * @name notDeepOwnInclude
+   * @param {Object} haystack
+   * @param {Object} needle
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.notDeepOwnInclude = function(exp, inc, msg) {
+    new Assertion(exp, msg, assert.notDeepOwnInclude, true)
+      .not.deep.own.include(inc);
+  };
+
   /**
    * ### .match(value, regexp, [message])
    *


Test patch:
diff --git a/test/assert.js b/test/assert.js
index d3a0f02..9cfff71 100644
--- a/test/assert.js
+++ b/test/assert.js
@@ -744,8 +744,106 @@ describe('assert', function () {
     }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to have deep property 'bar' of { b: 9 }, but got { b: 2 }");
 
     err(function () {
-      assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}});
-    }, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
+      assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}}, 'blah');
+    }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
+  });
+
+  it('nestedInclude and notNestedInclude', function() {
+    assert.nestedInclude({a: {b: ['x', 'y']}}, {'a.b[1]': 'y'});
+    assert.notNestedInclude({a: {b: ['x', 'y']}}, {'a.b[1]': 'x'});
+    assert.notNestedInclude({a: {b: ['x', 'y']}}, {'a.c': 'y'});
+
+    assert.notNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {x: 1}});
+
+    assert.nestedInclude({'.a': {'[b]': 'x'}}, {'\\.a.\\[b\\]': 'x'});
+    assert.notNestedInclude({'.a': {'[b]': 'x'}}, {'\\.a.\\[b\\]': 'y'});
+
+    err(function () {
+      assert.nestedInclude({a: {b: ['x', 'y']}}, {'a.b[1]': 'x'}, 'blah');
+    }, "blah: expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.b[1]' of 'x', but got 'y'");
+
+    err(function () {
+      assert.nestedInclude({a: {b: ['x', 'y']}}, {'a.b[1]': 'x'}, 'blah');
+    }, "blah: expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.b[1]' of 'x', but got 'y'");
+
+    err(function () {
+      assert.nestedInclude({a: {b: ['x', 'y']}}, {'a.c': 'y'});
+    }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.c'");
+
+    err(function () {
+      assert.notNestedInclude({a: {b: ['x', 'y']}}, {'a.b[1]': 'y'}, 'blah');
+    }, "blah: expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
+  });
+
+  it('deepNestedInclude and notDeepNestedInclude', function() {
+    assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {x: 1}});
+    assert.notDeepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {y: 2}});
+    assert.notDeepNestedInclude({a: {b: [{x: 1}]}}, {'a.c': {x: 1}});
+
+    assert.deepNestedInclude({'.a': {'[b]': {x: 1}}}, {'\\.a.\\[b\\]': {x: 1}});
+    assert.notDeepNestedInclude({'.a': {'[b]': {x: 1}}}, {'\\.a.\\[b\\]': {y: 2}});
+
+    err(function () {
+      assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {y: 2}}, 'blah');
+    }, "blah: expected { a: { b: [ [Object] ] } } to have deep nested property 'a.b[0]' of { y: 2 }, but got { x: 1 }");
+
+    err(function () {
+      assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {y: 2}}, 'blah');
+    }, "blah: expected { a: { b: [ [Object] ] } } to have deep nested property 'a.b[0]' of { y: 2 }, but got { x: 1 }");
+
+    err(function () {
+      assert.deepNestedInclude({a: {b: [{x: 1}]}}, {'a.c': {x: 1}});
+    }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.c'");
+
+    err(function () {
+      assert.notDeepNestedInclude({a: {b: [{x: 1}]}}, {'a.b[0]': {x: 1}}, 'blah');
+    }, "blah: expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
+  });
+
+  it('ownInclude and notOwnInclude', function() {
+    assert.ownInclude({a: 1}, {a: 1});
+    assert.notOwnInclude({a: 1}, {a: 3});
+    assert.notOwnInclude({a: 1}, {'toString': Object.prototype.toString});
+
+    assert.notOwnInclude({a: {b: 2}}, {a: {b: 2}});
+
+    err(function () {
+      assert.ownInclude({a: 1}, {a: 3}, 'blah');
+    }, "blah: expected { a: 1 } to have own property 'a' of 3, but got 1");
+
+    err(function () {
+      assert.ownInclude({a: 1}, {a: 3}, 'blah');
+    }, "blah: expected { a: 1 } to have own property 'a' of 3, but got 1");
+
+    err(function () {
+      assert.ownInclude({a: 1}, {'toString': Object.prototype.toString});
+    }, "expected { a: 1 } to have own property 'toString'");
+
+    err(function () {
+      assert.notOwnInclude({a: 1}, {a: 1}, 'blah');
+    }, "blah: expected { a: 1 } to not have own property 'a' of 1");
+  });
+
+  it('deepOwnInclude and notDeepOwnInclude', function() {
+    assert.deepOwnInclude({a: {b: 2}}, {a: {b: 2}});
+    assert.notDeepOwnInclude({a: {b: 2}}, {a: {c: 3}});
+    assert.notDeepOwnInclude({a: {b: 2}}, {'toString': Object.prototype.toString});
+
+    err(function () {
+      assert.deepOwnInclude({a: {b: 2}}, {a: {c: 3}}, 'blah');
+    }, "blah: expected { a: { b: 2 } } to have deep own property 'a' of { c: 3 }, but got { b: 2 }");
+
+    err(function () {
+      assert.deepOwnInclude({a: {b: 2}}, {a: {c: 3}}, 'blah');
+    }, "blah: expected { a: { b: 2 } } to have deep own property 'a' of { c: 3 }, but got { b: 2 }");
+
+    err(function () {
+      assert.deepOwnInclude({a: {b: 2}}, {'toString': Object.prototype.toString});
+    }, "expected { a: { b: 2 } } to have deep own property 'toString'");
+
+    err(function () {
+      assert.notDeepOwnInclude({a: {b: 2}}, {a: {b: 2}}, 'blah');
+    }, "blah: expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
   });
 
   it('keys(array|Object|arguments)', function(){
diff --git a/test/expect.js b/test/expect.js
index 8ef37d3..2194110 100644
--- a/test/expect.js
+++ b/test/expect.js
@@ -1797,16 +1797,16 @@ describe('expect', function () {
     }, "blah: expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");
 
     err(function () {
-      expect([obj1, obj2]).to.not.deep.include({a: 1});
-    }, "expected [ { a: 1 }, { b: 2 } ] to not deep include { a: 1 }");
+      expect([obj1, obj2], 'blah').to.not.deep.include({a: 1});
+    }, "blah: expected [ { a: 1 }, { b: 2 } ] to not deep include { a: 1 }");
 
     err(function () {
       expect({foo: obj1, bar: obj2}).to.deep.include({foo: {a: 1}, bar: {b: 9}});
     }, "expected { foo: { a: 1 }, bar: { b: 2 } } to have deep property 'bar' of { b: 9 }, but got { b: 2 }");
 
     err(function () {
-      expect({foo: obj1, bar: obj2}).to.not.deep.include({foo: {a: 1}, bar: {b: 2}});
-    }, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
+      expect({foo: obj1, bar: obj2}).to.not.deep.include({foo: {a: 1}, bar: {b: 2}}, 'blah');
+    }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
   });
 
   it('nested.include()', function () {
@@ -1832,8 +1832,12 @@ describe('expect', function () {
     }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.c'");
 
     err(function () {
-      expect({a: {b: ['x', 'y']}}).to.not.nested.include({'a.b[1]': 'y'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
+      expect({a: {b: ['x', 'y']}}).to.not.nested.include({'a.b[1]': 'y'}, 'blah');
+    }, "blah: expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
+
+    err(function () {
+      expect({a: {b: ['x', 'y']}}, 'blah').to.not.nested.include({'a.b[1]': 'y'});
+    }, "blah: expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
   });
 
   it('deep.nested.include()', function () {
@@ -1859,8 +1863,12 @@ describe('expect', function () {
     }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.c'");
 
     err(function () {
-      expect({a: {b: [{x: 1}]}}).to.not.deep.nested.include({'a.b[0]': {x: 1}});
-    }, "expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
+      expect({a: {b: [{x: 1}]}}).to.not.deep.nested.include({'a.b[0]': {x: 1}}, 'blah');
+    }, "blah: expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
+
+    err(function () {
+      expect({a: {b: [{x: 1}]}}, 'blah').to.not.deep.nested.include({'a.b[0]': {x: 1}});
+    }, "blah: expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
   });
 
   it('own.include()', function () {
@@ -1883,8 +1891,12 @@ describe('expect', function () {
     }, "expected { a: 1 } to have own property 'toString'");
 
     err(function () {
-      expect({a: 1}).to.not.own.include({a: 1});
-    }, "expected { a: 1 } to not have own property 'a' of 1");
+      expect({a: 1}).to.not.own.include({a: 1}, 'blah');
+    }, "blah: expected { a: 1 } to not have own property 'a' of 1");
+
+    err(function () {
+      expect({a: 1}, 'blah').to.not.own.include({a: 1});
+    }, "blah: expected { a: 1 } to not have own property 'a' of 1");
   });
 
   it('deep.own.include()', function () {
@@ -1906,8 +1918,12 @@ describe('expect', function () {
     }, "expected { a: { b: 2 } } to have deep own property 'toString'");
 
     err(function () {
-      expect({a: {b: 2}}).to.not.deep.own.include({a: {b: 2}});
-    }, "expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
+      expect({a: {b: 2}}).to.not.deep.own.include({a: {b: 2}}, 'blah');
+    }, "blah: expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
+
+    err(function () {
+      expect({a: {b: 2}}, 'blah').to.not.deep.own.include({a: {b: 2}});
+    }, "blah: expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
   });
 
   it('keys(array|Object|arguments)', function(){
diff --git a/test/should.js b/test/should.js
index fc09edd..e10a39b 100644
--- a/test/should.js
+++ b/test/should.js
@@ -1426,8 +1426,8 @@ describe('should', function() {
     }, "expected { foo: { a: 1 }, bar: { b: 2 } } to have deep property 'bar' of { b: 9 }, but got { b: 2 }");
 
     err(function () {
-      ({foo: obj1, bar: obj2}).should.not.deep.include({foo: {a: 1}, bar: {b: 2}});
-    }, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
+      ({foo: obj1, bar: obj2}).should.not.deep.include({foo: {a: 1}, bar: {b: 2}}, 'blah');
+    }, "blah: expected { foo: { a: 1 }, bar: { b: 2 } } to not have deep property 'foo' of { a: 1 }");
   });
 
   it('nested.include()', function () {
@@ -1449,8 +1449,8 @@ describe('should', function() {
     }, "expected { a: { b: [ 'x', 'y' ] } } to have nested property 'a.c'");
 
     err(function () {
-      ({a: {b: ['x', 'y']}}).should.not.nested.include({'a.b[1]': 'y'});
-    }, "expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
+      ({a: {b: ['x', 'y']}}).should.not.nested.include({'a.b[1]': 'y'}, 'blah');
+    }, "blah: expected { a: { b: [ 'x', 'y' ] } } to not have nested property 'a.b[1]' of 'y'");
   });
 
   it('deep.nested.include()', function () {
@@ -1472,8 +1472,8 @@ describe('should', function() {
     }, "expected { a: { b: [ [Object] ] } } to have deep nested property 'a.c'");
 
     err(function () {
-      ({a: {b: [{x: 1}]}}).should.not.deep.nested.include({'a.b[0]': {x: 1}});
-    }, "expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
+      ({a: {b: [{x: 1}]}}).should.not.deep.nested.include({'a.b[0]': {x: 1}}, 'blah');
+    }, "blah: expected { a: { b: [ [Object] ] } } to not have deep nested property 'a.b[0]' of { x: 1 }");
   });
 
   it('own.include()', function () {
@@ -1492,8 +1492,8 @@ describe('should', function() {
     }, "expected { a: 1 } to have own property 'toString'");
 
     err(function () {
-      ({a: 1}).should.not.own.include({a: 1});
-    }, "expected { a: 1 } to not have own property 'a' of 1");
+      ({a: 1}).should.not.own.include({a: 1}, 'blah');
+    }, "blah: expected { a: 1 } to not have own property 'a' of 1");
   });
 
   it('deep.own.include()', function () {
@@ -1511,8 +1511,8 @@ describe('should', function() {
     }, "expected { a: { b: 2 } } to have deep own property 'toString'");
 
     err(function () {
-      ({a: {b: 2}}).should.not.deep.own.include({a: {b: 2}});
-    }, "expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
+      ({a: {b: 2}}).should.not.deep.own.include({a: {b: 2}}, 'blah');
+    }, "blah: expected { a: { b: 2 } } to not have deep own property 'a' of { b: 2 }");
   });
 
   it('keys(array|Object|arguments)', function(){