Merge pull request #761 from meeber/deep-include

Add `.deep.include` for deep equality comparisons
This commit is contained in:
Keith Cirkel 2016-08-16 21:04:56 +01:00 committed by GitHub
commit 49ed210e6f
5 changed files with 169 additions and 6 deletions

View file

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

View file

@ -551,6 +551,36 @@ describe('assert', function () {
}, "expected \'foobar\' to not include \'bar\'"); }, "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(){ it('keys(array|Object|arguments)', function(){
assert.hasAllKeys({ foo: 1 }, [ 'foo' ]); assert.hasAllKeys({ foo: 1 }, [ 'foo' ]);
assert.hasAllKeys({ foo: 1, bar: 2 }, [ 'foo', 'bar' ]); 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"); }, "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(){ it('keys(array|Object|arguments)', function(){
expect({ foo: 1 }).to.have.keys(['foo']); expect({ foo: 1 }).to.have.keys(['foo']);
expect({ foo: 1 }).have.keys({ 'foo': 6 }); 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"); }, "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(){ it('keys(array|Object|arguments)', function(){
({ foo: 1 }).should.have.keys(['foo']); ({ foo: 1 }).should.have.keys(['foo']);
({ foo: 1 }).should.have.keys({ 'foo': 6 }); ({ foo: 1 }).should.have.keys({ 'foo': 6 });