@@ -89,8 +89,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
89
89
// Track variables assigned from userEvent.setup() (directly or via destructuring)
90
90
const userEventSetupVars = new Set < string > ( ) ;
91
91
92
- // Temporary: Map function names to property names that are assigned from userEvent.setup()
93
- const tempSetupFunctionProps = new Map < string , Set < string > > ( ) ;
92
+ // Track functions that return userEvent.setup() instances and their property names
93
+ const setupFunctions = new Map < string , Set < string > > ( ) ;
94
94
95
95
function reportUnhandledNode ( {
96
96
node,
@@ -121,6 +121,32 @@ export default createTestingLibraryRule<Options, MessageIds>({
121
121
}
122
122
}
123
123
124
+ function isUserEventSetupCall ( node : TSESTree . Node ) : boolean {
125
+ return (
126
+ node . type === AST_NODE_TYPES . CallExpression &&
127
+ node . callee . type === AST_NODE_TYPES . MemberExpression &&
128
+ node . callee . object . type === AST_NODE_TYPES . Identifier &&
129
+ node . callee . object . name === USER_EVENT_NAME &&
130
+ node . callee . property . type === AST_NODE_TYPES . Identifier &&
131
+ node . callee . property . name === USER_EVENT_SETUP_FUNCTION_NAME
132
+ ) ;
133
+ }
134
+
135
+ function findFunctionName ( node : TSESTree . Node ) : string | null {
136
+ let current : TSESTree . Node | undefined = node ;
137
+ while ( current ) {
138
+ if (
139
+ current . type === AST_NODE_TYPES . FunctionDeclaration ||
140
+ current . type === AST_NODE_TYPES . FunctionExpression ||
141
+ current . type === AST_NODE_TYPES . ArrowFunctionExpression
142
+ ) {
143
+ return getFunctionName ( current ) ;
144
+ }
145
+ current = current . parent ;
146
+ }
147
+ return null ;
148
+ }
149
+
124
150
const eventModules =
125
151
typeof options . eventModule === 'string'
126
152
? [ options . eventModule ]
@@ -131,39 +157,37 @@ export default createTestingLibraryRule<Options, MessageIds>({
131
157
return {
132
158
// Track variables assigned from userEvent.setup() and destructuring from setup functions
133
159
VariableDeclarator ( node : TSESTree . VariableDeclarator ) {
160
+ if ( ! isUserEventEnabled ) return ;
161
+
134
162
// Direct assignment: const user = userEvent.setup();
135
163
if (
136
- isUserEventEnabled &&
137
164
node . init &&
138
- node . init . type === AST_NODE_TYPES . CallExpression &&
139
- node . init . callee . type === AST_NODE_TYPES . MemberExpression &&
140
- node . init . callee . object . type === AST_NODE_TYPES . Identifier &&
141
- node . init . callee . object . name === USER_EVENT_NAME &&
142
- node . init . callee . property . type === AST_NODE_TYPES . Identifier &&
143
- node . init . callee . property . name === USER_EVENT_SETUP_FUNCTION_NAME &&
165
+ isUserEventSetupCall ( node . init ) &&
144
166
node . id . type === AST_NODE_TYPES . Identifier
145
167
) {
146
168
userEventSetupVars . add ( node . id . name ) ;
147
169
}
148
170
149
171
// Destructuring: const { user, myUser: alias } = setup(...)
150
172
if (
151
- isUserEventEnabled &&
152
173
node . id . type === AST_NODE_TYPES . ObjectPattern &&
153
174
node . init &&
154
175
node . init . type === AST_NODE_TYPES . CallExpression &&
155
- node . init . callee . type === AST_NODE_TYPES . Identifier &&
156
- tempSetupFunctionProps . has ( node . init . callee . name )
176
+ node . init . callee . type === AST_NODE_TYPES . Identifier
157
177
) {
158
- const setupProps = tempSetupFunctionProps . get ( node . init . callee . name ) ! ;
159
- for ( const prop of node . id . properties ) {
160
- if (
161
- prop . type === AST_NODE_TYPES . Property &&
162
- prop . key . type === AST_NODE_TYPES . Identifier &&
163
- setupProps . has ( prop . key . name ) &&
164
- prop . value . type === AST_NODE_TYPES . Identifier
165
- ) {
166
- userEventSetupVars . add ( prop . value . name ) ;
178
+ const functionName = node . init . callee . name ;
179
+ const setupProps = setupFunctions . get ( functionName ) ;
180
+
181
+ if ( setupProps ) {
182
+ for ( const prop of node . id . properties ) {
183
+ if (
184
+ prop . type === AST_NODE_TYPES . Property &&
185
+ prop . key . type === AST_NODE_TYPES . Identifier &&
186
+ setupProps . has ( prop . key . name ) &&
187
+ prop . value . type === AST_NODE_TYPES . Identifier
188
+ ) {
189
+ userEventSetupVars . add ( prop . value . name ) ;
190
+ }
167
191
}
168
192
}
169
193
}
@@ -172,54 +196,37 @@ export default createTestingLibraryRule<Options, MessageIds>({
172
196
// Track functions that return { ...: userEvent.setup(), ... }
173
197
ReturnStatement ( node : TSESTree . ReturnStatement ) {
174
198
if (
175
- isUserEventEnabled &&
176
- node . argument &&
177
- node . argument . type = == AST_NODE_TYPES . ObjectExpression
199
+ ! isUserEventEnabled ||
200
+ ! node . argument ||
201
+ node . argument . type ! == AST_NODE_TYPES . ObjectExpression
178
202
) {
179
- const setupProps = new Set < string > ( ) ;
180
- for ( const prop of node . argument . properties ) {
181
- if (
182
- prop . type === AST_NODE_TYPES . Property &&
183
- prop . key . type === AST_NODE_TYPES . Identifier
203
+ return ;
204
+ }
205
+
206
+ const setupProps = new Set < string > ( ) ;
207
+ for ( const prop of node . argument . properties ) {
208
+ if (
209
+ prop . type === AST_NODE_TYPES . Property &&
210
+ prop . key . type === AST_NODE_TYPES . Identifier
211
+ ) {
212
+ // Direct: foo: userEvent.setup()
213
+ if ( isUserEventSetupCall ( prop . value ) ) {
214
+ setupProps . add ( prop . key . name ) ;
215
+ }
216
+ // Indirect: foo: u, where u is a userEvent.setup() var
217
+ else if (
218
+ prop . value . type === AST_NODE_TYPES . Identifier &&
219
+ userEventSetupVars . has ( prop . value . name )
184
220
) {
185
- // Direct: foo: userEvent.setup()
186
- if (
187
- prop . value . type === AST_NODE_TYPES . CallExpression &&
188
- prop . value . callee . type === AST_NODE_TYPES . MemberExpression &&
189
- prop . value . callee . object . type === AST_NODE_TYPES . Identifier &&
190
- prop . value . callee . object . name === USER_EVENT_NAME &&
191
- prop . value . callee . property . type === AST_NODE_TYPES . Identifier &&
192
- prop . value . callee . property . name ===
193
- USER_EVENT_SETUP_FUNCTION_NAME
194
- ) {
195
- setupProps . add ( prop . key . name ) ;
196
- }
197
- // Indirect: foo: u, where u is a userEvent.setup() var
198
- else if (
199
- prop . value . type === AST_NODE_TYPES . Identifier &&
200
- userEventSetupVars . has ( prop . value . name )
201
- ) {
202
- setupProps . add ( prop . key . name ) ;
203
- }
221
+ setupProps . add ( prop . key . name ) ;
204
222
}
205
223
}
206
- if ( setupProps . size > 0 ) {
207
- // Find the function this return is in
208
- let parent : TSESTree . Node | undefined = node . parent ;
209
- while ( parent ) {
210
- if (
211
- parent . type === AST_NODE_TYPES . FunctionDeclaration ||
212
- parent . type === AST_NODE_TYPES . FunctionExpression ||
213
- parent . type === AST_NODE_TYPES . ArrowFunctionExpression
214
- ) {
215
- const name = getFunctionName ( parent ) ;
216
- if ( name ) {
217
- tempSetupFunctionProps . set ( name , setupProps ) ;
218
- }
219
- break ;
220
- }
221
- parent = parent . parent ;
222
- }
224
+ }
225
+
226
+ if ( setupProps . size > 0 ) {
227
+ const functionName = findFunctionName ( node ) ;
228
+ if ( functionName ) {
229
+ setupFunctions . set ( functionName , setupProps ) ;
223
230
}
224
231
}
225
232
} ,
0 commit comments