From bde66072899d32ccc7dbeac52b5d5039e5e3a1c1 Mon Sep 17 00:00:00 2001 From: Sylvain Leroux Date: Fri, 5 Jan 2018 20:11:37 +0100 Subject: [PATCH] feat: Add the `.fail([message])` interface Fix #1116. The `assert.fail` interface should accept being called with only 1 arguments to fail with a custom message. --- chai.js | 108 +++++++++++++++++++++++------------ lib/chai/interface/assert.js | 15 +++++ lib/chai/interface/expect.js | 15 ++++- lib/chai/interface/should.js | 14 +++++ test/assert.js | 28 ++++++++- test/expect.js | 22 +++++-- test/should.js | 22 +++++-- 7 files changed, 179 insertions(+), 45 deletions(-) diff --git a/chai.js b/chai.js index 792edbae9..c4fb91789 100644 --- a/chai.js +++ b/chai.js @@ -355,7 +355,7 @@ module.exports = { * @api public */ - proxyExcludedKeys: ['then', 'inspect', 'toJSON'] + proxyExcludedKeys: ['then', 'catch', 'inspect', 'toJSON'] }; },{}],5:[function(require,module,exports){ @@ -571,7 +571,6 @@ module.exports = function (chai, _) { flag(this, 'all', false); }); - /** * ### .all * @@ -950,9 +949,9 @@ module.exports = function (chai, _) { /** * ### .ok * - * Asserts that the target is loosely (`==`) equal to `true`. However, it's - * often best to assert that the target is strictly (`===`) or deeply equal to - * its expected value. + * Asserts that the target is a truthy value (considered `true` in boolean context). + * However, it's often best to assert that the target is strictly (`===`) or + * deeply equal to its expected value. * * expect(1).to.equal(1); // Recommended * expect(1).to.be.ok; // Not recommended @@ -1503,6 +1502,7 @@ module.exports = function (chai, _) { , ssfi = flag(this, 'ssfi') , objType = _.type(obj).toLowerCase() , nType = _.type(n).toLowerCase() + , errorMessage , shouldThrow = true; if (doLength) { @@ -1599,6 +1599,7 @@ module.exports = function (chai, _) { , ssfi = flag(this, 'ssfi') , objType = _.type(obj).toLowerCase() , nType = _.type(n).toLowerCase() + , errorMessage , shouldThrow = true; if (doLength) { @@ -1694,6 +1695,7 @@ module.exports = function (chai, _) { , ssfi = flag(this, 'ssfi') , objType = _.type(obj).toLowerCase() , nType = _.type(n).toLowerCase() + , errorMessage , shouldThrow = true; if (doLength) { @@ -1789,6 +1791,7 @@ module.exports = function (chai, _) { , ssfi = flag(this, 'ssfi') , objType = _.type(obj).toLowerCase() , nType = _.type(n).toLowerCase() + , errorMessage , shouldThrow = true; if (doLength) { @@ -1885,6 +1888,7 @@ module.exports = function (chai, _) { , objType = _.type(obj).toLowerCase() , startType = _.type(start).toLowerCase() , finishType = _.type(finish).toLowerCase() + , errorMessage , shouldThrow = true , range = (startType === 'date' && finishType === 'date') ? start.toUTCString() + '..' + finish.toUTCString() @@ -2119,10 +2123,30 @@ module.exports = function (chai, _) { , isOwn = flag(this, 'own') , flagMsg = flag(this, 'message') , obj = flag(this, 'object') - , ssfi = flag(this, 'ssfi'); + , ssfi = flag(this, 'ssfi') + , nameType = typeof name; + + flagMsg = flagMsg ? flagMsg + ': ' : ''; + + if (isNested) { + if (nameType !== 'string') { + throw new AssertionError( + flagMsg + 'the argument to property must be a string when using nested syntax', + undefined, + ssfi + ); + } + } else { + if (nameType !== 'string' && nameType !== 'number' && nameType !== 'symbol') { + throw new AssertionError( + flagMsg + 'the argument to property must be a string, number, or symbol', + undefined, + ssfi + ); + } + } if (isNested && isOwn) { - flagMsg = flagMsg ? flagMsg + ': ' : ''; throw new AssertionError( flagMsg + 'The "nested" and "own" flags cannot be combined.', undefined, @@ -2131,7 +2155,6 @@ module.exports = function (chai, _) { } if (obj === null || obj === undefined) { - flagMsg = flagMsg ? flagMsg + ': ' : ''; throw new AssertionError( flagMsg + 'Target cannot be null or undefined.', undefined, @@ -2610,6 +2633,7 @@ module.exports = function (chai, _) { , isDeep = flag(this, 'deep') , str , deepStr = '' + , actual , ok = true , flagMsg = flag(this, 'message'); @@ -2626,7 +2650,6 @@ module.exports = function (chai, _) { if (keysType !== 'Array') { keys = Array.prototype.slice.call(arguments); } - } else { actual = _.getOwnEnumerableProperties(obj); @@ -2659,8 +2682,7 @@ module.exports = function (chai, _) { var len = keys.length , any = flag(this, 'any') , all = flag(this, 'all') - , expected = keys - , actual; + , expected = keys; if (!any && !all) { all = true; @@ -3362,7 +3384,7 @@ module.exports = function (chai, _) { var contains = flag(this, 'contains'); var ordered = flag(this, 'ordered'); - var subject, failMsg, failNegateMsg, lengthCheck; + var subject, failMsg, failNegateMsg; if (contains) { subject = ordered ? 'an ordered superset' : 'a superset'; @@ -3434,7 +3456,6 @@ module.exports = function (chai, _) { Assertion.addMethod('oneOf', oneOf); - /** * ### .change(subject[, prop[, msg]]) * @@ -4082,7 +4103,7 @@ module.exports = function (chai, _) { var obj = flag(this, 'object'); this.assert( - typeof obj === "number" && isFinite(obj) + typeof obj === 'number' && isFinite(obj) , 'expected #{this} to be a finite number' , 'expected #{this} to not be a finite number' ); @@ -4096,9 +4117,7 @@ module.exports = function (chai, _) { * MIT Licensed */ - module.exports = function (chai, util) { - /*! * Chai dependencies. */ @@ -4135,10 +4154,18 @@ module.exports = function (chai, util) { }; /** + * ### .fail([message]) * ### .fail(actual, expected, [message], [operator]) * * Throw a failure. Node.js `assert` module-compatible. * + * assert.fail(); + * assert.fail("custom error message"); + * assert.fail(1, 2); + * assert.fail(1, 2, "custom error message"); + * assert.fail(1, 2, "custom error message", ">"); + * assert.fail(1, 2, undefined, ">"); + * * @name fail * @param {Mixed} actual * @param {Mixed} expected @@ -4149,6 +4176,13 @@ module.exports = function (chai, util) { */ assert.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + // Comply with Node's fail([message]) interface + + message = actual; + actual = undefined; + } + message = message || 'assert.fail()'; throw new chai.AssertionError(message, { actual: actual @@ -6034,8 +6068,8 @@ module.exports = function (chai, util) { * If `errMsgMatcher` is provided, it also asserts that the error thrown will have a * message matching `errMsgMatcher`. * - * assert.throws(fn, 'function throws a reference error'); - * assert.throws(fn, /function throws a reference error/); + * assert.throws(fn, 'Error thrown must have this msg'); + * assert.throws(fn, /Error thrown must have a msg that matches this/); * assert.throws(fn, ReferenceError); * assert.throws(fn, errorInstance); * assert.throws(fn, ReferenceError, 'Error thrown must be a ReferenceError and have this msg'); @@ -7202,6 +7236,7 @@ module.exports = function (chai, util) { }; /** + * ### .fail([message]) * ### .fail(actual, expected, [message], [operator]) * * Throw a failure. @@ -7216,6 +7251,11 @@ module.exports = function (chai, util) { */ chai.expect.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + message = actual; + actual = undefined; + } + message = message || 'expect.fail()'; throw new chai.AssertionError(message, { actual: actual @@ -7270,6 +7310,7 @@ module.exports = function (chai, util) { var should = {}; /** + * ### .fail([message]) * ### .fail(actual, expected, [message], [operator]) * * Throw a failure. @@ -7284,6 +7325,11 @@ module.exports = function (chai, util) { */ should.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + message = actual; + actual = undefined; + } + message = message || 'should.fail()'; throw new chai.AssertionError(message, { actual: actual @@ -7586,8 +7632,6 @@ module.exports = function addChainableMethod(ctx, name, method, chainingBehavior }; },{"../../chai":2,"./addLengthGuard":10,"./flag":15,"./proxify":30,"./transferFlags":32}],10:[function(require,module,exports){ -var config = require('../config'); - var fnLengthDesc = Object.getOwnPropertyDescriptor(function () {}, 'length'); /*! @@ -7649,7 +7693,7 @@ module.exports = function addLengthGuard (fn, assertionName, isChainable) { return fn; }; -},{"../config":4}],11:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ /*! * Chai - addMethod utility * Copyright(c) 2012-2014 Jake Luer @@ -7977,7 +8021,6 @@ module.exports = function getEnumerableProperties(object) { var flag = require('./flag') , getActual = require('./getActual') - , inspect = require('./inspect') , objDisplay = require('./objDisplay'); /** @@ -8017,7 +8060,7 @@ module.exports = function getMessage(obj, args) { return flagMsg ? flagMsg + ': ' + msg : msg; }; -},{"./flag":15,"./getActual":16,"./inspect":23,"./objDisplay":26}],19:[function(require,module,exports){ +},{"./flag":15,"./getActual":16,"./objDisplay":26}],19:[function(require,module,exports){ /*! * Chai - getOwnEnumerableProperties utility * Copyright(c) 2011-2016 Jake Luer @@ -8488,7 +8531,6 @@ function formatValue(ctx, value, recurseTimes) { return reduceToSingleString(output, base, braces); } - function formatPrimitive(ctx, value) { switch (typeof value) { case 'undefined': @@ -8518,12 +8560,10 @@ function formatPrimitive(ctx, value) { } } - function formatError(value) { return '[' + Error.prototype.toString.call(value) + ']'; } - function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { var output = []; for (var i = 0, l = value.length; i < l; ++i) { @@ -8626,12 +8666,8 @@ function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { return name + ': ' + str; } - function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.length + 1; }, 0); @@ -9154,10 +9190,12 @@ function stringDistance(strA, strB, memo) { if (strA.length === 0 || strB.length === 0) { memo[strA.length][strB.length] = Math.max(strA.length, strB.length); } else { + var sliceA = strA.slice(0, -1); + var sliceB = strB.slice(0, -1); memo[strA.length][strB.length] = Math.min( - stringDistance(strA.slice(0, -1), strB, memo) + 1, - stringDistance(strA, strB.slice(0, -1), memo) + 1, - stringDistance(strA.slice(0, -1), strB.slice(0, -1), memo) + + stringDistance(sliceA, strB, memo) + 1, + stringDistance(strA, sliceB, memo) + 1, + stringDistance(sliceA, sliceB, memo) + (strA.slice(-1) === strB.slice(-1) ? 0 : 1) ); } @@ -9554,7 +9592,7 @@ FakeMap.prototype = { return key[this._key]; }, set: function setMap(key, value) { - if (!Object.isFrozen(key)) { + if (Object.isExtensible(key)) { Object.defineProperty(key, this._key, { value: value, configurable: true, @@ -10704,4 +10742,4 @@ module.exports = function typeDetect(obj) { module.exports.typeDetect = module.exports; },{}]},{},[1])(1) -}); +}); \ No newline at end of file diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js index 7772e2c4b..5f46c804c 100644 --- a/lib/chai/interface/assert.js +++ b/lib/chai/interface/assert.js @@ -41,10 +41,18 @@ module.exports = function (chai, util) { }; /** + * ### .fail([message]) * ### .fail(actual, expected, [message], [operator]) * * Throw a failure. Node.js `assert` module-compatible. * + * assert.fail(); + * assert.fail("custom error message"); + * assert.fail(1, 2); + * assert.fail(1, 2, "custom error message"); + * assert.fail(1, 2, "custom error message", ">"); + * assert.fail(1, 2, undefined, ">"); + * * @name fail * @param {Mixed} actual * @param {Mixed} expected @@ -55,6 +63,13 @@ module.exports = function (chai, util) { */ assert.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + // Comply with Node's fail([message]) interface + + message = actual; + actual = undefined; + } + message = message || 'assert.fail()'; throw new chai.AssertionError(message, { actual: actual diff --git a/lib/chai/interface/expect.js b/lib/chai/interface/expect.js index 8c34072fb..3c46c1a87 100644 --- a/lib/chai/interface/expect.js +++ b/lib/chai/interface/expect.js @@ -10,9 +10,17 @@ module.exports = function (chai, util) { }; /** + * ### .fail([message]) * ### .fail(actual, expected, [message], [operator]) * - * Throw a failure. + * Throw a failure. + * + * expect.fail(); + * expect.fail("custom error message"); + * expect.fail(1, 2); + * expect.fail(1, 2, "custom error message"); + * expect.fail(1, 2, "custom error message", ">"); + * expect.fail(1, 2, undefined, ">"); * * @name fail * @param {Mixed} actual @@ -24,6 +32,11 @@ module.exports = function (chai, util) { */ chai.expect.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + message = actual; + actual = undefined; + } + message = message || 'expect.fail()'; throw new chai.AssertionError(message, { actual: actual diff --git a/lib/chai/interface/should.js b/lib/chai/interface/should.js index d7525b955..5406e5e44 100644 --- a/lib/chai/interface/should.js +++ b/lib/chai/interface/should.js @@ -42,10 +42,19 @@ module.exports = function (chai, util) { var should = {}; /** + * ### .fail([message]) * ### .fail(actual, expected, [message], [operator]) * * Throw a failure. * + * should.fail(); + * should.fail("custom error message"); + * should.fail(1, 2); + * should.fail(1, 2, "custom error message"); + * should.fail(1, 2, "custom error message", ">"); + * should.fail(1, 2, undefined, ">"); + * + * * @name fail * @param {Mixed} actual * @param {Mixed} expected @@ -56,6 +65,11 @@ module.exports = function (chai, util) { */ should.fail = function (actual, expected, message, operator) { + if (arguments.length < 2) { + message = actual; + actual = undefined; + } + message = message || 'should.fail()'; throw new chai.AssertionError(message, { actual: actual diff --git a/test/assert.js b/test/assert.js index d23cb3709..354cbe34b 100644 --- a/test/assert.js +++ b/test/assert.js @@ -14,12 +14,38 @@ describe('assert', function () { }, "expected foo to equal `bar`"); }); + describe("fail", function() { + it('should accept a message as the 3rd argument', function () { + err(function() { + assert.fail(0, 1, 'this has failed'); + }, /this has failed/); + }); + + it('should accept a message as the only argument', function () { + err(function() { + assert.fail('this has failed'); + }, /this has failed/); + }); + + it('should produce a default message when called without any arguments', function () { + err(function() { + assert.fail(); + }, /assert\.fail()/); + }); + }); + it('fail', function () { chai.expect(function () { - assert.fail(0, 1, 'this has failed'); + assert.fail('this has failed'); }).to.throw(chai.AssertionError, /this has failed/); }); + it('fail', function () { + chai.expect(function () { + assert.fail(); + }).to.throw(chai.AssertionError, /assert\.fail/); + }); + it('isTrue', function () { assert.isTrue(true); diff --git a/test/expect.js b/test/expect.js index dd16994a0..8d658f2ff 100644 --- a/test/expect.js +++ b/test/expect.js @@ -236,10 +236,24 @@ describe('expect', function () { , 'of', 'same', 'but', 'does' ].forEach(test); }); - it('fail', function () { - err(function() { - expect.fail(0, 1, 'this has failed'); - }, /this has failed/); + describe("fail", function() { + it('should accept a message as the 3rd argument', function () { + err(function() { + expect.fail(0, 1, 'this has failed'); + }, /this has failed/); + }); + + it('should accept a message as the only argument', function () { + err(function() { + expect.fail('this has failed'); + }, /this has failed/); + }); + + it('should produce a default message when called without any arguments', function () { + err(function() { + expect.fail(); + }, /expect\.fail()/); + }); }); it('true', function(){ diff --git a/test/should.js b/test/should.js index 2b62af318..1eb10a46f 100644 --- a/test/should.js +++ b/test/should.js @@ -233,10 +233,24 @@ describe('should', function() { , 'of', 'same', 'but', 'does' ].forEach(test); }); - it('fail', function () { - err(function() { - should.fail(0, 1, 'this has failed'); - }, 'this has failed'); + describe("fail", function() { + it('should accept a message as the 3rd argument', function () { + err(function() { + should.fail(0, 1, 'this has failed'); + }, /this has failed/); + }); + + it('should accept a message as the only argument', function () { + err(function() { + should.fail('this has failed'); + }, /this has failed/); + }); + + it('should produce a default message when called without any arguments', function () { + err(function() { + should.fail(); + }, /should\.fail()/); + }); }); it('root exist', function () {