mirror of
https://github.com/chaijs/chai
synced 2024-11-15 08:17:14 +00:00
Merge pull request #761 from meeber/deep-include
Add `.deep.include` for deep equality comparisons
This commit is contained in:
commit
49ed210e6f
5 changed files with 169 additions and 6 deletions
|
@ -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);
|
||||||
|
|
|
@ -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])
|
||||||
*
|
*
|
||||||
|
|
|
@ -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' ]);
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
|
@ -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 });
|
||||||
|
|
Loading…
Reference in a new issue