Repository: chaijs/chai (js)

Issue:
Inspect doesn't work properly for Maps and Sets - It displays an empty object
As I was implementing the `.deep` flag support on the `keys` assertion I noticed that `utils.inspect` isn't working properly when it comes to Maps and Sets. Instead of returning the values inside the Map or Set it returns the following string: `{}`.

You can reproduce this behavior by doing, for example:

``` js
chai.use(function (_chai, utils) {
  var inspectSet = _utilsinspect(new Set([{iAmAn: 'item'}, {thisIs: 'anotherItem'}]));
  var inspectMap = utils.inspect(new Map([[{iAmA: 'key'}, 'aValue']]))
  console.log(inspectSet); // --> {}
  console.log(inspectMap); // --> {}
});
```

What do you guys think should be the desired output when inspecting maps or sets?

IMO the ideal output would be:

``` js
chai.use(function (_chai, utils) {
  var inspectSet = _utilsinspect(new Set([{iAmAn: 'item' }, {thisIs: 'anotherItem'}]));
  var inspectMap = utils.inspect(new Map([[{iAmA: 'key' }, 'aValue']]))
  console.log(inspectSet); // Desired --> [ {iAmAn: 'item' }, { thisIs: 'anotherItem' } ]
   console.log(inspectMap); // Desired --> [ [ { iAmA: 'key' }, 'aValue' ] ]
});
```

---

I think this wouldn't be a difficult fix. Maybe adding a check alongside [these other checks](https://github.com/chaijs/chai/blob/4.x.x/lib/chai/utils/inspect.js#L177) and then calling a function to iterate through every entry and format the Map/Set members should be enough. This will be very similiar to what we already do in [`formatArray`](https://github.com/chaijs/chai/blob/4.x.x/lib/chai/utils/inspect.js#L225).

I will make sure to submit a PR for this before implementing the `deep` flag for Maps/Sets because this will be very useful to create good test cases.

Please let me know if you've got any idea or opinion about this :wink: 


Pull request description:
Deep flag support for keys assertion
This adds support for the `deep` flag on the `keys` assertion (for Maps/Sets) as I've talked to @keithamus at #633.

This PR should include:
- [x] Deep flag support for the keys assertion (Maps/Sets)
- [x] Updated doc comments with examples and a general explanation about the `deep` flag on this assertion
- [x] Tests on the `should` interface
- [x] Tests on the `expect` interface
- [x] Bindings for these on the `assert` interface
- [x] Tests on the `assert` interface

Currently the only thing missing are the bindings for these assertions on the assert interface, I didn't do it yet because I'm not sure if adding lots of bindings for this to the `assert` interface would be cool. If we added bindings for every possible combination of the `keys` assertion flags we would have:

1. hasAnyKeys
2. hasAllKeys
3. containsAllKeys
4. doesNotHaveAnyKeys
5. doesNotHaveAllKeys
6. hasAnyDeepKeys
7. hasAllDeepKeys
8. containsAllDeepKeys
9. doesNotHaveAnyDeepKeys
10. doesNotHaveAllDeepKeys

IMO we should have all of them on the `assert` interface because I think it should have the same features as the other interfaces do, but I just wanted to make sure you guys agree with me before adding all these bindings.

----------------------------------------

### Important Detail: 

Currently we need to fix #662 in order to enable all tests, so I think we may keep this open but on hold until we've got `inspect` to work properly for `maps` and `sets` so we can fully test this.
What's your opinion on this?

------------------------------------------

Thanks in advance, please feel free to point any possible mistakes/improvements :smile: 

Patch:
diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js
index 2980c79..98ea0e0 100644
--- a/lib/chai/core/assertions.js
+++ b/lib/chai/core/assertions.js
@@ -71,7 +71,7 @@ module.exports = function (chai, _) {
   /**
    * ### .deep
    *
-   * Sets the `deep` flag, later used by the `equal`, `include`, `members`, and
+   * Sets the `deep` flag, later used by the `equal`, `include`, `members`, `keys` and
    * `property` assertions.
    *
    *     const obj = {a: 1};
@@ -1328,6 +1328,10 @@ module.exports = function (chai, _) {
    * can have objects as keys, so, in this case, you can pass multiple objects as arguments if
    * you want to.
    *
+   * The default behavior when it comes to Maps and Sets is to use strict equality
+   * (===) to compare values. You can also use the `deep` flag if you want a deep comparison
+   * instead of a strict comparison.
+   *
    *     expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz');
    *     expect({ foo: 1, bar: 2 }).to.have.any.keys('foo');
    *     expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz');
@@ -1338,9 +1342,13 @@ module.exports = function (chai, _) {
    *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);
    *     expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys({'bar': 6});
    *     expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.key({objKey: 'value'});
+   *     expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.deep.key({objKey: 'value'});
    *     expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.any.keys([{objKey: 'value'}, {anotherKey: 'anotherValue'}]);
+   *     expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.any.deep.keys([{objKey: 'value'}, {anotherKey: 'anotherValue'}]);
    *     expect(new Map([['firstKey', 'firstValue'], [1, 2]])).to.contain.all.keys('firstKey', 1);
+   *     expect(new Map([['firstKey', 'firstValue'], [1, 2]])).to.contain.all.deep.keys('firstKey', 1);
    *     expect(new Set([['foo', 'bar'], ['example', 1]])).to.have.any.keys('foo');
+   *     expect(new Set([['foo', 'bar'], ['example', 1]])).to.have.any.deep.keys('foo');
    *
    * @name keys
    * @alias key
@@ -1351,11 +1359,14 @@ module.exports = function (chai, _) {
 
   function assertKeys (keys) {
     var obj = flag(this, 'object')
+      , isDeep = flag(this, 'deep')
       , str
+      , deepStr = ''
       , ok = true
       , mixedArgsMsg = 'when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments';
 
     if (_.type(obj) === 'map' || _.type(obj) === 'set') {
+      deepStr = isDeep ? 'deeply ' : '';
       actual = [];
 
       // Map and Set '.keys' aren't supported in IE 11. Therefore, use .forEach.
@@ -1402,7 +1413,11 @@ module.exports = function (chai, _) {
     if (any) {
       ok = expected.some(function(expectedKey) {
         return actual.some(function(actualKey) {
-          return expectedKey === actualKey;
+          if (isDeep) {
+            return _.eql(expectedKey, actualKey);
+          } else {
+            return expectedKey === actualKey;
+          }
         });
       });
     }
@@ -1411,7 +1426,11 @@ module.exports = function (chai, _) {
     if (all) {
       ok = expected.every(function(expectedKey) {
         return actual.some(function(actualKey) {
-          return expectedKey === actualKey;
+          if (isDeep) {
+            return _.eql(expectedKey, actualKey);
+          } else {
+            return expectedKey === actualKey;
+          }
         });
       });
 
@@ -1445,8 +1464,8 @@ module.exports = function (chai, _) {
     // Assertion
     this.assert(
         ok
-      , 'expected #{this} to ' + str
-      , 'expected #{this} to not ' + str
+      , 'expected #{this} to ' + deepStr + str
+      , 'expected #{this} to not ' + deepStr + str
       , expected.slice(0).sort(_.compareByInspect)
       , actual.sort(_.compareByInspect)
       , true
diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js
index 0e8d97a..47395c5 100644
--- a/lib/chai/interface/assert.js
+++ b/lib/chai/interface/assert.js
@@ -1420,10 +1420,6 @@ module.exports = function (chai, util) {
    */
 
   assert.hasAnyKeys = function (obj, keys, msg) {
-    if (keys === undefined) {
-      new Assertion(obj, msg).to.not.have.all.keys();
-    }
-
     new Assertion(obj, msg).to.have.any.keys(keys);
   }
 
@@ -1448,10 +1444,6 @@ module.exports = function (chai, util) {
    */
 
   assert.hasAllKeys = function (obj, keys, msg) {
-    if (keys === undefined) {
-      new Assertion(obj, msg).to.not.have.all.keys();
-    }
-
     new Assertion(obj, msg).to.have.all.keys(keys);
   }
 
@@ -1480,10 +1472,6 @@ module.exports = function (chai, util) {
    */
 
   assert.containsAllKeys = function (obj, keys, msg) {
-    if (keys === undefined) {
-      new Assertion(obj, msg).to.not.have.all.keys();
-    }
-
     new Assertion(obj, msg).to.contain.all.keys(keys);
   }
 
@@ -1508,10 +1496,6 @@ module.exports = function (chai, util) {
    */
 
   assert.doesNotHaveAnyKeys = function (obj, keys, msg) {
-    if (keys === undefined) {
-      new Assertion(obj, msg).to.not.have.all.keys();
-    }
-
     new Assertion(obj, msg).to.not.have.any.keys(keys);
   }
 
@@ -1536,14 +1520,142 @@ module.exports = function (chai, util) {
    */
 
   assert.doesNotHaveAllKeys = function (obj, keys, msg) {
-    if (keys === undefined) {
-      new Assertion(obj, msg).to.not.have.all.keys();
-    }
-
     new Assertion(obj, msg).to.not.have.all.keys(keys);
   }
 
   /**
+   * ### .hasAnyDeepKeys(object, [keys], [message])
+   *
+   * Asserts that `object` has at least one of the `keys` provided.
+   * Since Sets and Maps can have objects as keys you can use this assertion to perform
+   * a deep comparison.
+   * You can also provide a single object instead of a `keys` array and its keys
+   * will be used as the expected set of keys.
+   *
+   *     assert.hasAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {one: 'one'});
+   *     assert.hasAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), [{one: 'one'}, {two: 'two'}]);
+   *     assert.hasAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{one: 'one'}, {two: 'two'}]);
+   *     assert.hasAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {one: 'one'});
+   *     assert.hasAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {three: 'three'}]);
+   *     assert.hasAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {two: 'two'}]);
+   *
+   * @name doesNotHaveAllKeys
+   * @param {Mixed} object
+   * @param {Array|Object} keys
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.hasAnyDeepKeys = function (obj, keys, msg) {
+    new Assertion(obj, msg).to.have.any.deep.keys(keys);
+  }
+
+ /**
+   * ### .hasAllDeepKeys(object, [keys], [message])
+   *
+   * Asserts that `object` has all and only all of the `keys` provided.
+   * Since Sets and Maps can have objects as keys you can use this assertion to perform
+   * a deep comparison.
+   * You can also provide a single object instead of a `keys` array and its keys
+   * will be used as the expected set of keys.
+   *
+   *     assert.hasAllDeepKeys(new Map([[{one: 'one'}, 'valueOne']]), {one: 'one'});
+   *     assert.hasAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{one: 'one'}, {two: 'two'}]);
+   *     assert.hasAllDeepKeys(new Set([{one: 'one'}]), {one: 'one'});
+   *     assert.hasAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {two: 'two'}]);
+   *
+   * @name hasAllDeepKeys
+   * @param {Mixed} object
+   * @param {Array|Object} keys
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.hasAllDeepKeys = function (obj, keys, msg) {
+    new Assertion(obj, msg).to.have.all.deep.keys(keys);
+  }
+
+ /**
+   * ### .containsAllDeepKeys(object, [keys], [message])
+   *
+   * Asserts that `object` contains all of the `keys` provided.
+   * Since Sets and Maps can have objects as keys you can use this assertion to perform
+   * a deep comparison.
+   * You can also provide a single object instead of a `keys` array and its keys
+   * will be used as the expected set of keys.
+   *
+   *     assert.containsAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {one: 'one'});
+   *     assert.containsAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{one: 'one'}, {two: 'two'}]);
+   *     assert.containsAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {one: 'one'});
+   *     assert.containsAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {two: 'two'}]);
+   *
+   * @name containsAllDeepKeys
+   * @param {Mixed} object
+   * @param {Array|Object} keys
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.containsAllDeepKeys = function (obj, keys, msg) {
+    new Assertion(obj, msg).to.contain.all.deep.keys(keys);
+  }
+
+ /**
+   * ### .doesNotHaveAnyDeepKeys(object, [keys], [message])
+   *
+   * Asserts that `object` has none of the `keys` provided.
+   * Since Sets and Maps can have objects as keys you can use this assertion to perform
+   * a deep comparison.
+   * You can also provide a single object instead of a `keys` array and its keys
+   * will be used as the expected set of keys.
+   *
+   *     assert.doesNotHaveAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {thisDoesNot: 'exist'});
+   *     assert.doesNotHaveAnyDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{twenty: 'twenty'}, {fifty: 'fifty'}]);
+   *     assert.doesNotHaveAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {twenty: 'twenty'});
+   *     assert.doesNotHaveAnyDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{twenty: 'twenty'}, {fifty: 'fifty'}]);
+   *
+   * @name doesNotHaveAnyDeepKeys
+   * @param {Mixed} object
+   * @param {Array|Object} keys
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.doesNotHaveAnyDeepKeys = function (obj, keys, msg) {
+    new Assertion(obj, msg).to.not.have.any.deep.keys(keys);
+  }
+
+ /**
+   * ### .doesNotHaveAllDeepKeys(object, [keys], [message])
+   *
+   * Asserts that `object` does not have at least one of the `keys` provided.
+   * Since Sets and Maps can have objects as keys you can use this assertion to perform
+   * a deep comparison.
+   * You can also provide a single object instead of a `keys` array and its keys
+   * will be used as the expected set of keys.
+   *
+   *     assert.doesNotHaveAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [1, 2]]), {thisDoesNot: 'exist'});
+   *     assert.doesNotHaveAllDeepKeys(new Map([[{one: 'one'}, 'valueOne'], [{two: 'two'}, 'valueTwo']]), [{twenty: 'twenty'}, {one: 'one'}]);
+   *     assert.doesNotHaveAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), {twenty: 'twenty'});
+   *     assert.doesNotHaveAllDeepKeys(new Set([{one: 'one'}, {two: 'two'}]), [{one: 'one'}, {fifty: 'fifty'}]);
+   *
+   * @name doesNotHaveAllDeepKeys
+   * @param {Mixed} object
+   * @param {Array|Object} keys
+   * @param {String} message
+   * @namespace Assert
+   * @api public
+   */
+
+  assert.doesNotHaveAllDeepKeys = function (obj, keys, msg) {
+    new Assertion(obj, msg).to.not.have.all.deep.keys(keys);
+  }
+
+ /**
    * ### .throws(fn, [errorLike/string/regexp], [string/regexp], [message])
    *
    * If `errorLike` is an `Error` constructor, asserts that `fn` will throw an error that is an


Test patch:
diff --git a/test/assert.js b/test/assert.js
index e8219c6..093a4db 100644
--- a/test/assert.js
+++ b/test/assert.js
@@ -747,6 +747,38 @@ describe('assert', function () {
       assert.doesNotHaveAnyKeys(testMap, [ 'thisDoesNotExist', 'thisToo', {iDoNot: 'exist'} ]);
       assert.doesNotHaveAllKeys(testMap, [ aKey, {iDoNot: 'exist'} ]);
 
+      // Ensure the assertions above use strict equality
+      assert.doesNotHaveAnyKeys(testMap, {thisIs: 'anExampleObject'});
+      assert.doesNotHaveAllKeys(testMap, [ {thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'} ]);
+
+      err(function(){
+        assert.hasAnyKeys(testMap, [ {thisIs: 'anExampleObject'} ]);
+      });
+
+      err(function(){
+        assert.hasAllKeys(testMap, [ {thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'} ]);
+      });
+
+      err(function(){
+        assert.containsAllKeys(testMap, [ {thisIs: 'anExampleObject'} ]);
+      });
+
+      // Tests for the deep variations of the keys assertion
+      assert.hasAnyDeepKeys(testMap, {thisIs: 'anExampleObject'});
+      assert.hasAnyDeepKeys(testMap, [{thisIs: 'anExampleObject'}, {three: 'three'}]);
+      assert.hasAnyDeepKeys(testMap, [{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      assert.hasAllDeepKeys(testMap, [{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      assert.containsAllDeepKeys(testMap, {thisIs: 'anExampleObject'});
+      assert.containsAllDeepKeys(testMap, [{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      assert.doesNotHaveAnyDeepKeys(testMap, {thisDoesNot: 'exist'});
+      assert.doesNotHaveAnyDeepKeys(testMap, [{twenty: 'twenty'}, {fifty: 'fifty'}]);
+
+      assert.doesNotHaveAllDeepKeys(testMap, {thisDoesNot: 'exist'});
+      assert.doesNotHaveAllDeepKeys(testMap, [{twenty: 'twenty'}, {thisIs: 'anExampleObject'}]);
+
       var weirdMapKey1 = Object.create(null)
         , weirdMapKey2 = {toString: NaN}
         , weirdMapKey3 = []
@@ -779,45 +811,35 @@ describe('assert', function () {
 
       errMap.set({1: 20}, 'number');
 
-      err(function(){
-        assert.hasAllKeys(errMap);
-      }, "keys required");
-
       err(function(){
         assert.hasAllKeys(errMap, []);
       }, "keys required");
 
-      err(function(){
-        assert.containsAllKeys(errMap);
-      }, "keys required");
-
       err(function(){
         assert.containsAllKeys(errMap, []);
       }, "keys required");
 
-      err(function(){
-        assert.doesNotHaveAllKeys(errMap);
-      }, "keys required");
-
       err(function(){
         assert.doesNotHaveAllKeys(errMap, []);
       }, "keys required");
 
-      err(function(){
-        assert.hasAnyKeys(errMap);
-      }, "keys required");
-
       err(function(){
         assert.hasAnyKeys(errMap, []);
       }, "keys required");
 
-      err(function(){
-        assert.doesNotHaveAnyKeys(errMap);
-      }, "keys required");
-
       err(function(){
         assert.doesNotHaveAnyKeys(errMap, []);
       }, "keys required");
+
+      // Uncomment this after solving https://github.com/chaijs/chai/issues/662
+      // This should fail because of referential equality (this is a strict comparison)
+      // err(function(){
+      //   assert.containsAllKeys(new Map([[{foo: 1}, 'bar']]), { foo: 1 });
+      // }, 'expected [ [ { foo: 1 }, 'bar' ] ] to contain key { foo: 1 }');
+
+      // err(function(){
+      //   assert.containsAllDeepKeys(new Map([[{foo: 1}, 'bar']]), { iDoNotExist: 0 })
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }');
     }
 
     if (typeof Set !== 'undefined') {
@@ -844,6 +866,38 @@ describe('assert', function () {
       assert.doesNotHaveAnyKeys(testSet, [ 20, 1, {iDoNot: 'exist'} ]);
       assert.doesNotHaveAllKeys(testSet, [ 'thisDoesNotExist', 'thisToo', {iDoNot: 'exist'} ]);
 
+      // Ensure the assertions above use strict equality
+      assert.doesNotHaveAnyKeys(testSet, {thisIs: 'anExampleObject'});
+      assert.doesNotHaveAllKeys(testSet, [ {thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'} ]);
+
+      err(function(){
+        assert.hasAnyKeys(testSet, [ {thisIs: 'anExampleObject'} ]);
+      });
+
+      err(function(){
+        assert.hasAllKeys(testSet, [ {thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'} ]);
+      });
+
+      err(function(){
+        assert.containsAllKeys(testSet, [ {thisIs: 'anExampleObject'} ]);
+      });
+
+      // Tests for the deep variations of the keys assertion
+      assert.hasAnyDeepKeys(testSet, {thisIs: 'anExampleObject'});
+      assert.hasAnyDeepKeys(testSet, [{thisIs: 'anExampleObject'}, {three: 'three'}]);
+      assert.hasAnyDeepKeys(testSet, [{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      assert.hasAllDeepKeys(testSet, [{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      assert.containsAllDeepKeys(testSet, {thisIs: 'anExampleObject'});
+      assert.containsAllDeepKeys(testSet, [{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      assert.doesNotHaveAnyDeepKeys(testSet, {twenty: 'twenty'});
+      assert.doesNotHaveAnyDeepKeys(testSet, [{twenty: 'twenty'}, {fifty: 'fifty'}]);
+
+      assert.doesNotHaveAllDeepKeys(testSet, {twenty: 'twenty'});
+      assert.doesNotHaveAllDeepKeys(testSet, [{thisIs: 'anExampleObject'}, {fifty: 'fifty'}]);
+
       var weirdSetKey1 = Object.create(null)
         , weirdSetKey2 = {toString: NaN}
         , weirdSetKey3 = []
@@ -877,83 +931,53 @@ describe('assert', function () {
       errSet.add({1: 20});
       errSet.add('number');
 
-      err(function(){
-        assert.hasAllKeys(errSet);
-      }, "keys required");
-
       err(function(){
         assert.hasAllKeys(errSet, []);
       }, "keys required");
 
-      err(function(){
-        assert.containsAllKeys(errSet);
-      }, "keys required");
-
       err(function(){
         assert.containsAllKeys(errSet, []);
       }, "keys required");
 
-      err(function(){
-        assert.doesNotHaveAllKeys(errSet);
-      }, "keys required");
-
       err(function(){
         assert.doesNotHaveAllKeys(errSet, []);
       }, "keys required");
 
-      err(function(){
-        assert.hasAnyKeys(errSet);
-      }, "keys required");
-
       err(function(){
         assert.hasAnyKeys(errSet, []);
       }, "keys required");
 
-      err(function(){
-        assert.doesNotHaveAnyKeys(errSet);
-      }, "keys required");
-
       err(function(){
         assert.doesNotHaveAnyKeys(errSet, []);
       }, "keys required");
-    }
 
-    err(function(){
-      assert.hasAllKeys({ foo: 1 });
-    }, "keys required");
+      // Uncomment this after solving https://github.com/chaijs/chai/issues/662
+      // This should fail because of referential equality (this is a strict comparison)
+      // err(function(){
+      //   assert.containsAllKeys(new Set([{foo: 1}]), { foo: 1 });
+      // }, 'expected [ [ { foo: 1 }, 'bar' ] ] to contain key { foo: 1 }');
 
-    err(function(){
-      assert.hasAllKeys({ foo: 1 }, []);
-    }, "keys required");
+      // err(function(){
+      //   assert.containsAllDeepKeys(new Set([{foo: 1}]), { iDoNotExist: 0 })
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }');
+    }
 
     err(function(){
-      assert.containsAllKeys({ foo: 1 });
+      assert.hasAllKeys({ foo: 1 }, []);
     }, "keys required");
 
     err(function(){
       assert.containsAllKeys({ foo: 1 }, []);
     }, "keys required");
 
-    err(function(){
-      assert.doesNotHaveAllKeys({ foo: 1 });
-    }, "keys required");
-
     err(function(){
       assert.doesNotHaveAllKeys({ foo: 1 }, []);
     }, "keys required");
 
-    err(function(){
-      assert.hasAnyKeys({ foo: 1 });
-    }, "keys required");
-
     err(function(){
       assert.hasAnyKeys({ foo: 1 }, []);
     }, "keys required");
 
-    err(function(){
-      assert.doesNotHaveAnyKeys({ foo: 1 });
-    }, "keys required");
-
     err(function(){
       assert.doesNotHaveAnyKeys({ foo: 1 }, []);
     }, "keys required");
@@ -1023,7 +1047,6 @@ describe('assert', function () {
       assert.doesNotHaveAllKeys({ foo: 1, bar: 2 }, { 'foo': 1, 'bar': 1});
     }, "expected { foo: 1, bar: 2 } to not have keys 'foo', and 'bar'");
 
-
     err(function() {
       assert.hasAnyKeys({ foo: 1 }, 'baz');
     }, "expected { foo: 1 } to have key 'baz'");
@@ -1035,7 +1058,6 @@ describe('assert', function () {
     err(function(){
       assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, { 'foo': 1, 'baz': 1});
     }, "expected { foo: 1, bar: 2 } to not have keys 'foo', or 'baz'");
-
   });
 
   it('lengthOf', function() {
diff --git a/test/expect.js b/test/expect.js
index fb8f8e3..ea77167 100644
--- a/test/expect.js
+++ b/test/expect.js
@@ -1303,6 +1303,47 @@ describe('expect', function () {
       expect(testMap).to.not.have.any.keys([20, 1, {13: 37}]);
       expect(testMap).to.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);
 
+      // Ensure the assertions above use strict equality
+      err(function() {
+        expect(testMap).to.have.any.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        expect(testMap).to.have.all.keys({thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'});
+      });
+
+      err(function() {
+        expect(testMap).to.contain.all.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        expect(testMap).to.have.any.keys([{thisIs: 'anExampleObject'}]);
+      });
+
+      err(function() {
+        expect(testMap).to.have.all.keys([{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+      });
+
+      // Using the same assertions as above but with `.deep` flag instead of using referential equality
+      expect(testMap).to.have.any.deep.keys({thisIs: 'anExampleObject'});
+      expect(testMap).to.have.any.deep.keys('thisDoesNotExist', 'thisToo', {thisIs: 'anExampleObject'});
+
+      expect(testMap).to.contain.all.deep.keys({thisIs: 'anExampleObject'});
+      expect(testMap).to.not.contain.all.deep.keys({thisIs: 'anExampleObject'}, 'thisDoesNotExist');
+
+      expect(testMap).to.not.have.any.deep.keys({iDoNot: 'exist'});
+      expect(testMap).to.not.have.any.deep.keys('thisIsNotAkey', {iDoNot: 'exist'}, {33: 20});
+      expect(testMap).to.not.have.all.deep.keys('thisDoesNotExist', 'thisToo', {doingThisBecauseOf: 'referential equality'});
+
+      expect(testMap).to.have.any.deep.keys([{thisIs: 'anExampleObject'}]);
+      expect(testMap).to.have.any.deep.keys([20, 1, {thisIs: 'anExampleObject'}]);
+
+      expect(testMap).to.have.all.deep.keys({thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'});
+
+      expect(testMap).to.not.have.any.deep.keys([{13: 37}, 'thisDoesNotExist', 'thisToo']);
+      expect(testMap).to.not.have.any.deep.keys([20, 1, {13: 37}]);
+      expect(testMap).to.not.have.all.deep.keys([{thisIs: 'anExampleObject'}, {'iDoNot': 'exist'}]);
+
       var weirdMapKey1 = Object.create(null)
         , weirdMapKey2 = {toString: NaN}
         , weirdMapKey3 = []
@@ -1353,6 +1394,16 @@ describe('expect', function () {
       err(function(){
         expect(errMap).to.contain.keys([]);
       }, "keys required");
+
+      // Uncomment this after solving https://github.com/chaijs/chai/issues/662
+      // This should fail because of referential equality (this is a strict comparison)
+      // err(function(){
+      //   expect(new Map([[{foo: 1}, 'bar']])).to.contain.keys({ foo: 1 });
+      // }, 'expected [ [ { foo: 1 }, 'bar' ] ] to contain key { foo: 1 }');
+
+      // err(function(){
+      //   expect(new Map([[{foo: 1}, 'bar']])).to.contain.deep.keys({ iDoNotExist: 0 })
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }');
     }
 
     if (typeof Set !== 'undefined') {
@@ -1383,6 +1434,47 @@ describe('expect', function () {
       expect(testSet).to.not.have.any.keys([20, 1, {13: 37}]);
       expect(testSet).to.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);
 
+      // Ensure the assertions above use strict equality
+      err(function() {
+        expect(testSet).to.have.any.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        expect(testSet).to.have.all.keys({thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'});
+      });
+
+      err(function() {
+        expect(testSet).to.contain.all.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        expect(testSet).to.have.any.keys([{thisIs: 'anExampleObject'}]);
+      });
+
+      err(function() {
+        expect(testSet).to.have.all.keys([{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+      });
+
+      // Using the same assertions as above but with `.deep` flag instead of using referential equality
+      expect(testSet).to.have.any.deep.keys({thisIs: 'anExampleObject'});
+      expect(testSet).to.have.any.deep.keys('thisDoesNotExist', 'thisToo', {thisIs: 'anExampleObject'});
+
+      expect(testSet).to.contain.all.deep.keys({thisIs: 'anExampleObject'});
+      expect(testSet).to.not.contain.all.deep.keys({thisIs: 'anExampleObject'}, 'thisDoesNotExist');
+
+      expect(testSet).to.not.have.any.deep.keys({iDoNot: 'exist'});
+      expect(testSet).to.not.have.any.deep.keys('thisIsNotAkey', {iDoNot: 'exist'}, {33: 20});
+      expect(testSet).to.not.have.all.deep.keys('thisDoesNotExist', 'thisToo', {doingThisBecauseOf: 'referential equality'});
+
+      expect(testSet).to.have.any.deep.keys([{thisIs: 'anExampleObject'}]);
+      expect(testSet).to.have.any.deep.keys([20, 1, {thisIs: 'anExampleObject'}]);
+
+      expect(testSet).to.have.all.deep.keys([{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      expect(testSet).to.not.have.any.deep.keys([{13: 37}, 'thisDoesNotExist', 'thisToo']);
+      expect(testSet).to.not.have.any.deep.keys([20, 1, {13: 37}]);
+      expect(testSet).to.not.have.all.deep.keys([{thisIs: 'anExampleObject'}, {'iDoNot': 'exist'}]);
+ 
       var weirdSetKey1 = Object.create(null)
         , weirdSetKey2 = {toString: NaN}
         , weirdSetKey3 = []
@@ -1432,6 +1524,16 @@ describe('expect', function () {
       err(function(){
         expect(errSet).to.contain.keys([]);
       }, "keys required");
+
+      // Uncomment this after solving https://github.com/chaijs/chai/issues/662
+      // This should fail because of referential equality (this is a strict comparison)
+      // err(function(){
+      //   expect(new Set([{foo: 1}])).to.contain.keys({ foo: 1 });
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { foo: 1 }');
+
+      // err(function(){
+      //   expect(new Set([{foo: 1}])).to.contain.deep.keys({ iDoNotExist: 0 });
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }');
     }
 
     err(function(){
diff --git a/test/should.js b/test/should.js
index c52c9cc..78972fd 100644
--- a/test/should.js
+++ b/test/should.js
@@ -1149,6 +1149,44 @@ describe('should', function() {
       testMap.should.not.have.any.keys([20, 1, {13: 37}]);
       testMap.should.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);
 
+      // Ensure the assertions above use strict equality
+      err(function() {
+        testMap.should.have.any.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        testMap.should.have.all.keys({thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'});
+      });
+
+      err(function() {
+        testMap.should.contain.all.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        testMap.should.have.any.keys([{thisIs: 'anExampleObject'}]);
+      });
+
+      err(function() {
+        testMap.should.have.all.keys([{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+      });
+
+      // Using the same assertions as above but with `.deep` flag instead of using referential equality
+      testMap.should.have.any.deep.keys({thisIs: 'anExampleObject'});
+      testMap.should.have.any.deep.keys('thisDoesNotExist', 'thisToo', {thisIs: 'anExampleObject'});
+
+      testMap.should.contain.all.deep.keys({thisIs: 'anExampleObject'});
+      testMap.should.not.contain.all.deep.keys({thisIs: 'anExampleObject'}, 'thisDoesNotExist');
+      testMap.should.not.have.any.deep.keys({iDoNot: 'exist'});
+      testMap.should.not.have.all.deep.keys('thisDoesNotExist', 'thisToo', {doingThisBecauseOf: 'referential equality'});
+
+      testMap.should.have.any.deep.keys([{thisIs: 'anExampleObject'}]);
+      testMap.should.have.any.deep.keys([20, 1, {thisIs: 'anExampleObject'}]);
+
+      testMap.should.have.all.deep.keys([{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+
+      testMap.should.not.have.any.deep.keys([{13: 37}, 'thisDoesNotExist', 'thisToo']);
+      testMap.should.not.have.any.deep.keys([20, 1, {13: 37}]);
+
       var weirdMapKey1 = Object.create(null)
         , weirdMapKey2 = {toString: NaN}
         , weirdMapKey3 = []
@@ -1199,6 +1237,16 @@ describe('should', function() {
       err(function(){
         errMap.should.contain.keys([]);
       }, "keys required");
+
+      // Uncomment these after solving https://github.com/chaijs/chai/issues/662
+      // This should fail because of referential equality (this is a strict comparison)
+      // err(function(){
+      //   new Map([[{foo: 1}, 'bar']]).should.contain.keys({ foo: 1 });
+      // }, 'expected [ [ { foo: 1 }, 'bar' ] ] to contain key { foo: 1 }');
+
+      // err(function(){
+      //   new Map([[{foo: 1}, 'bar']]).should.contain.deep.keys({ iDoNotExist: 0 });
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }');
     }
 
     if (typeof Set !== 'undefined') {
@@ -1229,6 +1277,48 @@ describe('should', function() {
       testSet.should.not.have.any.keys([20, 1, {13: 37}]);
       testSet.should.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);
 
+      // Ensure the assertions above use strict equality
+      err(function() {
+        testSet.should.have.any.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        testSet.should.have.all.keys({thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'});
+      });
+
+      err(function() {
+        testSet.should.contain.all.keys({thisIs: 'anExampleObject'});
+      });
+
+      err(function() {
+        testSet.should.have.any.keys([{thisIs: 'anExampleObject'}]);
+      });
+
+      err(function() {
+        testSet.should.have.all.keys([{thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'}]);
+      });
+
+
+      // Using the same assertions as above but with `.deep` flag instead of using referential equality
+      testSet.should.have.any.deep.keys({thisIs: 'anExampleObject'});
+      testSet.should.have.any.deep.keys('thisDoesNotExist', 'thisToo', {thisIs: 'anExampleObject'});
+
+      testSet.should.contain.all.deep.keys({thisIs: 'anExampleObject'});
+      testSet.should.not.contain.all.deep.keys({thisIs: 'anExampleObject'}, 'thisDoesNotExist');
+
+      testSet.should.not.have.any.deep.keys({iDoNot: 'exist'});
+      testSet.should.not.have.any.deep.keys('thisIsNotAkey', {iDoNot: 'exist'}, {33: 20});
+      testSet.should.not.have.all.deep.keys('thisDoesNotExist', 'thisToo', {anotherObj: 'foo'});
+
+      testSet.should.have.any.deep.keys([{thisIs: 'anExampleObject'}]);
+      testSet.should.have.any.deep.keys([20, 1, {thisIs: 'anExampleObject'}]);
+
+      testSet.should.have.all.deep.keys({thisIs: 'anExampleObject'}, {doingThisBecauseOf: 'referential equality'});
+
+      testSet.should.not.have.any.deep.keys([{13: 37}, 'thisDoesNotExist', 'thisToo']);
+      testSet.should.not.have.any.deep.keys([20, 1, {13: 37}]);
+      testSet.should.not.have.all.deep.keys([{thisIs: 'anExampleObject'}, {'iDoNot': 'exist'}])
+
       var weirdSetKey1 = Object.create(null)
         , weirdSetKey2 = {toString: NaN}
         , weirdSetKey3 = []
@@ -1279,6 +1369,16 @@ describe('should', function() {
       err(function(){
         errSet.should.contain.keys([]);
       }, "keys required");
+
+      // Uncomment these after solving https://github.com/chaijs/chai/issues/662
+      // This should fail because of referential equality (this is a strict comparison)
+      // err(function(){
+      //   new Set([{foo: 1}]).should.contain.keys({ foo: 1 });
+      // }, 'expected [ { foo: 1 } ] to contain key { foo: 1 }');
+
+      // err(function(){
+      //   new Set([{foo: 1}]).should.contain.deep.keys({ iDoNotExist: 0 });
+      // }, 'expected [ { foo: 1 } ] to deeply contain key { iDoNotExist: 0 }');
     }
 
     err(function(){