Skip to content
/ jest Public
forked from jestjs/jest

Commit 68e7025

Browse files
authored
feat(jest-message-util): add support for error causes (jestjs#13868)
1 parent 66fb417 commit 68e7025

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
### Features
44

5+
- `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause)
6+
57
### Fixes
68

79
- `[jest-mock]` Clear mock state when `jest.restoreAllMocks()` is called ([#13867](https://github.com/facebook/jest/pull/13867))

packages/jest-message-util/src/__tests__/__snapshots__/messages.test.ts.snap

+15
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,18 @@ exports[`should not exclude vendor from stack trace 1`] = `
112112
<dim> <dim>at Object.asyncFn (</intensity><dim>__tests__/vendor/sulu/node_modules/sulu-content-bundle/best_component.js<dim>:1:5)</intensity><dim></intensity>
113113
"
114114
`;
115+
116+
exports[`should return the error cause if there is one 1`] = `
117+
" <bold>● </intensity>Test suite failed to run
118+
119+
Test exception
120+
121+
<dim>at Object.<anonymous> (</intensity>packages/jest-message-util/src/__tests__/messages.test.ts<dim>:418:17)</intensity>
122+
123+
Cause:
124+
Cause Error
125+
126+
<dim>at Object.<anonymous> (</intensity>packages/jest-message-util/src/__tests__/messages.test.ts<dim>:421:17)</intensity>
127+
128+
"
129+
`;

packages/jest-message-util/src/__tests__/messages.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -413,3 +413,21 @@ it('getTopFrame should return a path for mjs files', () => {
413413

414414
expect(frame!.file).toBe(expectedFile);
415415
});
416+
417+
it('should return the error cause if there is one', () => {
418+
const error = new Error('Test exception');
419+
// TODO pass `cause` to the `Error` constructor when lowest supported Node version is 16.9.0 and above
420+
// See https://github.com/nodejs/node/blob/main/doc/changelogs/CHANGELOG_V16.md#error-cause
421+
error.cause = new Error('Cause Error');
422+
const message = formatExecError(
423+
error,
424+
{
425+
rootDir: '',
426+
testMatch: [],
427+
},
428+
{
429+
noStackTrace: false,
430+
},
431+
);
432+
expect(message).toMatchSnapshot();
433+
});

packages/jest-message-util/src/index.ts

+26-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import * as path from 'path';
99
import {fileURLToPath} from 'url';
10+
import {types} from 'util';
1011
import {codeFrameColumns} from '@babel/code-frame';
1112
import chalk = require('chalk');
1213
import * as fs from 'graceful-fs';
@@ -122,18 +123,20 @@ function warnAboutWrongTestEnvironment(error: string, env: 'jsdom' | 'node') {
122123
// `before/after each` hooks). If it's thrown, none of the tests in the file
123124
// are executed.
124125
export const formatExecError = (
125-
error: Error | TestResult.SerializableError | string | undefined,
126+
error: Error | TestResult.SerializableError | string | number | undefined,
126127
config: StackTraceConfig,
127128
options: StackTraceOptions,
128129
testPath?: string,
129130
reuseMessage?: boolean,
131+
noTitle?: boolean,
130132
): string => {
131133
if (!error || typeof error === 'number') {
132134
error = new Error(`Expected an Error, but "${String(error)}" was thrown`);
133135
error.stack = '';
134136
}
135137

136138
let message, stack;
139+
let cause = '';
137140

138141
if (typeof error === 'string' || !error) {
139142
error || (error = 'EMPTY ERROR');
@@ -145,6 +148,25 @@ export const formatExecError = (
145148
typeof error.stack === 'string'
146149
? error.stack
147150
: `thrown: ${prettyFormat(error, {maxDepth: 3})}`;
151+
if ('cause' in error) {
152+
const prefix = '\n\nCause:\n';
153+
if (typeof error.cause === 'string' || typeof error.cause === 'number') {
154+
cause += `${prefix}${error.cause}`;
155+
} else if (types.isNativeError(error.cause)) {
156+
const formatted = formatExecError(
157+
error.cause,
158+
config,
159+
options,
160+
testPath,
161+
reuseMessage,
162+
true,
163+
);
164+
cause += `${prefix}${formatted}`;
165+
}
166+
}
167+
}
168+
if (cause !== '') {
169+
cause = indentAllLines(cause);
148170
}
149171

150172
const separated = separateMessageFromStack(stack || '');
@@ -174,13 +196,14 @@ export const formatExecError = (
174196

175197
let messageToUse;
176198

177-
if (reuseMessage) {
199+
if (reuseMessage || noTitle) {
178200
messageToUse = ` ${message.trim()}`;
179201
} else {
180202
messageToUse = `${EXEC_ERROR_MESSAGE}\n\n${message}`;
181203
}
204+
const title = noTitle ? '' : `${TITLE_INDENT + TITLE_BULLET}`;
182205

183-
return `${TITLE_INDENT + TITLE_BULLET + messageToUse + stack}\n`;
206+
return `${title + messageToUse + stack + cause}\n`;
184207
};
185208

186209
const removeInternalStackEntries = (

0 commit comments

Comments
 (0)