Skip to content

Commit 23caac1

Browse files
committed
fix: flag unhandled async methods called from setup() return value
1 parent 35e2b40 commit 23caac1

File tree

3 files changed

+83
-12
lines changed

3 files changed

+83
-12
lines changed

lib/create-testing-library-rule/detect-testing-library-utils.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ type IsAsyncUtilFn = (
7373
validNames?: readonly (typeof ASYNC_UTILS)[number][]
7474
) => boolean;
7575
type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean;
76-
type IsUserEventMethodFn = (node: TSESTree.Identifier) => boolean;
76+
type IsUserEventMethodFn = (
77+
node: TSESTree.Identifier,
78+
userEventSetupVars?: Set<string>
79+
) => boolean;
7780
type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean;
7881
type IsCreateEventUtil = (
7982
node: TSESTree.CallExpression | TSESTree.Identifier
@@ -558,7 +561,10 @@ export function detectTestingLibraryUtils<
558561
return regularCall || wildcardCall || wildcardCallWithCallExpression;
559562
};
560563

561-
const isUserEventMethod: IsUserEventMethodFn = (node) => {
564+
const isUserEventMethod: IsUserEventMethodFn = (
565+
node,
566+
userEventSetupVars
567+
) => {
562568
const userEvent = findImportedUserEventSpecifier();
563569
let userEventName: string | undefined;
564570

@@ -568,10 +574,6 @@ export function detectTestingLibraryUtils<
568574
userEventName = USER_EVENT_NAME;
569575
}
570576

571-
if (!userEventName) {
572-
return false;
573-
}
574-
575577
const parentMemberExpression: TSESTree.MemberExpression | undefined =
576578
node.parent && isMemberExpression(node.parent)
577579
? node.parent
@@ -583,18 +585,33 @@ export function detectTestingLibraryUtils<
583585

584586
// make sure that given node it's not userEvent object itself
585587
if (
586-
[userEventName, USER_EVENT_NAME].includes(node.name) ||
588+
(userEventName &&
589+
[userEventName, USER_EVENT_NAME].includes(node.name)) ||
587590
(ASTUtils.isIdentifier(parentMemberExpression.object) &&
588591
parentMemberExpression.object.name === node.name)
589592
) {
590593
return false;
591594
}
592595

593-
// check userEvent.click() usage
594-
return (
596+
// check userEvent.click() usage (imported identifier)
597+
if (
598+
userEventName &&
595599
ASTUtils.isIdentifier(parentMemberExpression.object) &&
596600
parentMemberExpression.object.name === userEventName
597-
);
601+
) {
602+
return true;
603+
}
604+
605+
// check user.click() usage where user is a variable from userEvent.setup()
606+
if (
607+
userEventSetupVars &&
608+
ASTUtils.isIdentifier(parentMemberExpression.object) &&
609+
userEventSetupVars.has(parentMemberExpression.object.name)
610+
) {
611+
return true;
612+
}
613+
614+
return false;
598615
};
599616

600617
/**

lib/rules/await-async-events.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { ASTUtils, TSESLint, TSESTree } from '@typescript-eslint/utils';
1+
import {
2+
AST_NODE_TYPES,
3+
ASTUtils,
4+
TSESLint,
5+
TSESTree,
6+
} from '@typescript-eslint/utils';
27

38
import { createTestingLibraryRule } from '../create-testing-library-rule';
49
import {
@@ -81,6 +86,9 @@ export default createTestingLibraryRule<Options, MessageIds>({
8186
create(context, [options], helpers) {
8287
const functionWrappersNames: string[] = [];
8388

89+
// Track variables assigned from userEvent.setup()
90+
const userEventSetupVars = new Set<string>();
91+
8492
function reportUnhandledNode({
8593
node,
8694
closestCallExpression,
@@ -118,10 +126,28 @@ export default createTestingLibraryRule<Options, MessageIds>({
118126
const isUserEventEnabled = eventModules.includes(USER_EVENT_NAME);
119127

120128
return {
129+
// Track variables assigned from userEvent.setup()
130+
VariableDeclarator(node: TSESTree.VariableDeclarator) {
131+
if (
132+
isUserEventEnabled &&
133+
node.init &&
134+
node.init.type === AST_NODE_TYPES.CallExpression &&
135+
node.init.callee.type === AST_NODE_TYPES.MemberExpression &&
136+
node.init.callee.object.type === AST_NODE_TYPES.Identifier &&
137+
node.init.callee.object.name === USER_EVENT_NAME &&
138+
node.init.callee.property.type === AST_NODE_TYPES.Identifier &&
139+
node.init.callee.property.name === USER_EVENT_SETUP_FUNCTION_NAME &&
140+
node.id.type === AST_NODE_TYPES.Identifier
141+
) {
142+
userEventSetupVars.add(node.id.name);
143+
}
144+
},
145+
121146
'CallExpression Identifier'(node: TSESTree.Identifier) {
122147
if (
123148
(isFireEventEnabled && helpers.isFireEventMethod(node)) ||
124-
(isUserEventEnabled && helpers.isUserEventMethod(node))
149+
(isUserEventEnabled &&
150+
helpers.isUserEventMethod(node, userEventSetupVars))
125151
) {
126152
if (node.name === USER_EVENT_SETUP_FUNCTION_NAME) {
127153
return;

tests/lib/rules/await-async-events.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,34 @@ ruleTester.run(RULE_NAME, rule, {
10831083
`,
10841084
}) as const
10851085
),
1086+
...USER_EVENT_ASYNC_FUNCTIONS.map(
1087+
(eventMethod) =>
1088+
({
1089+
code: `
1090+
import userEvent from '${testingFramework}'
1091+
test('unhandled promise from event method called from userEvent.setup() return value is invalid', async () => {
1092+
const user = userEvent.setup();
1093+
user.${eventMethod}(getByLabelText('username'))
1094+
})
1095+
`,
1096+
errors: [
1097+
{
1098+
line: 5,
1099+
column: 11,
1100+
messageId: 'awaitAsyncEvent',
1101+
data: { name: eventMethod },
1102+
},
1103+
],
1104+
options: [{ eventModule: 'userEvent' }],
1105+
output: `
1106+
import userEvent from '${testingFramework}'
1107+
test('unhandled promise from event method called from userEvent.setup() return value is invalid', async () => {
1108+
const user = userEvent.setup();
1109+
await user.${eventMethod}(getByLabelText('username'))
1110+
})
1111+
`,
1112+
}) as const
1113+
),
10861114
]),
10871115
{
10881116
code: `

0 commit comments

Comments
 (0)