diff --git a/CHANGELOG.md b/CHANGELOG.md index cb82e49b0b11..123229924e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[expect]` Update `toThrow()` to be able to use [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13606](https://github.com/facebook/jest/pull/13606)) - `[jest-core]` allow to use workerIdleMemoryLimit with only 1 worker or runInBand option ([#13846](https://github.com/facebook/jest/pull/13846)) - `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13868](https://github.com/facebook/jest/pull/13868) & [#13912](https://github.com/facebook/jest/pull/13912)) - `[jest-runtime]` Revert `import assertions` for JSON modules as it's been relegated to Stage 2 ([#13911](https://github.com/facebook/jest/pull/13911)) diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.ts b/packages/expect/src/__tests__/toThrowMatchers.test.ts index 281e5ec44a56..7384bf89ebac 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.ts +++ b/packages/expect/src/__tests__/toThrowMatchers.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {alignedAnsiStyleSerializer} from '@jest/test-utils'; +import {alignedAnsiStyleSerializer, onNodeVersions} from '@jest/test-utils'; import jestExpect from '../'; expect.addSnapshotSerializer(alignedAnsiStyleSerializer); @@ -278,6 +278,58 @@ describe.each(['toThrowError', 'toThrow'] as const)('%s', toThrow => { }); }); + describe('error message and cause', () => { + const errorA = new Error('A'); + const errorB = new Error('B', {cause: errorA}); + const expected = new Error('good', {cause: errorB}); + + describe('pass', () => { + test('isNot false', () => { + jestExpect(() => { + throw new Error('good', {cause: errorB}); + })[toThrow](expected); + }); + + test('isNot true, incorrect message', () => { + jestExpect(() => { + throw new Error('bad', {cause: errorB}); + }).not[toThrow](expected); + }); + + onNodeVersions('>=16.9.0', () => { + test('isNot true, incorrect cause', () => { + jestExpect(() => { + throw new Error('good', {cause: errorA}); + }).not[toThrow](expected); + }); + }); + }); + + describe('fail', () => { + onNodeVersions('>=16.9.0', () => { + test('isNot false, incorrect message', () => { + expect(() => + jestExpect(() => { + throw new Error('bad', {cause: errorB}); + })[toThrow](expected), + ).toThrow( + /^(?=.*Expected message and cause: ).*Received message and cause: /s, + ); + }); + + test('isNot true, incorrect cause', () => { + expect(() => + jestExpect(() => { + throw new Error('good', {cause: errorA}); + })[toThrow](expected), + ).toThrow( + /^(?=.*Expected message and cause: ).*Received message and cause: /s, + ); + }); + }); + }); + }); + describe('asymmetric', () => { describe('any-Class', () => { describe('pass', () => { diff --git a/packages/expect/src/__tests__/tsconfig.json b/packages/expect/src/__tests__/tsconfig.json index d603a17f8dfe..6c754376a8c0 100644 --- a/packages/expect/src/__tests__/tsconfig.json +++ b/packages/expect/src/__tests__/tsconfig.json @@ -1,5 +1,8 @@ { "extends": "../../../../tsconfig.test.json", + "compilerOptions": { + "lib": ["es2022.error"] + }, "include": ["./**/*"], "references": [{"path": "../../"}, {"path": "../../../test-utils"}] } diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index 99af5807aa10..77eff9008e8b 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -225,14 +225,23 @@ const toThrowExpectedObject = ( thrown: Thrown | null, expected: Error, ): SyncExpectationResult => { - const pass = thrown !== null && thrown.message === expected.message; + const expectedMessageAndCause = createMessageAndCause(expected); + const thrownMessageAndCause = + thrown !== null ? createMessageAndCause(thrown.value) : null; + const pass = + thrown !== null && + thrown.message === expected.message && + thrownMessageAndCause === expectedMessageAndCause; const message = pass ? () => // eslint-disable-next-line prefer-template matcherHint(matcherName, undefined, undefined, options) + '\n\n' + - formatExpected('Expected message: not ', expected.message) + + formatExpected( + `Expected ${messageAndCause(expected)}: not `, + expectedMessageAndCause, + ) + (thrown !== null && thrown.hasMessage ? formatStack(thrown) : formatReceived('Received value: ', thrown, 'value')) @@ -242,22 +251,27 @@ const toThrowExpectedObject = ( '\n\n' + (thrown === null ? // eslint-disable-next-line prefer-template - formatExpected('Expected message: ', expected.message) + + formatExpected( + `Expected ${messageAndCause(expected)}: `, + expectedMessageAndCause, + ) + '\n' + DID_NOT_THROW : thrown.hasMessage ? // eslint-disable-next-line prefer-template printDiffOrStringify( - expected.message, - thrown.message, - 'Expected message', - 'Received message', + expectedMessageAndCause, + thrownMessageAndCause, + `Expected ${messageAndCause(expected)}`, + `Received ${messageAndCause(thrown.value)}`, true, ) + '\n' + formatStack(thrown) - : formatExpected('Expected message: ', expected.message) + - formatReceived('Received value: ', thrown, 'value')); + : formatExpected( + `Expected ${messageAndCause(expected)}: `, + expectedMessageAndCause, + ) + formatReceived('Received value: ', thrown, 'value')); return {message, pass}; }; @@ -447,4 +461,26 @@ const formatStack = (thrown: Thrown | null) => }, ); +function createMessageAndCauseMessage(error: Error): string { + if (error.cause instanceof Error) { + return `{ message: ${error.message}, cause: ${createMessageAndCauseMessage( + error.cause, + )}}`; + } + + return `{ message: ${error.message} }`; +} + +function createMessageAndCause(error: Error) { + if (error.cause instanceof Error) { + return createMessageAndCauseMessage(error); + } + + return error.message; +} + +function messageAndCause(error: Error) { + return error.cause === undefined ? 'message' : 'message and cause'; +} + export default matchers; diff --git a/packages/expect/tsconfig.json b/packages/expect/tsconfig.json index 8d7a33c00c62..6e4edc6dc4da 100644 --- a/packages/expect/tsconfig.json +++ b/packages/expect/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "lib": ["es2020", "dom"], + "lib": ["es2020", "es2021.promise", "es2022.error", "dom"], "rootDir": "src", "outDir": "build" },