@@ -12,6 +12,7 @@ import {
12
12
hasImportMatch ,
13
13
ImportModuleNode ,
14
14
isImportDeclaration ,
15
+ isImportDefaultSpecifier ,
15
16
isImportNamespaceSpecifier ,
16
17
isImportSpecifier ,
17
18
isLiteral ,
@@ -20,9 +21,9 @@ import {
20
21
} from './node-utils' ;
21
22
import {
22
23
ABSENCE_MATCHERS ,
24
+ ALL_QUERIES_COMBINATIONS ,
23
25
ASYNC_UTILS ,
24
26
PRESENCE_MATCHERS ,
25
- ALL_QUERIES_COMBINATIONS ,
26
27
} from './utils' ;
27
28
28
29
export type TestingLibrarySettings = {
@@ -67,6 +68,7 @@ type IsAsyncUtilFn = (
67
68
validNames ?: readonly typeof ASYNC_UTILS [ number ] [ ]
68
69
) => boolean ;
69
70
type IsFireEventMethodFn = ( node : TSESTree . Identifier ) => boolean ;
71
+ type IsUserEventMethodFn = ( node : TSESTree . Identifier ) => boolean ;
70
72
type IsRenderUtilFn = ( node : TSESTree . Identifier ) => boolean ;
71
73
type IsRenderVariableDeclaratorFn = (
72
74
node : TSESTree . VariableDeclarator
@@ -99,6 +101,7 @@ export interface DetectionHelpers {
99
101
isFireEventUtil : ( node : TSESTree . Identifier ) => boolean ;
100
102
isUserEventUtil : ( node : TSESTree . Identifier ) => boolean ;
101
103
isFireEventMethod : IsFireEventMethodFn ;
104
+ isUserEventMethod : IsUserEventMethodFn ;
102
105
isRenderUtil : IsRenderUtilFn ;
103
106
isRenderVariableDeclarator : IsRenderVariableDeclaratorFn ;
104
107
isDebugUtil : IsDebugUtilFn ;
@@ -109,6 +112,9 @@ export interface DetectionHelpers {
109
112
isNodeComingFromTestingLibrary : IsNodeComingFromTestingLibraryFn ;
110
113
}
111
114
115
+ const USER_EVENT_PACKAGE = '@testing-library/user-event' ;
116
+ const FIRE_EVENT_NAME = 'fireEvent' ;
117
+ const USER_EVENT_NAME = 'userEvent' ;
112
118
const RENDER_NAME = 'render' ;
113
119
114
120
/**
@@ -125,6 +131,7 @@ export function detectTestingLibraryUtils<
125
131
) : TSESLint . RuleListener => {
126
132
let importedTestingLibraryNode : ImportModuleNode | null = null ;
127
133
let importedCustomModuleNode : ImportModuleNode | null = null ;
134
+ let importedUserEventLibraryNode : ImportModuleNode | null = null ;
128
135
129
136
// Init options based on shared ESLint settings
130
137
const customModule = context . settings [ 'testing-library/utils-module' ] ;
@@ -174,69 +181,6 @@ export function detectTestingLibraryUtils<
174
181
return isNodeComingFromTestingLibrary ( referenceNodeIdentifier ) ;
175
182
}
176
183
177
- /**
178
- * Determines whether a given node is a simulate event util related to
179
- * Testing Library or not.
180
- *
181
- * In order to determine this, the node must match:
182
- * - indicated simulate event name: fireEvent or userEvent
183
- * - imported from valid Testing Library module (depends on Aggressive
184
- * Reporting)
185
- *
186
- */
187
- function isTestingLibrarySimulateEventUtil (
188
- node : TSESTree . Identifier ,
189
- utilName : 'fireEvent' | 'userEvent'
190
- ) : boolean {
191
- const simulateEventUtil = findImportedUtilSpecifier ( utilName ) ;
192
- let simulateEventUtilName : string | undefined ;
193
-
194
- if ( simulateEventUtil ) {
195
- simulateEventUtilName = ASTUtils . isIdentifier ( simulateEventUtil )
196
- ? simulateEventUtil . name
197
- : simulateEventUtil . local . name ;
198
- } else if ( isAggressiveModuleReportingEnabled ( ) ) {
199
- simulateEventUtilName = utilName ;
200
- }
201
-
202
- if ( ! simulateEventUtilName ) {
203
- return false ;
204
- }
205
-
206
- const parentMemberExpression :
207
- | TSESTree . MemberExpression
208
- | undefined = isMemberExpression ( node . parent ) ? node . parent : undefined ;
209
-
210
- if ( ! parentMemberExpression ) {
211
- return false ;
212
- }
213
-
214
- // make sure that given node it's not fireEvent/userEvent object itself
215
- if (
216
- [ simulateEventUtilName , utilName ] . includes ( node . name ) ||
217
- ( ASTUtils . isIdentifier ( parentMemberExpression . object ) &&
218
- parentMemberExpression . object . name === node . name )
219
- ) {
220
- return false ;
221
- }
222
-
223
- // check fireEvent.click()/userEvent.click() usage
224
- const regularCall =
225
- ASTUtils . isIdentifier ( parentMemberExpression . object ) &&
226
- parentMemberExpression . object . name === simulateEventUtilName ;
227
-
228
- // check testingLibraryUtils.fireEvent.click() or
229
- // testingLibraryUtils.userEvent.click() usage
230
- const wildcardCall =
231
- isMemberExpression ( parentMemberExpression . object ) &&
232
- ASTUtils . isIdentifier ( parentMemberExpression . object . object ) &&
233
- parentMemberExpression . object . object . name === simulateEventUtilName &&
234
- ASTUtils . isIdentifier ( parentMemberExpression . object . property ) &&
235
- parentMemberExpression . object . property . name === utilName ;
236
-
237
- return regularCall || wildcardCall ;
238
- }
239
-
240
184
/**
241
185
* Determines whether aggressive module reporting is enabled or not.
242
186
*
@@ -403,7 +347,90 @@ export function detectTestingLibraryUtils<
403
347
* Determines whether a given node is fireEvent method or not
404
348
*/
405
349
const isFireEventMethod : IsFireEventMethodFn = ( node ) => {
406
- return isTestingLibrarySimulateEventUtil ( node , 'fireEvent' ) ;
350
+ const fireEventUtil = findImportedUtilSpecifier ( FIRE_EVENT_NAME ) ;
351
+ let fireEventUtilName : string | undefined ;
352
+
353
+ if ( fireEventUtil ) {
354
+ fireEventUtilName = ASTUtils . isIdentifier ( fireEventUtil )
355
+ ? fireEventUtil . name
356
+ : fireEventUtil . local . name ;
357
+ } else if ( isAggressiveModuleReportingEnabled ( ) ) {
358
+ fireEventUtilName = FIRE_EVENT_NAME ;
359
+ }
360
+
361
+ if ( ! fireEventUtilName ) {
362
+ return false ;
363
+ }
364
+
365
+ const parentMemberExpression :
366
+ | TSESTree . MemberExpression
367
+ | undefined = isMemberExpression ( node . parent ) ? node . parent : undefined ;
368
+
369
+ if ( ! parentMemberExpression ) {
370
+ return false ;
371
+ }
372
+
373
+ // make sure that given node it's not fireEvent object itself
374
+ if (
375
+ [ fireEventUtilName , FIRE_EVENT_NAME ] . includes ( node . name ) ||
376
+ ( ASTUtils . isIdentifier ( parentMemberExpression . object ) &&
377
+ parentMemberExpression . object . name === node . name )
378
+ ) {
379
+ return false ;
380
+ }
381
+
382
+ // check fireEvent.click() usage
383
+ const regularCall =
384
+ ASTUtils . isIdentifier ( parentMemberExpression . object ) &&
385
+ parentMemberExpression . object . name === fireEventUtilName ;
386
+
387
+ // check testingLibraryUtils.fireEvent.click() usage
388
+ const wildcardCall =
389
+ isMemberExpression ( parentMemberExpression . object ) &&
390
+ ASTUtils . isIdentifier ( parentMemberExpression . object . object ) &&
391
+ parentMemberExpression . object . object . name === fireEventUtilName &&
392
+ ASTUtils . isIdentifier ( parentMemberExpression . object . property ) &&
393
+ parentMemberExpression . object . property . name === FIRE_EVENT_NAME ;
394
+
395
+ return regularCall || wildcardCall ;
396
+ } ;
397
+
398
+ const isUserEventMethod : IsUserEventMethodFn = ( node ) => {
399
+ const userEvent = findImportedUserEventSpecifier ( ) ;
400
+ let userEventName : string | undefined ;
401
+
402
+ if ( userEvent ) {
403
+ userEventName = userEvent . name ;
404
+ } else if ( isAggressiveModuleReportingEnabled ( ) ) {
405
+ userEventName = USER_EVENT_NAME ;
406
+ }
407
+
408
+ if ( ! userEventName ) {
409
+ return false ;
410
+ }
411
+
412
+ const parentMemberExpression :
413
+ | TSESTree . MemberExpression
414
+ | undefined = isMemberExpression ( node . parent ) ? node . parent : undefined ;
415
+
416
+ if ( ! parentMemberExpression ) {
417
+ return false ;
418
+ }
419
+
420
+ // make sure that given node it's not userEvent object itself
421
+ if (
422
+ [ userEventName , USER_EVENT_NAME ] . includes ( node . name ) ||
423
+ ( ASTUtils . isIdentifier ( parentMemberExpression . object ) &&
424
+ parentMemberExpression . object . name === node . name )
425
+ ) {
426
+ return false ;
427
+ }
428
+
429
+ // check userEvent.click() usage
430
+ return (
431
+ ASTUtils . isIdentifier ( parentMemberExpression . object ) &&
432
+ parentMemberExpression . object . name === userEventName
433
+ ) ;
407
434
} ;
408
435
409
436
/**
@@ -553,6 +580,27 @@ export function detectTestingLibraryUtils<
553
580
}
554
581
} ;
555
582
583
+ const findImportedUserEventSpecifier : ( ) => TSESTree . Identifier | null = ( ) => {
584
+ if ( ! importedUserEventLibraryNode ) {
585
+ return null ;
586
+ }
587
+
588
+ if ( isImportDeclaration ( importedUserEventLibraryNode ) ) {
589
+ const userEventIdentifier = importedUserEventLibraryNode . specifiers . find (
590
+ ( specifier ) => isImportDefaultSpecifier ( specifier )
591
+ ) ;
592
+
593
+ if ( userEventIdentifier ) {
594
+ return userEventIdentifier . local ;
595
+ }
596
+ } else {
597
+ const requireNode = importedUserEventLibraryNode . parent as TSESTree . VariableDeclarator ;
598
+ return requireNode . id as TSESTree . Identifier ;
599
+ }
600
+
601
+ return null ;
602
+ } ;
603
+
556
604
const getImportedUtilSpecifier = (
557
605
node : TSESTree . MemberExpression | TSESTree . Identifier
558
606
) : TSESTree . ImportClause | TSESTree . Identifier | undefined => {
@@ -607,6 +655,7 @@ export function detectTestingLibraryUtils<
607
655
isFireEventUtil,
608
656
isUserEventUtil,
609
657
isFireEventMethod,
658
+ isUserEventMethod,
610
659
isRenderUtil,
611
660
isRenderVariableDeclarator,
612
661
isDebugUtil,
@@ -644,6 +693,15 @@ export function detectTestingLibraryUtils<
644
693
) {
645
694
importedCustomModuleNode = node ;
646
695
}
696
+
697
+ // check only if user-event import not found yet so we avoid
698
+ // to override importedUserEventLibraryNode after it's found
699
+ if (
700
+ ! importedUserEventLibraryNode &&
701
+ String ( node . source . value ) === USER_EVENT_PACKAGE
702
+ ) {
703
+ importedUserEventLibraryNode = node ;
704
+ }
647
705
} ,
648
706
649
707
// Check if Testing Library related modules are loaded with required.
@@ -676,6 +734,18 @@ export function detectTestingLibraryUtils<
676
734
) {
677
735
importedCustomModuleNode = callExpression ;
678
736
}
737
+
738
+ if (
739
+ ! importedCustomModuleNode &&
740
+ args . some (
741
+ ( arg ) =>
742
+ isLiteral ( arg ) &&
743
+ typeof arg . value === 'string' &&
744
+ arg . value === USER_EVENT_PACKAGE
745
+ )
746
+ ) {
747
+ importedUserEventLibraryNode = callExpression ;
748
+ }
679
749
} ,
680
750
} ;
681
751
0 commit comments