Skip to content

Commit 816df6e

Browse files
authored
fix(await-async-query): get correct Identifier related to CallExpression (#374)
Closes #359
1 parent 632b492 commit 816df6e

File tree

2 files changed

+76
-14
lines changed

2 files changed

+76
-14
lines changed

lib/rules/await-async-query.ts

+22-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils';
22
import {
33
findClosestCallExpressionNode,
4+
getDeepestIdentifierNode,
45
getFunctionName,
56
getInnermostReturningFunction,
67
getVariableReferences,
@@ -27,9 +28,10 @@ export default createTestingLibraryRule<Options, MessageIds>({
2728
},
2829
},
2930
messages: {
30-
awaitAsyncQuery: 'promise returned from {{ name }} query must be handled',
31+
awaitAsyncQuery:
32+
'promise returned from `{{ name }}` query must be handled',
3133
asyncQueryWrapper:
32-
'promise returned from {{ name }} wrapper over async query must be handled',
34+
'promise returned from `{{ name }}` wrapper over async query must be handled',
3335
},
3436
schema: [],
3537
},
@@ -46,10 +48,16 @@ export default createTestingLibraryRule<Options, MessageIds>({
4648
}
4749

4850
return {
49-
'CallExpression Identifier'(node: TSESTree.Identifier) {
50-
if (helpers.isAsyncQuery(node)) {
51+
CallExpression(node) {
52+
const identifierNode = getDeepestIdentifierNode(node);
53+
54+
if (!identifierNode) {
55+
return;
56+
}
57+
58+
if (helpers.isAsyncQuery(identifierNode)) {
5159
// detect async query used within wrapper function for later analysis
52-
detectAsyncQueryWrapper(node);
60+
detectAsyncQueryWrapper(identifierNode);
5361

5462
const closestCallExpressionNode = findClosestCallExpressionNode(
5563
node,
@@ -68,11 +76,11 @@ export default createTestingLibraryRule<Options, MessageIds>({
6876
// check direct usage of async query:
6977
// const element = await findByRole('button')
7078
if (references && references.length === 0) {
71-
if (!isPromiseHandled(node)) {
79+
if (!isPromiseHandled(identifierNode)) {
7280
return context.report({
73-
node,
81+
node: identifierNode,
7482
messageId: 'awaitAsyncQuery',
75-
data: { name: node.name },
83+
data: { name: identifierNode.name },
7684
});
7785
}
7886
}
@@ -86,19 +94,19 @@ export default createTestingLibraryRule<Options, MessageIds>({
8694
!isPromiseHandled(reference.identifier)
8795
) {
8896
return context.report({
89-
node,
97+
node: identifierNode,
9098
messageId: 'awaitAsyncQuery',
91-
data: { name: node.name },
99+
data: { name: identifierNode.name },
92100
});
93101
}
94102
}
95-
} else if (functionWrappersNames.includes(node.name)) {
103+
} else if (functionWrappersNames.includes(identifierNode.name)) {
96104
// check async queries used within a wrapper previously detected
97-
if (!isPromiseHandled(node)) {
105+
if (!isPromiseHandled(identifierNode)) {
98106
return context.report({
99-
node,
107+
node: identifierNode,
100108
messageId: 'asyncQueryWrapper',
101-
data: { name: node.name },
109+
data: { name: identifierNode.name },
102110
});
103111
}
104112
}

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

+54
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,32 @@ ruleTester.run(RULE_NAME, rule, {
274274
})
275275
`,
276276

277+
// https://github.com/testing-library/eslint-plugin-testing-library/issues/359
278+
`// issue #359
279+
import { render, screen } from 'mocks/test-utils'
280+
import userEvent from '@testing-library/user-event'
281+
282+
const testData = {
283+
name: 'John Doe',
284+
285+
password: 'extremeSecret',
286+
}
287+
288+
const selectors = {
289+
username: () => screen.findByRole('textbox', { name: /username/i }),
290+
email: () => screen.findByRole('textbox', { name: /e-mail/i }),
291+
password: () => screen.findByLabelText(/password/i),
292+
}
293+
294+
test('this is a valid case', async () => {
295+
render(<SomeComponent />)
296+
userEvent.type(await selectors.username(), testData.name)
297+
userEvent.type(await selectors.email(), testData.email)
298+
userEvent.type(await selectors.password(), testData.password)
299+
// ...
300+
})
301+
`,
302+
277303
// edge case for coverage
278304
// valid async query usage without any function defined
279305
// so there is no innermost function scope found
@@ -449,5 +475,33 @@ ruleTester.run(RULE_NAME, rule, {
449475
`,
450476
errors: [{ messageId: 'awaitAsyncQuery', line: 3, column: 25 }],
451477
},
478+
479+
{
480+
code: `// similar to issue #359 but forcing an error in no-awaited wrapper
481+
import { render, screen } from 'mocks/test-utils'
482+
import userEvent from '@testing-library/user-event'
483+
484+
const testData = {
485+
name: 'John Doe',
486+
487+
password: 'extremeSecret',
488+
}
489+
490+
const selectors = {
491+
username: () => screen.findByRole('textbox', { name: /username/i }),
492+
email: () => screen.findByRole('textbox', { name: /e-mail/i }),
493+
password: () => screen.findByLabelText(/password/i),
494+
}
495+
496+
test('this is a valid case', async () => {
497+
render(<SomeComponent />)
498+
userEvent.type(selectors.username(), testData.name) // <-- unhandled here
499+
userEvent.type(await selectors.email(), testData.email)
500+
userEvent.type(await selectors.password(), testData.password)
501+
// ...
502+
})
503+
`,
504+
errors: [{ messageId: 'asyncQueryWrapper', line: 19, column: 34 }],
505+
},
452506
],
453507
});

0 commit comments

Comments
 (0)