diff --git a/CHANGELOG.md b/CHANGELOG.md index f4c3d7053925..47506e2c9b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features - `[@jest/core]` Instrument significant lifecycle events with [`performance.mark()`](https://nodejs.org/docs/latest-v16.x/api/perf_hooks.html#performancemarkname-options) ([#13859](https://github.com/facebook/jest/pull/13859)) +- `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ### Fixes diff --git a/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap b/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap index 0add9fd94e0c..9f629042d425 100644 --- a/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap +++ b/packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap @@ -112,3 +112,18 @@ exports[`should not exclude vendor from stack trace 1`] = ` at Object.asyncFn (__tests__/vendor/sulu/node_modules/sulu-content-bundle/best_component.js:1:5) " `; + +exports[`should return the error cause if there is one 1`] = ` +" ● Test suite failed to run + + Test exception + + at Object. (packages/jest-message-util/src/__tests__/messages.test.ts:418:17) + + Cause: + Cause Error + + at Object. (packages/jest-message-util/src/__tests__/messages.test.ts:421:17) + +" +`; diff --git a/packages/jest-message-util/src/__tests__/messages.test.ts b/packages/jest-message-util/src/__tests__/messages.test.ts index 7eeabb8dc159..bb2b9d9eb3fe 100644 --- a/packages/jest-message-util/src/__tests__/messages.test.ts +++ b/packages/jest-message-util/src/__tests__/messages.test.ts @@ -413,3 +413,39 @@ it('getTopFrame should return a path for mjs files', () => { expect(frame!.file).toBe(expectedFile); }); + +it('should return the error cause if there is one', () => { + const error = new Error('Test exception'); + // TODO pass `cause` to the `Error` constructor when lowest supported Node version is 16.9.0 and above + // See https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V16.md#error-cause + error.cause = new Error('Cause Error'); + const message = formatExecError( + error, + { + rootDir: '', + testMatch: [], + }, + { + noStackTrace: false, + }, + ); + expect(message).toMatchSnapshot(); +}); + +it('should print all the errors contained in an AggregateError', () => { + const error = new AggregateError([ + new Error('error 1'), + new Error('error 2'), + ]); + const message = formatExecError( + error, + { + rootDir: '', + testMatch: [], + }, + { + noStackTrace: false, + }, + ); + console.log(message); +}); diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index 7ef98dda1931..4c9b206703c1 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -16,6 +16,7 @@ import StackUtils = require('stack-utils'); import type {Config, TestResult} from '@jest/types'; import {format as prettyFormat} from 'pretty-format'; import type {Frame} from './types'; +import {types} from 'util'; export type {Frame} from './types'; @@ -122,11 +123,12 @@ function warnAboutWrongTestEnvironment(error: string, env: 'jsdom' | 'node') { // `before/after each` hooks). If it's thrown, none of the tests in the file // are executed. export const formatExecError = ( - error: Error | TestResult.SerializableError | string | undefined, + error: Error | TestResult.SerializableError | string | number | undefined, config: StackTraceConfig, options: StackTraceOptions, testPath?: string, reuseMessage?: boolean, + noTitle?: boolean, ): string => { if (!error || typeof error === 'number') { error = new Error(`Expected an Error, but "${String(error)}" was thrown`); @@ -134,6 +136,7 @@ export const formatExecError = ( } let message, stack; + let cause = ''; if (typeof error === 'string' || !error) { error || (error = 'EMPTY ERROR'); @@ -145,6 +148,25 @@ export const formatExecError = ( typeof error.stack === 'string' ? error.stack : `thrown: ${prettyFormat(error, {maxDepth: 3})}`; + if ('cause' in error) { + const prefix = '\n\nCause:\n'; + if (typeof error.cause === 'string' || typeof error.cause === 'number') { + cause += `${prefix}${error.cause}`; + } else if (types.isNativeError(error.cause)) { + const formatted = formatExecError( + error.cause, + config, + options, + testPath, + reuseMessage, + true, + ); + cause += `${prefix}${formatted}`; + } + } + } + if (cause !== '') { + cause = indentAllLines(cause); } const separated = separateMessageFromStack(stack || ''); @@ -174,13 +196,14 @@ export const formatExecError = ( let messageToUse; - if (reuseMessage) { + if (reuseMessage || noTitle) { messageToUse = ` ${message.trim()}`; } else { messageToUse = `${EXEC_ERROR_MESSAGE}\n\n${message}`; } + const title = noTitle ? '' : `${TITLE_INDENT + TITLE_BULLET}`; - return `${TITLE_INDENT + TITLE_BULLET + messageToUse + stack}\n`; + return `${title + messageToUse + stack + cause}\n`; }; const removeInternalStackEntries = (