diff --git a/CHANGELOG.md b/CHANGELOG.md index 363abee8a97e..0f62bc1c9ea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ ### Fixes +- `[expect]` Fix `error causes` bug ([#15338](https://github.com/jestjs/jest/pull/15338)) - `[babel-plugin-jest-hoist]` Use `denylist` instead of the deprecated `blacklist` for Babel 8 support ([#14109](https://github.com/jestjs/jest/pull/14109)) - `[expect]` Check error instance type for `toThrow/toThrowError` ([#14576](https://github.com/jestjs/jest/pull/14576)) - `[expect]` Improve diff for failing `expect.objectContaining` ([#15038](https://github.com/jestjs/jest/pull/15038)) diff --git a/packages/expect/src/__tests__/toThrowMatchers.test.ts b/packages/expect/src/__tests__/toThrowMatchers.test.ts index 2a7d19f68da8..572dbaa6da52 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.ts +++ b/packages/expect/src/__tests__/toThrowMatchers.test.ts @@ -307,6 +307,38 @@ describe('toThrow', () => { throw new Error('good', {cause: errorA}); }).not.toThrow(expected); }); + + test('isNot false, cause is object', () => { + jestExpect(() => { + throw errorB; + }).toThrow({ + message: 'B', + // eslint-disable-next-line sort-keys + cause: { + message: 'A', + }, + }); + }); + + test('isNot false, cause is string', () => { + jestExpect(() => { + throw new Error('Message', {cause: 'line 123'}); + }).toThrow({ + message: 'Message', + // eslint-disable-next-line sort-keys + cause: 'line 123', + }); + }); + + test('isNot false, cause is boolean', () => { + jestExpect(() => { + throw new Error('Message', {cause: true}); + }).toThrow({ + message: 'Message', + // eslint-disable-next-line sort-keys + cause: true, + }); + }); }); describe('fail', () => { diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index dfda024afb8c..79734c4470b6 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -467,22 +467,36 @@ const formatStack = (thrown: Thrown | null) => }, ); -function createMessageAndCauseMessage(error: Error): string { - if (error.cause instanceof Error) { - return `{ message: ${error.message}, cause: ${createMessageAndCauseMessage( - error.cause, - )}}`; +function createMessageAndCause(error: Error) { + if (error.cause) { + return JSON.stringify(buildSerializeError(error)); } return `{ message: ${error.message} }`; } -function createMessageAndCause(error: Error) { - if (error.cause instanceof Error) { - return createMessageAndCauseMessage(error); +function buildSerializeError(error) { + if (!isObject(error)) { + return error; + } + + const result = {}; + for (const name of Object.getOwnPropertyNames(error)) { + if (['stack', 'fileName', 'lineNumber'].includes(name)) { + continue; + } + if (name === 'cause') { + result[name] = buildSerializeError(error['cause']); + continue; + } + result[name] = error[name]; } - return error.message; + return result; +} + +function isObject(obj) { + return !!obj && typeof obj === 'object'; } function messageAndCause(error: Error) {