Add ES6 collection support to include() (#994)

* fix error messages tests

* add tests

* add implementation

* performance tweaks

* add tests for SameValueZero

* drop weakmap support

* update docs
This commit is contained in:
Aleksey Shvayka 2017-06-24 01:35:07 +03:00 committed by Lucas Fernandes da Costa
parent 3c932e21e6
commit 2eddd79002
4 changed files with 309 additions and 36 deletions

View file

@ -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.
@ -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});
@ -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') {
@ -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));
}

View file

@ -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();
@ -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 () {
@ -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()
@ -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');
@ -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 }");

View file

@ -1882,6 +1882,48 @@ describe('expect', function () {
expect({foo: obj1, bar: obj2}).to.not.include({foo: {a: 1}});
expect({foo: obj1, bar: obj2}).to.not.include({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);
map.set('c', -0);
map.set('d', NaN);
expect(map).to.include(val);
expect(map).to.not.include([{a: 1}]);
expect(map).to.include(2);
expect(map).to.not.include(3);
expect(map).to.include(0);
expect(map).to.include(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);
expect(set).to.include(val);
expect(set).to.not.include([{a: 1}]);
expect(set).to.include(2);
expect(set).to.not.include(3);
expect(set).to.include(0);
expect(set).to.include(NaN);
}
if (typeof WeakSet === 'function') {
var ws = new WeakSet();
var val = [{a: 1}];
ws.add(val);
expect(ws).to.include(val);
expect(ws).to.not.include([{a: 1}]);
expect(ws).to.not.include({});
}
if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol()
@ -1936,39 +1978,39 @@ describe('expect', function () {
err(function(){
expect(true).to.include(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(){
expect(true, 'blah').to.include(true);
}, "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(){
expect(42.0).to.include(42);
}, "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(){
expect(null).to.include(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(){
expect(undefined).to.include(42);
}, "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(){
expect(true).to.not.include(true);
}, "object tested must be an array, an object, or a string, but boolean given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but boolean given");
err(function(){
expect(42.0).to.not.include(42);
}, "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(){
expect(null).to.not.include(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(){
expect(undefined).to.not.include(42);
}, "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('deep.include()', function () {
@ -1984,6 +2026,26 @@ describe('expect', function () {
expect({foo: obj1, bar: obj2}).to.not.deep.include({baz: {a: 1}});
expect({foo: obj1, bar: obj2}).to.not.deep.include({foo: {a: 1}, bar: {b: 9}});
if (typeof Map === 'function') {
var map = new Map();
map.set(1, [{a: 1}]);
expect(map).to.deep.include([{a: 1}]);
}
if (typeof Set === 'function') {
var set = new Set();
set.add([{a: 1}]);
expect(set).to.deep.include([{a: 1}]);
}
if (typeof WeakSet === 'function') {
err(function() {
expect(new WeakSet()).to.deep.include({}, 'foo');
}, 'foo: unable to use .deep.include with WeakSet');
}
err(function () {
expect([obj1, obj2]).to.deep.include({a: 9}, 'blah');
}, "blah: expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");

View file

@ -1540,6 +1540,48 @@ describe('should', function() {
({foo: obj1, bar: obj2}).should.not.include({foo: {a: 1}});
({foo: obj1, bar: obj2}).should.not.include({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);
map.set('c', -0);
map.set('d', NaN);
map.should.include(val);
map.should.not.include([{a: 1}]);
map.should.include(2);
map.should.not.include(3);
map.should.include(0);
map.should.include(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);
set.should.include(val);
set.should.not.include([{a: 1}]);
set.should.include(2);
set.should.not.include(3);
set.should.include(0);
set.should.include(NaN);
}
if (typeof WeakSet === 'function') {
var ws = new WeakSet();
var val = [{a: 1}];
ws.add(val);
ws.should.include(val);
ws.should.not.include([{a: 1}]);
ws.should.not.include({});
}
if (typeof Symbol === 'function') {
var sym1 = Symbol()
, sym2 = Symbol()
@ -1582,19 +1624,19 @@ describe('should', function() {
err(function(){
(true).should.include(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(){
(42).should.include(4);
}, "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(){
(true).should.not.include(true);
}, "object tested must be an array, an object, or a string, but boolean given");
}, "object tested must be an array, a map, an object, a set, a string, or a weakset, but boolean given");
err(function(){
(42).should.not.include(4);
}, "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");
});
it('deep.include()', function () {
@ -1610,6 +1652,26 @@ describe('should', function() {
({foo: obj1, bar: obj2}).should.not.deep.include({baz: {a: 1}});
({foo: obj1, bar: obj2}).should.not.deep.include({foo: {a: 1}, bar: {b: 9}});
if (typeof Map === 'function') {
var map = new Map();
map.set(1, [{a: 1}]);
map.should.deep.include([{a: 1}]);
}
if (typeof Set === 'function') {
var set = new Set();
set.add([{a: 1}]);
set.should.deep.include([{a: 1}]);
}
if (typeof WeakSet === 'function') {
err(function() {
new WeakSet().should.deep.include({}, 'foo');
}, 'foo: unable to use .deep.include with WeakSet');
}
err(function () {
[obj1, obj2].should.deep.include({a: 9}, 'blah');
}, "blah: expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");