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
31 changes: 31 additions & 0 deletions packages/expect/src/__tests__/toThrowMatchers.test.ts
Expand Up @@ -278,6 +278,37 @@ 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);
});

test('isNot true, incorrect cause', () => {
// less than v16 does not yet support Error.cause
if (Number(process.version.split('.')[0].slice(1)) < 16) {
expect(true).toBe(true);
} else {
jestExpect(() => {
throw new Error('good', {cause: errorA});
}).not[toThrow](expected);
}
});
});
});

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"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the entire 2022, just pull in what's needed for Error.prototype.cause

},
"include": ["./**/*"],
"references": [{"path": "../../"}]
}
57 changes: 48 additions & 9 deletions packages/expect/src/toThrowMatchers.ts
Expand Up @@ -225,14 +225,48 @@ const toThrowExpectedObject = (
thrown: Thrown | null,
expected: Error,
): SyncExpectationResult => {
const pass = thrown !== null && thrown.message === expected.message;
function createMessageAndCause(error: Error): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not put these helpers inside the matcher - move them to the module level

if (error.cause instanceof Error) {
return _createMessageAndCause(error);
} else {
return error.message;
}
}

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

function expectedMessageAndCause(error: Error) {
return error.cause === undefined
? error.message
: createMessageAndCause(error);
}

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

const pass =
thrown !== null &&
thrown.message === expected.message &&
createMessageAndCause(thrown.value) === createMessageAndCause(expected);

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(expected),
) +
(thrown !== null && thrown.hasMessage
? formatStack(thrown)
: formatReceived('Received value: ', thrown, 'value'))
Expand All @@ -242,22 +276,27 @@ const toThrowExpectedObject = (
'\n\n' +
(thrown === null
? // eslint-disable-next-line prefer-template
formatExpected('Expected message: ', expected.message) +
formatExpected(
`Expected ${messageAndCause(expected)}: `,
expectedMessageAndCause(expected),
) +
'\n' +
DID_NOT_THROW
: thrown.hasMessage
? // eslint-disable-next-line prefer-template
printDiffOrStringify(
expected.message,
thrown.message,
'Expected message',
'Received message',
createMessageAndCause(expected),
createMessageAndCause(thrown.value),
`Expected ${messageAndCause(expected)}`,
`Received ${messageAndCause(thrown.value)}`,
true,
) +
'\n' +
formatStack(thrown)
: formatExpected('Expected message: ', expected.message) +
formatReceived('Received value: ', thrown, 'value'));
: formatExpected(
`Expected ${messageAndCause}: `,
expectedMessageAndCause,
) + formatReceived('Received value: ', thrown, 'value'));

return {message, pass};
};
Expand Down
2 changes: 1 addition & 1 deletion packages/expect/tsconfig.json
@@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"lib": ["es2020", "dom"],
"lib": ["es2022", "dom"],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, only pull in the error one from 2022

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried some patterns.

    "lib": ["es2020","es2022.error", "dom"],

    "lib": ["es2022.error", "es2020", "dom"],

    "lib": ["es2022.error", "dom"],

However, the following errors occured in compilation.
https://github.com/facebook/jest/actions/runs/3470037100/jobs/5797753863

Error: node_modules/typescript/lib/lib.es2022.error.d.ts(69,8): error TS2304: Cannot find name 'AggregateError'.
Error: node_modules/typescript/lib/lib.es2022.error.d.ts(74,8): error TS2304: Cannot find name 'AggregateError'.

I investigated but could not find the cause.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the cause. AggregateError is defined in es2021.promise.

I could build by following config.

    "lib": ["es2020", "es2021.promise", "es2022.error", "dom"],

However, I cannot the reason why "es2020" and "dom" are set. I could build by following config too.

    "lib": ["es2021.promise", "es2022.error" ],

Can you tell me the reason why "es2020" and "dom" are needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commied below because I was afraid of side effects.

    "lib": ["es2020", "es2021.promise", "es2022.error", "dom"],

"rootDir": "src",
"outDir": "build"
},
Expand Down