Skip to content

Commit e33ea7b

Browse files
committed
fix(await-async-events): improve fixer
1 parent 7238f76 commit e33ea7b

File tree

4 files changed

+164
-20
lines changed

4 files changed

+164
-20
lines changed

Diff for: lib/node-utils/index.ts

+24
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
isBlockStatement,
1414
isCallExpression,
1515
isExpressionStatement,
16+
isFunctionExpression,
17+
isFunctionDeclaration,
1618
isImportDeclaration,
1719
isImportNamespaceSpecifier,
1820
isImportSpecifier,
@@ -95,6 +97,28 @@ export function findClosestVariableDeclaratorNode(
9597
return findClosestVariableDeclaratorNode(node.parent);
9698
}
9799

100+
export function findClosestFunctionExpressionNode(
101+
node: TSESTree.Node | undefined
102+
):
103+
| TSESTree.ArrowFunctionExpression
104+
| TSESTree.FunctionExpression
105+
| TSESTree.FunctionDeclaration
106+
| null {
107+
if (!node) {
108+
return null;
109+
}
110+
111+
if (
112+
isArrowFunctionExpression(node) ||
113+
isFunctionExpression(node) ||
114+
isFunctionDeclaration(node)
115+
) {
116+
return node;
117+
}
118+
119+
return findClosestFunctionExpressionNode(node.parent);
120+
}
121+
98122
/**
99123
* TODO: remove this one in favor of {@link findClosestCallExpressionNode}
100124
*/

Diff for: lib/node-utils/is-node-of-type.ts

+3
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ export const isReturnStatement = ASTUtils.isNodeOfType(
5959
export const isFunctionExpression = ASTUtils.isNodeOfType(
6060
AST_NODE_TYPES.FunctionExpression
6161
);
62+
export const isFunctionDeclaration = ASTUtils.isNodeOfType(
63+
AST_NODE_TYPES.FunctionDeclaration
64+
);

Diff for: lib/rules/await-async-events.ts

+36-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ASTUtils, TSESLint, TSESTree } from '@typescript-eslint/utils';
33
import { createTestingLibraryRule } from '../create-testing-library-rule';
44
import {
55
findClosestCallExpressionNode,
6+
findClosestFunctionExpressionNode,
67
getFunctionName,
78
getInnermostReturningFunction,
89
getVariableReferences,
@@ -142,7 +143,25 @@ export default createTestingLibraryRule<Options, MessageIds>({
142143
closestCallExpression,
143144
fix: (fixer) => {
144145
if (isMemberExpression(node.parent)) {
145-
return fixer.insertTextBefore(node.parent, 'await ');
146+
const memberExpressionFixer = fixer.insertTextBefore(
147+
node.parent,
148+
'await '
149+
);
150+
const functionExpression =
151+
findClosestFunctionExpressionNode(node);
152+
153+
if (functionExpression && !functionExpression.async) {
154+
// Mutate the actual node so if other nodes exist in this
155+
// function expression body they don't also try to fix it.
156+
functionExpression.async = true;
157+
158+
return [
159+
memberExpressionFixer,
160+
fixer.insertTextBefore(functionExpression, 'async '),
161+
];
162+
}
163+
164+
return memberExpressionFixer;
146165
}
147166

148167
return null;
@@ -175,7 +194,22 @@ export default createTestingLibraryRule<Options, MessageIds>({
175194
closestCallExpression,
176195
messageId: 'awaitAsyncEventWrapper',
177196
fix: (fixer) => {
178-
return fixer.insertTextBefore(node, 'await ');
197+
const nodeFixer = fixer.insertTextBefore(node, 'await ');
198+
const functionExpression =
199+
findClosestFunctionExpressionNode(node);
200+
201+
if (functionExpression && !functionExpression.async) {
202+
// Mutate the actual node so if other nodes exist in this
203+
// function expression body they don't also try to fix it.
204+
functionExpression.async = true;
205+
206+
return [
207+
nodeFixer,
208+
fixer.insertTextBefore(functionExpression, 'async '),
209+
];
210+
}
211+
212+
return nodeFixer;
179213
},
180214
});
181215
}

Diff for: tests/lib/rules/await-async-events.test.ts

+101-18
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ ruleTester.run(RULE_NAME, rule, {
348348
({
349349
code: `
350350
import { fireEvent } from '${testingFramework}'
351-
test('unhandled promise from event method is invalid', async () => {
351+
test('unhandled promise from event method is invalid', () => {
352352
fireEvent.${eventMethod}(getByLabelText('username'))
353353
})
354354
`,
@@ -367,6 +367,64 @@ ruleTester.run(RULE_NAME, rule, {
367367
test('unhandled promise from event method is invalid', async () => {
368368
await fireEvent.${eventMethod}(getByLabelText('username'))
369369
})
370+
`,
371+
} as const)
372+
),
373+
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
374+
(eventMethod) =>
375+
({
376+
code: `
377+
import { fireEvent } from '${testingFramework}'
378+
379+
fireEvent.${eventMethod}(getByLabelText('username'))
380+
`,
381+
errors: [
382+
{
383+
line: 4,
384+
column: 7,
385+
endColumn: 17 + eventMethod.length,
386+
messageId: 'awaitAsyncEvent',
387+
data: { name: eventMethod },
388+
},
389+
],
390+
options: [{ eventModule: 'fireEvent' }],
391+
output: `
392+
import { fireEvent } from '${testingFramework}'
393+
394+
await fireEvent.${eventMethod}(getByLabelText('username'))
395+
`,
396+
} as const)
397+
),
398+
...FIRE_EVENT_ASYNC_FUNCTIONS.map(
399+
(eventMethod) =>
400+
({
401+
code: `
402+
import { fireEvent } from '${testingFramework}'
403+
404+
function run() {
405+
fireEvent.${eventMethod}(getByLabelText('username'))
406+
}
407+
408+
test('should handle external function', run)
409+
`,
410+
errors: [
411+
{
412+
line: 5,
413+
column: 9,
414+
endColumn: 19 + eventMethod.length,
415+
messageId: 'awaitAsyncEvent',
416+
data: { name: eventMethod },
417+
},
418+
],
419+
options: [{ eventModule: 'fireEvent' }],
420+
output: `
421+
import { fireEvent } from '${testingFramework}'
422+
423+
async function run() {
424+
await fireEvent.${eventMethod}(getByLabelText('username'))
425+
}
426+
427+
test('should handle external function', run)
370428
`,
371429
} as const)
372430
),
@@ -429,7 +487,7 @@ ruleTester.run(RULE_NAME, rule, {
429487
({
430488
code: `
431489
import { fireEvent } from '${testingFramework}'
432-
test('several unhandled promises from event methods is invalid', async () => {
490+
test('several unhandled promises from event methods is invalid', async function() {
433491
fireEvent.${eventMethod}(getByLabelText('username'))
434492
fireEvent.${eventMethod}(getByLabelText('username'))
435493
})
@@ -451,7 +509,7 @@ ruleTester.run(RULE_NAME, rule, {
451509
options: [{ eventModule: 'fireEvent' }],
452510
output: `
453511
import { fireEvent } from '${testingFramework}'
454-
test('several unhandled promises from event methods is invalid', async () => {
512+
test('several unhandled promises from event methods is invalid', async function() {
455513
await fireEvent.${eventMethod}(getByLabelText('username'))
456514
await fireEvent.${eventMethod}(getByLabelText('username'))
457515
})
@@ -466,7 +524,7 @@ ruleTester.run(RULE_NAME, rule, {
466524
},
467525
code: `
468526
import { fireEvent } from '${testingFramework}'
469-
test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => {
527+
test('unhandled promise from event method with aggressive reporting opted-out is invalid', function() {
470528
fireEvent.${eventMethod}(getByLabelText('username'))
471529
})
472530
`,
@@ -481,7 +539,7 @@ ruleTester.run(RULE_NAME, rule, {
481539
options: [{ eventModule: 'fireEvent' }],
482540
output: `
483541
import { fireEvent } from '${testingFramework}'
484-
test('unhandled promise from event method with aggressive reporting opted-out is invalid', async () => {
542+
test('unhandled promise from event method with aggressive reporting opted-out is invalid', async function() {
485543
await fireEvent.${eventMethod}(getByLabelText('username'))
486544
})
487545
`,
@@ -514,7 +572,7 @@ ruleTester.run(RULE_NAME, rule, {
514572
import { fireEvent } from 'test-utils'
515573
test(
516574
'unhandled promise from event method imported from custom module with aggressive reporting opted-out is invalid',
517-
() => {
575+
async () => {
518576
await fireEvent.${eventMethod}(getByLabelText('username'))
519577
})
520578
`,
@@ -547,7 +605,7 @@ ruleTester.run(RULE_NAME, rule, {
547605
import { fireEvent } from '${testingFramework}'
548606
test(
549607
'unhandled promise from event method imported from default module with aggressive reporting opted-out is invalid',
550-
() => {
608+
async () => {
551609
await fireEvent.${eventMethod}(getByLabelText('username'))
552610
})
553611
`,
@@ -578,7 +636,7 @@ ruleTester.run(RULE_NAME, rule, {
578636
import { fireEvent } from '${testingFramework}'
579637
test(
580638
'unhandled promise from event method kept in a var is invalid',
581-
() => {
639+
async () => {
582640
const promise = await fireEvent.${eventMethod}(getByLabelText('username'))
583641
})
584642
`,
@@ -609,7 +667,7 @@ ruleTester.run(RULE_NAME, rule, {
609667
options: [{ eventModule: 'fireEvent' }],
610668
output: `
611669
import { fireEvent } from '${testingFramework}'
612-
test('unhandled promise returned from function wrapping event method is invalid', () => {
670+
test('unhandled promise returned from function wrapping event method is invalid', async () => {
613671
function triggerEvent() {
614672
doSomething()
615673
return fireEvent.${eventMethod}(getByLabelText('username'))
@@ -627,7 +685,7 @@ ruleTester.run(RULE_NAME, rule, {
627685
({
628686
code: `
629687
import userEvent from '${testingFramework}'
630-
test('unhandled promise from event method is invalid', async () => {
688+
test('unhandled promise from event method is invalid', () => {
631689
userEvent.${eventMethod}(getByLabelText('username'))
632690
})
633691
`,
@@ -646,6 +704,31 @@ ruleTester.run(RULE_NAME, rule, {
646704
test('unhandled promise from event method is invalid', async () => {
647705
await userEvent.${eventMethod}(getByLabelText('username'))
648706
})
707+
`,
708+
} as const)
709+
),
710+
...USER_EVENT_ASYNC_FUNCTIONS.map(
711+
(eventMethod) =>
712+
({
713+
code: `
714+
import userEvent from '${testingFramework}'
715+
716+
userEvent.${eventMethod}(getByLabelText('username'))
717+
`,
718+
errors: [
719+
{
720+
line: 4,
721+
column: 7,
722+
endColumn: 17 + eventMethod.length,
723+
messageId: 'awaitAsyncEvent',
724+
data: { name: eventMethod },
725+
},
726+
],
727+
options: [{ eventModule: 'userEvent' }],
728+
output: `
729+
import userEvent from '${testingFramework}'
730+
731+
await userEvent.${eventMethod}(getByLabelText('username'))
649732
`,
650733
} as const)
651734
),
@@ -654,7 +737,7 @@ ruleTester.run(RULE_NAME, rule, {
654737
({
655738
code: `
656739
import testingLibraryUserEvent from '${testingFramework}'
657-
test('unhandled promise imported from alternate name event method is invalid', async () => {
740+
test('unhandled promise imported from alternate name event method is invalid', () => {
658741
testingLibraryUserEvent.${eventMethod}(getByLabelText('username'))
659742
})
660743
`,
@@ -681,7 +764,7 @@ ruleTester.run(RULE_NAME, rule, {
681764
({
682765
code: `
683766
import userEvent from '${testingFramework}'
684-
test('several unhandled promises from event methods is invalid', async () => {
767+
test('several unhandled promises from event methods is invalid', () => {
685768
userEvent.${eventMethod}(getByLabelText('username'))
686769
userEvent.${eventMethod}(getByLabelText('username'))
687770
})
@@ -734,7 +817,7 @@ ruleTester.run(RULE_NAME, rule, {
734817
import userEvent from '${testingFramework}'
735818
test(
736819
'unhandled promise from event method kept in a var is invalid',
737-
() => {
820+
async () => {
738821
const promise = await userEvent.${eventMethod}(getByLabelText('username'))
739822
})
740823
`,
@@ -745,7 +828,7 @@ ruleTester.run(RULE_NAME, rule, {
745828
({
746829
code: `
747830
import userEvent from '${testingFramework}'
748-
test('unhandled promise returned from function wrapping event method is invalid', () => {
831+
test('unhandled promise returned from function wrapping event method is invalid', function() {
749832
function triggerEvent() {
750833
doSomething()
751834
return userEvent.${eventMethod}(getByLabelText('username'))
@@ -765,7 +848,7 @@ ruleTester.run(RULE_NAME, rule, {
765848
options: [{ eventModule: 'userEvent' }],
766849
output: `
767850
import userEvent from '${testingFramework}'
768-
test('unhandled promise returned from function wrapping event method is invalid', () => {
851+
test('unhandled promise returned from function wrapping event method is invalid', async function() {
769852
function triggerEvent() {
770853
doSomething()
771854
return userEvent.${eventMethod}(getByLabelText('username'))
@@ -781,7 +864,7 @@ ruleTester.run(RULE_NAME, rule, {
781864
code: `
782865
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
783866
import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}'
784-
test('unhandled promises from multiple event modules', async () => {
867+
test('unhandled promises from multiple event modules', () => {
785868
fireEvent.click(getByLabelText('username'))
786869
userEvent.click(getByLabelText('username'))
787870
})
@@ -814,7 +897,7 @@ ruleTester.run(RULE_NAME, rule, {
814897
code: `
815898
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
816899
import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}'
817-
test('unhandled promise from userEvent relying on default options', async () => {
900+
test('unhandled promise from userEvent relying on default options', async function() {
818901
fireEvent.click(getByLabelText('username'))
819902
userEvent.click(getByLabelText('username'))
820903
})
@@ -830,7 +913,7 @@ ruleTester.run(RULE_NAME, rule, {
830913
output: `
831914
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
832915
import { fireEvent } from '${FIRE_EVENT_ASYNC_FRAMEWORKS[0]}'
833-
test('unhandled promise from userEvent relying on default options', async () => {
916+
test('unhandled promise from userEvent relying on default options', async function() {
834917
fireEvent.click(getByLabelText('username'))
835918
await userEvent.click(getByLabelText('username'))
836919
})

0 commit comments

Comments
 (0)