Skip to content

Commit a407098

Browse files
authored
feat(valid-expect): supporting automatically fixing missing await in some cases (#1574)
* feat: add 'fixable' to valid-expect * test: add test to valid-expect * docs: update doc for valid-expect * docs: added additional note * fix: docs * docs: using fancy alerts * fix: format
1 parent f47cc3c commit a407098

File tree

4 files changed

+110
-1
lines changed

4 files changed

+110
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ set to warn in.\
363363
| [require-to-throw-message](docs/rules/require-to-throw-message.md) | Require a message for `toThrow()` | | | | |
364364
| [require-top-level-describe](docs/rules/require-top-level-describe.md) | Require test cases and hooks to be inside a `describe` block | | | | |
365365
| [valid-describe-callback](docs/rules/valid-describe-callback.md) | Enforce valid `describe()` callback || | | |
366-
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage || | | |
366+
| [valid-expect](docs/rules/valid-expect.md) | Enforce valid `expect()` usage || | 🔧 | |
367367
| [valid-expect-in-promise](docs/rules/valid-expect-in-promise.md) | Require promises that have expectations in their chain to be valid || | | |
368368
| [valid-title](docs/rules/valid-title.md) | Enforce valid titles || | 🔧 | |
369369

docs/rules/valid-expect.md

+6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,14 @@
33
💼 This rule is enabled in the ✅ `recommended`
44
[config](https://github.com/jest-community/eslint-plugin-jest/blob/main/README.md#shareable-configurations).
55

6+
🔧 This rule is automatically fixable by the
7+
[`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
8+
69
<!-- end auto-generated rule header -->
710

11+
> [!NOTE] Test function will be fixed if it is async and does not have await in
12+
> the async assertion.
13+
814
Ensure `expect()` is called with a single argument and there is an actual
915
expectation made.
1016

src/rules/__tests__/valid-expect.test.ts

+69
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,8 @@ ruleTester.run('valid-expect', rule, {
571571
// usages in async function
572572
{
573573
code: 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.toBeDefined(); });',
574+
output:
575+
'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.toBeDefined(); });',
574576
errors: [
575577
{
576578
column: 36,
@@ -582,6 +584,8 @@ ruleTester.run('valid-expect', rule, {
582584
},
583585
{
584586
code: 'test("valid-expect", async () => { expect(Promise.resolve(2)).resolves.not.toBeDefined(); });',
587+
output:
588+
'test("valid-expect", async () => { await expect(Promise.resolve(2)).resolves.not.toBeDefined(); });',
585589
errors: [
586590
{
587591
column: 36,
@@ -621,6 +625,12 @@ ruleTester.run('valid-expect', rule, {
621625
expect(Promise.resolve(1)).rejects.toBeDefined();
622626
});
623627
`,
628+
output: dedent`
629+
test("valid-expect", async () => {
630+
await expect(Promise.resolve(2)).resolves.not.toBeDefined();
631+
await expect(Promise.resolve(1)).rejects.toBeDefined();
632+
});
633+
`,
624634
errors: [
625635
{
626636
line: 2,
@@ -646,6 +656,12 @@ ruleTester.run('valid-expect', rule, {
646656
expect(Promise.resolve(1)).rejects.toBeDefined();
647657
});
648658
`,
659+
output: dedent`
660+
test("valid-expect", async () => {
661+
await expect(Promise.resolve(2)).resolves.not.toBeDefined();
662+
await expect(Promise.resolve(1)).rejects.toBeDefined();
663+
});
664+
`,
649665
errors: [
650666
{
651667
line: 3,
@@ -667,6 +683,12 @@ ruleTester.run('valid-expect', rule, {
667683
return expect(Promise.resolve(1)).rejects.toBeDefined();
668684
});
669685
`,
686+
output: dedent`
687+
test("valid-expect", async () => {
688+
await expect(Promise.resolve(2)).resolves.not.toBeDefined();
689+
await expect(Promise.resolve(1)).rejects.toBeDefined();
690+
});
691+
`,
670692
options: [{ alwaysAwait: true }],
671693
errors: [
672694
{
@@ -691,6 +713,12 @@ ruleTester.run('valid-expect', rule, {
691713
return expect(Promise.resolve(1)).rejects.toBeDefined();
692714
});
693715
`,
716+
output: dedent`
717+
test("valid-expect", async () => {
718+
await expect(Promise.resolve(2)).resolves.not.toBeDefined();
719+
return expect(Promise.resolve(1)).rejects.toBeDefined();
720+
});
721+
`,
694722
errors: [
695723
{
696724
line: 2,
@@ -709,6 +737,12 @@ ruleTester.run('valid-expect', rule, {
709737
return expect(Promise.resolve(1)).rejects.toBeDefined();
710738
});
711739
`,
740+
output: dedent`
741+
test("valid-expect", async () => {
742+
await expect(Promise.resolve(2)).resolves.not.toBeDefined();
743+
await expect(Promise.resolve(1)).rejects.toBeDefined();
744+
});
745+
`,
712746
options: [{ alwaysAwait: true }],
713747
errors: [
714748
{
@@ -726,6 +760,12 @@ ruleTester.run('valid-expect', rule, {
726760
return expect(Promise.resolve(1)).toReject();
727761
});
728762
`,
763+
output: dedent`
764+
test("valid-expect", async () => {
765+
await expect(Promise.resolve(2)).toResolve();
766+
await expect(Promise.resolve(1)).toReject();
767+
});
768+
`,
729769
options: [{ alwaysAwait: true }],
730770
errors: [
731771
{
@@ -771,6 +811,27 @@ ruleTester.run('valid-expect', rule, {
771811
},
772812
],
773813
},
814+
{
815+
code: dedent`
816+
test("valid-expect", async () => {
817+
Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined());
818+
});
819+
`,
820+
output: dedent`
821+
test("valid-expect", async () => {
822+
await Promise.reject(expect(Promise.resolve(2)).resolves.not.toBeDefined());
823+
});
824+
`,
825+
errors: [
826+
{
827+
line: 2,
828+
column: 3,
829+
endColumn: 72,
830+
messageId: 'promisesWithAsyncAssertionsMustBeAwaited',
831+
data: { orReturned: ' or returned' },
832+
},
833+
],
834+
},
774835
{
775836
code: dedent`
776837
test("valid-expect", () => {
@@ -961,6 +1022,14 @@ ruleTester.run('valid-expect', rule, {
9611022
});
9621023
});
9631024
`,
1025+
output: dedent`
1026+
test("valid-expect", () => {
1027+
return expect(functionReturningAPromise()).resolves.toEqual(1).then(async () => {
1028+
await expect(Promise.resolve(2)).resolves.toBe(1);
1029+
await expect(Promise.resolve(4)).resolves.toBe(4);
1030+
});
1031+
});
1032+
`,
9641033
errors: [
9651034
{
9661035
line: 4,

src/rules/valid-expect.ts

+34
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
ModifierName,
99
createRule,
1010
getAccessorValue,
11+
getSourceCode,
12+
isFunction,
1113
isSupportedAccessor,
1214
parseJestFnCallWithReason,
1315
} from './utils';
@@ -48,6 +50,18 @@ const findPromiseCallExpressionNode = (node: TSESTree.Node) =>
4850
? getPromiseCallExpressionNode(node.parent)
4951
: null;
5052

53+
const findFirstAsyncFunction = ({
54+
parent,
55+
}: TSESTree.Node): TSESTree.Node | null => {
56+
if (!parent) {
57+
return null;
58+
}
59+
60+
return isFunction(parent) && parent.async
61+
? parent
62+
: findFirstAsyncFunction(parent);
63+
};
64+
5165
const getParentIfThenified = (node: TSESTree.Node): TSESTree.Node => {
5266
const grandParentNode = node.parent?.parent;
5367

@@ -127,6 +141,7 @@ export default createRule<[Options], MessageIds>({
127141
promisesWithAsyncAssertionsMustBeAwaited:
128142
'Promises which return async assertions must be awaited{{ orReturned }}',
129143
},
144+
fixable: 'code',
130145
type: 'suggestion',
131146
schema: [
132147
{
@@ -339,6 +354,25 @@ export default createRule<[Options], MessageIds>({
339354
? 'asyncMustBeAwaited'
340355
: 'promisesWithAsyncAssertionsMustBeAwaited',
341356
node,
357+
fix(fixer) {
358+
if (!findFirstAsyncFunction(finalNode)) {
359+
return [];
360+
}
361+
const returnStatement =
362+
finalNode.parent?.type === AST_NODE_TYPES.ReturnStatement
363+
? finalNode.parent
364+
: null;
365+
366+
if (alwaysAwait && returnStatement) {
367+
const sourceCodeText =
368+
getSourceCode(context).getText(returnStatement);
369+
const replacedText = sourceCodeText.replace('return', 'await');
370+
371+
return fixer.replaceText(returnStatement, replacedText);
372+
}
373+
374+
return fixer.insertTextBefore(finalNode, 'await ');
375+
},
342376
});
343377

344378
if (isParentArrayExpression) {

0 commit comments

Comments
 (0)