From b06d9619bb178cff5a09924a6fe8ffec154dbd90 Mon Sep 17 00:00:00 2001 From: Grant Snodgrass Date: Sat, 2 Feb 2019 00:37:22 +0000 Subject: [PATCH] WIP: Convert `.throw` to chainable method --- lib/chai/core/assertions.js | 64 +++++++++++++++++++++---------------- test/expect.js | 26 +++++++++++++-- 2 files changed, 60 insertions(+), 30 deletions(-) diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index 766e46b12..15345b73f 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -2532,14 +2532,16 @@ module.exports = function (chai, _) { } /** + * ### .throw * ### .throw([errLike[, errMsgMatcher[, msg]]]) * ### .throw([errMsgMatcher[, msg]]) * - * When invoked without any arguments, `.throw` invokes the target function - * and asserts that it throws. + * When used as a property assertion or when invoked without any arguments, + * `.throw` invokes the target function and asserts that it throws. * * var badFn = function () { throw TypeError('Illegal salmon!'); }; * + * expect(badFn).to.throw; * expect(badFn).to.throw(); * * When invoked with one argument, and it's a function (typically a @@ -2602,19 +2604,19 @@ module.exports = function (chai, _) { * * expect(badFn).to.throw(TypeError).with.property('code', 42); * - * When invoked without any arguments, `.throw` can be negated by adding - * `.not` earlier in the chain. However, `.throw` cannot be negated when - * invoking it with one or more arguments. Doing so would create uncertain - * expectations by asserting that the target function either doesn't throw, or - * that it throws but the thrown value doesn't match the criteria described by - * one or more of the arguments. + * When used as a property assertion or when invoked without any arguments, + * `.throw` can be negated by adding `.not` earlier in the chain. However, + * `.throw` cannot be negated when invoking it with one or more arguments. + * Doing so would create uncertain expectations by asserting that the target + * function either doesn't throw, or that it throws but the thrown value + * doesn't match the criteria described by one or more of the arguments. * * When the target function isn't expected to throw, it's best to assert * exactly that. * * var goodFn = function () {}; * - * expect(goodFn).to.not.throw(); + * expect(goodFn).to.not.throw; * * When the target function is expected to throw, it's often best to assert * that it throws a value that matches some criteria, rather than asserting @@ -2626,7 +2628,7 @@ module.exports = function (chai, _) { * expect(badFn).to.throw(TypeError('Illegal salmon!')); // Recommended * expect(badFn).to.not.throw(ReferenceError); // Not supported * // Supported but not recommended: - * expect(badFn).to.throw().but.not.an.instanceof(ReferenceError); + * expect(badFn).to.throw.but.not.an.instanceof(ReferenceError); * * `.throw` accepts an optional `msg` argument which is a custom error message * to show when the assertion fails. The message can also be given as the @@ -2636,7 +2638,7 @@ module.exports = function (chai, _) { * var goodFn = function () {}; * * expect(goodFn).to.throw(TypeError, 'x', 'nooo why fail??'); - * expect(goodFn, 'nooo why fail??').to.throw(); + * expect(goodFn, 'nooo why fail??').to.throw; * * Due to limitations in ES5, using `.throw` with arguments may not always * work as expected when using a transpiler such as Babel or TypeScript. In @@ -2653,14 +2655,14 @@ module.exports = function (chai, _) { * `fn` throws, provide `fn` instead of `fn()` as the target for the * assertion. * - * expect(fn).to.throw(); // Good! Tests `fn` as desired - * expect(fn()).to.throw(); // Bad! Tests result of `fn()`, not `fn` + * expect(fn).to.throw; // Good! Tests `fn` as desired + * expect(fn()).to.throw; // Bad! Tests result of `fn()`, not `fn` * * If you need to assert that your function `fn` throws when passed certain * arguments, then wrap a call to `fn` inside of another function. * - * expect(function () { fn(42); }).to.throw(); // Function expression - * expect(() => fn(42)).to.throw(); // ES6 arrow function + * expect(function () { fn(42); }).to.throw; // Function expression + * expect(() => fn(42)).to.throw; // ES6 arrow function * * Another common mistake is to provide an object method (or any stand-alone * function that relies on `this`) as the target of the assertion. Doing so is @@ -2670,9 +2672,9 @@ module.exports = function (chai, _) { * method or function call inside of another function. Another solution is to * use `bind`. * - * expect(function () { cat.meow(); }).to.throw(); // Function expression - * expect(() => cat.meow()).to.throw(); // ES6 arrow function - * expect(cat.meow.bind(cat)).to.throw(); // Bind + * expect(function () { cat.meow(); }).to.throw; // Function expression + * expect(() => cat.meow()).to.throw; // ES6 arrow function + * expect(cat.meow.bind(cat)).to.throw; // Bind * * Finally, it's worth mentioning that it's a best practice in JavaScript to * only throw `Error` instances. This includes `Error` instances created from @@ -2708,14 +2710,10 @@ module.exports = function (chai, _) { * @api public */ - function assertThrows (errLike, errMsgMatcher, msg) { - if (msg) flag(this, 'message', msg); + function assertThrows () { var obj = flag(this, 'object'); var ssfi = flag(this, 'ssfi'); var flagMsg = flag(this, 'message'); - var negate = flag(this, 'negate'); - - var cri = createErrCriteria(errLike, errMsgMatcher, negate); new Assertion(obj, flagMsg, ssfi, true).is.a('function'); @@ -2735,24 +2733,34 @@ module.exports = function (chai, _) { wasErrThrown, 'expected #{this} to throw', 'expected #{this} to not throw but #{act} was thrown', - errLike || errMsgMatcher, + wasErrThrown, caughtErr ); // Set the caught error as the target of future assertions. flag(this, 'object', caughtErr); + } + + function assertThrowsCriteria (errLike, errMsgMatcher, msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + var ssfi = flag(this, 'ssfi'); + var flagMsg = flag(this, 'message'); + var negate = flag(this, 'negate'); + + var cri = createErrCriteria(errLike, errMsgMatcher, negate); assertErrCriteria( - caughtErr, + obj, cri, flagMsg, ssfi ); } - Assertion.addMethod('throw', assertThrows); - Assertion.addMethod('throws', assertThrows); - Assertion.addMethod('Throw', assertThrows); + Assertion.addChainableMethod('throw', assertThrowsCriteria, assertThrows); + Assertion.addChainableMethod('throws', assertThrowsCriteria, assertThrows); + Assertion.addChainableMethod('Throw', assertThrowsCriteria, assertThrows); /** * ### .respondTo(method[, msg]) diff --git a/test/expect.js b/test/expect.js index f243050d4..023bec724 100644 --- a/test/expect.js +++ b/test/expect.js @@ -2992,6 +2992,28 @@ describe('expect', function () { var badFnObj = function () { throw {a: 1}; }; var badFnStr = function () { throw "Illegal salmon!"; }; + describe("used as a property-based assertion", function () { + describe("normal", function () { + it("passes when target throws", function () { + expect(badFnErr).to.throw; + }); + it("fails when target doesn't throw", function () { + err(function () { + expect(goodFn, 'blah').to.throw; + }, /^blah: expected \[Function(: goodFn)*\] to throw$/); + }); + }); + describe("negated", function () { + it("passes when target doesn't throw", function () { + expect(goodFn).to.not.throw; + }); + it("fails when target throws", function () { + err(function () { + expect(badFnErr, 'blah').to.not.throw; + }, /^blah: expected \[Function(: badFnErr)*\] to not throw but \[Error: Illegal salmon!\] was thrown$/); + }); + }); + }); describe("invoked with no args", function () { describe("normal", function () { it("passes when target throws", function () { @@ -3204,12 +3226,12 @@ describe('expect', function () { describe("negated", function () { it("fails when errLike is defined", function () { err(function () { - expect(badFnErr).to.not.throw(TypeError); + expect(goodFn).to.not.throw(TypeError); }, /^errLike and errMsgMatcher must both be undefined when negate is true$/, true); }); it("fails when errMsgMatcher is defined", function () { err(function () { - expect(badFnErr).to.not.throw(undefined, 'salmon'); + expect(goodFn).to.not.throw(undefined, 'salmon'); }, /^errLike and errMsgMatcher must both be undefined when negate is true$/, true); }); });