Skip to content

Commit

Permalink
Add ES6 collection support to include() (#994)
Browse files Browse the repository at this point in the history
* fix error messages tests

* add tests

* add implementation

* performance tweaks

* add tests for SameValueZero

* drop weakmap support

* update docs
  • Loading branch information
shvaikalesh authored and lucasfcosta committed Jun 23, 2017
1 parent 3c932e2 commit 2eddd79
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 36 deletions.
91 changes: 76 additions & 15 deletions lib/chai/core/assertions.js
Expand Up @@ -330,6 +330,16 @@ module.exports = function (chai, _) {
*
* expect({a: 1, b: 2, c: 3}).to.include({a: 1, b: 2});
*
* When the target is a Set or WeakSet, `.include` asserts that the given `val` is a
* member of the target. SameValueZero equality algorithm is used.
*
* expect(new Set([1, 2])).to.include(2);
*
* When the target is a Map, `.include` asserts that the given `val` is one of
* the values of the target. SameValueZero equality algorithm is used.
*
* expect(new Map([['a', 1], ['b', 2]])).to.include(2);
*
* Because `.include` does different things based on the target's type, it's
* important to check the target's type before using `.include`. See the `.a`
* doc for info on testing a target's type.
Expand All @@ -338,8 +348,8 @@ module.exports = function (chai, _) {
*
* By default, strict (`===`) equality is used to compare array members and
* object properties. Add `.deep` earlier in the chain to use deep equality
* instead. See the `deep-eql` project page for info on the deep equality
* algorithm: https://github.com/chaijs/deep-eql.
* instead (WeakSet targets are not supported). See the `deep-eql` project
* page for info on the deep equality algorithm: https://github.com/chaijs/deep-eql.
*
* // Target array deeply (but not strictly) includes `{a: 1}`
* expect([{a: 1}]).to.deep.include({a: 1});
Expand Down Expand Up @@ -449,25 +459,24 @@ module.exports = function (chai, _) {
* @api public
*/

function includeChainingBehavior () {
flag(this, 'contains', true);
function SameValueZero(a, b) {
return (_.isNaN(a) && _.isNaN(b)) || a === b;
}

function isDeepIncluded (arr, val) {
return arr.some(function (arrVal) {
return _.eql(arrVal, val);
});
function includeChainingBehavior () {
flag(this, 'contains', true);
}

function include (val, msg) {
if (msg) flag(this, 'message', msg);

_.expectTypes(this, ['array', 'object', 'string']);
_.expectTypes(this, [
'array', 'object', 'string',
'map', 'set', 'weakset',
]);

var obj = flag(this, 'object')
, objType = _.type(obj).toLowerCase()
, isDeep = flag(this, 'deep')
, descriptor = isDeep ? 'deep ' : '';
, objType = _.type(obj).toLowerCase();

// This block is for asserting a subset of properties in an object.
if (objType === 'object') {
Expand Down Expand Up @@ -504,10 +513,62 @@ module.exports = function (chai, _) {
return;
}

// Assert inclusion in an array or substring in a string.
var isDeep = flag(this, 'deep')
, descriptor = isDeep ? 'deep ' : ''
, included = false;

switch (objType) {
case 'string':
included = obj.indexOf(val) !== -1;
break;

case 'weakset':
if (isDeep) {
var flagMsg = flag(this, 'message')
, ssfi = flag(this, 'ssfi');
flagMsg = flagMsg ? flagMsg + ': ' : '';

throw new AssertionError(
flagMsg + 'unable to use .deep.include with WeakSet',
undefined,
ssfi
);
}

included = obj.has(val);
break;

case 'map':
var isEql = isDeep ? _.eql : SameValueZero;
obj.forEach(function (item) {
included = included || isEql(item, val);
});
break;

case 'set':
if (isDeep) {
obj.forEach(function (item) {
included = included || _.eql(item, val);
});
} else {
included = obj.has(val);
}
break;

case 'array':
if (isDeep) {
included = obj.some(function (item) {
return _.eql(item, val);
})
} else {
included = obj.indexOf(val) !== -1;
}
break;
}

// Assert inclusion in collection or substring in a string.
this.assert(
objType === 'string' || !isDeep ? ~obj.indexOf(val)
: isDeepIncluded(obj, val)
included
, 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val)
, 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val));
}
Expand Down
104 changes: 96 additions & 8 deletions test/assert.js
Expand Up @@ -633,6 +633,42 @@ describe('assert', function () {
assert.include({foo: obj1, bar: obj2}, {foo: obj1});
assert.include({foo: obj1, bar: obj2}, {foo: obj1, bar: obj2});

if (typeof Map === 'function') {
var map = new Map();
var val = [{a: 1}];
map.set('a', val);
map.set('b', 2);
map.set('c', -0);
map.set('d', NaN);

assert.include(map, val);
assert.include(map, 2);
assert.include(map, 0);
assert.include(map, NaN);
}

if (typeof Set === 'function') {
var set = new Set();
var val = [{a: 1}];
set.add(val);
set.add(2);
set.add(-0);
set.add(NaN);

assert.include(set, val);
assert.include(set, 2);
assert.include(set, 0);
assert.include(set, NaN);
}

if (typeof WeakSet === 'function') {
var ws = new WeakSet();
var val = [{a: 1}];
ws.add(val);

assert.include(ws, val);
}

if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol();
Expand All @@ -653,19 +689,19 @@ describe('assert', function () {

err(function(){
assert.include(true, true, 'blah');
}, "blah: object tested must be an array, an object, or a string, but boolean given");
}, "blah: object tested must be an array, a map, an object, a set, a string, or a weakset, but boolean given");

err(function () {
assert.include(42, 'bar');
}, "object tested must be an array, an object, or a string, but number given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but number given");

err(function(){
assert.include(null, 42);
}, "object tested must be an array, an object, or a string, but null given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but null given");

err(function () {
assert.include(undefined, 'bar');
}, "object tested must be an array, an object, or a string, but undefined given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but undefined given");
});

it('notInclude', function () {
Expand All @@ -678,6 +714,38 @@ describe('assert', function () {
assert.notInclude({foo: obj1, bar: obj2}, {foo: {a: 1}});
assert.notInclude({foo: obj1, bar: obj2}, {foo: obj1, bar: {b: 2}});

if (typeof Map === 'function') {
var map = new Map();
var val = [{a: 1}];
map.set('a', val);
map.set('b', 2);

assert.notInclude(map, [{a: 1}]);
assert.notInclude(map, 3);
}

if (typeof Set === 'function') {
var set = new Set();
var val = [{a: 1}];
set.add(val);
set.add(2);

assert.include(set, val);
assert.include(set, 2);

assert.notInclude(set, [{a: 1}]);
assert.notInclude(set, 3);
}

if (typeof WeakSet === 'function') {
var ws = new WeakSet();
var val = [{a: 1}];
ws.add(val);

assert.notInclude(ws, [{a: 1}]);
assert.notInclude(ws, {});
}

if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol()
Expand All @@ -699,19 +767,19 @@ describe('assert', function () {

err(function(){
assert.notInclude(true, true, 'blah');
}, "blah: object tested must be an array, an object, or a string, but boolean given");
}, "blah: object tested must be an array, a map, an object, a set, a string, or a weakset, but boolean given");

err(function () {
assert.notInclude(42, 'bar');
}, "object tested must be an array, an object, or a string, but number given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but number given");

err(function(){
assert.notInclude(null, 42);
}, "object tested must be an array, an object, or a string, but null given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but null given");

err(function () {
assert.notInclude(undefined, 'bar');
}, "object tested must be an array, an object, or a string, but undefined given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but undefined given");

err(function () {
assert.notInclude('foobar', 'bar');
Expand All @@ -731,6 +799,26 @@ describe('assert', function () {
assert.notDeepInclude({foo: obj1, bar: obj2}, {baz: {a: 1}});
assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}});

if (typeof Map === 'function') {
var map = new Map();
map.set(1, [{a: 1}]);

assert.deepInclude(map, [{a: 1}]);
}

if (typeof Set === 'function') {
var set = new Set();
set.add([{a: 1}]);

assert.deepInclude(set, [{a: 1}]);
}

if (typeof WeakSet === 'function') {
err(function() {
assert.deepInclude(new WeakSet(), {}, 'foo');
}, 'foo: unable to use .deep.include with WeakSet');
}

err(function () {
assert.deepInclude([obj1, obj2], {a: 9}, 'blah');
}, "blah: expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");
Expand Down

0 comments on commit 2eddd79

Please sign in to comment.