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

Update toThrow() to be able to use Error.cause #13606

Merged
merged 18 commits into from Feb 15, 2023
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[expect]` Update `toThrow()` to be able to use [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13606](https://github.com/facebook/jest/pull/13606))
- `[jest-core]` allow to use workerIdleMemoryLimit with only 1 worker or runInBand option ([#13846](https://github.com/facebook/jest/pull/13846))
- `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13868](https://github.com/facebook/jest/pull/13868) & [#13912](https://github.com/facebook/jest/pull/13912))
- `[jest-runtime]` Revert `import assertions` for JSON modules as it's been relegated to Stage 2 ([#13911](https://github.com/facebook/jest/pull/13911))
Expand Down
54 changes: 53 additions & 1 deletion packages/expect/src/__tests__/toThrowMatchers.test.ts
Expand Up @@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/

import {alignedAnsiStyleSerializer} from '@jest/test-utils';
import {alignedAnsiStyleSerializer, onNodeVersions} from '@jest/test-utils';
import jestExpect from '../';

expect.addSnapshotSerializer(alignedAnsiStyleSerializer);
Expand Down Expand Up @@ -278,6 +278,58 @@ describe.each(['toThrowError', 'toThrow'] as const)('%s', toThrow => {
});
});

describe('error message and cause', () => {
const errorA = new Error('A');
const errorB = new Error('B', {cause: errorA});
const expected = new Error('good', {cause: errorB});

describe('pass', () => {
test('isNot false', () => {
jestExpect(() => {
throw new Error('good', {cause: errorB});
})[toThrow](expected);
});

test('isNot true, incorrect message', () => {
jestExpect(() => {
throw new Error('bad', {cause: errorB});
}).not[toThrow](expected);
});

onNodeVersions('>=16.9.0', () => {
test('isNot true, incorrect cause', () => {
jestExpect(() => {
throw new Error('good', {cause: errorA});
}).not[toThrow](expected);
});
});
});

describe('fail', () => {
onNodeVersions('>=16.9.0', () => {
test('isNot false, incorrect message', () => {
expect(() =>
jestExpect(() => {
throw new Error('bad', {cause: errorB});
})[toThrow](expected),
).toThrow(
/^(?=.*Expected message and cause: ).*Received message and cause: /s,
);
});

test('isNot true, incorrect cause', () => {
expect(() =>
jestExpect(() => {
throw new Error('good', {cause: errorA});
})[toThrow](expected),
).toThrow(
/^(?=.*Expected message and cause: ).*Received message and cause: /s,
);
});
});
});
});

describe('asymmetric', () => {
describe('any-Class', () => {
describe('pass', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/expect/src/__tests__/tsconfig.json
@@ -1,5 +1,8 @@
{
"extends": "../../../../tsconfig.test.json",
"compilerOptions": {
"lib": ["es2022.error"]
},
"include": ["./**/*"],
"references": [{"path": "../../"}, {"path": "../../../test-utils"}]
}
54 changes: 45 additions & 9 deletions packages/expect/src/toThrowMatchers.ts
Expand Up @@ -225,14 +225,23 @@ const toThrowExpectedObject = (
thrown: Thrown | null,
expected: Error,
): SyncExpectationResult => {
const pass = thrown !== null && thrown.message === expected.message;
const expectedMessageAndCause = createMessageAndCause(expected);
const thrownMessageAndCause =
thrown !== null ? createMessageAndCause(thrown.value) : null;
const pass =
thrown !== null &&
thrown.message === expected.message &&
thrownMessageAndCause === expectedMessageAndCause;

const message = pass
? () =>
// eslint-disable-next-line prefer-template
matcherHint(matcherName, undefined, undefined, options) +
'\n\n' +
formatExpected('Expected message: not ', expected.message) +
formatExpected(
`Expected ${messageAndCause(expected)}: not `,
expectedMessageAndCause,
) +
(thrown !== null && thrown.hasMessage
? formatStack(thrown)
: formatReceived('Received value: ', thrown, 'value'))
Expand All @@ -242,22 +251,27 @@ const toThrowExpectedObject = (
'\n\n' +
(thrown === null
? // eslint-disable-next-line prefer-template
formatExpected('Expected message: ', expected.message) +
formatExpected(
`Expected ${messageAndCause(expected)}: `,
expectedMessageAndCause,
) +
'\n' +
DID_NOT_THROW
: thrown.hasMessage
? // eslint-disable-next-line prefer-template
printDiffOrStringify(
expected.message,
thrown.message,
'Expected message',
'Received message',
expectedMessageAndCause,
thrownMessageAndCause,
`Expected ${messageAndCause(expected)}`,
`Received ${messageAndCause(thrown.value)}`,
true,
) +
'\n' +
formatStack(thrown)
: formatExpected('Expected message: ', expected.message) +
formatReceived('Received value: ', thrown, 'value'));
: formatExpected(
`Expected ${messageAndCause(expected)}: `,
expectedMessageAndCause,
) + formatReceived('Received value: ', thrown, 'value'));

return {message, pass};
};
Expand Down Expand Up @@ -447,4 +461,26 @@ const formatStack = (thrown: Thrown | null) =>
},
);

function createMessageAndCauseMessage(error: Error): string {
if (error.cause instanceof Error) {
return `{ message: ${error.message}, cause: ${createMessageAndCauseMessage(
error.cause,
)}}`;
}

return `{ message: ${error.message} }`;
}

function createMessageAndCause(error: Error) {
if (error.cause instanceof Error) {
return createMessageAndCauseMessage(error);
}

return error.message;
}

function messageAndCause(error: Error) {
return error.cause === undefined ? 'message' : 'message and cause';
}

export default matchers;
2 changes: 1 addition & 1 deletion packages/expect/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["es2020", "dom"],
"lib": ["es2020", "es2021.promise", "es2022.error", "dom"],
"rootDir": "src",
"outDir": "build"
},
Expand Down