Add .deep.include for deep equality comparisons

This commit is contained in:
Grant Snodgrass 2016-08-14 16:02:26 -04:00
parent 38739f45fd
commit 7d1dd301c1
5 changed files with 169 additions and 6 deletions

View file

@ -71,11 +71,13 @@ module.exports = function (chai, _) {
/**
* ### .deep
*
* Sets the `deep` flag, later used by the `equal`, `members`, and `property`
* assertions.
* Sets the `deep` flag, later used by the `equal`, `include`, `members`, and
* `property` assertions.
*
* const obj = {a: 1};
* expect(obj).to.deep.equal({a: 1});
* expect([obj]).to.deep.include({a:1});
* expect({foo: obj}).to.deep.include({foo: {a:1}});
* expect([obj]).to.have.deep.members([{a: 1}]);
* expect({foo: obj}).to.have.deep.property('foo', {a: 1});
*
@ -239,6 +241,14 @@ module.exports = function (chai, _) {
* 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 the `deep` flag is set, deep equality is used instead. For instance:
*
* var obj1 = {a: 1}
* , obj2 = {b: 2};
* expect([obj1, obj2]).to.deep.include({a: 1});
* expect({foo: obj1, bar: obj2}).to.deep.include({foo: {a: 1}});
* expect({foo: obj1, bar: obj2}).to.deep.include({foo: {a: 1}, bar: {b: 2}});
*
* These assertions can also be used as property based language chains,
* enabling the `contains` flag for the `keys` assertion. For instance:
*
@ -248,6 +258,10 @@ module.exports = function (chai, _) {
* @alias contain
* @alias includes
* @alias contains
* @alias deep.include
* @alias deep.contain
* @alias deep.includes
* @alias deep.contains
* @param {Object|String|Number} obj
* @param {String} message _optional_
* @namespace BDD
@ -258,11 +272,19 @@ module.exports = function (chai, _) {
flag(this, 'contains', true);
}
function isDeepIncluded (arr, val) {
return arr.some(function (arrVal) {
return _.eql(arrVal, val);
});
}
function include (val, msg) {
_.expectTypes(this, ['array', 'object', 'string']);
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
var obj = flag(this, 'object')
, isDeep = flag(this, 'deep')
, descriptor = isDeep ? 'deep ' : '';
// This block is for asserting a subset of properties in an object.
if (_.type(obj) === 'object') {
@ -300,9 +322,10 @@ module.exports = function (chai, _) {
// Assert inclusion in an array or substring in a string.
this.assert(
typeof obj !== "undefined" && typeof obj !== "null" && ~obj.indexOf(val)
, 'expected #{this} to include ' + _.inspect(val)
, 'expected #{this} to not include ' + _.inspect(val));
typeof obj === 'string' || !isDeep ? ~obj.indexOf(val)
: isDeepIncluded(obj, val)
, 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val)
, 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val));
}
Assertion.addChainableMethod('include', include, includeChainingBehavior);

View file

@ -894,6 +894,56 @@ module.exports = function (chai, util) {
new Assertion(exp, msg, assert.notInclude).not.include(inc);
};
/**
* ### .deepInclude(haystack, needle, [message])
*
* Asserts that `haystack` includes `needle`. Can be used to assert the
* inclusion of a value in an array or a subset of properties in an object.
* Deep equality is used.
*
* var obj1 = {a: 1}
* , obj2 = {b: 2};
* assert.deepInclude([obj1, obj2], {a: 1});
* assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}});
* assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}});
*
* @name deepInclude
* @param {Array|String} haystack
* @param {Mixed} needle
* @param {String} message
* @namespace Assert
* @api public
*/
assert.deepInclude = function (exp, inc, msg) {
new Assertion(exp, msg, assert.include).deep.include(inc);
};
/**
* ### .notDeepInclude(haystack, needle, [message])
*
* Asserts that `haystack` does not include `needle`. Can be used to assert
* the absence of a value in an array or a subset of properties in an object.
* Deep equality is used.
*
* var obj1 = {a: 1}
* , obj2 = {b: 2};
* assert.notDeepInclude([obj1, obj2], {a: 9});
* assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 9}});
* assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}});
*
* @name notDeepInclude
* @param {Array|String} haystack
* @param {Mixed} needle
* @param {String} message
* @namespace Assert
* @api public
*/
assert.notDeepInclude = function (exp, inc, msg) {
new Assertion(exp, msg, assert.notInclude).not.deep.include(inc);
};
/**
* ### .match(value, regexp, [message])
*

View file

@ -551,6 +551,36 @@ describe('assert', function () {
}, "expected \'foobar\' to not include \'bar\'");
});
it('deepInclude and notDeepInclude', function () {
var obj1 = {a: 1}
, obj2 = {b: 2};
assert.deepInclude([obj1, obj2], {a: 1});
assert.notDeepInclude([obj1, obj2], {a: 9});
assert.notDeepInclude([obj1, obj2], {z: 1});
assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}});
assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}});
assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 9}});
assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {z: 1}});
assert.notDeepInclude({foo: obj1, bar: obj2}, {baz: {a: 1}});
assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}});
err(function () {
assert.deepInclude([obj1, obj2], {a: 9});
}, "expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");
err(function () {
assert.notDeepInclude([obj1, obj2], {a: 1});
}, "expected [ { a: 1 }, { b: 2 } ] to not deep include { a: 1 }");
err(function () {
assert.deepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 9}});
}, "expected { foo: { a: 1 }, bar: { b: 2 } } to have a deep property 'bar' of { b: 9 }, but got { b: 2 }");
err(function () {
assert.notDeepInclude({foo: obj1, bar: obj2}, {foo: {a: 1}, bar: {b: 2}});
}, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have a deep property 'foo' of { a: 1 }");
});
it('keys(array|Object|arguments)', function(){
assert.hasAllKeys({ foo: 1 }, [ 'foo' ]);
assert.hasAllKeys({ foo: 1, bar: 2 }, [ 'foo', 'bar' ]);

View file

@ -809,6 +809,36 @@ describe('expect', function () {
}, "object tested must be an array, an object, or a string, but undefined given");
});
it('deep.include()', function () {
var obj1 = {a: 1}
, obj2 = {b: 2};
expect([obj1, obj2]).to.deep.include({a: 1});
expect([obj1, obj2]).to.not.deep.include({a: 9});
expect([obj1, obj2]).to.not.deep.include({z: 1});
expect({foo: obj1, bar: obj2}).to.deep.include({foo: {a: 1}});
expect({foo: obj1, bar: obj2}).to.deep.include({foo: {a: 1}, bar: {b: 2}});
expect({foo: obj1, bar: obj2}).to.not.deep.include({foo: {a: 9}});
expect({foo: obj1, bar: obj2}).to.not.deep.include({foo: {z: 1}});
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}});
err(function () {
expect([obj1, obj2]).to.deep.include({a: 9});
}, "expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");
err(function () {
expect([obj1, obj2]).to.not.deep.include({a: 1});
}, "expected [ { a: 1 }, { b: 2 } ] to not deep include { a: 1 }");
err(function () {
expect({foo: obj1, bar: obj2}).to.deep.include({foo: {a: 1}, bar: {b: 9}});
}, "expected { foo: { a: 1 }, bar: { b: 2 } } to have a deep property 'bar' of { b: 9 }, but got { b: 2 }");
err(function () {
expect({foo: obj1, bar: obj2}).to.not.deep.include({foo: {a: 1}, bar: {b: 2}});
}, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have a deep property 'foo' of { a: 1 }");
});
it('keys(array|Object|arguments)', function(){
expect({ foo: 1 }).to.have.keys(['foo']);
expect({ foo: 1 }).have.keys({ 'foo': 6 });

View file

@ -681,6 +681,36 @@ describe('should', function() {
}, "object tested must be an array, an object, or a string, but number given");
});
it('deep.include()', function () {
var obj1 = {a: 1}
, obj2 = {b: 2};
[obj1, obj2].should.deep.include({a: 1});
[obj1, obj2].should.not.deep.include({a: 9});
[obj1, obj2].should.not.deep.include({z: 1});
({foo: obj1, bar: obj2}).should.deep.include({foo: {a: 1}});
({foo: obj1, bar: obj2}).should.deep.include({foo: {a: 1}, bar: {b: 2}});
({foo: obj1, bar: obj2}).should.not.deep.include({foo: {a: 9}});
({foo: obj1, bar: obj2}).should.not.deep.include({foo: {z: 1}});
({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}});
err(function () {
[obj1, obj2].should.deep.include({a: 9});
}, "expected [ { a: 1 }, { b: 2 } ] to deep include { a: 9 }");
err(function () {
[obj1, obj2].should.not.deep.include({a: 1});
}, "expected [ { a: 1 }, { b: 2 } ] to not deep include { a: 1 }");
err(function () {
({foo: obj1, bar: obj2}).should.deep.include({foo: {a: 1}, bar: {b: 9}});
}, "expected { foo: { a: 1 }, bar: { b: 2 } } to have a deep property 'bar' of { b: 9 }, but got { b: 2 }");
err(function () {
({foo: obj1, bar: obj2}).should.not.deep.include({foo: {a: 1}, bar: {b: 2}});
}, "expected { foo: { a: 1 }, bar: { b: 2 } } to not have a deep property 'foo' of { a: 1 }");
});
it('keys(array|Object|arguments)', function(){
({ foo: 1 }).should.have.keys(['foo']);
({ foo: 1 }).should.have.keys({ 'foo': 6 });