Fix bug when asserting some valid ES6 keys

- Resolution of https://github.com/chaijs/chai/issues/674
- Add compareByInspect utility for use with assertKeys sorts
- Add getOwnEnumerableProperties utility
- Add getOwnEnumerablePropertySymbols utility
- Add Symbol support to the inspect utility
- Add tests to utilities, should, expect, and assert
This commit is contained in:
meeber 2016-04-08 19:52:30 -04:00
parent 4f7284662c
commit e22835116c
10 changed files with 473 additions and 4 deletions

View file

@ -1170,7 +1170,7 @@ module.exports = function (chai, _) {
}
} else {
actual = Object.keys(obj);
actual = _.getOwnEnumerableProperties(obj);
switch (_.type(keys)) {
case 'array':
@ -1184,7 +1184,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');
@ -1248,8 +1251,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
);
}

View file

@ -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;
};

View file

@ -0,0 +1,29 @@
/*!
* Chai - getOwnEnumerableProperties utility
* Copyright(c) 2011-2016 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
/*!
* Module dependancies
*/
var getOwnEnumerablePropertySymbols = require('./getOwnEnumerablePropertySymbols');
/**
* ### .getOwnEnumerableProperties(object)
*
* This allows the retrieval of directly-owned enumerable property names and
* symbols of an object. This function is necessary because Object.keys only
* returns enumerable property names, not enumerable property symbols.
*
* @param {Object} object
* @returns {Array}
* @namespace Utils
* @name getOwnEnumerableProperties
* @api public
*/
module.exports = function getOwnEnumerableProperties(obj) {
return Object.keys(obj).concat(getOwnEnumerablePropertySymbols(obj));
};

View file

@ -0,0 +1,27 @@
/*!
* Chai - getOwnEnumerablePropertySymbols utility
* Copyright(c) 2011-2016 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/
/**
* ### .getOwnEnumerablePropertySymbols(object)
*
* This allows the retrieval of directly-owned enumerable property symbols of an
* object. This function is necessary because Object.getOwnPropertySymbols
* returns both enumerable and non-enumerable property symbols.
*
* @param {Object} object
* @returns {Array}
* @namespace Utils
* @name getOwnEnumerablePropertySymbols
* @api public
*/
module.exports = function getOwnEnumerablePropertySymbols(obj) {
if (typeof Object.getOwnPropertySymbols !== 'function') return [];
return Object.getOwnPropertySymbols(obj).filter(function (sym) {
return Object.getOwnPropertyDescriptor(obj, sym).enumerable;
});
};

View file

@ -128,3 +128,21 @@ exports.addChainableMethod = require('./addChainableMethod');
*/
exports.overwriteChainableMethod = require('./overwriteChainableMethod');
/*!
* Compare by inspect method
*/
exports.compareByInspect = require('./compareByInspect');
/*!
* Get own enumerable property symbols method
*/
exports.getOwnEnumerablePropertySymbols = require('./getOwnEnumerablePropertySymbols');
/*!
* Get own enumerable properties method
*/
exports.getOwnEnumerableProperties = require('./getOwnEnumerableProperties');

View file

@ -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) {

View file

@ -558,6 +558,42 @@ describe('assert', function () {
assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, { baz: 1, biz: 2, fake: 3 });
assert.doesNotHaveAnyKeys({ foo: 1, bar: 2 }, { baz: 1 });
var enumProp1 = 'enumProp1'
, enumProp2 = 'enumProp2'
, nonEnumProp = 'nonEnumProp'
, obj = {};
obj[enumProp1] = 'enumProp1';
obj[enumProp2] = 'enumProp2';
Object.defineProperty(obj, nonEnumProp, {
enumerable: false,
value: 'nonEnumProp'
});
assert.hasAllKeys(obj, [enumProp1, enumProp2]);
assert.doesNotHaveAllKeys(obj, [enumProp1, enumProp2, nonEnumProp]);
if (typeof Symbol === 'function') {
var sym1 = Symbol('sym1')
, sym2 = Symbol('sym2')
, sym3 = Symbol('sym3')
, str = 'str'
, obj = {};
obj[sym1] = 'sym1';
obj[sym2] = 'sym2';
obj[str] = 'str';
Object.defineProperty(obj, sym3, {
enumerable: false,
value: 'sym3'
});
assert.hasAllKeys(obj, [sym1, sym2, str]);
assert.doesNotHaveAllKeys(obj, [sym1, sym2, sym3, str]);
}
if (typeof Map !== 'undefined') {
var aKey = {thisIs: 'anExampleObject'};
var anotherKey = {doingThisBecauseOf: 'referential equality'};
@ -577,6 +613,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");
@ -637,6 +692,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");

View file

@ -743,6 +743,42 @@ 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 });
var enumProp1 = 'enumProp1'
, enumProp2 = 'enumProp2'
, nonEnumProp = 'nonEnumProp'
, obj = {};
obj[enumProp1] = 'enumProp1';
obj[enumProp2] = 'enumProp2';
Object.defineProperty(obj, nonEnumProp, {
enumerable: false,
value: 'nonEnumProp'
});
expect(obj).to.have.all.keys([enumProp1, enumProp2]);
expect(obj).to.not.have.all.keys([enumProp1, enumProp2, nonEnumProp]);
if (typeof Symbol === 'function') {
var sym1 = Symbol('sym1')
, sym2 = Symbol('sym2')
, sym3 = Symbol('sym3')
, str = 'str'
, obj = {};
obj[sym1] = 'sym1';
obj[sym2] = 'sym2';
obj[str] = 'str';
Object.defineProperty(obj, sym3, {
enumerable: false,
value: 'sym3'
});
expect(obj).to.have.all.keys([sym1, sym2, str]);
expect(obj).to.not.have.all.keys([sym1, sym2, sym3, str]);
}
if (typeof Map !== 'undefined') {
var aKey = {thisIs: 'anExampleObject'};
var anotherKey = {doingThisBecauseOf: 'referential equality'};
@ -766,6 +802,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");
@ -806,6 +864,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");

View file

@ -585,6 +585,42 @@ describe('should', function() {
({ 1: 1, 2: 2 }).should.have.any.keys(1, 3);
({ 1: 1, 2: 2 }).should.contain.keys(1);
var enumProp1 = 'enumProp1'
, enumProp2 = 'enumProp2'
, nonEnumProp = 'nonEnumProp'
, obj = {};
obj[enumProp1] = 'enumProp1';
obj[enumProp2] = 'enumProp2';
Object.defineProperty(obj, nonEnumProp, {
enumerable: false,
value: 'nonEnumProp'
});
obj.should.have.all.keys([enumProp1, enumProp2]);
obj.should.not.have.all.keys([enumProp1, enumProp2, nonEnumProp]);
if (typeof Symbol === 'function') {
var sym1 = Symbol('sym1')
, sym2 = Symbol('sym2')
, sym3 = Symbol('sym3')
, str = 'str'
, obj = {};
obj[sym1] = 'sym1';
obj[sym2] = 'sym2';
obj[str] = 'str';
Object.defineProperty(obj, sym3, {
enumerable: false,
value: 'sym3'
});
obj.should.have.all.keys([sym1, sym2, str]);
obj.should.not.have.all.keys([sym1, sym2, sym3, str]);
}
if (typeof Map !== 'undefined') {
var aKey = {thisIs: 'anExampleObject'};
var anotherKey = {doingThisBecauseOf: 'referential equality'};
@ -607,6 +643,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");
@ -647,6 +705,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");

View file

@ -630,6 +630,15 @@ describe('utilities', function () {
});
});
it('inspect Symbol', function () {
if (typeof Symbol !== 'function') return;
chai.use(function (_chai, _) {
expect(_.inspect(Symbol())).to.equal('Symbol()');
expect(_.inspect(Symbol('cat'))).to.equal('Symbol(cat)');
});
});
it('addChainableMethod', function () {
chai.use(function (_chai, _) {
_chai.Assertion.addChainableMethod('x',
@ -704,4 +713,119 @@ 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);
}
});
});
describe('getOwnEnumerablePropertySymbols', function () {
var gettem;
beforeEach(function () {
chai.use(function (_chai, _) {
gettem = _.getOwnEnumerablePropertySymbols;
});
});
it('returns an empty array if no symbols', function () {
var obj = {}
, cat = 'cat';
obj[cat] = 42;
expect(gettem(obj)).to.not.include(cat);
});
it('returns enumerable symbols only', function () {
if (typeof Symbol !== 'function') return;
var cat = Symbol('cat')
, dog = Symbol('dog')
, frog = Symbol('frog')
, cow = 'cow'
, obj = {};
obj[cat] = 'meow';
obj[dog] = 'woof';
Object.defineProperty(obj, frog, {
enumerable: false,
value: 'ribbit'
});
obj[cow] = 'moo';
expect(gettem(obj)).to.have.same.members([cat, dog]);
});
});
describe('getOwnEnumerableProperties', function () {
var gettem;
beforeEach(function () {
chai.use(function (_chai, _) {
gettem = _.getOwnEnumerableProperties;
});
});
it('returns enumerable property names if no symbols', function () {
var cat = 'cat'
, dog = 'dog'
, frog = 'frog'
, obj = {};
obj[cat] = 'meow'
obj[dog] = 'woof';
Object.defineProperty(obj, frog, {
enumerable: false,
value: 'ribbit'
});
expect(gettem(obj)).to.have.same.members([cat, dog]);
});
it('returns enumerable property names and symbols', function () {
if (typeof Symbol !== 'function') return;
var cat = Symbol('cat')
, dog = Symbol('dog')
, frog = Symbol('frog')
, bird = 'bird'
, cow = 'cow'
, obj = {};
obj[cat] = 'meow';
obj[dog] = 'woof';
obj[bird] = 'chirp';
Object.defineProperty(obj, frog, {
enumerable: false,
value: 'ribbit'
});
Object.defineProperty(obj, cow, {
enumerable: false,
value: 'moo'
});
expect(gettem(obj)).to.have.same.members([cat, dog, bird]);
});
});
});