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..54339e38b74c 100644 --- a/packages/expect/src/__tests__/toThrowMatchers.test.ts +++ b/packages/expect/src/__tests__/toThrowMatchers.test.ts @@ -307,6 +307,37 @@ describe('toThrow', () => { throw new Error('good', {cause: errorA}); }).not.toThrow(expected); }); + + test('isNot false, compare Error with object', () => { + jestExpect(() => { + throw errorB; + }).toThrow({ + cause: { + message: 'A', + }, + message: 'B', + }); + }); + + test('isNot false, cause is string', () => { + jestExpect(() => { + throw new Error('Message', {cause: 'line 123'}); + }).toThrow({ + cause: 'line 123', + message: 'Message', + }); + }); + + test('isNot false, cause is object', () => { + jestExpect(() => { + throw new Error('Message', { + cause: {prop1: true, prop2: false, prop3: null, prop4: undefined}, + }); + }).toThrow({ + cause: {prop1: true, prop2: false, prop3: null, prop4: undefined}, + message: 'Message', + }); + }); }); describe('fail', () => { diff --git a/packages/expect/src/toThrowMatchers.ts b/packages/expect/src/toThrowMatchers.ts index dfda024afb8c..dcbb9299a4ad 100644 --- a/packages/expect/src/toThrowMatchers.ts +++ b/packages/expect/src/toThrowMatchers.ts @@ -467,22 +467,38 @@ 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: {[key: string]: any}) { + if (error.cause) { + return JSON.stringify(buildSerializeError(error), (_, value) => { + return value === undefined ? String(undefined) : value; + }); } - return `{ message: ${error.message} }`; + return error.message; } -function createMessageAndCause(error: Error) { - if (error.cause instanceof Error) { - return createMessageAndCauseMessage(error); +function buildSerializeError(error: {[key: string]: any}) { + if (!isObject(error)) { + return error; } - return error.message; + const result: {[key: string]: any} = {}; + for (const name of Object.getOwnPropertyNames(error).sort()) { + if (['stack', 'fileName', 'lineNumber'].includes(name)) { + continue; + } + if (name === 'cause') { + result[name] = buildSerializeError(error['cause']); + continue; + } + result[name] = error[name]; + } + + return result; +} + +function isObject(obj: unknown) { + return !!obj && typeof obj === 'object'; } function messageAndCause(error: Error) {