From 788e667f039f96ea715fee4ccf3008f84e82dfe6 Mon Sep 17 00:00:00 2001 From: BondarenkoAlex Date: Tue, 8 Oct 2024 17:33:02 +0300 Subject: [PATCH] fix: fix error cause bug Closes jestjs/jest#15316, jestjs/jest#15111 --- CHANGELOG.md | 1 + .../src/__tests__/toThrowMatchers.test.ts | 46 +++++++++++++++++++ packages/expect/src/toThrowMatchers.ts | 32 +++++++++---- 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 363abee8a97e..20d9adcf882d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ ### Fixes +- `[expect]` Fix `error causes` - `[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..643ff7153407 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', () => { @@ -329,6 +361,20 @@ describe('toThrow', () => { /^(?=.*Expected message and cause: ).*Received message and cause: /s, ); }); + + test('isNot true, incorrect cause object', () => { + expect(() => + jestExpect(() => { + throw new Error('Message', {cause: {cause: undefined}}); + }).toThrow({ + message: 'Message', + // eslint-disable-next-line sort-keys + cause: {cause: null}, + }), + ).toThrow( + /^(?=.*Expected message and cause: ).*Received message and cause: /s, + ); + }); }); }); 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) {