Skip to content

Commit 164fa0e

Browse files
authored
fix(no-unnecessary-act): report userEvent with a different variable name (#1103)
Fixes #758
1 parent af8c8d9 commit 164fa0e

File tree

2 files changed

+125
-15
lines changed

2 files changed

+125
-15
lines changed

lib/rules/no-unnecessary-act.ts

Lines changed: 54 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import {
77
getStatementCallExpression,
88
isEmptyFunction,
99
isExpressionStatement,
10+
isMemberExpression,
1011
isReturnStatement,
1112
} from '../node-utils';
13+
import { resolveToTestingLibraryFn } from '../utils';
1214

1315
import type { TSESTree } from '@typescript-eslint/utils';
1416

@@ -58,6 +60,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
5860
],
5961

6062
create(context, [{ isStrict = true }], helpers) {
63+
const userEventInstanceNames = new Set<string>();
64+
6165
function getStatementIdentifier(statement: TSESTree.Statement) {
6266
const callExpression = getStatementCallExpression(statement);
6367

@@ -87,11 +91,18 @@ export default createTestingLibraryRule<Options, MessageIds>({
8791
return null;
8892
}
8993

90-
/**
91-
* Determines whether some call is non Testing Library related for a given list of statements.
92-
*/
93-
function hasSomeNonTestingLibraryCall(
94-
statements: TSESTree.Statement[]
94+
function hasUserEventInstanceName(identifier: TSESTree.Identifier) {
95+
if (!isMemberExpression(identifier.parent)) {
96+
return false;
97+
}
98+
99+
const propertyIdentifier = getPropertyIdentifierNode(identifier.parent);
100+
return userEventInstanceNames.has(propertyIdentifier?.name ?? '');
101+
}
102+
103+
function hasStatementReference(
104+
statements: TSESTree.Statement[],
105+
predicate: (identifier: TSESTree.Identifier) => boolean
95106
): boolean {
96107
return statements.some((statement) => {
97108
const identifier = getStatementIdentifier(statement);
@@ -100,20 +111,31 @@ export default createTestingLibraryRule<Options, MessageIds>({
100111
return false;
101112
}
102113

103-
return !helpers.isTestingLibraryUtil(identifier);
114+
return predicate(identifier);
104115
});
105116
}
106117

107-
function hasTestingLibraryCall(statements: TSESTree.Statement[]) {
108-
return statements.some((statement) => {
109-
const identifier = getStatementIdentifier(statement);
110-
111-
if (!identifier) {
112-
return false;
113-
}
118+
/**
119+
* Determines whether some call is non Testing Library related for a given list of statements.
120+
*/
121+
function hasSomeNonTestingLibraryCall(
122+
statements: TSESTree.Statement[]
123+
): boolean {
124+
return hasStatementReference(
125+
statements,
126+
(identifier) =>
127+
!helpers.isTestingLibraryUtil(identifier) &&
128+
!hasUserEventInstanceName(identifier)
129+
);
130+
}
114131

115-
return helpers.isTestingLibraryUtil(identifier);
116-
});
132+
function hasTestingLibraryCall(statements: TSESTree.Statement[]) {
133+
return hasStatementReference(
134+
statements,
135+
(identifier) =>
136+
helpers.isTestingLibraryUtil(identifier) ||
137+
hasUserEventInstanceName(identifier)
138+
);
117139
}
118140

119141
function checkNoUnnecessaryActFromBlockStatement(
@@ -196,7 +218,24 @@ export default createTestingLibraryRule<Options, MessageIds>({
196218
});
197219
}
198220

221+
function registerUserEventInstance(
222+
node: TSESTree.CallExpression & { parent: TSESTree.VariableDeclarator }
223+
) {
224+
const propertyIdentifier = getPropertyIdentifierNode(node);
225+
const deepestIdentifier = getDeepestIdentifierNode(node);
226+
const testingLibraryFn = resolveToTestingLibraryFn(node, context);
227+
228+
if (
229+
propertyIdentifier?.name === testingLibraryFn?.local &&
230+
deepestIdentifier?.name === 'setup' &&
231+
ASTUtils.isIdentifier(node.parent?.id)
232+
) {
233+
userEventInstanceNames.add(node.parent.id.name);
234+
}
235+
}
236+
199237
return {
238+
'VariableDeclarator > CallExpression': registerUserEventInstance,
200239
'CallExpression > ArrowFunctionExpression > BlockStatement':
201240
checkNoUnnecessaryActFromBlockStatement,
202241
'CallExpression > FunctionExpression > BlockStatement':

tests/lib/rules/no-unnecessary-act.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ const validNonStrictTestCases: RuleValidTestCase[] = [
6363
});
6464
`,
6565
},
66+
{
67+
code: `// case: RTL act wrapping both userEvent and non-RTL calls
68+
import { act } from '@testing-library/react'
69+
import userEvent from '@testing-library/user-event'
70+
71+
test('valid case', async () => {
72+
const user = userEvent.setup();
73+
74+
await act(async () => {
75+
await user.click(element);
76+
stuffThatDoesNotUseRTL()
77+
});
78+
})
79+
`,
80+
},
6681
];
6782

6883
const validTestCases: RuleValidTestCase[] = [
@@ -245,6 +260,28 @@ const invalidStrictTestCases: RuleInvalidTestCase[] =
245260
},
246261
],
247262
},
263+
{
264+
code: `
265+
import { act } from '${testingFramework}'
266+
import userEvent from '@testing-library/user-event'
267+
268+
test('invalid case', async () => {
269+
const user = userEvent.setup();
270+
271+
await act(async () => {
272+
await user.click(element);
273+
stuffThatDoesNotUseRTL()
274+
});
275+
})
276+
`,
277+
errors: [
278+
{
279+
messageId: 'noUnnecessaryActTestingLibraryUtil',
280+
line: 8,
281+
column: 15,
282+
},
283+
],
284+
},
248285
]);
249286

250287
const invalidTestCases: RuleInvalidTestCase[] = [
@@ -566,6 +603,40 @@ const invalidTestCases: RuleInvalidTestCase[] = [
566603
{ messageId: 'noUnnecessaryActEmptyFunction', line: 8, column: 9 },
567604
],
568605
},
606+
{
607+
code: `
608+
import { act } from '@testing-library/react'
609+
import userEvent from '@testing-library/user-event'
610+
611+
test('invalid case', async () => {
612+
const user = userEvent.setup();
613+
614+
await act(async () => {
615+
await user.click(element);
616+
});
617+
})
618+
`,
619+
errors: [
620+
{ messageId: 'noUnnecessaryActTestingLibraryUtil', line: 8, column: 15 },
621+
],
622+
},
623+
{
624+
code: `
625+
import { act } from '@testing-library/react'
626+
import userEvent from '@testing-library/user-event'
627+
628+
test('invalid case', async () => {
629+
const user = userEvent.setup();
630+
631+
act(async () => {
632+
await user.click(element);
633+
});
634+
})
635+
`,
636+
errors: [
637+
{ messageId: 'noUnnecessaryActTestingLibraryUtil', line: 8, column: 9 },
638+
],
639+
},
569640
{
570641
settings: {
571642
'testing-library/utils-module': 'test-utils',

0 commit comments

Comments
 (0)