Add .deep.property for deep equality comparisons

This commit is contained in:
Grant Snodgrass 2016-07-25 20:44:58 -04:00
parent d4e8a4aee5
commit 6d6bf60dba
5 changed files with 239 additions and 4 deletions

View file

@ -70,9 +70,13 @@ module.exports = function (chai, _) {
/**
* ### .deep
*
* Sets the `deep` flag, later used by the `equal` assertion.
* Sets the `deep` flag, later used by the `equal`, `members`, and `property`
* assertions.
*
* expect(foo).to.deep.equal({ bar: 'baz' });
* const obj = {a: 1};
* expect(obj).to.deep.equal({a: 1});
* expect([obj]).to.have.deep.members([{a: 1}]);
* expect({foo: obj}).to.have.deep.property('foo', {a: 1});
*
* @name deep
* @namespace BDD
@ -850,6 +854,13 @@ module.exports = function (chai, _) {
* expect(obj).to.not.have.property('foo', 'baz');
* expect(obj).to.not.have.property('baz', 'bar');
*
* If the `deep` flag is set, asserts that the value of the property is deeply
* equal to `value`.
*
* var obj = { foo: { bar: 'baz' } };
* expect(obj).to.have.deep.property('foo', { bar: 'baz' });
* expect(obj).to.not.have.deep.property('foo', { bar: 'quux' });
*
* If the `nested` flag is set, you can use dot- and bracket-notation for
* nested references into objects and arrays.
*
@ -861,6 +872,11 @@ module.exports = function (chai, _) {
* expect(deepObj).to.have.nested.property('teas[1]', 'matcha');
* expect(deepObj).to.have.nested.property('teas[2].tea', 'konacha');
*
* The `deep` and `nested` flags can be combined.
*
* expect({ foo: { bar: { baz: 'quux' } } })
* .to.have.deep.nested.property('foo.bar', { baz: 'quux' });
*
* You can also use an array as the starting point of a `nested.property`
* assertion, or traverse nested arrays.
*
@ -900,6 +916,7 @@ module.exports = function (chai, _) {
* expect(deepCss).to.have.nested.property('\\.link.\\[target\\]', 42);
*
* @name property
* @alias deep.property
* @alias nested.property
* @param {String} name
* @param {Mixed} value (optional)
@ -913,7 +930,10 @@ module.exports = function (chai, _) {
if (msg) flag(this, 'message', msg);
var isNested = !!flag(this, 'nested')
, descriptor = isNested ? 'nested property ' : 'property '
, isDeep = !!flag(this, 'deep')
, descriptor = (isDeep ? 'deep ' : '')
+ (isNested ? 'nested ' : '')
+ 'property '
, negate = flag(this, 'negate')
, obj = flag(this, 'object')
, pathInfo = isNested ? _.getPathInfo(name, obj) : null
@ -938,7 +958,7 @@ module.exports = function (chai, _) {
if (arguments.length > 1) {
this.assert(
hasProperty && val === value
hasProperty && (isDeep ? _.eql(val, value) : val === value)
, 'expected #{this} to have a ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}'
, 'expected #{this} to not have a ' + descriptor + _.inspect(name) + ' of #{act}'
, val

View file

@ -1024,6 +1024,50 @@ module.exports = function (chai, util) {
new Assertion(obj, msg).to.not.have.property(prop, val);
};
/**
* ### .deepPropertyVal(object, property, value, [message])
*
* Asserts that `object` has a property named by `property` with a value given
* by `value`. Uses a deep equality check.
*
* assert.deepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'matcha' });
*
* @name deepPropertyVal
* @param {Object} object
* @param {String} property
* @param {Mixed} value
* @param {String} message
* @namespace Assert
* @api public
*/
assert.deepPropertyVal = function (obj, prop, val, msg) {
new Assertion(obj, msg).to.have.deep.property(prop, val);
};
/**
* ### .notDeepPropertyVal(object, property, value, [message])
*
* Asserts that `object` does _not_ have a property named by `property` with
* value given by `value`. Uses a deep equality check.
*
* assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { black: 'matcha' });
* assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'tea', { green: 'oolong' });
* assert.notDeepPropertyVal({ tea: { green: 'matcha' } }, 'coffee', { green: 'matcha' });
*
* @name notDeepPropertyVal
* @param {Object} object
* @param {String} property
* @param {Mixed} value
* @param {String} message
* @namespace Assert
* @api public
*/
assert.notDeepPropertyVal = function (obj, prop, val, msg) {
new Assertion(obj, msg).to.not.have.deep.property(prop, val);
};
/**
* ### .nestedPropertyVal(object, property, value, [message])
*
@ -1069,6 +1113,52 @@ module.exports = function (chai, util) {
new Assertion(obj, msg).to.not.have.nested.property(prop, val);
};
/**
* ### .deepNestedPropertyVal(object, property, value, [message])
*
* Asserts that `object` has a property named by `property` with a value given
* by `value`. `property` can use dot- and bracket-notation for nested
* reference. Uses a deep equality check.
*
* assert.deepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { matcha: 'yum' });
*
* @name deepNestedPropertyVal
* @param {Object} object
* @param {String} property
* @param {Mixed} value
* @param {String} message
* @namespace Assert
* @api public
*/
assert.deepNestedPropertyVal = function (obj, prop, val, msg) {
new Assertion(obj, msg).to.have.deep.nested.property(prop, val);
};
/**
* ### .notDeepNestedPropertyVal(object, property, value, [message])
*
* Asserts that `object` does _not_ have a property named by `property` with
* value given by `value`. `property` can use dot- and bracket-notation for
* nested reference. Uses a deep equality check.
*
* assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { oolong: 'yum' });
* assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.green', { matcha: 'yuck' });
* assert.notDeepNestedPropertyVal({ tea: { green: { matcha: 'yum' } } }, 'tea.black', { matcha: 'yum' });
*
* @name notDeepNestedPropertyVal
* @param {Object} object
* @param {String} property
* @param {Mixed} value
* @param {String} message
* @namespace Assert
* @api public
*/
assert.notDeepNestedPropertyVal = function (obj, prop, val, msg) {
new Assertion(obj, msg).to.not.have.deep.nested.property(prop, val);
}
/**
* ### .lengthOf(object, length, [message])
*

View file

@ -947,6 +947,7 @@ describe('assert', function () {
assert.notProperty(obj, 'foo.bar');
assert.notPropertyVal(simpleObj, 'foo', 'flow');
assert.notPropertyVal(simpleObj, 'flow', 'bar');
assert.notPropertyVal(obj, 'foo', {bar: 'baz'});
assert.notNestedProperty(obj, 'foo.baz');
assert.nestedPropertyVal(obj, 'foo.bar', 'baz');
assert.notNestedPropertyVal(obj, 'foo.bar', 'flow');
@ -989,6 +990,46 @@ describe('assert', function () {
}, "expected { foo: { bar: 'baz' } } to not have a nested property 'foo.bar' of 'baz'");
});
it('deepPropertyVal', function () {
var obj = {a: {b: 1}};
assert.deepPropertyVal(obj, 'a', {b: 1});
assert.notDeepPropertyVal(obj, 'a', {b: 7});
assert.notDeepPropertyVal(obj, 'a', {z: 1});
assert.notDeepPropertyVal(obj, 'z', {b: 1});
err(function () {
assert.deepPropertyVal(obj, 'a', {b: 7}, 'blah');
}, "blah: expected { a: { b: 1 } } to have a deep property 'a' of { b: 7 }, but got { b: 1 }");
err(function () {
assert.deepPropertyVal(obj, 'z', {b: 1}, 'blah');
}, "blah: expected { a: { b: 1 } } to have a deep property 'z'");
err(function () {
assert.notDeepPropertyVal(obj, 'a', {b: 1}, 'blah');
}, "blah: expected { a: { b: 1 } } to not have a deep property 'a' of { b: 1 }");
});
it('deepNestedPropertyVal', function () {
var obj = {a: {b: {c: 1}}};
assert.deepNestedPropertyVal(obj, 'a.b', {c: 1});
assert.notDeepNestedPropertyVal(obj, 'a.b', {c: 7});
assert.notDeepNestedPropertyVal(obj, 'a.b', {z: 1});
assert.notDeepNestedPropertyVal(obj, 'a.z', {c: 1});
err(function () {
assert.deepNestedPropertyVal(obj, 'a.b', {c: 7}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.b' of { c: 7 }, but got { c: 1 }");
err(function () {
assert.deepNestedPropertyVal(obj, 'a.z', {c: 1}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.z'");
err(function () {
assert.notDeepNestedPropertyVal(obj, 'a.b', {c: 1}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to not have a deep nested property 'a.b' of { c: 1 }");
});
it('throws / throw / Throw', function() {
['throws', 'throw', 'Throw'].forEach(function (throws) {
assert[throws](function() { throw new Error('foo'); });

View file

@ -534,6 +534,7 @@ describe('expect', function () {
expect('asd').to.have.property('constructor', String);
expect('test').to.not.have.property('length', 3);
expect('test').to.not.have.property('foo', 4);
expect({a: {b: 1}}).to.not.have.property('a', {b: 1});
var deepObj = {
green: { tea: 'matcha' }
@ -589,6 +590,26 @@ describe('expect', function () {
}, "blah: expected 'asd' to have a property 'constructor' of [Function: Number], but got [Function: String]");
});
it('deep.property(name, val)', function () {
var obj = {a: {b: 1}};
expect(obj).to.have.deep.property('a', {b: 1});
expect(obj).to.not.have.deep.property('a', {b: 7});
expect(obj).to.not.have.deep.property('a', {z: 1});
expect(obj).to.not.have.deep.property('z', {b: 1});
err(function () {
expect(obj).to.have.deep.property('a', {b: 7}, 'blah');
}, "blah: expected { a: { b: 1 } } to have a deep property 'a' of { b: 7 }, but got { b: 1 }");
err(function () {
expect(obj).to.have.deep.property('z', {b: 1}, 'blah');
}, "blah: expected { a: { b: 1 } } to have a deep property 'z'");
err(function () {
expect(obj).to.not.have.deep.property('a', {b: 1}, 'blah');
}, "blah: expected { a: { b: 1 } } to not have a deep property 'a' of { b: 1 }");
});
it('nested.property(name, val)', function(){
expect({ foo: { bar: 'baz' } })
.to.have.nested.property('foo.bar', 'baz');
@ -596,6 +617,7 @@ describe('expect', function () {
.to.not.have.nested.property('foo.bar', 'quux');
expect({ foo: { bar: 'baz' } })
.to.not.have.nested.property('foo.quux', 'baz');
expect({a: {b: {c: 1}}}).to.not.have.nested.property('a.b', {c: 1});
err(function(){
expect({ foo: { bar: 'baz' } })
@ -607,6 +629,26 @@ describe('expect', function () {
}, "blah: expected { foo: { bar: 'baz' } } to not have a nested property 'foo.bar' of 'baz'");
});
it('deep.nested.property(name, val)', function () {
var obj = {a: {b: {c: 1}}};
expect(obj).to.have.deep.nested.property('a.b', {c: 1});
expect(obj).to.not.have.deep.nested.property('a.b', {c: 7});
expect(obj).to.not.have.deep.nested.property('a.b', {z: 1});
expect(obj).to.not.have.deep.nested.property('a.z', {c: 1});
err(function () {
expect(obj).to.have.deep.nested.property('a.b', {c: 7}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.b' of { c: 7 }, but got { c: 1 }");
err(function () {
expect(obj).to.have.deep.nested.property('a.z', {c: 1}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.z'");
err(function () {
expect(obj).to.not.have.deep.nested.property('a.b', {c: 1}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to not have a deep nested property 'a.b' of { c: 1 }");
});
it('ownProperty(name)', function(){
expect('test').to.have.ownProperty('length');
expect('test').to.haveOwnProperty('length');

View file

@ -457,6 +457,7 @@ describe('should', function() {
({ 1: 1 }).should.have.property(1, 1);
'test'.should.not.have.property('length', 3);
'test'.should.not.have.property('foo', 4);
({a: {b: 1}}).should.not.have.property('a', {b: 1});
err(function(){
'asd'.should.have.property('length', 4, 'blah');
@ -471,10 +472,31 @@ describe('should', function() {
}, "blah: expected 'asd' to have a property 'constructor' of [Function: Number], but got [Function: String]");
});
it('deep.property(name, val)', function () {
var obj = {a: {b: 1}};
obj.should.have.deep.property('a', {b: 1});
obj.should.not.have.deep.property('a', {b: 7});
obj.should.not.have.deep.property('a', {z: 1});
obj.should.not.have.deep.property('z', {b: 1});
err(function () {
obj.should.have.deep.property('a', {b: 7}, 'blah');
}, "blah: expected { a: { b: 1 } } to have a deep property 'a' of { b: 7 }, but got { b: 1 }");
err(function () {
obj.should.have.deep.property('z', {b: 1}, 'blah');
}, "blah: expected { a: { b: 1 } } to have a deep property 'z'");
err(function () {
obj.should.not.have.deep.property('a', {b: 1}, 'blah');
}, "blah: expected { a: { b: 1 } } to not have a deep property 'a' of { b: 1 }");
});
it('nested.property(name, val)', function(){
({ foo: { bar: 'baz' } }).should.have.nested.property('foo.bar', 'baz');
({ foo: { bar: 'baz' } }).should.not.have.nested.property('foo.bar', 'quux');
({ foo: { bar: 'baz' } }).should.not.have.nested.property('foo.quux', 'baz');
({a: {b: {c: 1}}}).should.not.have.nested.property('a.b', {c: 1});
err(function(){
({ foo: { bar: 'baz' } }).should.have.nested.property('foo.bar', 'quux', 'blah');
@ -484,6 +506,26 @@ describe('should', function() {
}, "blah: expected { foo: { bar: 'baz' } } to not have a nested property 'foo.bar' of 'baz'");
});
it('deep.nested.property(name, val)', function () {
var obj = {a: {b: {c: 1}}};
obj.should.have.deep.nested.property('a.b', {c: 1});
obj.should.not.have.deep.nested.property('a.b', {c: 7});
obj.should.not.have.deep.nested.property('a.b', {z: 1});
obj.should.not.have.deep.nested.property('a.z', {c: 1});
err(function () {
obj.should.have.deep.nested.property('a.b', {c: 7}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.b' of { c: 7 }, but got { c: 1 }");
err(function () {
obj.should.have.deep.nested.property('a.z', {c: 1}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to have a deep nested property 'a.z'");
err(function () {
obj.should.not.have.deep.nested.property('a.b', {c: 1}, 'blah');
}, "blah: expected { a: { b: { c: 1 } } } to not have a deep nested property 'a.b' of { c: 1 }");
});
it('ownProperty(name)', function(){
'test'.should.have.ownProperty('length');
'test'.should.haveOwnProperty('length');