Skip to content

Commit

Permalink
Fix bug when asserting some valid ES6 keys
Browse files Browse the repository at this point in the history
- Resolution of #674
- Add compareByInspect util for use with assertKeys sorts
- Add Symbol support to the inspect utility
- Add tests to utilities, should, expect, and assert
  • Loading branch information
meeber committed Apr 9, 2016
1 parent 4f72846 commit 7b228ae
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 3 deletions.
14 changes: 11 additions & 3 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,11 @@ module.exports = function (chai, _) {
} else {
actual = Object.keys(obj);

if (typeof Object.getOwnPropertySymbols === 'function') {
// Object.keys excludes Symbols so we need this too
actual = actual.concat(Object.getOwnPropertySymbols(obj));
}

switch (_.type(keys)) {
case 'array':
if (arguments.length > 1) throw new Error(mixedArgsMsg);
Expand All @@ -1184,7 +1189,10 @@ module.exports = function (chai, _) {
keys = Array.prototype.slice.call(arguments);
}

keys = keys.map(String);
// Only stringify non-Symbols because Symbols would become "Symbol()"
keys = keys.map(function (val) {
return typeof val === 'symbol' ? val : String(val);
});
}

if (!keys.length) throw new Error('keys required');
Expand Down Expand Up @@ -1248,8 +1256,8 @@ module.exports = function (chai, _) {
ok
, 'expected #{this} to ' + str
, 'expected #{this} to not ' + str
, expected.slice(0).sort()
, actual.sort()
, expected.slice(0).sort(_.compareByInspect)
, actual.sort(_.compareByInspect)
, true
);
}
Expand Down
31 changes: 31 additions & 0 deletions lib/chai/utils/compareByInspect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*!
* Chai - compareByInspect utility
* Copyright(c) 2011-2016 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/

/*!
* Module dependancies
*/

var inspect = require('./inspect');

/**
* ### .compareByInspect (mixed, mixed)
*
* To be used as a compareFunction with Array.prototype.sort. Compares elements
* using inspect instead of default behavior of using toString so that Symbols
* and objects with irregular/missing toString can still be sorted without a
* TypeError.
*
* @param {Mixed} first element to compare
* @param {Mixed} second element to compare
* @returns {Number} -1 if 'a' should come before 'b'; otherwise 1
* @name compareByInspect
* @namespace Utils
* @api public
*/

module.exports = function (a, b) {
return inspect(a) < inspect(b) ? -1 : 1;
};
6 changes: 6 additions & 0 deletions lib/chai/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,9 @@ exports.addChainableMethod = require('./addChainableMethod');
*/

exports.overwriteChainableMethod = require('./overwriteChainableMethod');

/*!
* Compare by inspect method
*/

exports.compareByInspect = require('./compareByInspect');
3 changes: 3 additions & 0 deletions lib/chai/utils/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ function formatPrimitive(ctx, value) {

case 'boolean':
return ctx.stylize('' + value, 'boolean');

case 'symbol':
return ctx.stylize(value.toString(), 'symbol');
}
// For some reason typeof null is "object", so special case here.
if (value === null) {
Expand Down
49 changes: 49 additions & 0 deletions test/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,17 @@ describe('assert', function () {
assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, { baz: 1, biz: 2, fake: 3 });
assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, { baz: 1 });

if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol()
, sym3 = Symbol()
, obj = {};
obj[sym1] = 'val1';
obj[sym2] = 'val2';
assert.hasAllKeys(obj, [sym1, sym2]);
assert.doesNotHaveAllKeys(obj, [sym1, sym3]);
}

if (typeof Map !== 'undefined') {
var aKey = {thisIs: 'anExampleObject'};
var anotherKey = {doingThisBecauseOf: 'referential equality'};
Expand All @@ -577,6 +588,25 @@ describe('assert', function () {
assert.doesNotHaveAnyKeys(new Map([[aKey, 'aValue'], [anotherKey, 'anotherValue']]), [ 'thisDoesNotExist', 'thisToo', {iDoNot: 'exist'} ]);
assert.doesNotHaveAllKeys(new Map([[aKey, 'aValue'], [anotherKey, 'anotherValue']]), [ aKey, {iDoNot: 'exist'} ]);

var weirdMapKey1 = Object.create(null)
, weirdMapKey2 = {toString: NaN}
, weirdMapKey3 = [];
assert.hasAllKeys(new Map([[weirdMapKey1, 'val1'], [weirdMapKey2, 'val2']]), [weirdMapKey1, weirdMapKey2]);
assert.doesNotHaveAllKeys(new Map([[weirdMapKey1, 'val1'], [weirdMapKey2, 'val2']]), [weirdMapKey1, weirdMapKey3]);

if (typeof Symbol === 'function') {
var symMapKey1 = Symbol()
, symMapKey2 = Symbol()
, symMapKey3 = Symbol();

assert.hasAllKeys(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]), [symMapKey1, symMapKey2]);
assert.hasAnyKeys(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]), [symMapKey1, symMapKey3]);
assert.containsAllKeys(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]), [symMapKey2, symMapKey1]);

assert.doesNotHaveAllKeys(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]), [symMapKey1, symMapKey3]);
assert.doesNotHaveAnyKeys(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]), [symMapKey3]);
}

err(function(){
assert.hasAllKeys(new Map([[{1: 20}, 'number']]));
}, "keys required");
Expand Down Expand Up @@ -637,6 +667,25 @@ describe('assert', function () {
assert.doesNotHaveAnyKeys(new Set([aKey, anotherKey]), [ 20, 1, {iDoNot: 'exist'} ]);
assert.doesNotHaveAllKeys(new Set([aKey, anotherKey]), [ 'thisDoesNotExist', 'thisToo', {iDoNot: 'exist'} ]);

var weirdSetKey1 = Object.create(null)
, weirdSetKey2 = {toString: NaN}
, weirdSetKey3 = [];
assert.hasAllKeys(new Set([weirdSetKey1, weirdSetKey2]), [weirdSetKey1, weirdSetKey2]);
assert.doesNotHaveAllKeys(new Set([weirdSetKey1, weirdSetKey2]), [weirdSetKey1, weirdSetKey3]);

if (typeof Symbol === 'function') {
var symSetKey1 = Symbol()
, symSetKey2 = Symbol()
, symSetKey3 = Symbol();

assert.hasAllKeys(new Set([symSetKey1, symSetKey2]), [symSetKey1, symSetKey2]);
assert.hasAnyKeys(new Set([symSetKey1, symSetKey2]), [symSetKey1, symSetKey3]);
assert.containsAllKeys(new Set([symSetKey1, symSetKey2]), [symSetKey2, symSetKey1]);

assert.doesNotHaveAllKeys(new Set([symSetKey1, symSetKey2]), [symSetKey1, symSetKey3]);
assert.doesNotHaveAnyKeys(new Set([symSetKey1, symSetKey2]), [symSetKey3]);
}

err(function(){
assert.hasAllKeys(new Set([{1: 20}, 'number']));
}, "keys required");
Expand Down
55 changes: 55 additions & 0 deletions test/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,17 @@ describe('expect', function () {
expect({ foo: 1, bar: 2 }).not.have.all.keys({ 'baz': 8, 'foo': 7 });
expect({ foo: 1, bar: 2 }).not.contain.all.keys({ 'baz': 8, 'foo': 7 });

if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol()
, sym3 = Symbol()
, obj = {};
obj[sym1] = 'val1';
obj[sym2] = 'val2';
expect(obj).to.have.all.keys([sym1, sym2]);
expect(obj).to.not.have.all.keys([sym1, sym3]);
}

if (typeof Map !== 'undefined') {
var aKey = {thisIs: 'anExampleObject'};
var anotherKey = {doingThisBecauseOf: 'referential equality'};
Expand All @@ -766,6 +777,28 @@ describe('expect', function () {
expect(new Map([[aKey, 'aValue'], [anotherKey, 'anotherValue']])).to.not.have.any.keys([20, 1, {13: 37}]);
expect(new Map([[aKey, 'aValue'], [anotherKey, 'anotherValue']])).to.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);

var weirdMapKey1 = Object.create(null)
, weirdMapKey2 = {toString: NaN}
, weirdMapKey3 = [];
expect(new Map([[weirdMapKey1, 'val1'], [weirdMapKey2, 'val2']])).to.have.all.keys([weirdMapKey1, weirdMapKey2]);
expect(new Map([[weirdMapKey1, 'val1'], [weirdMapKey2, 'val2']])).to.not.have.all.keys([weirdMapKey1, weirdMapKey3]);

if (typeof Symbol === 'function') {
var symMapKey1 = Symbol()
, symMapKey2 = Symbol()
, symMapKey3 = Symbol();

expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.have.all.keys(symMapKey1, symMapKey2);
expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.have.any.keys(symMapKey1, symMapKey3);
expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.contain.all.keys(symMapKey2, symMapKey1);
expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.contain.any.keys(symMapKey3, symMapKey1);

expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.not.have.all.keys(symMapKey1, symMapKey3);
expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.not.have.any.keys(symMapKey3);
expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.not.contain.all.keys(symMapKey3, symMapKey1);
expect(new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']])).to.not.contain.any.keys(symMapKey3);
}

err(function(){
expect(new Map().set({ foo: 1 })).to.have.keys();
}, "keys required");
Expand Down Expand Up @@ -806,6 +839,28 @@ describe('expect', function () {
expect(new Set([aKey, anotherKey])).to.not.have.any.keys([20, 1, {13: 37}]);
expect(new Set([aKey, anotherKey])).to.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);

var weirdSetKey1 = Object.create(null)
, weirdSetKey2 = {toString: NaN}
, weirdSetKey3 = [];
expect(new Set([weirdSetKey1, weirdSetKey2])).to.have.all.keys([weirdSetKey1, weirdSetKey2]);
expect(new Set([weirdSetKey1, weirdSetKey2])).to.not.have.all.keys([weirdSetKey1, weirdSetKey3]);

if (typeof Symbol === 'function') {
var symSetKey1 = Symbol()
, symSetKey2 = Symbol()
, symSetKey3 = Symbol();

expect(new Set([symSetKey1, symSetKey2])).to.have.all.keys(symSetKey1, symSetKey2);
expect(new Set([symSetKey1, symSetKey2])).to.have.any.keys(symSetKey1, symSetKey3);
expect(new Set([symSetKey1, symSetKey2])).to.contain.all.keys(symSetKey2, symSetKey1);
expect(new Set([symSetKey1, symSetKey2])).to.contain.any.keys(symSetKey3, symSetKey1);

expect(new Set([symSetKey1, symSetKey2])).to.not.have.all.keys(symSetKey1, symSetKey3);
expect(new Set([symSetKey1, symSetKey2])).to.not.have.any.keys(symSetKey3);
expect(new Set([symSetKey1, symSetKey2])).to.not.contain.all.keys(symSetKey3, symSetKey1);
expect(new Set([symSetKey1, symSetKey2])).to.not.contain.any.keys(symSetKey3);
}

err(function(){
expect(new Set().add({ foo: 1 })).to.have.keys();
}, "keys required");
Expand Down
55 changes: 55 additions & 0 deletions test/should.js
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,17 @@ describe('should', function() {
({ 1: 1, 2: 2 }).should.have.any.keys(1, 3);
({ 1: 1, 2: 2 }).should.contain.keys(1);

if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol()
, sym3 = Symbol()
, obj = {};
obj[sym1] = 'val1';
obj[sym2] = 'val2';
obj.should.have.all.keys([sym1, sym2]);
obj.should.not.have.all.keys([sym1, sym3]);
}

if (typeof Map !== 'undefined') {
var aKey = {thisIs: 'anExampleObject'};
var anotherKey = {doingThisBecauseOf: 'referential equality'};
Expand All @@ -607,6 +618,28 @@ describe('should', function() {
new Map([[aKey, 'aValue'], [anotherKey, 'anotherValue']]).should.not.have.any.keys([20, 1, {13: 37}]);
new Map([[aKey, 'aValue'], [anotherKey, 'anotherValue']]).should.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);

var weirdMapKey1 = Object.create(null)
, weirdMapKey2 = {toString: NaN}
, weirdMapKey3 = [];
new Map([[weirdMapKey1, 'val1'], [weirdMapKey2, 'val2']]).should.have.all.keys([weirdMapKey1, weirdMapKey2]);
new Map([[weirdMapKey1, 'val1'], [weirdMapKey2, 'val2']]).should.not.have.all.keys([weirdMapKey1, weirdMapKey3]);

if (typeof Symbol === 'function') {
var symMapKey1 = Symbol()
, symMapKey2 = Symbol()
, symMapKey3 = Symbol();

new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.have.all.keys(symMapKey1, symMapKey2);
new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.have.any.keys(symMapKey1, symMapKey3);
new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.contain.all.keys(symMapKey2, symMapKey1);
new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.contain.any.keys(symMapKey3, symMapKey1);

new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.not.have.all.keys(symMapKey1, symMapKey3);
new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.not.have.any.keys(symMapKey3);
new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.not.contain.all.keys(symMapKey3, symMapKey1);
new Map([[symMapKey1, 'symValue1'], [symMapKey2, 'symValue2']]).should.not.contain.any.keys(symMapKey3);
}

err(function(){
new Map().set({ foo: 1 }).should.have.keys();
}, "keys required");
Expand Down Expand Up @@ -647,6 +680,28 @@ describe('should', function() {
new Set([aKey, anotherKey]).should.not.have.any.keys([20, 1, {13: 37}]);
new Set([aKey, anotherKey]).should.not.have.all.keys([aKey, {'iDoNot': 'exist'}]);

var weirdSetKey1 = Object.create(null)
, weirdSetKey2 = {toString: NaN}
, weirdSetKey3 = [];
new Set([weirdSetKey1, weirdSetKey2]).should.have.all.keys([weirdSetKey1, weirdSetKey2]);
new Set([weirdSetKey1, weirdSetKey2]).should.not.have.all.keys([weirdSetKey1, weirdSetKey3]);

if (typeof Symbol === 'function') {
var symSetKey1 = Symbol()
, symSetKey2 = Symbol()
, symSetKey3 = Symbol();

new Set([symSetKey1, symSetKey2]).should.have.all.keys(symSetKey1, symSetKey2);
new Set([symSetKey1, symSetKey2]).should.have.any.keys(symSetKey1, symSetKey3);
new Set([symSetKey1, symSetKey2]).should.contain.all.keys(symSetKey2, symSetKey1);
new Set([symSetKey1, symSetKey2]).should.contain.any.keys(symSetKey3, symSetKey1);

new Set([symSetKey1, symSetKey2]).should.not.have.all.keys(symSetKey1, symSetKey3);
new Set([symSetKey1, symSetKey2]).should.not.have.any.keys(symSetKey3);
new Set([symSetKey1, symSetKey2]).should.not.contain.all.keys(symSetKey3, symSetKey1);
new Set([symSetKey1, symSetKey2]).should.not.contain.any.keys(symSetKey3);
}

err(function(){
new Set().add({ foo: 1 }).should.have.keys();
}, "keys required");
Expand Down
29 changes: 29 additions & 0 deletions test/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,15 @@ describe('utilities', function () {
});
});

it('inspect Symbol', function () {
chai.use(function (_chai, _) {
if (typeof Symbol === 'function') {
expect(_.inspect(Symbol())).to.equal('Symbol()');
expect(_.inspect(Symbol('duck'))).to.equal('Symbol(duck)');
}
});
});

it('addChainableMethod', function () {
chai.use(function (_chai, _) {
_chai.Assertion.addChainableMethod('x',
Expand Down Expand Up @@ -704,4 +713,24 @@ describe('utilities', function () {
});
});

it('compareByInspect', function () {
chai.use(function (_chai, _) {
var cbi = _.compareByInspect;

// "'c" is less than "'d"
expect(cbi('cat', 'dog')).to.equal(-1);
expect(cbi('dog', 'cat')).to.equal(1);
expect(cbi('cat', 'cat')).to.equal(1);

// "{ cat: [ [ 'dog', 1" is less than "{ cat [ [ 'dog', 2"
expect(cbi({'cat': [['dog', 1]]}, {'cat': [['dog', 2]]})).to.equal(-1);
expect(cbi({'cat': [['dog', 2]]}, {'cat': [['dog', 1]]})).to.equal(1);

if (typeof Symbol === 'function') {
// "Symbol(c" is less than "Symbol(d"
expect(cbi(Symbol('cat'), Symbol('dog'))).to.equal(-1);
expect(cbi(Symbol('dog'), Symbol('cat'))).to.equal(1);
}
});
});
});

0 comments on commit 7b228ae

Please sign in to comment.