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.error"]
},
"include": ["./**/*"],
"references": [{"path": "../../"}]
}
51 changes: 42 additions & 9 deletions packages/expect/src/toThrowMatchers.ts
Expand Up @@ -225,14 +225,20 @@ const toThrowExpectedObject = (
thrown: Thrown | null,
expected: Error,
): SyncExpectationResult => {
const pass = thrown !== null && thrown.message === expected.message;
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 `,
createMessageAndCause(expected),
) +
(thrown !== null && thrown.hasMessage
? formatStack(thrown)
: formatReceived('Received value: ', thrown, 'value'))
Expand All @@ -242,22 +248,27 @@ const toThrowExpectedObject = (
'\n\n' +
(thrown === null
? // eslint-disable-next-line prefer-template
formatExpected('Expected message: ', expected.message) +
formatExpected(
`Expected ${messageAndCause(expected)}: `,
createMessageAndCause(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(expected)}: `,
createMessageAndCause(expected),
) + formatReceived('Received value: ', thrown, 'value'));

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

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

const createMessageAndCause = (error: Error) => {
if (error.cause instanceof Error) {
return _createMessageAndCause(error);
} else {
return error.message;
}
};
Copy link
Member

Choose a reason for hiding this comment

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

why are these 2 functions?

Copy link
Contributor Author

@ibuibu ibuibu Nov 15, 2022

Choose a reason for hiding this comment

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

Currently, if { message: 'foo' } is given, the standard output is "Expected message: foo".

To maintain this current behavior, I separated the functions so that only error.message is returned in the first loop.

Copy link
Member

Choose a reason for hiding this comment

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

_createMessageAndCause is only called from within itself or createMessageAndCause, can't they be combined?


const 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": ["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