diff --git a/chai.js b/chai.js index 38efb8f8..0396cb17 100644 --- a/chai.js +++ b/chai.js @@ -150,6 +150,8 @@ module.exports = function (_chai, util) { * from within another assertion. It's also temporarily set to `true` before * an overwritten assertion gets called by the overwriting assertion. * + * - `eql`: This flag contains the deepEqual function to be used by the assertion. + * * @param {Mixed} obj target of the assertion * @param {String} msg (optional) custom error message * @param {Function} ssfi (optional) starting point for removing stack frames @@ -162,6 +164,7 @@ module.exports = function (_chai, util) { flag(this, 'lockSsfi', lockSsfi); flag(this, 'object', obj); flag(this, 'message', msg); + flag(this, 'eql', config.deepEqual ?? util.eql); return util.proxify(this); } @@ -365,7 +368,33 @@ module.exports = { * @api public */ - proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON'] + proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON'], + + /** + * ### config.deepEqual + * + * User configurable property, defines which a custom function to use for deepEqual + * comparisons. + * By default, the function used is the one from the `deep-eql` package without custom comparator. + * + * // use a custom comparator + * chai.config.deepEqual = (expected, actual) => { + * return chai.util.eql(expected, actual, { + * comparator: (expected, actual) => { + * // for non number comparison, use the default behavior + * if(typeof expected !== 'number') return null; + * // allow a difference of 10 between compared numbers + * return typeof actual === 'number' && Math.abs(actual - expected) < 10 + * } + * }) + * }; + * + * @param {Function} + * @api public + */ + + deepEqual: null + }; },{}],5:[function(require,module,exports){ @@ -849,7 +878,8 @@ module.exports = function (chai, _) { , negate = flag(this, 'negate') , ssfi = flag(this, 'ssfi') , isDeep = flag(this, 'deep') - , descriptor = isDeep ? 'deep ' : ''; + , descriptor = isDeep ? 'deep ' : '' + , isEql = isDeep ? flag(this, 'eql') : SameValueZero; flagMsg = flagMsg ? flagMsg + ': ' : ''; @@ -873,7 +903,6 @@ module.exports = function (chai, _) { break; case 'map': - var isEql = isDeep ? _.eql : SameValueZero; obj.forEach(function (item) { included = included || isEql(item, val); }); @@ -882,7 +911,7 @@ module.exports = function (chai, _) { case 'set': if (isDeep) { obj.forEach(function (item) { - included = included || _.eql(item, val); + included = included || isEql(item, val); }); } else { included = obj.has(val); @@ -892,7 +921,7 @@ module.exports = function (chai, _) { case 'array': if (isDeep) { included = obj.some(function (item) { - return _.eql(item, val); + return isEql(item, val); }) } else { included = obj.indexOf(val) !== -1; @@ -1464,8 +1493,9 @@ module.exports = function (chai, _) { function assertEql(obj, msg) { if (msg) flag(this, 'message', msg); + var eql = flag(this, 'eql'); this.assert( - _.eql(obj, flag(this, 'object')) + eql(obj, flag(this, 'object')) , 'expected #{this} to deeply equal #{exp}' , 'expected #{this} to not deeply equal #{exp}' , obj @@ -2233,7 +2263,8 @@ module.exports = function (chai, _) { var isDeep = flag(this, 'deep') , negate = flag(this, 'negate') , pathInfo = isNested ? _.getPathInfo(obj, name) : null - , value = isNested ? pathInfo.value : obj[name]; + , value = isNested ? pathInfo.value : obj[name] + , isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2;; var descriptor = ''; if (isDeep) descriptor += 'deep '; @@ -2260,7 +2291,7 @@ module.exports = function (chai, _) { if (arguments.length > 1) { this.assert( - hasProperty && (isDeep ? _.eql(val, value) : val === value) + hasProperty && isEql(val, value) , 'expected #{this} to have ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' , 'expected #{this} to not have ' + descriptor + _.inspect(name) + ' of #{act}' , val @@ -2408,9 +2439,10 @@ module.exports = function (chai, _) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object'); var actualDescriptor = Object.getOwnPropertyDescriptor(Object(obj), name); + var eql = flag(this, 'eql'); if (actualDescriptor && descriptor) { this.assert( - _.eql(descriptor, actualDescriptor) + eql(descriptor, actualDescriptor) , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to match ' + _.inspect(descriptor) + ', got ' + _.inspect(actualDescriptor) , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to not match ' + _.inspect(descriptor) , descriptor @@ -2764,7 +2796,8 @@ module.exports = function (chai, _) { var len = keys.length , any = flag(this, 'any') , all = flag(this, 'all') - , expected = keys; + , expected = keys + , isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2; if (!any && !all) { all = true; @@ -2774,11 +2807,7 @@ module.exports = function (chai, _) { if (any) { ok = expected.some(function(expectedKey) { return actual.some(function(actualKey) { - if (isDeep) { - return _.eql(expectedKey, actualKey); - } else { - return expectedKey === actualKey; - } + return isEql(expectedKey, actualKey); }); }); } @@ -2787,11 +2816,7 @@ module.exports = function (chai, _) { if (all) { ok = expected.every(function(expectedKey) { return actual.some(function(actualKey) { - if (isDeep) { - return _.eql(expectedKey, actualKey); - } else { - return expectedKey === actualKey; - } + return isEql(expectedKey, actualKey); }); }); @@ -3479,7 +3504,7 @@ module.exports = function (chai, _) { failNegateMsg = 'expected #{this} to not have the same ' + subject + ' as #{exp}'; } - var cmp = flag(this, 'deep') ? _.eql : undefined; + var cmp = flag(this, 'deep') ? flag(this, 'eql') : undefined; this.assert( isSubsetOf(subset, obj, cmp, contains, ordered) @@ -3535,7 +3560,8 @@ module.exports = function (chai, _) { , flagMsg = flag(this, 'message') , ssfi = flag(this, 'ssfi') , contains = flag(this, 'contains') - , isDeep = flag(this, 'deep'); + , isDeep = flag(this, 'deep') + , eql = flag(this, 'eql'); new Assertion(list, flagMsg, ssfi, true).to.be.an('array'); if (contains) { @@ -3549,7 +3575,7 @@ module.exports = function (chai, _) { } else { if (isDeep) { this.assert( - list.some(function(possibility) { return _.eql(expected, possibility) }) + list.some(function(possibility) { return eql(expected, possibility) }) , 'expected #{this} to deeply equal one of #{exp}' , 'expected #{this} to deeply equal one of #{exp}' , list diff --git a/lib/chai/assertion.js b/lib/chai/assertion.js index 8ed93ae7..b54b64cd 100644 --- a/lib/chai/assertion.js +++ b/lib/chai/assertion.js @@ -52,6 +52,8 @@ module.exports = function (_chai, util) { * from within another assertion. It's also temporarily set to `true` before * an overwritten assertion gets called by the overwriting assertion. * + * - `eql`: This flag contains the deepEqual function to be used by the assertion. + * * @param {Mixed} obj target of the assertion * @param {String} msg (optional) custom error message * @param {Function} ssfi (optional) starting point for removing stack frames @@ -64,6 +66,7 @@ module.exports = function (_chai, util) { flag(this, 'lockSsfi', lockSsfi); flag(this, 'object', obj); flag(this, 'message', msg); + flag(this, 'eql', config.deepEqual ?? util.eql); return util.proxify(this); } diff --git a/lib/chai/config.js b/lib/chai/config.js index 412a0c80..d7cbdb9d 100644 --- a/lib/chai/config.js +++ b/lib/chai/config.js @@ -90,5 +90,31 @@ module.exports = { * @api public */ - proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON'] + proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON'], + + /** + * ### config.deepEqual + * + * User configurable property, defines which a custom function to use for deepEqual + * comparisons. + * By default, the function used is the one from the `deep-eql` package without custom comparator. + * + * // use a custom comparator + * chai.config.deepEqual = (expected, actual) => { + * return chai.util.eql(expected, actual, { + * comparator: (expected, actual) => { + * // for non number comparison, use the default behavior + * if(typeof expected !== 'number') return null; + * // allow a difference of 10 between compared numbers + * return typeof actual === 'number' && Math.abs(actual - expected) < 10 + * } + * }) + * }; + * + * @param {Function} + * @api public + */ + + deepEqual: null + }; diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index 990664b3..19bdbbc5 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -478,7 +478,8 @@ module.exports = function (chai, _) { , negate = flag(this, 'negate') , ssfi = flag(this, 'ssfi') , isDeep = flag(this, 'deep') - , descriptor = isDeep ? 'deep ' : ''; + , descriptor = isDeep ? 'deep ' : '' + , isEql = isDeep ? flag(this, 'eql') : SameValueZero; flagMsg = flagMsg ? flagMsg + ': ' : ''; @@ -502,7 +503,6 @@ module.exports = function (chai, _) { break; case 'map': - var isEql = isDeep ? _.eql : SameValueZero; obj.forEach(function (item) { included = included || isEql(item, val); }); @@ -511,7 +511,7 @@ module.exports = function (chai, _) { case 'set': if (isDeep) { obj.forEach(function (item) { - included = included || _.eql(item, val); + included = included || isEql(item, val); }); } else { included = obj.has(val); @@ -521,7 +521,7 @@ module.exports = function (chai, _) { case 'array': if (isDeep) { included = obj.some(function (item) { - return _.eql(item, val); + return isEql(item, val); }) } else { included = obj.indexOf(val) !== -1; @@ -1093,8 +1093,9 @@ module.exports = function (chai, _) { function assertEql(obj, msg) { if (msg) flag(this, 'message', msg); + var eql = flag(this, 'eql'); this.assert( - _.eql(obj, flag(this, 'object')) + eql(obj, flag(this, 'object')) , 'expected #{this} to deeply equal #{exp}' , 'expected #{this} to not deeply equal #{exp}' , obj @@ -1862,7 +1863,8 @@ module.exports = function (chai, _) { var isDeep = flag(this, 'deep') , negate = flag(this, 'negate') , pathInfo = isNested ? _.getPathInfo(obj, name) : null - , value = isNested ? pathInfo.value : obj[name]; + , value = isNested ? pathInfo.value : obj[name] + , isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2;; var descriptor = ''; if (isDeep) descriptor += 'deep '; @@ -1889,7 +1891,7 @@ module.exports = function (chai, _) { if (arguments.length > 1) { this.assert( - hasProperty && (isDeep ? _.eql(val, value) : val === value) + hasProperty && isEql(val, value) , 'expected #{this} to have ' + descriptor + _.inspect(name) + ' of #{exp}, but got #{act}' , 'expected #{this} to not have ' + descriptor + _.inspect(name) + ' of #{act}' , val @@ -2037,9 +2039,10 @@ module.exports = function (chai, _) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object'); var actualDescriptor = Object.getOwnPropertyDescriptor(Object(obj), name); + var eql = flag(this, 'eql'); if (actualDescriptor && descriptor) { this.assert( - _.eql(descriptor, actualDescriptor) + eql(descriptor, actualDescriptor) , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to match ' + _.inspect(descriptor) + ', got ' + _.inspect(actualDescriptor) , 'expected the own property descriptor for ' + _.inspect(name) + ' on #{this} to not match ' + _.inspect(descriptor) , descriptor @@ -2393,7 +2396,8 @@ module.exports = function (chai, _) { var len = keys.length , any = flag(this, 'any') , all = flag(this, 'all') - , expected = keys; + , expected = keys + , isEql = isDeep ? flag(this, 'eql') : (val1, val2) => val1 === val2; if (!any && !all) { all = true; @@ -2403,11 +2407,7 @@ module.exports = function (chai, _) { if (any) { ok = expected.some(function(expectedKey) { return actual.some(function(actualKey) { - if (isDeep) { - return _.eql(expectedKey, actualKey); - } else { - return expectedKey === actualKey; - } + return isEql(expectedKey, actualKey); }); }); } @@ -2416,11 +2416,7 @@ module.exports = function (chai, _) { if (all) { ok = expected.every(function(expectedKey) { return actual.some(function(actualKey) { - if (isDeep) { - return _.eql(expectedKey, actualKey); - } else { - return expectedKey === actualKey; - } + return isEql(expectedKey, actualKey); }); }); @@ -3108,7 +3104,7 @@ module.exports = function (chai, _) { failNegateMsg = 'expected #{this} to not have the same ' + subject + ' as #{exp}'; } - var cmp = flag(this, 'deep') ? _.eql : undefined; + var cmp = flag(this, 'deep') ? flag(this, 'eql') : undefined; this.assert( isSubsetOf(subset, obj, cmp, contains, ordered) @@ -3164,7 +3160,8 @@ module.exports = function (chai, _) { , flagMsg = flag(this, 'message') , ssfi = flag(this, 'ssfi') , contains = flag(this, 'contains') - , isDeep = flag(this, 'deep'); + , isDeep = flag(this, 'deep') + , eql = flag(this, 'eql'); new Assertion(list, flagMsg, ssfi, true).to.be.an('array'); if (contains) { @@ -3178,7 +3175,7 @@ module.exports = function (chai, _) { } else { if (isDeep) { this.assert( - list.some(function(possibility) { return _.eql(expected, possibility) }) + list.some(function(possibility) { return eql(expected, possibility) }) , 'expected #{this} to deeply equal one of #{exp}' , 'expected #{this} to deeply equal one of #{exp}' , list diff --git a/test/configuration.js b/test/configuration.js index 71273141..e0e6689b 100644 --- a/test/configuration.js +++ b/test/configuration.js @@ -810,4 +810,23 @@ describe('configuration', function () { } }); }); + + describe('deepEqual', function() { + it('should use custom deepEqual function for deepEqual comparison', function(){ + chai.config.deepEqual = (expected, actual) => { + return chai.util.eql(expected, actual, { + comparator: (expected, actual) => { + // for non number comparison, use the default behavior + if(typeof expected !== 'number') return null; + // allow a difference of 10 between compared numbers + return typeof actual === 'number' && Math.abs(actual - expected) < 10 + } + }) + }; + assert.deepEqual({v: 1}, {v: 10}); + err(function() { + assert.deepEqual({v: 1}, {v: 100}); + }, "expected { v: 1 } to deeply equal { v: 100 }"); + }) + }) });