diff --git a/chai.js b/chai.js index a2689f327..67b70508c 100644 --- a/chai.js +++ b/chai.js @@ -14,7 +14,7 @@ var used = []; * Chai version */ -exports.version = '4.0.2'; +exports.version = '4.1.0'; /*! * Assertion Error @@ -691,6 +691,16 @@ module.exports = function (chai, _) { * * expect({a: 1, b: 2, c: 3}).to.include({a: 1, b: 2}); * + * When the target is a Set or WeakSet, `.include` asserts that the given `val` is a + * member of the target. SameValueZero equality algorithm is used. + * + * expect(new Set([1, 2])).to.include(2); + * + * When the target is a Map, `.include` asserts that the given `val` is one of + * the values of the target. SameValueZero equality algorithm is used. + * + * expect(new Map([['a', 1], ['b', 2]])).to.include(2); + * * Because `.include` does different things based on the target's type, it's * important to check the target's type before using `.include`. See the `.a` * doc for info on testing a target's type. @@ -699,8 +709,8 @@ module.exports = function (chai, _) { * * By default, strict (`===`) equality is used to compare array members and * object properties. Add `.deep` earlier in the chain to use deep equality - * instead. See the `deep-eql` project page for info on the deep equality - * algorithm: https://github.com/chaijs/deep-eql. + * instead (WeakSet targets are not supported). See the `deep-eql` project + * page for info on the deep equality algorithm: https://github.com/chaijs/deep-eql. * * // Target array deeply (but not strictly) includes `{a: 1}` * expect([{a: 1}]).to.deep.include({a: 1}); @@ -810,25 +820,24 @@ module.exports = function (chai, _) { * @api public */ - function includeChainingBehavior () { - flag(this, 'contains', true); + function SameValueZero(a, b) { + return (_.isNaN(a) && _.isNaN(b)) || a === b; } - function isDeepIncluded (arr, val) { - return arr.some(function (arrVal) { - return _.eql(arrVal, val); - }); + function includeChainingBehavior () { + flag(this, 'contains', true); } function include (val, msg) { if (msg) flag(this, 'message', msg); - _.expectTypes(this, ['array', 'object', 'string'], flag(this, 'ssfi')); + _.expectTypes(this, [ + 'array', 'object', 'string', + 'map', 'set', 'weakset', + ]); var obj = flag(this, 'object') - , objType = _.type(obj).toLowerCase() - , isDeep = flag(this, 'deep') - , descriptor = isDeep ? 'deep ' : ''; + , objType = _.type(obj).toLowerCase(); // This block is for asserting a subset of properties in an object. if (objType === 'object') { @@ -865,10 +874,62 @@ module.exports = function (chai, _) { return; } - // Assert inclusion in an array or substring in a string. + var isDeep = flag(this, 'deep') + , descriptor = isDeep ? 'deep ' : '' + , included = false; + + switch (objType) { + case 'string': + included = obj.indexOf(val) !== -1; + break; + + case 'weakset': + if (isDeep) { + var flagMsg = flag(this, 'message') + , ssfi = flag(this, 'ssfi'); + flagMsg = flagMsg ? flagMsg + ': ' : ''; + + throw new AssertionError( + flagMsg + 'unable to use .deep.include with WeakSet', + undefined, + ssfi + ); + } + + included = obj.has(val); + break; + + case 'map': + var isEql = isDeep ? _.eql : SameValueZero; + obj.forEach(function (item) { + included = included || isEql(item, val); + }); + break; + + case 'set': + if (isDeep) { + obj.forEach(function (item) { + included = included || _.eql(item, val); + }); + } else { + included = obj.has(val); + } + break; + + case 'array': + if (isDeep) { + included = obj.some(function (item) { + return _.eql(item, val); + }) + } else { + included = obj.indexOf(val) !== -1; + } + break; + } + + // Assert inclusion in collection or substring in a string. this.assert( - objType === 'string' || !isDeep ? ~obj.indexOf(val) - : isDeepIncluded(obj, val) + included , 'expected #{this} to ' + descriptor + 'include ' + _.inspect(val) , 'expected #{this} to not ' + descriptor + 'include ' + _.inspect(val)); } @@ -1385,7 +1446,7 @@ module.exports = function (chai, _) { /** * ### .above(n[, msg]) * - * Asserts that the target is a number greater than the given number `n`. + * Asserts that the target is a number or a date greater than the given number or date `n` respectively. * However, it's often best to assert that the target is equal to its expected * value. * @@ -1430,21 +1491,29 @@ module.exports = function (chai, _) { var obj = flag(this, 'object') , doLength = flag(this, 'doLength') , flagMsg = flag(this, 'message') - , ssfi = flag(this, 'ssfi'); + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , shouldThrow = true; if (doLength) { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to above must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to above must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { - new Assertion(obj, flagMsg, ssfi, true).is.a('number'); + shouldThrow = false; } - if (typeof n !== 'number') { - flagMsg = flagMsg ? flagMsg + ': ' : ''; - throw new AssertionError( - flagMsg + 'the argument to above must be a number', - undefined, - ssfi - ); + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); } if (doLength) { @@ -1459,8 +1528,9 @@ module.exports = function (chai, _) { } else { this.assert( obj > n - , 'expected #{this} to be above ' + n - , 'expected #{this} to be at most ' + n + , 'expected #{this} to be above #{exp}' + , 'expected #{this} to be at most #{exp}' + , n ); } } @@ -1472,8 +1542,8 @@ module.exports = function (chai, _) { /** * ### .least(n[, msg]) * - * Asserts that the target is a number greater than or equal to the given - * number `n`. However, it's often best to assert that the target is equal to + * Asserts that the target is a number or a date greater than or equal to the given + * number or date `n` respectively. However, it's often best to assert that the target is equal to * its expected value. * * expect(2).to.equal(2); // Recommended @@ -1517,21 +1587,29 @@ module.exports = function (chai, _) { var obj = flag(this, 'object') , doLength = flag(this, 'doLength') , flagMsg = flag(this, 'message') - , ssfi = flag(this, 'ssfi'); + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , shouldThrow = true; if (doLength) { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to least must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to least must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { - new Assertion(obj, flagMsg, ssfi, true).is.a('number'); + shouldThrow = false; } - if (typeof n !== 'number') { - flagMsg = flagMsg ? flagMsg + ': ' : ''; - throw new AssertionError( - flagMsg + 'the argument to least must be a number', - undefined, - ssfi - ); + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); } if (doLength) { @@ -1546,8 +1624,9 @@ module.exports = function (chai, _) { } else { this.assert( obj >= n - , 'expected #{this} to be at least ' + n - , 'expected #{this} to be below ' + n + , 'expected #{this} to be at least #{exp}' + , 'expected #{this} to be below #{exp}' + , n ); } } @@ -1558,7 +1637,7 @@ module.exports = function (chai, _) { /** * ### .below(n[, msg]) * - * Asserts that the target is a number less than the given number `n`. + * Asserts that the target is a number or a date less than the given number or date `n` respectively. * However, it's often best to assert that the target is equal to its expected * value. * @@ -1603,21 +1682,29 @@ module.exports = function (chai, _) { var obj = flag(this, 'object') , doLength = flag(this, 'doLength') , flagMsg = flag(this, 'message') - , ssfi = flag(this, 'ssfi'); + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , shouldThrow = true; if (doLength) { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to below must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to below must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { - new Assertion(obj, flagMsg, ssfi, true).is.a('number'); + shouldThrow = false; } - if (typeof n !== 'number') { - flagMsg = flagMsg ? flagMsg + ': ' : ''; - throw new AssertionError( - flagMsg + 'the argument to below must be a number', - undefined, - ssfi - ); + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); } if (doLength) { @@ -1632,8 +1719,9 @@ module.exports = function (chai, _) { } else { this.assert( obj < n - , 'expected #{this} to be below ' + n - , 'expected #{this} to be at least ' + n + , 'expected #{this} to be below #{exp}' + , 'expected #{this} to be at least #{exp}' + , n ); } } @@ -1645,8 +1733,8 @@ module.exports = function (chai, _) { /** * ### .most(n[, msg]) * - * Asserts that the target is a number less than or equal to the given number - * `n`. However, it's often best to assert that the target is equal to its + * Asserts that the target is a number or a date less than or equal to the given number + * or date `n` respectively. However, it's often best to assert that the target is equal to its * expected value. * * expect(1).to.equal(1); // Recommended @@ -1689,21 +1777,29 @@ module.exports = function (chai, _) { var obj = flag(this, 'object') , doLength = flag(this, 'doLength') , flagMsg = flag(this, 'message') - , ssfi = flag(this, 'ssfi'); + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , nType = _.type(n).toLowerCase() + , shouldThrow = true; if (doLength) { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && nType !== 'date')) { + errorMessage = msgPrefix + 'the argument to most must be a date'; + } else if (nType !== 'number' && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the argument to most must be a number'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { - new Assertion(obj, flagMsg, ssfi, true).is.a('number'); + shouldThrow = false; } - if (typeof n !== 'number') { - flagMsg = flagMsg ? flagMsg + ': ' : ''; - throw new AssertionError( - flagMsg + 'the argument to most must be a number', - undefined, - ssfi - ); + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); } if (doLength) { @@ -1718,8 +1814,9 @@ module.exports = function (chai, _) { } else { this.assert( obj <= n - , 'expected #{this} to be at most ' + n - , 'expected #{this} to be above ' + n + , 'expected #{this} to be at most #{exp}' + , 'expected #{this} to be above #{exp}' + , n ); } } @@ -1730,8 +1827,8 @@ module.exports = function (chai, _) { /** * ### .within(start, finish[, msg]) * - * Asserts that the target is a number greater than or equal to the given - * number `start`, and less than or equal to the given number `finish`. + * Asserts that the target is a number or a date greater than or equal to the given + * number or date `start`, and less than or equal to the given number or date `finish` respectively. * However, it's often best to assert that the target is equal to its expected * value. * @@ -1773,24 +1870,35 @@ module.exports = function (chai, _) { Assertion.addMethod('within', function (start, finish, msg) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') - , range = start + '..' + finish , doLength = flag(this, 'doLength') , flagMsg = flag(this, 'message') - , ssfi = flag(this, 'ssfi'); + , msgPrefix = ((flagMsg) ? flagMsg + ': ' : '') + , ssfi = flag(this, 'ssfi') + , objType = _.type(obj).toLowerCase() + , startType = _.type(start).toLowerCase() + , finishType = _.type(finish).toLowerCase() + , shouldThrow = true + , range = (startType === 'date' && finishType === 'date') + ? start.toUTCString() + '..' + finish.toUTCString() + : start + '..' + finish; if (doLength) { new Assertion(obj, flagMsg, ssfi, true).to.have.property('length'); + } + + if (!doLength && (objType === 'date' && (startType !== 'date' || finishType !== 'date'))) { + errorMessage = msgPrefix + 'the arguments to within must be dates'; + } else if ((startType !== 'number' || finishType !== 'number') && (doLength || objType === 'number')) { + errorMessage = msgPrefix + 'the arguments to within must be numbers'; + } else if (!doLength && (objType !== 'date' && objType !== 'number')) { + var printObj = (objType === 'string') ? "'" + obj + "'" : obj; + errorMessage = msgPrefix + 'expected ' + printObj + ' to be a number or a date'; } else { - new Assertion(obj, flagMsg, ssfi, true).is.a('number'); + shouldThrow = false; } - if (typeof start !== 'number' || typeof finish !== 'number') { - flagMsg = flagMsg ? flagMsg + ': ' : ''; - throw new AssertionError( - flagMsg + 'the arguments to within must be numbers', - undefined, - ssfi - ); + if (shouldThrow) { + throw new AssertionError(errorMessage, undefined, ssfi); } if (doLength) { @@ -2005,6 +2113,7 @@ module.exports = function (chai, _) { var isNested = flag(this, 'nested') , isOwn = flag(this, 'own') , flagMsg = flag(this, 'message') + , obj = flag(this, 'object') , ssfi = flag(this, 'ssfi'); if (isNested && isOwn) { @@ -2016,9 +2125,17 @@ module.exports = function (chai, _) { ); } + if (obj === null || obj === undefined) { + flagMsg = flagMsg ? flagMsg + ': ' : ''; + throw new AssertionError( + flagMsg + 'Target cannot be null or undefined.', + undefined, + ssfi + ); + } + var isDeep = flag(this, 'deep') , negate = flag(this, 'negate') - , obj = flag(this, 'object') , pathInfo = isNested ? _.getPathInfo(obj, name) : null , value = isNested ? pathInfo.value : obj[name]; @@ -7720,8 +7837,6 @@ module.exports = function compareByInspect(a, b) { * * @param {Mixed} obj constructed Assertion * @param {Array} type A list of allowed types for this assertion - * @param {Function} ssfi starting point for removing implementation frames from - * stack trace of AssertionError * @namespace Utils * @name expectTypes * @api public @@ -7731,8 +7846,9 @@ var AssertionError = require('assertion-error'); var flag = require('./flag'); var type = require('type-detect'); -module.exports = function expectTypes(obj, types, ssfi) { +module.exports = function expectTypes(obj, types) { var flagMsg = flag(obj, 'message'); + var ssfi = flag(obj, 'ssfi'); flagMsg = flagMsg ? flagMsg + ': ' : ''; @@ -7740,7 +7856,7 @@ module.exports = function expectTypes(obj, types, ssfi) { types = types.map(function (t) { return t.toLowerCase(); }); types.sort(); - // Transforms ['lorem', 'ipsum'] into 'a lirum, or an ipsum' + // Transforms ['lorem', 'ipsum'] into 'a lorem, or an ipsum' var str = types.map(function (t, index) { var art = ~[ 'a', 'e', 'i', 'o', 'u' ].indexOf(t.charAt(0)) ? 'an' : 'a'; var or = types.length > 1 && index === types.length - 1 ? 'or ' : ''; @@ -8257,7 +8373,7 @@ function formatValue(ctx, value, recurseTimes) { var container = document.createElementNS(ns, '_'); container.appendChild(value.cloneNode(false)); - html = container.innerHTML + var html = container.innerHTML .replace('><', '>' + value.innerHTML + '<'); container.innerHTML = ''; return html; diff --git a/lib/chai.js b/lib/chai.js index 50de77193..c1afbee92 100644 --- a/lib/chai.js +++ b/lib/chai.js @@ -10,7 +10,7 @@ var used = []; * Chai version */ -exports.version = '4.0.2'; +exports.version = '4.1.0'; /*! * Assertion Error diff --git a/package.json b/package.json index 5e65d6eef..d0c86fc69 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "Veselin Todorov ", "John Firebaugh " ], - "version": "4.0.2", + "version": "4.1.0", "repository": { "type": "git", "url": "https://github.com/chaijs/chai"