From cbb8d584b358ddbf55824d935c1a151d9c67ba1a Mon Sep 17 00:00:00 2001 From: il3ven Date: Sat, 26 Mar 2022 23:46:13 +0530 Subject: [PATCH 1/7] add support for function in `throws`/`throwsAsync` --- docs/03-assertions.md | 4 ++-- lib/assert.js | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/docs/03-assertions.md b/docs/03-assertions.md index ad7725f99..c33b7f905 100644 --- a/docs/03-assertions.md +++ b/docs/03-assertions.md @@ -172,7 +172,7 @@ Assert that an error is thrown. `fn` must be a function which should throw. The * `instanceOf`: a constructor, the thrown error must be an instance of * `is`: the thrown error must be strictly equal to `expectation.is` -* `message`: either a string, which is compared against the thrown error's message, or a regular expression, which is matched against this message +* `message`: either a *string*, which is compared against the thrown error's message, a *regular expression*, which is matched against this message or a *function* which is passed the thrown error message and must return a boolean. true makes the assertion pass * `name`: the expected `.name` value of the thrown error * `code`: the expected `.code` value of the thrown error @@ -204,7 +204,7 @@ The thrown value *must* be an error. It is returned so you can run more assertio * `instanceOf`: a constructor, the thrown error must be an instance of * `is`: the thrown error must be strictly equal to `expectation.is` -* `message`: either a string, which is compared against the thrown error's message, or a regular expression, which is matched against this message +* `message`: either a *string*, which is compared against the thrown error's message, a *regular expression*, which is matched against this message or a *function* which is passed the thrown error message and must return a boolean. true makes the assertion pass * `name`: the expected `.name` value of the thrown error * `code`: the expected `.code` value of the thrown error diff --git a/lib/assert.js b/lib/assert.js index 70d24d1bf..568fef71b 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -106,11 +106,16 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d }); } - if (hasOwnProperty(expectations, 'message') && typeof expectations.message !== 'string' && !(expectations.message instanceof RegExp)) { + if ( + hasOwnProperty(expectations, "message") && + typeof expectations.message !== "string" && + !(expectations.message instanceof RegExp) && + !(expectations.message instanceof Function) + ) { throw new AssertionError({ assertion, message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string or regular expression`, - values: [formatWithLabel('Called with:', expectations)], + values: [formatWithLabel("Called with:", expectations)], }); } @@ -230,6 +235,19 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s }); } + if (expectations.message instanceof Function && expectations.message(actual.message) === false) { + throw new AssertionError({ + assertion, + message, + savedError, + actualStack, + values: [ + formatWithLabel(`${prefix} unexpected exception:`, actual), + formatWithLabel('Expected message to return true:', expectations.message), + ], + }); + } + if (typeof expectations.code !== 'undefined' && actual.code !== expectations.code) { throw new AssertionError({ assertion, From 7490cab378ba604e345d7d8892d21992faa910f0 Mon Sep 17 00:00:00 2001 From: il3ven Date: Sun, 27 Mar 2022 13:09:07 +0530 Subject: [PATCH 2/7] fix lint errors --- lib/assert.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 568fef71b..4b54a5485 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -107,15 +107,15 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d } if ( - hasOwnProperty(expectations, "message") && - typeof expectations.message !== "string" && - !(expectations.message instanceof RegExp) && - !(expectations.message instanceof Function) + hasOwnProperty(expectations, 'message') + && typeof expectations.message !== 'string' + && !(expectations.message instanceof RegExp) + && !(expectations.message instanceof Function) ) { throw new AssertionError({ assertion, message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string or regular expression`, - values: [formatWithLabel("Called with:", expectations)], + values: [formatWithLabel('Called with:', expectations)], }); } From b1af8da3dc9e5de5ff673dc1c98d462ed1ab0535 Mon Sep 17 00:00:00 2001 From: il3ven Date: Sun, 3 Apr 2022 23:49:19 +0530 Subject: [PATCH 3/7] add suggestions mentioned in review The commit includes changes in docs, `instaceOf Function` and TS types. --- docs/03-assertions.md | 10 ++++++++-- lib/assert.js | 2 +- types/assertions.d.ts | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/03-assertions.md b/docs/03-assertions.md index c33b7f905..6714a84a9 100644 --- a/docs/03-assertions.md +++ b/docs/03-assertions.md @@ -172,7 +172,10 @@ Assert that an error is thrown. `fn` must be a function which should throw. The * `instanceOf`: a constructor, the thrown error must be an instance of * `is`: the thrown error must be strictly equal to `expectation.is` -* `message`: either a *string*, which is compared against the thrown error's message, a *regular expression*, which is matched against this message or a *function* which is passed the thrown error message and must return a boolean. true makes the assertion pass +* `message`: the following types are valid: + * *string* - it is compared against the thrown error's message + * *regular expression* - it is matched against this message + * *function* - it is passed the thrown error message and must return a boolean for whether the assertion passed * `name`: the expected `.name` value of the thrown error * `code`: the expected `.code` value of the thrown error @@ -204,7 +207,10 @@ The thrown value *must* be an error. It is returned so you can run more assertio * `instanceOf`: a constructor, the thrown error must be an instance of * `is`: the thrown error must be strictly equal to `expectation.is` -* `message`: either a *string*, which is compared against the thrown error's message, a *regular expression*, which is matched against this message or a *function* which is passed the thrown error message and must return a boolean. true makes the assertion pass +* `message`: the following types are valid: + * *string* - it is compared against the thrown error's message + * *regular expression* - it is matched against this message + * *function* - it is passed the thrown error message and must return a boolean for whether the assertion passed * `name`: the expected `.name` value of the thrown error * `code`: the expected `.code` value of the thrown error diff --git a/lib/assert.js b/lib/assert.js index 4b54a5485..2213feab6 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -110,7 +110,7 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d hasOwnProperty(expectations, 'message') && typeof expectations.message !== 'string' && !(expectations.message instanceof RegExp) - && !(expectations.message instanceof Function) + && !(typeof expectations.message === 'function') ) { throw new AssertionError({ assertion, diff --git a/types/assertions.d.ts b/types/assertions.d.ts index ce5a97a14..00c561307 100644 --- a/types/assertions.d.ts +++ b/types/assertions.d.ts @@ -12,7 +12,7 @@ export type ThrowsExpectation = { is?: Error; /** The thrown error must have a message that equals the given string, or matches the regular expression. */ - message?: string | RegExp; + message?: string | RegExp | Function; /** The thrown error must have a name that equals the given string. */ name?: string; From e1dff0263a2af5e95eab7a8e804e4f4736d09898 Mon Sep 17 00:00:00 2001 From: il3ven Date: Mon, 4 Apr 2022 14:49:51 +0530 Subject: [PATCH 4/7] update types and error message --- lib/assert.js | 4 ++-- types/assertions.d.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/assert.js b/lib/assert.js index 2213feab6..5d5477071 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -114,7 +114,7 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d ) { throw new AssertionError({ assertion, - message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string or regular expression`, + message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string, regular expression or a function`, values: [formatWithLabel('Called with:', expectations)], }); } @@ -235,7 +235,7 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s }); } - if (expectations.message instanceof Function && expectations.message(actual.message) === false) { + if (typeof expectations.message === 'function' && expectations.message(actual.message) === false) { throw new AssertionError({ assertion, message, diff --git a/types/assertions.d.ts b/types/assertions.d.ts index 00c561307..0d2c8506b 100644 --- a/types/assertions.d.ts +++ b/types/assertions.d.ts @@ -12,7 +12,7 @@ export type ThrowsExpectation = { is?: Error; /** The thrown error must have a message that equals the given string, or matches the regular expression. */ - message?: string | RegExp | Function; + message?: string | RegExp | ((message: string) => boolean); /** The thrown error must have a name that equals the given string. */ name?: string; From 2916fbbafa5b2d9c21e72a6a54040a081f165ac1 Mon Sep 17 00:00:00 2001 From: il3ven Date: Tue, 19 Apr 2022 23:32:41 +0530 Subject: [PATCH 5/7] fix message in test-tap/assert.js --- test-tap/assert.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-tap/assert.js b/test-tap/assert.js index c21bbc7d9..c8ab25e38 100644 --- a/test-tap/assert.js +++ b/test-tap/assert.js @@ -1066,7 +1066,7 @@ test('.throws() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throws(() => {}, {message: null}), { assertion: 'throws', - message: 'The `message` property of the second argument to `t.throws()` must be a string or regular expression', + message: 'The `message` property of the second argument to `t.throws()` must be a string, regular expression or a function', values: [{label: 'Called with:', formatted: /message: null/}], }); @@ -1136,7 +1136,7 @@ test('.throwsAsync() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throwsAsync(() => {}, {message: null}), { assertion: 'throwsAsync', - message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string or regular expression', + message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string, regular expression or a function', values: [{label: 'Called with:', formatted: /message: null/}], }, {expectBoolean: false}); From 646d06bd5c5c3eba423a29439fa2d826cde543fd Mon Sep 17 00:00:00 2001 From: il3ven Date: Wed, 4 May 2022 01:01:16 +0530 Subject: [PATCH 6/7] add test cases for `throws` --- test-tap/assert.js | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/test-tap/assert.js b/test-tap/assert.js index c8ab25e38..d5521f6cb 100644 --- a/test-tap/assert.js +++ b/test-tap/assert.js @@ -899,6 +899,54 @@ test('.throws()', gather(t => { formatted: /null/, }], }); + + // Fails because the string in the message is incorrect + failsWith( + t, + () => assertions.throws(() => { throw new Error('error') }, { message: 'my error' }), + { + assertion: 'throws', + message: "", + values: [ + { label: 'Function threw unexpected exception:', formatted: /error/ }, + { label: 'Expected message to equal:', formatted: /my error/ } + ], + } + ); + + passes(t, () => assertions.throws(() => { throw new Error('error') }, { message: 'error' })); + + // Fails because the regular expression in the message is incorrect + failsWith( + t, + () => assertions.throws(() => { throw new Error('error') }, { message: /my error/ }), + { + assertion: 'throws', + message: "", + values: [ + { label: 'Function threw unexpected exception:', formatted: /error/ }, + { label: 'Expected message to match:', formatted: /my error/ } + ], + } + ); + + passes(t, () => assertions.throws(() => { throw new Error('error') }, { message: /error/ })); + + // Fails because the function in the message returns false + failsWith( + t, + () => assertions.throws(() => { throw new Error('error') }, { message: () => false }), + { + assertion: 'throws', + message: "", + values: [ + { label: 'Function threw unexpected exception:', formatted: /error/ }, + { label: 'Expected message to return true:', formatted: /Function/ } + ], + } + ); + + passes(t, () => assertions.throws(() => { throw new Error('error') }, { message: () => true })); })); test('.throws() returns the thrown error', t => { From c712e61788fa0b5ccbacbb5c8b1909810541b458 Mon Sep 17 00:00:00 2001 From: il3ven Date: Wed, 4 May 2022 01:21:57 +0530 Subject: [PATCH 7/7] fix lint errors --- test-tap/assert.js | 60 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/test-tap/assert.js b/test-tap/assert.js index d5521f6cb..d4c0b369c 100644 --- a/test-tap/assert.js +++ b/test-tap/assert.js @@ -903,50 +903,74 @@ test('.throws()', gather(t => { // Fails because the string in the message is incorrect failsWith( t, - () => assertions.throws(() => { throw new Error('error') }, { message: 'my error' }), + () => + assertions.throws( + () => { + throw new Error('error'); + }, + {message: 'my error'}, + ), { assertion: 'throws', - message: "", + message: '', values: [ - { label: 'Function threw unexpected exception:', formatted: /error/ }, - { label: 'Expected message to equal:', formatted: /my error/ } + {label: 'Function threw unexpected exception:', formatted: /error/}, + {label: 'Expected message to equal:', formatted: /my error/}, ], - } + }, ); - passes(t, () => assertions.throws(() => { throw new Error('error') }, { message: 'error' })); + passes(t, () => assertions.throws(() => { + throw new Error('error'); + }, {message: 'error'})); // Fails because the regular expression in the message is incorrect failsWith( t, - () => assertions.throws(() => { throw new Error('error') }, { message: /my error/ }), + () => + assertions.throws( + () => { + throw new Error('error'); + }, + {message: /my error/}, + ), { assertion: 'throws', - message: "", + message: '', values: [ - { label: 'Function threw unexpected exception:', formatted: /error/ }, - { label: 'Expected message to match:', formatted: /my error/ } + {label: 'Function threw unexpected exception:', formatted: /error/}, + {label: 'Expected message to match:', formatted: /my error/}, ], - } + }, ); - passes(t, () => assertions.throws(() => { throw new Error('error') }, { message: /error/ })); + passes(t, () => assertions.throws(() => { + throw new Error('error'); + }, {message: /error/})); // Fails because the function in the message returns false failsWith( t, - () => assertions.throws(() => { throw new Error('error') }, { message: () => false }), + () => + assertions.throws( + () => { + throw new Error('error'); + }, + {message: () => false}, + ), { assertion: 'throws', - message: "", + message: '', values: [ - { label: 'Function threw unexpected exception:', formatted: /error/ }, - { label: 'Expected message to return true:', formatted: /Function/ } + {label: 'Function threw unexpected exception:', formatted: /error/}, + {label: 'Expected message to return true:', formatted: /Function/}, ], - } + }, ); - passes(t, () => assertions.throws(() => { throw new Error('error') }, { message: () => true })); + passes(t, () => assertions.throws(() => { + throw new Error('error'); + }, {message: () => true})); })); test('.throws() returns the thrown error', t => {