Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jest-message-util): add support for error causes #13868

Merged
merged 4 commits into from Feb 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,8 @@

### Features

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

### Fixes

- `[jest-mock]` Clear mock state when `jest.restoreAllMocks()` is called ([#13867](https://github.com/facebook/jest/pull/13867))
Expand Down
Expand Up @@ -112,3 +112,18 @@ exports[`should not exclude vendor from stack trace 1`] = `
<dim> <dim>at Object.asyncFn (</intensity><dim>__tests__/vendor/sulu/node_modules/sulu-content-bundle/best_component.js<dim>:1:5)</intensity><dim></intensity>
"
`;

exports[`should return the error cause if there is one 1`] = `
" <bold>● </intensity>Test suite failed to run

Test exception

<dim>at Object.<anonymous> (</intensity>packages/jest-message-util/src/__tests__/messages.test.ts<dim>:418:17)</intensity>

Cause:
Cause Error

<dim>at Object.<anonymous> (</intensity>packages/jest-message-util/src/__tests__/messages.test.ts<dim>:421:17)</intensity>

"
`;
18 changes: 18 additions & 0 deletions packages/jest-message-util/src/__tests__/messages.test.ts
Expand Up @@ -413,3 +413,21 @@ 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();
});
29 changes: 26 additions & 3 deletions packages/jest-message-util/src/index.ts
Expand Up @@ -7,6 +7,7 @@

import * as path from 'path';
import {fileURLToPath} from 'url';
import {types} from 'util';
import {codeFrameColumns} from '@babel/code-frame';
import chalk = require('chalk');
import * as fs from 'graceful-fs';
Expand Down Expand Up @@ -122,18 +123,20 @@ 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`);
error.stack = '';
}

let message, stack;
let cause = '';

if (typeof error === 'string' || !error) {
error || (error = 'EMPTY ERROR');
Expand All @@ -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 || '');
Expand Down Expand Up @@ -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 = (
Expand Down