From 68cd1fb893789850f175f8e000a63e4543f6fcc1 Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Tue, 12 Nov 2019 15:30:18 +0900 Subject: [PATCH 1/7] chore(travis): add Node13 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) 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" From 36a8d181dbf29e79805c9fc7be6958d6d690ffc3 Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Tue, 12 Nov 2019 15:48:44 +0900 Subject: [PATCH 2/7] test: add characterization tests for Node13 --- test/test.js | 96 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/test/test.js b/test/test.js index 1095aa2..160c6e8 100644 --- a/test/test.js +++ b/test/test.js @@ -179,15 +179,29 @@ 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'); + // Node 13 + if (impl.name === 'official implementation' && semver.satisfies(process.version, '>= 13.0.0')) { + it('when actual error is NOT an instanceof ``, rejects with AssertionError.', function () { + return rejects( + willReject(new TypeError('the original error message')), + RangeError + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof assert.AssertionError); + assert(/The error is expected to be an instance of "RangeError". Received "TypeError"/.test(err.message)); + assert(/the original error message/.test(err.message)); + }); }); - }); + } else { + 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'); + }); + }); + } describe('works well with ES2015 classes that extends Error', function () { class ES2015Error extends Error { } @@ -201,15 +215,29 @@ 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'); + // Node 13 + if (impl.name === 'official implementation' && semver.satisfies(process.version, '>= 13.0.0')) { + it('unmatch case, rejects with AssertionError.', function () { + return rejects( + willReject(new AnotherES2015Error('bar')), + ES2015Error + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof assert.AssertionError); + assert(/The error is expected to be an instance of "ES2015Error". Received "AnotherES2015Error"/.test(err.message)); + assert(/bar/.test(err.message)); + }); }); - }); + } else { + 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'); + }); + }); + } }); it('appends `error.name` as expected error class name to the message if the `promiseFn` is not rejected.', function () { return rejects( @@ -232,17 +260,33 @@ 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) { - return ((err instanceof TypeError) && /type/.test(err)); - } - ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof RangeError); - assert.equal(err.message, 'Wrong range'); + // Node 13 + if (impl.name === 'official implementation' && semver.satisfies(process.version, '>= 13.0.0')) { + it('when returned value of validation function is NOT `true`, rejects with AssertionError.', function () { + return rejects( + willReject(new RangeError('Wrong range')), + function (err) { + return ((err instanceof TypeError) && /type/.test(err)); + } + ).then(shouldNotBeFulfilled, function (err) { + assert(err instanceof assert.AssertionError); + assert(/The validation function is expected to return "true". Received false/.test(err.message)); + assert(/RangeError: Wrong range/.test(err.message)); + }); }); - }); + } else { + 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'); + }); + }); + } 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'); From 56f39024d17ac31b492efcb914e47b7bdad869b8 Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Wed, 13 Nov 2019 09:34:32 +0900 Subject: [PATCH 3/7] test: add characterization tests for Node13 --- test/test.js | 69 +++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/test/test.js b/test/test.js index 160c6e8..9143155 100644 --- a/test/test.js +++ b/test/test.js @@ -179,26 +179,29 @@ implementations.forEach(function (impl) { assert(nothing === undefined); }, shouldNotBeRejected); }); - // Node 13 - if (impl.name === 'official implementation' && semver.satisfies(process.version, '>= 13.0.0')) { - it('when actual error is NOT an instanceof ``, rejects with AssertionError.', function () { + // < 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 assert.AssertionError); - assert(/The error is expected to be an instance of "RangeError". Received "TypeError"/.test(err.message)); - assert(/the original error message/.test(err.message)); + assert(err instanceof TypeError); + assert.equal(err.message, 'the original error message'); }); }); } else { - it('when actual error is NOT an instanceof ``, rejects with the actual error.', function () { + it('when actual error is NOT an instanceof ``, rejects with AssertionError.', function () { + var te = new TypeError('the original error message'); return rejects( - willReject(new TypeError('the original error message')), + willReject(te), RangeError ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof TypeError); - assert.equal(err.message, 'the original error message'); + 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)); }); }); } @@ -215,26 +218,29 @@ implementations.forEach(function (impl) { assert(nothing === undefined); }, shouldNotBeRejected); }); - // Node 13 - if (impl.name === 'official implementation' && semver.satisfies(process.version, '>= 13.0.0')) { - it('unmatch case, rejects with AssertionError.', function () { + // < 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 assert.AssertionError); - assert(/The error is expected to be an instance of "ES2015Error". Received "AnotherES2015Error"/.test(err.message)); - assert(/bar/.test(err.message)); + assert(err instanceof AnotherES2015Error); + assert.equal(err.message, 'bar'); }); }); } else { - it('unmatch case, rejects with the original error.', function () { + it('unmatch case, rejects with AssertionError.', function () { + var another = new AnotherES2015Error('bar'); return rejects( - willReject(new AnotherES2015Error('bar')), + willReject(another), ES2015Error ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof AnotherES2015Error); - assert.equal(err.message, 'bar'); + 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)); }); }); } @@ -260,30 +266,33 @@ implementations.forEach(function (impl) { assert(nothing === undefined); }, shouldNotBeRejected); }); - // Node 13 - if (impl.name === 'official implementation' && semver.satisfies(process.version, '>= 13.0.0')) { - it('when returned value of validation function is NOT `true`, rejects with AssertionError.', function () { + // < 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 assert.AssertionError); - assert(/The validation function is expected to return "true". Received false/.test(err.message)); - assert(/RangeError: Wrong range/.test(err.message)); + assert(err instanceof RangeError); + assert.equal(err.message, 'Wrong range'); }); }); } else { - it('when returned value of validation function is NOT `true`, rejects with the actual error.', function () { + it('when returned value of validation function is NOT `true`, rejects with AssertionError.', function () { + var e = new RangeError('Wrong range'); return rejects( - willReject(new RangeError('Wrong range')), + willReject(e), function (err) { return ((err instanceof TypeError) && /type/.test(err)); } ).then(shouldNotBeFulfilled, function (err) { - assert(err instanceof RangeError); - assert.equal(err.message, 'Wrong range'); + assert(err instanceof assert.AssertionError); + assert.equal(err.actual, e); + // assert.equal(err.expected, handler); + assert(/The validation function is expected to return "true". Received false/.test(err.message)); + assert(/RangeError: Wrong range/.test(err.message)); }); }); } From 6624cc1f815543efc4d38b6c8f8439f83589c58e Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Wed, 13 Nov 2019 09:35:44 +0900 Subject: [PATCH 4/7] feat: If the validation function passed to assert.throws() or assert.rejects() returns a value other than true, an assertion error will be thrown instead of the original error to highlight the programming mistake refs: https://github.com/nodejs/node/pull/28263 --- index.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 97f7ab6..0fd4d66 100644 --- a/index.js +++ b/index.js @@ -99,10 +99,17 @@ function wantReject (stackStartFn, thennable, errorHandler, message) { return reject(actualRejectionResult); } } - if (errorHandler.call({}, actualRejectionResult) === true) { + var handlerFuncResult = errorHandler.call({}, actualRejectionResult); + if (handlerFuncResult === true) { return resolve(); } else { - return reject(actualRejectionResult); + return reject(new AssertionError({ + actual: actualRejectionResult, + expected: errorHandler, + message: message || 'The validation function is expected to return "true". Received ' + handlerFuncResult + '\n\nCaught error:\n\n' + actualRejectionResult, + operator: stackStartFn.name, + stackStartFn: stackStartFn + })); } } if (typeof errorHandler === 'object') { From aa73271b87f6861544d102f2572ece649cdd0c7b Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Wed, 13 Nov 2019 09:37:09 +0900 Subject: [PATCH 5/7] feat: If a constructor function is passed to validate the instance of errors thrown in assert.throws() or assert.reject(), an assertion error will be thrown instead of the original error refs: https://github.com/nodejs/node/pull/28263 --- index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 0fd4d66..5403822 100644 --- a/index.js +++ b/index.js @@ -96,7 +96,13 @@ 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 + })); } } var handlerFuncResult = errorHandler.call({}, actualRejectionResult); From 0e84f10b3cb4dcb351cec6d3019b8af03523a8f5 Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Sat, 18 Apr 2020 16:56:28 +0900 Subject: [PATCH 6/7] chore: sync with upstream changes in validation function message --- index.js | 3 ++- test/test.js | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 5403822..9e48b7c 100644 --- a/index.js +++ b/index.js @@ -109,10 +109,11 @@ function wantReject (stackStartFn, thennable, errorHandler, message) { if (handlerFuncResult === true) { return resolve(); } else { + var validationFunctionName = errorHandler.name ? 'The "' + errorHandler.name + '" validation function' : 'The validation function'; return reject(new AssertionError({ actual: actualRejectionResult, expected: errorHandler, - message: message || 'The validation function is expected to return "true". Received ' + handlerFuncResult + '\n\nCaught error:\n\n' + actualRejectionResult, + message: message || validationFunctionName + ' is expected to return "true". Received ' + handlerFuncResult + '\n\nCaught error:\n\n' + actualRejectionResult, operator: stackStartFn.name, stackStartFn: stackStartFn })); diff --git a/test/test.js b/test/test.js index 9143155..6f390dc 100644 --- a/test/test.js +++ b/test/test.js @@ -282,17 +282,19 @@ implementations.forEach(function (impl) { } 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)); + }; return rejects( willReject(e), - function (err) { - return ((err instanceof TypeError) && /type/.test(err)); - } + handlerFn ).then(shouldNotBeFulfilled, function (err) { assert(err instanceof assert.AssertionError); assert.equal(err.actual, e); - // assert.equal(err.expected, handler); - assert(/The validation function is expected to return "true". Received false/.test(err.message)); - assert(/RangeError: Wrong range/.test(err.message)); + 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}]`); }); }); } From 1b3da41ed334a0f5b3345e9fb7ef22fa09040fca Mon Sep 17 00:00:00 2001 From: Takuto Wada Date: Sat, 18 Apr 2020 17:45:34 +0900 Subject: [PATCH 7/7] docs(README): sync with behavior changes in Node13 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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.