Skip to content

Commit 89e5f97

Browse files
committed
feat(await-async-events): instance of userEvent is recognized as async
feat(await-async-events): added comments feat(await-async-events): better test case
1 parent b531af8 commit 89e5f97

File tree

4 files changed

+115
-5
lines changed

4 files changed

+115
-5
lines changed

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ type IsAsyncUtilFn = (
7272
validNames?: readonly (typeof ASYNC_UTILS)[number][]
7373
) => boolean;
7474
type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean;
75-
type IsUserEventMethodFn = (node: TSESTree.Identifier) => boolean;
75+
type IsUserEventMethodFn = (
76+
node: TSESTree.Identifier,
77+
userEventSession?: string
78+
) => boolean;
7679
type IsRenderUtilFn = (node: TSESTree.Identifier) => boolean;
7780
type IsCreateEventUtil = (
7881
node: TSESTree.CallExpression | TSESTree.Identifier
@@ -557,11 +560,16 @@ export function detectTestingLibraryUtils<
557560
return regularCall || wildcardCall || wildcardCallWithCallExpression;
558561
};
559562

560-
const isUserEventMethod: IsUserEventMethodFn = (node) => {
563+
const isUserEventMethod: IsUserEventMethodFn = (
564+
node,
565+
userEventInstance
566+
) => {
561567
const userEvent = findImportedUserEventSpecifier();
562568
let userEventName: string | undefined;
563569

564-
if (userEvent) {
570+
if (userEventInstance) {
571+
userEventName = userEventInstance;
572+
} else if (userEvent) {
565573
userEventName = userEvent.name;
566574
} else if (isAggressiveModuleReportingEnabled()) {
567575
userEventName = USER_EVENT_NAME;

lib/node-utils/index.ts

+30
Original file line numberDiff line numberDiff line change
@@ -679,3 +679,33 @@ export function findImportSpecifier(
679679
return (property as TSESTree.Property).key as TSESTree.Identifier;
680680
}
681681
}
682+
683+
/**
684+
* Finds if the userEvent is used as an instance
685+
*/
686+
687+
export function getUserEventInstance(
688+
context: TSESLint.RuleContext<string, unknown[]>
689+
): string | undefined {
690+
const { tokensAndComments } = context.getSourceCode();
691+
/**
692+
* Check for the following pattern:
693+
* userEvent.setup(
694+
* For a line like this:
695+
* const user = userEvent.setup();
696+
* function will return 'user'
697+
*/
698+
for (const [index, token] of tokensAndComments.entries()) {
699+
if (
700+
token.type === 'Identifier' &&
701+
token.value === 'userEvent' &&
702+
tokensAndComments[index + 1].value === '.' &&
703+
tokensAndComments[index + 2].value === 'setup' &&
704+
tokensAndComments[index + 3].value === '(' &&
705+
tokensAndComments[index - 1].value === '='
706+
) {
707+
return tokensAndComments[index - 2].value;
708+
}
709+
}
710+
return undefined;
711+
}

lib/rules/await-async-events.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
findClosestFunctionExpressionNode,
77
getFunctionName,
88
getInnermostReturningFunction,
9+
getUserEventInstance,
910
getVariableReferences,
1011
isMemberExpression,
1112
isPromiseHandled,
@@ -121,9 +122,12 @@ export default createTestingLibraryRule<Options, MessageIds>({
121122

122123
return {
123124
'CallExpression Identifier'(node: TSESTree.Identifier) {
125+
// Check if userEvent is used as an instance, like const user = userEvent.setup()
126+
const userEventInstance = getUserEventInstance(context);
124127
if (
125128
(isFireEventEnabled && helpers.isFireEventMethod(node)) ||
126-
(isUserEventEnabled && helpers.isUserEventMethod(node))
129+
(isUserEventEnabled &&
130+
helpers.isUserEventMethod(node, userEventInstance))
127131
) {
128132
detectEventMethodWrapper(node);
129133

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

+69-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const USER_EVENT_ASYNC_FUNCTIONS = [
3232
'upload',
3333
] as const;
3434
const FIRE_EVENT_ASYNC_FRAMEWORKS = [
35-
'@testing-library/vue',
35+
// '@testing-library/vue',
3636
'@marko/testing-library',
3737
] as const;
3838
const USER_EVENT_ASYNC_FRAMEWORKS = ['@testing-library/user-event'] as const;
@@ -361,6 +361,16 @@ ruleTester.run(RULE_NAME, rule, {
361361
`,
362362
options: [{ eventModule: ['userEvent', 'fireEvent'] }] as Options,
363363
},
364+
{
365+
code: `
366+
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
367+
test('userEvent as instance', async () => {
368+
const user = userEvent.setup()
369+
await user.click(getByLabelText('username'))
370+
})
371+
`,
372+
options: [{ eventModule: ['userEvent'] }] as Options,
373+
},
364374
]),
365375
],
366376

@@ -947,6 +957,34 @@ ruleTester.run(RULE_NAME, rule, {
947957
}
948958
949959
triggerEvent()
960+
`,
961+
} as const)
962+
),
963+
...USER_EVENT_ASYNC_FUNCTIONS.map(
964+
(eventMethod) =>
965+
({
966+
code: `
967+
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
968+
test('instance of userEvent is recognized as async event', async function() {
969+
const user = userEvent.setup()
970+
user.${eventMethod}(getByLabelText('username'))
971+
})
972+
`,
973+
errors: [
974+
{
975+
line: 5,
976+
column: 5,
977+
messageId: 'awaitAsyncEvent',
978+
data: { name: eventMethod },
979+
},
980+
],
981+
options: [{ eventModule: 'userEvent' }],
982+
output: `
983+
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
984+
test('instance of userEvent is recognized as async event', async function() {
985+
const user = userEvent.setup()
986+
await user.${eventMethod}(getByLabelText('username'))
987+
})
950988
`,
951989
} as const)
952990
),
@@ -1008,6 +1046,36 @@ ruleTester.run(RULE_NAME, rule, {
10081046
fireEvent.click(getByLabelText('username'))
10091047
await userEvent.click(getByLabelText('username'))
10101048
})
1049+
`,
1050+
},
1051+
{
1052+
code: `
1053+
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
1054+
let user;
1055+
beforeEach(() => {
1056+
user = userEvent.setup()
1057+
})
1058+
test('instance of userEvent is recognized as async event when instance is initialized in beforeEach', async function() {
1059+
user.click(getByLabelText('username'))
1060+
})
1061+
`,
1062+
errors: [
1063+
{
1064+
line: 8,
1065+
column: 5,
1066+
messageId: 'awaitAsyncEvent',
1067+
data: { name: 'click' },
1068+
},
1069+
],
1070+
output: `
1071+
import userEvent from '${USER_EVENT_ASYNC_FRAMEWORKS[0]}'
1072+
let user;
1073+
beforeEach(() => {
1074+
user = userEvent.setup()
1075+
})
1076+
test('instance of userEvent is recognized as async event when instance is initialized in beforeEach', async function() {
1077+
await user.click(getByLabelText('username'))
1078+
})
10111079
`,
10121080
},
10131081
],

0 commit comments

Comments
 (0)