Skip to content

Commit

Permalink
Merge pull request #683 from lucasfcosta/throw-using-checkerror
Browse files Browse the repository at this point in the history
Throw using checkError module
  • Loading branch information
keithamus committed Jun 24, 2016
2 parents eb531ae + b9436a9 commit d08002e
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 106 deletions.
199 changes: 103 additions & 96 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1290,6 +1290,7 @@ module.exports = function (chai, _) {
*
* var err = new ReferenceError('This is a bad function.');
* var fn = function () { throw err; }
* expect(fn).to.throw();
* expect(fn).to.throw(ReferenceError);
* expect(fn).to.throw(Error);
* expect(fn).to.throw(/bad function/);
Expand All @@ -1306,130 +1307,136 @@ module.exports = function (chai, _) {
* @name throw
* @alias throws
* @alias Throw
* @param {ErrorConstructor} constructor
* @param {String|RegExp} expected error message
* @param {Error|ErrorConstructor} errorLike
* @param {String|RegExp} errMsgMatcher error message
* @param {String} message _optional_
* @see https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error#Error_types
* @returns error for chaining (null if no error)
* @namespace BDD
* @api public
*/

function assertThrows (constructor, errMsg, msg) {
function assertThrows (errorLike, errMsgMatcher, msg) {
if (msg) flag(this, 'message', msg);
var obj = flag(this, 'object');
var negate = flag(this, 'negate') || false;
new Assertion(obj, msg).is.a('function');

var thrown = false
, desiredError = null
, name = null
, thrownError = null;

if (arguments.length === 0) {
errMsg = null;
constructor = null;
} else if (constructor && (constructor instanceof RegExp || 'string' === typeof constructor)) {
errMsg = constructor;
constructor = null;
} else if (constructor && constructor instanceof Error) {
desiredError = constructor;
constructor = null;
errMsg = null;
} else if (typeof constructor === 'function') {
name = constructor.prototype.name;
if (!name || (name === 'Error' && constructor !== Error)) {
name = constructor.name || (new constructor()).name;
}
} else {
constructor = null;
if (errorLike instanceof RegExp || typeof errorLike === 'string') {
errMsgMatcher = errorLike;
errorLike = null;
}

var caughtErr;
try {
obj();
} catch (err) {
// first, check desired error
if (desiredError) {
this.assert(
err === desiredError
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}'
, (desiredError instanceof Error ? desiredError.toString() : desiredError)
, (err instanceof Error ? err.toString() : err)
);

flag(this, 'object', err);
return this;
caughtErr = err;
}

// If we have the negate flag enabled and at least one valid argument it means we do expect an error
// but we want it to match a given set of criteria
var everyArgIsUndefined = errorLike === undefined && errMsgMatcher === undefined;

// If we've got the negate flag enabled and both args, we should only fail if both aren't compatible
// See Issue #551 and PR #683@GitHub
var everyArgIsDefined = Boolean(errorLike && errMsgMatcher);
var errorLikeFail = false;
var errMsgMatcherFail = false;

// Checking if error was thrown
if (everyArgIsUndefined || !everyArgIsUndefined && !negate) {
// We need this to display results correctly according to their types
var errorLikeString = 'an error';
if (errorLike instanceof Error) {
errorLikeString = '#{exp}';
} else if (errorLike) {
errorLikeString = errorLike.name;
}

// next, check constructor
if (constructor) {
this.assert(
err instanceof constructor
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp} but #{act} was thrown'
, name
, (err instanceof Error ? err.toString() : err)
);

if (!errMsg) {
flag(this, 'object', err);
return this;
this.assert(
caughtErr
, 'expected #{this} to throw ' + errorLikeString
, 'expected #{this} to not throw an error but #{act} was thrown'
, errorLike && errorLike.toString()
, (caughtErr instanceof Error ?
caughtErr.toString() : (typeof caughtErr === 'string' ? caughtErr : caughtErr && caughtErr.name))
);
}

if (errorLike && caughtErr) {
// We should compare instances only if `errorLike` is an instance of `Error`
if (errorLike instanceof Error) {
var isCompatibleInstance = _.checkError.compatibleInstance(caughtErr, errorLike);

if (isCompatibleInstance === negate) {
// These checks were created to ensure we won't fail too soon when we've got both args and a negate
// See Issue #551 and PR #683@GitHub
if (everyArgIsDefined && negate) {
errorLikeFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr && !negate ? ' but #{act} was thrown' : '')
, errorLike.toString()
, caughtErr.toString()
);
}
}
}

// next, check message
var message = 'error' === _.type(err) && "message" in err
? err.message
: '' + err;

if ((message != null) && errMsg && errMsg instanceof RegExp) {
this.assert(
errMsg.exec(message)
, 'expected #{this} to throw error matching #{exp} but got #{act}'
, 'expected #{this} to throw error not matching #{exp}'
, errMsg
, message
);

flag(this, 'object', err);
return this;
} else if ((message != null) && errMsg && 'string' === typeof errMsg) {
this.assert(
~message.indexOf(errMsg)
, 'expected #{this} to throw error including #{exp} but got #{act}'
, 'expected #{this} to throw error not including #{act}'
, errMsg
, message
);

flag(this, 'object', err);
return this;
} else {
thrown = true;
thrownError = err;
var isCompatibleConstructor = _.checkError.compatibleConstructor(caughtErr, errorLike);
if (isCompatibleConstructor === negate) {
if (everyArgIsDefined && negate) {
errorLikeFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '')
, (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike))
, (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr))
);
}
}
}

var actuallyGot = ''
, expectedThrown = name !== null
? name
: desiredError
? '#{exp}' //_.inspect(desiredError)
: 'an error';
if (errMsgMatcher) {
// Here we check compatible messages
var placeholder = 'including';
if (errMsgMatcher instanceof RegExp) {
placeholder = 'matching'
}

if (thrown) {
actuallyGot = ' but #{act} was thrown'
var isCompatibleMessage = _.checkError.compatibleMessage(caughtErr, errMsgMatcher);
if (isCompatibleMessage === negate) {
if (everyArgIsDefined && negate) {
errMsgMatcherFail = true;
} else {
this.assert(
negate
, 'expected #{this} to throw error ' + placeholder + ' #{exp} but got #{act}'
, 'expected #{this} to throw error not ' + placeholder + ' #{exp}'
, errMsgMatcher
, _.checkError.getMessage(caughtErr)
);
}
}
}

this.assert(
thrown === true
, 'expected #{this} to throw ' + expectedThrown + actuallyGot
, 'expected #{this} to not throw ' + expectedThrown + actuallyGot
, (desiredError instanceof Error ? desiredError.toString() : desiredError)
, (thrownError instanceof Error ? thrownError.toString() : thrownError)
);
// If both assertions failed and both should've matched we throw an error
if (errorLikeFail && errMsgMatcherFail) {
this.assert(
negate
, 'expected #{this} to throw #{exp} but #{act} was thrown'
, 'expected #{this} to not throw #{exp}' + (caughtErr ? ' but #{act} was thrown' : '')
, (errorLike instanceof Error ? errorLike.toString() : errorLike && _.checkError.getConstructorName(errorLike))
, (caughtErr instanceof Error ? caughtErr.toString() : caughtErr && _.checkError.getConstructorName(caughtErr))
);
}

flag(this, 'object', thrownError);
flag(this, 'object', caughtErr);
};

Assertion.addMethod('throw', assertThrows);
Expand Down
6 changes: 6 additions & 0 deletions lib/chai/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,9 @@ exports.getOwnEnumerablePropertySymbols = require('./getOwnEnumerablePropertySym
*/

exports.getOwnEnumerableProperties = require('./getOwnEnumerableProperties');

/*!
* Checks error against a given set of criteria
*/

exports.checkError = require('check-error');
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
},
"dependencies": {
"assertion-error": "^1.0.1",
"check-error": "^1.0.1",
"deep-eql": "^0.1.3",
"type-detect": "^2.0.1"
},
Expand Down
7 changes: 7 additions & 0 deletions test/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -1163,6 +1163,9 @@ describe('expect', function () {
expect(badFn).to.throw(Error, /testing/);
expect(badFn).to.throw(Error, 'testing');

expect(badFn).to.not.throw(Error, 'I am the wrong error message');
expect(badFn).to.not.throw(TypeError, 'testing');

err(function(){
expect(goodFn).to.throw();
}, "expected [Function] to throw an error");
Expand Down Expand Up @@ -1234,6 +1237,10 @@ describe('expect', function () {
err(function () {
(customErrFn).should.not.throw();
}, "expected [Function] to not throw an error but 'CustomError: foo' was thrown");

err(function(){
expect(badFn).to.not.throw(Error, 'testing');
}, "expected [Function] to not throw 'Error' but 'Error: testing' was thrown");
});

it('respondTo', function(){
Expand Down
7 changes: 7 additions & 0 deletions test/should.js
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,9 @@ describe('should', function() {
should.throw(badFn, Error, /testing/);
should.throw(badFn, Error, 'testing');

(badFn).should.not.throw(Error, 'I am the wrong error message');
(badFn).should.not.throw(TypeError, 'testing');

err(function(){
(goodFn).should.throw();
}, "expected [Function] to throw an error");
Expand Down Expand Up @@ -1090,6 +1093,10 @@ describe('should', function() {
err(function () {
(customErrFn).should.not.throw();
}, "expected [Function] to not throw an error but 'CustomError: foo' was thrown");

err(function(){
(badFn).should.not.throw(Error, 'testing');
}, "expected [Function] to not throw 'Error' but 'Error: testing' was thrown");
});

it('respondTo', function(){
Expand Down
20 changes: 10 additions & 10 deletions test/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ describe('utilities', function () {
info.value.should.equal(obj.dimensions.units);
info.name.should.equal('units');
info.exists.should.be.true;
});
});

it('should handle non-existent property', function() {
var info = gpi('dimensions.size', obj);
Expand All @@ -118,7 +118,7 @@ describe('utilities', function () {
expect(info.value).to.be.undefined;
info.name.should.equal('size');
info.exists.should.be.false;
});
});

it('should handle array index', function() {
var info = gpi('primes[2]', obj);
Expand All @@ -127,7 +127,7 @@ describe('utilities', function () {
info.value.should.equal(obj.primes[2]);
info.name.should.equal(2);
info.exists.should.be.true;
});
});

it('should handle dimensional array', function() {
var info = gpi('dimensions.lengths[2][1]', obj);
Expand All @@ -136,7 +136,7 @@ describe('utilities', function () {
info.value.should.equal(obj.dimensions.lengths[2][1]);
info.name.should.equal(1);
info.exists.should.be.true;
});
});

it('should handle out of bounds array index', function() {
var info = gpi('dimensions.lengths[3]', obj);
Expand Down Expand Up @@ -180,13 +180,13 @@ describe('utilities', function () {
hp(1, arr).should.be.true;
hp(3, arr).should.be.false;
});

it('should handle literal types', function() {
var s = 'string literal';
hp('length', s).should.be.true;
hp(3, s).should.be.true;
hp(14, s).should.be.false;

hp('foo', 1).should.be.false;
});

Expand Down Expand Up @@ -770,13 +770,13 @@ describe('utilities', function () {

it('returns enumerable symbols only', function () {
if (typeof Symbol !== 'function') return;

var cat = Symbol('cat')
, dog = Symbol('dog')
, frog = Symbol('frog')
, cow = 'cow'
, obj = {};

obj[cat] = 'meow';
obj[dog] = 'woof';

Expand Down Expand Up @@ -819,14 +819,14 @@ describe('utilities', function () {

it('returns enumerable property names and symbols', function () {
if (typeof Symbol !== 'function') return;

var cat = Symbol('cat')
, dog = Symbol('dog')
, frog = Symbol('frog')
, bird = 'bird'
, cow = 'cow'
, obj = {};

obj[cat] = 'meow';
obj[dog] = 'woof';
obj[bird] = 'chirp';
Expand Down

0 comments on commit d08002e

Please sign in to comment.