diff --git a/.travis.yml b/.travis.yml index 9169391..a9ee89c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,4 @@ node_js: - "8" # to be removed on "December 2019" - "10" # to be removed on "April 2021" - "12" # to be removed on "April 2022" + - "13" # to be removed on "June 2020" diff --git a/README.md b/README.md index a2b69ba..3a65d00 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,11 @@ SPEC - when messages does not match, rejects with the actual error. - if `error` is a `` (constructor function), validate instanceof using constructor (works well with ES2015 classes that extends Error). - when actual error is an instanceof ``, resolves with undefined. - - when actual error is NOT an instanceof ``, rejects with the actual error. + - when actual error is NOT an instanceof ``, rejects with AssertionError. - appends `error.name` as expected error class name to the message if the `promiseFn` is not rejected. - if `error` is a ``, run custom validation against actual rejection result. - when validation function returns `true`, resolves with undefined. - - when returned value of validation function is NOT `true`, rejects with the actual error. + - when returned value of validation function is NOT `true`, rejects with AssertionError. - if Error is thrown from validation function, rejects with the error. - if `error` is an ``, that is an object where each property will be tested for. - when all key-value pairs in `error` are the same as key-value pairs from actual rejected result, resolves with undefined. Note that only properties on the error object will be tested. diff --git a/index.js b/index.js index 97f7ab6..9e48b7c 100644 --- a/index.js +++ b/index.js @@ -96,13 +96,27 @@ function wantReject (stackStartFn, thennable, errorHandler, message) { // Dealing with ES2015 class that extends Error // see: https://github.com/nodejs/node/issues/3188 // see: https://github.com/nodejs/node/pull/4166 - return reject(actualRejectionResult); + return reject(new AssertionError({ + actual: actualRejectionResult, + expected: errorHandler, + message: message || 'The error is expected to be an instance of "' + errorHandler.name + '". Received "' + actualRejectionResult.constructor.name + '"\n\nError message:\n\n' + actualRejectionResult.message, + operator: stackStartFn.name, + stackStartFn: stackStartFn + })); } } - if (errorHandler.call({}, actualRejectionResult) === true) { + var handlerFuncResult = errorHandler.call({}, actualRejectionResult); + if (handlerFuncResult === true) { return resolve(); } else { - return reject(actualRejectionResult); + var validationFunctionName = errorHandler.name ? 'The "' + errorHandler.name + '" validation function' : 'The validation function'; + return reject(new AssertionError({ + actual: actualRejectionResult, + expected: errorHandler, + message: message || validationFunctionName + ' is expected to return "true". Received ' + handlerFuncResult + '\n\nCaught error:\n\n' + actualRejectionResult, + operator: stackStartFn.name, + stackStartFn: stackStartFn + })); } } if (typeof errorHandler === 'object') { diff --git a/test/test.js b/test/test.js index 1095aa2..6f390dc 100644 --- a/test/test.js +++ b/test/test.js @@ -179,15 +179,32 @@ implementations.forEach(function (impl) { assert(nothing === undefined); }, shouldNotBeRejected); }); - it('when actual error is NOT an instanceof ``, rejects with the actual error.', function () { - return rejects( - willReject(new TypeError('the original error message')), - RangeError - ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof TypeError); - assert.equal(err.message, 'the original error message'); + // < Node13 + if (impl.name === 'official implementation' && semver.satisfies(process.version, '< 13.0.0')) { + it('when actual error is NOT an instanceof ``, rejects with the actual error.', function () { + return rejects( + willReject(new TypeError('the original error message')), + RangeError + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof TypeError); + assert.equal(err.message, 'the original error message'); + }); }); - }); + } else { + it('when actual error is NOT an instanceof ``, rejects with AssertionError.', function () { + var te = new TypeError('the original error message'); + return rejects( + willReject(te), + RangeError + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof assert.AssertionError); + assert.equal(err.actual, te); + assert.equal(err.expected, RangeError); + assert(/The error is expected to be an instance of "RangeError". Received "TypeError"/.test(err.message)); + assert(/the original error message/.test(err.message)); + }); + }); + } describe('works well with ES2015 classes that extends Error', function () { class ES2015Error extends Error { } @@ -201,15 +218,32 @@ implementations.forEach(function (impl) { assert(nothing === undefined); }, shouldNotBeRejected); }); - it('unmatch case, rejects with the original error.', function () { - return rejects( - willReject(new AnotherES2015Error('bar')), - ES2015Error - ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof AnotherES2015Error); - assert.equal(err.message, 'bar'); + // < Node13 + if (impl.name === 'official implementation' && semver.satisfies(process.version, '< 13.0.0')) { + it('unmatch case, rejects with the original error.', function () { + return rejects( + willReject(new AnotherES2015Error('bar')), + ES2015Error + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof AnotherES2015Error); + assert.equal(err.message, 'bar'); + }); }); - }); + } else { + it('unmatch case, rejects with AssertionError.', function () { + var another = new AnotherES2015Error('bar'); + return rejects( + willReject(another), + ES2015Error + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof assert.AssertionError); + assert.equal(err.actual, another); + assert.equal(err.expected, ES2015Error); + assert(/The error is expected to be an instance of "ES2015Error". Received "AnotherES2015Error"/.test(err.message)); + assert(/bar/.test(err.message)); + }); + }); + } }); it('appends `error.name` as expected error class name to the message if the `promiseFn` is not rejected.', function () { return rejects( @@ -232,17 +266,38 @@ implementations.forEach(function (impl) { assert(nothing === undefined); }, shouldNotBeRejected); }); - it('when returned value of validation function is NOT `true`, rejects with the actual error.', function () { - return rejects( - willReject(new RangeError('Wrong range')), - function (err) { + // < Node13 + if (impl.name === 'official implementation' && semver.satisfies(process.version, '< 13.0.0')) { + it('when returned value of validation function is NOT `true`, rejects with the actual error.', function () { + return rejects( + willReject(new RangeError('Wrong range')), + function (err) { + return ((err instanceof TypeError) && /type/.test(err)); + } + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof RangeError); + assert.equal(err.message, 'Wrong range'); + }); + }); + } else { + it('when returned value of validation function is NOT `true`, rejects with AssertionError.', function () { + var e = new RangeError('Wrong range'); + const handlerFn = (err) => { return ((err instanceof TypeError) && /type/.test(err)); - } - ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof RangeError); - assert.equal(err.message, 'Wrong range'); + }; + return rejects( + willReject(e), + handlerFn + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof assert.AssertionError); + assert.equal(err.actual, e); + assert.equal(err.expected, handlerFn); + + assert(/The "handlerFn" validation function is expected to return "true". Received false/.test(err.message), `actual [${err.message}]`); + assert(/RangeError: Wrong range/.test(err.message), `actual [${err.message}]`); + }); }); - }); + } it('if Error is thrown from validation function, rejects with the error.', function () { var e = new RangeError('the original error message'); var te = new TypeError('some programming error');