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-inline-message): Support Error.cause with jasmine too #13966

Merged
merged 3 commits into from Mar 1, 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: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -8,7 +8,7 @@
- `[jest-config]` Add `openHandlesTimeout` option to configure possible open handles warning. ([#13875](https://github.com/facebook/jest/pull/13875))
- `[@jest/create-cache-key-function]` Allow passing `length` argument to `createCacheKey()` function and set its default value to `16` on Windows ([#13827](https://github.com/facebook/jest/pull/13827))
- `[jest-message-util]` Add support for [AggregateError](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError) ([#13946](https://github.com/facebook/jest/pull/13946) & [#13947](https://github.com/facebook/jest/pull/13947))
- `[jest-message-util]` Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in `test` and `it` ([#13935](https://github.com/facebook/jest/pull/13935))
- `[jest-message-util]` Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in `test` and `it` ([#13935](https://github.com/facebook/jest/pull/13935) & [#13966](https://github.com/facebook/jest/pull/13966))
- `[jest-worker]` Add `start` method to worker farms ([#13937](https://github.com/facebook/jest/pull/13937))

### Fixes
Expand Down
63 changes: 30 additions & 33 deletions e2e/__tests__/__snapshots__/failures.test.ts.snap
Expand Up @@ -493,39 +493,36 @@ exports[`works with error with cause 1`] = `
`;

exports[`works with error with cause thrown outside tests 1`] = `
"FAIL __tests__/errorWithCauseInDescribe.test.js
● Test suite failed to run

error during f

10 |
11 | function buildErrorWithCause(message: string, opts: {cause: unknown}): Error {
> 12 | const error = new Error(message, opts);
| ^
13 | if (opts.cause !== error.cause) {
14 | // Error with cause not supported in legacy versions of node, we just polyfill it
15 | Object.assign(error, opts);

at buildErrorWithCause (__tests__/errorWithCauseInDescribe.test.js:12:17)
at buildErrorWithCause (__tests__/errorWithCauseInDescribe.test.js:27:11)
at f (__tests__/errorWithCauseInDescribe.test.js:32:3)
at Object.describe (__tests__/errorWithCauseInDescribe.test.js:31:1)

Cause:
error during g

19 |
20 | function g() {
> 21 | throw new Error('error during g');
| ^
22 | }
23 | function f() {
24 | try {

at g (__tests__/errorWithCauseInDescribe.test.js:21:9)
at g (__tests__/errorWithCauseInDescribe.test.js:25:5)
at f (__tests__/errorWithCauseInDescribe.test.js:32:3)
at Object.describe (__tests__/errorWithCauseInDescribe.test.js:31:1)"
"error during f

10 |
11 | function buildErrorWithCause(message: string, opts: {cause: unknown}): Error {
> 12 | const error = new Error(message, opts);
| ^
13 | if (opts.cause !== error.cause) {
14 | // Error with cause not supported in legacy versions of node, we just polyfill it
15 | Object.assign(error, opts);

at buildErrorWithCause (__tests__/errorWithCauseInDescribe.test.js:12:17)
at buildErrorWithCause (__tests__/errorWithCauseInDescribe.test.js:27:11)
at f (__tests__/errorWithCauseInDescribe.test.js:32:3)
at Object.describe (__tests__/errorWithCauseInDescribe.test.js:31:1)

Cause:
error during g

19 |
20 | function g() {
> 21 | throw new Error('error during g');
| ^
22 | }
23 | function f() {
24 | try {

at g (__tests__/errorWithCauseInDescribe.test.js:21:9)
at g (__tests__/errorWithCauseInDescribe.test.js:25:5)
at f (__tests__/errorWithCauseInDescribe.test.js:32:3)
at Object.describe (__tests__/errorWithCauseInDescribe.test.js:31:1)"
`;

exports[`works with node assert 1`] = `
Expand Down
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/testFailingJasmine.test.ts.snap
Expand Up @@ -46,7 +46,7 @@ FAIL __tests__/worksWithConcurrentMode.test.js
15 | });
16 |

at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:164:11)
at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:166:11)
at Suite.failing (__tests__/worksWithConcurrentMode.test.js:13:17)
at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1)

Expand Down Expand Up @@ -80,7 +80,7 @@ FAIL __tests__/worksWithConcurrentOnlyMode.test.js
15 | });
16 |

at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:164:11)
at Function.failing (../../packages/jest-jasmine2/build/jasmineAsyncInstall.js:166:11)
at Suite.failing (__tests__/worksWithConcurrentOnlyMode.test.js:13:22)
at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1)

Expand Down
26 changes: 16 additions & 10 deletions e2e/__tests__/failures.test.ts
Expand Up @@ -6,7 +6,6 @@
*/

import * as path from 'path';
import {isJestJasmineRun} from '@jest/test-utils';
import {extractSummary, runYarnInstall} from '../Utils';
import runJest from '../runJest';

Expand Down Expand Up @@ -94,22 +93,29 @@ test('works with snapshot failures with hint', () => {
).toMatchSnapshot();
});

(isJestJasmineRun() ? test.skip : test)('works with error with cause', () => {
test('works with error with cause', () => {
const {stderr} = runJest(dir, ['errorWithCause.test.js']);
const summary = normalizeDots(cleanStderr(stderr));

expect(summary).toMatchSnapshot();
});

(isJestJasmineRun() ? test.skip : test)(
'works with error with cause thrown outside tests',
() => {
const {stderr} = runJest(dir, ['errorWithCauseInDescribe.test.js']);
const summary = normalizeDots(cleanStderr(stderr));
test('works with error with cause thrown outside tests', () => {
const {stderr} = runJest(dir, ['errorWithCauseInDescribe.test.js']);
const summary = normalizeDots(cleanStderr(stderr));

const sanitizedSummary = summary
.replace(/ Suite\.f /g, ' f ') // added by jasmine runner
.split('\n')
.map(line => line.trim()) // jasmine runner does not come with the same indentation
.join('\n');

expect(summary).toMatchSnapshot();
},
);
expect(
// jasmine runner differ from circus one in this case, we just start
// the comparison when the stack starts to be reported
sanitizedSummary.substring(sanitizedSummary.indexOf('error during f')),
).toMatchSnapshot();
});

test('errors after test has completed', () => {
const {stderr} = runJest(dir, ['errorAfterTestComplete.test.js']);
Expand Down
14 changes: 10 additions & 4 deletions packages/jest-jasmine2/src/jasmineAsyncInstall.ts
Expand Up @@ -69,8 +69,8 @@ function promisifyLifeCycleFunction(
// in the stack in the Error object. This line stringifies the stack
// property to allow garbage-collecting objects on the stack
// https://crbug.com/v8/7142
// eslint-disable-next-line no-self-assign
extraError.stack = extraError.stack;
const originalExtraErrorStack = extraError.stack;
extraError.stack = originalExtraErrorStack;

// We make *all* functions async and run `done` right away if they
// didn't return a promise.
Expand All @@ -85,6 +85,9 @@ function promisifyLifeCycleFunction(

if (message) {
extraError.message = message;
extraError.stack =
originalExtraErrorStack &&
originalExtraErrorStack.replace('Error: ', `Error: ${message}`);
}
done.fail(checkIsError ? error : extraError);
});
Expand Down Expand Up @@ -146,8 +149,8 @@ function promisifyIt(
// in the stack in the Error object. This line stringifies the stack
// property to allow garbage-collecting objects on the stack
// https://crbug.com/v8/7142
// eslint-disable-next-line no-self-assign
extraError.stack = extraError.stack;
const originalExtraErrorStack = extraError.stack;
extraError.stack = originalExtraErrorStack;

const asyncJestTest = function (done: DoneFn) {
const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
Expand All @@ -159,6 +162,9 @@ function promisifyIt(

if (message) {
extraError.message = message;
extraError.stack =
originalExtraErrorStack &&
originalExtraErrorStack.replace('Error: ', `Error: ${message}`);
}

if (jasmine.Spec.isPendingSpecException(error)) {
Expand Down
30 changes: 25 additions & 5 deletions packages/jest-message-util/src/index.ts
Expand Up @@ -433,6 +433,27 @@ function formatErrorStack(
return `${message}\n${stack}${cause}`;
}

function failureDetailsToErrorOrStack(
failureDetails: unknown,
content: string,
): Error | string {
if (!failureDetails) {
return content;
}
if (types.isNativeError(failureDetails) || failureDetails instanceof Error) {
return failureDetails; // receiving raw errors for jest-circus
}
if (
typeof failureDetails === 'object' &&
'error' in failureDetails &&
(types.isNativeError(failureDetails.error) ||
failureDetails.error instanceof Error)
) {
return failureDetails.error; // receiving instances of FailedAssertion for jest-jasmine
}
return content;
}

export const formatResultsErrors = (
testResults: Array<TestResult.AssertionResult>,
config: StackTraceConfig,
Expand All @@ -459,11 +480,10 @@ export const formatResultsErrors = (

return failedResults
.map(({result, content, failureDetails}) => {
const rootErrorOrStack: Error | string =
failureDetails &&
(types.isNativeError(failureDetails) || failureDetails instanceof Error)
? failureDetails
: content;
const rootErrorOrStack = failureDetailsToErrorOrStack(
failureDetails,
content,
);

const title = `${chalk.bold.red(
TITLE_INDENT +
Expand Down