Skip to content

Commit cc040a5

Browse files
committed
Merge "main" branch into "306_null-identifiers"
2 parents c20dbb9 + 7e9d5c5 commit cc040a5

12 files changed

+293
-112
lines changed

lib/detect-testing-library-utils.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -342,12 +342,20 @@ export function detectTestingLibraryUtils<
342342
* Not to be confused with {@link isUserEventMethod}
343343
*/
344344
const isUserEventUtil = (node: TSESTree.Identifier): boolean => {
345-
return isTestingLibraryUtil(
346-
node,
347-
(identifierNodeName, originalNodeName) => {
348-
return [identifierNodeName, originalNodeName].includes('userEvent');
349-
}
350-
);
345+
const userEvent = findImportedUserEventSpecifier();
346+
let userEventName: string | undefined;
347+
348+
if (userEvent) {
349+
userEventName = userEvent.name;
350+
} else if (isAggressiveModuleReportingEnabled()) {
351+
userEventName = USER_EVENT_NAME;
352+
}
353+
354+
if (!userEventName) {
355+
return false;
356+
}
357+
358+
return node.name === userEventName;
351359
};
352360

353361
/**

lib/node-utils.ts

-13
Original file line numberDiff line numberDiff line change
@@ -153,19 +153,6 @@ export function findClosestCallNode(
153153
}
154154
}
155155

156-
export function isCallExpressionCallee(
157-
node: TSESTree.CallExpression,
158-
identifier: TSESTree.Identifier
159-
): boolean {
160-
const nodeInnerIdentifier = getDeepestIdentifierNode(node);
161-
162-
if (nodeInnerIdentifier) {
163-
return nodeInnerIdentifier.name === identifier.name;
164-
}
165-
166-
return false;
167-
}
168-
169156
export function isObjectExpression(
170157
node: TSESTree.Expression
171158
): node is TSESTree.ObjectExpression {

lib/rules/await-async-utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
7979
node,
8080
messageId: 'awaitAsyncUtil',
8181
data: {
82-
name: referenceNode.name,
82+
name: node.name,
8383
},
8484
});
8585
}

lib/rules/no-await-sync-query.ts

+7-13
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { TSESTree } from '@typescript-eslint/experimental-utils';
22
import { createTestingLibraryRule } from '../create-testing-library-rule';
3-
import {
4-
findClosestCallExpressionNode,
5-
isCallExpressionCallee,
6-
} from '../node-utils';
3+
import { getDeepestIdentifierNode } from '../node-utils';
74

85
export const RULE_NAME = 'no-await-sync-query';
96
export type MessageIds = 'noAwaitSyncQuery';
@@ -28,22 +25,19 @@ export default createTestingLibraryRule<Options, MessageIds>({
2825

2926
create(context, _, helpers) {
3027
return {
31-
'AwaitExpression > CallExpression Identifier'(node: TSESTree.Identifier) {
32-
const closestCallExpression = findClosestCallExpressionNode(node, true);
33-
if (!closestCallExpression) {
34-
return;
35-
}
28+
'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) {
29+
const deepestIdentifierNode = getDeepestIdentifierNode(node);
3630

37-
if (!isCallExpressionCallee(closestCallExpression, node)) {
31+
if (!deepestIdentifierNode) {
3832
return;
3933
}
4034

41-
if (helpers.isSyncQuery(node)) {
35+
if (helpers.isSyncQuery(deepestIdentifierNode)) {
4236
context.report({
43-
node,
37+
node: deepestIdentifierNode,
4438
messageId: 'noAwaitSyncQuery',
4539
data: {
46-
name: node.name,
40+
name: deepestIdentifierNode.name,
4741
},
4842
});
4943
}

lib/rules/no-wait-for-multiple-assertions.ts

+23-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { TSESTree } from '@typescript-eslint/experimental-utils';
2-
import { getPropertyIdentifierNode } from '../node-utils';
2+
import {
3+
getPropertyIdentifierNode,
4+
isExpressionStatement,
5+
} from '../node-utils';
36
import { createTestingLibraryRule } from '../create-testing-library-rule';
47

58
export const RULE_NAME = 'no-wait-for-multiple-assertions';
@@ -23,16 +26,21 @@ export default createTestingLibraryRule<Options, MessageIds>({
2326
},
2427
defaultOptions: [],
2528
create: function (context, _, helpers) {
26-
function totalExpect(body: Array<TSESTree.Node>): Array<TSESTree.Node> {
29+
function getExpectNodes(
30+
body: Array<TSESTree.Node>
31+
): Array<TSESTree.ExpressionStatement> {
2732
return body.filter((node) => {
28-
const expressionIdentifier = getPropertyIdentifierNode(node);
33+
if (!isExpressionStatement(node)) {
34+
return false;
35+
}
2936

37+
const expressionIdentifier = getPropertyIdentifierNode(node);
3038
if (!expressionIdentifier) {
3139
return false;
3240
}
3341

3442
return expressionIdentifier.name === 'expect';
35-
});
43+
}) as Array<TSESTree.ExpressionStatement>;
3644
}
3745

3846
function reportMultipleAssertion(node: TSESTree.BlockStatement) {
@@ -52,14 +60,20 @@ export default createTestingLibraryRule<Options, MessageIds>({
5260
return;
5361
}
5462

55-
if (totalExpect(node.body).length <= 1) {
63+
const expectNodes = getExpectNodes(node.body);
64+
65+
if (expectNodes.length <= 1) {
5666
return;
5767
}
5868

59-
context.report({
60-
node: callExpressionNode,
61-
messageId: 'noWaitForMultipleAssertion',
62-
});
69+
for (let i = 0; i < expectNodes.length; i++) {
70+
if (i !== 0) {
71+
context.report({
72+
node: expectNodes[i],
73+
messageId: 'noWaitForMultipleAssertion',
74+
});
75+
}
76+
}
6377
}
6478

6579
return {

lib/rules/no-wait-for-side-effects.ts

+21-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { TSESTree } from '@typescript-eslint/experimental-utils';
2-
import { getPropertyIdentifierNode } from '../node-utils';
2+
import {
3+
getPropertyIdentifierNode,
4+
isExpressionStatement,
5+
} from '../node-utils';
36
import { createTestingLibraryRule } from '../create-testing-library-rule';
47

58
export const RULE_NAME = 'no-wait-for-side-effects';
@@ -23,10 +26,15 @@ export default createTestingLibraryRule<Options, MessageIds>({
2326
},
2427
defaultOptions: [],
2528
create: function (context, _, helpers) {
26-
function hasSideEffects(body: Array<TSESTree.Node>): boolean {
27-
return body.some((node) => {
28-
const expressionIdentifier = getPropertyIdentifierNode(node);
29+
function getSideEffectNodes(
30+
body: TSESTree.Node[]
31+
): TSESTree.ExpressionStatement[] {
32+
return body.filter((node) => {
33+
if (!isExpressionStatement(node)) {
34+
return false;
35+
}
2936

37+
const expressionIdentifier = getPropertyIdentifierNode(node);
3038
if (!expressionIdentifier) {
3139
return false;
3240
}
@@ -35,7 +43,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
3543
helpers.isFireEventUtil(expressionIdentifier) ||
3644
helpers.isUserEventUtil(expressionIdentifier)
3745
);
38-
});
46+
}) as TSESTree.ExpressionStatement[];
3947
}
4048

4149
function reportSideEffects(node: TSESTree.BlockStatement) {
@@ -55,14 +63,17 @@ export default createTestingLibraryRule<Options, MessageIds>({
5563
return;
5664
}
5765

58-
if (!hasSideEffects(node.body)) {
66+
const sideEffectNodes = getSideEffectNodes(node.body);
67+
if (sideEffectNodes.length === 0) {
5968
return;
6069
}
6170

62-
context.report({
63-
node: callExpressionNode,
64-
messageId: 'noSideEffectsWaitFor',
65-
});
71+
for (const sideEffectNode of sideEffectNodes) {
72+
context.report({
73+
node: sideEffectNode,
74+
messageId: 'noSideEffectsWaitFor',
75+
});
76+
}
6677
}
6778

6879
return {

lib/rules/prefer-find-by.ts

+30-14
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const RULE_NAME = 'prefer-find-by';
1616
export type MessageIds = 'preferFindBy';
1717
type Options = [];
1818

19-
export const WAIT_METHODS = ['waitFor', 'waitForElement', 'wait'];
19+
export const WAIT_METHODS = ['waitFor', 'waitForElement', 'wait'] as const;
2020

2121
export function getFindByQueryVariant(
2222
queryMethod: string
@@ -54,13 +54,13 @@ export default createTestingLibraryRule<Options, MessageIds>({
5454
type: 'suggestion',
5555
docs: {
5656
description:
57-
'Suggest using find* instead of waitFor to wait for elements',
57+
'Suggest using `find*` query instead of `waitFor` + `get*` to wait for elements',
5858
category: 'Best Practices',
5959
recommended: 'warn',
6060
},
6161
messages: {
6262
preferFindBy:
63-
'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}',
63+
'Prefer `{{queryVariant}}{{queryMethod}}` query over using `{{waitForMethodName}}` + `{{prevQuery}}`',
6464
},
6565
fixable: 'code',
6666
schema: [],
@@ -72,30 +72,39 @@ export default createTestingLibraryRule<Options, MessageIds>({
7272

7373
/**
7474
* Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario
75-
* @param {TSESTree.CallExpression} node - The CallExpresion node that contains the wait* method
76-
* @param {'findBy' | 'findAllBy'} replacementParams.queryVariant - The variant method used to query: findBy/findByAll.
77-
* @param {string} replacementParams.queryMethod - Suffix string to build the query method (the query-part that comes after the "By"): LabelText, Placeholder, Text, Role, Title, etc.
78-
* @param {ReportFixFunction} replacementParams.fix - Function that applies the fix to correct the code
75+
* @param node - The CallExpresion node that contains the wait* method
76+
* @param replacementParams - Object with info for error message and autofix:
77+
* @param replacementParams.queryVariant - The variant method used to query: findBy/findAllBy.
78+
* @param replacementParams.prevQuery - The query originally used inside `waitFor`
79+
* @param replacementParams.queryMethod - Suffix string to build the query method (the query-part that comes after the "By"): LabelText, Placeholder, Text, Role, Title, etc.
80+
* @param replacementParams.waitForMethodName - wait for method used: waitFor/wait/waitForElement
81+
* @param replacementParams.fix - Function that applies the fix to correct the code
7982
*/
8083
function reportInvalidUsage(
8184
node: TSESTree.CallExpression,
82-
{
83-
queryVariant,
84-
queryMethod,
85-
fix,
86-
}: {
85+
replacementParams: {
8786
queryVariant: 'findBy' | 'findAllBy';
8887
queryMethod: string;
88+
prevQuery: string;
89+
waitForMethodName: string;
8990
fix: TSESLint.ReportFixFunction;
9091
}
9192
) {
93+
const {
94+
queryMethod,
95+
queryVariant,
96+
prevQuery,
97+
waitForMethodName,
98+
fix,
99+
} = replacementParams;
92100
context.report({
93101
node,
94102
messageId: 'preferFindBy',
95103
data: {
96104
queryVariant,
97105
queryMethod,
98-
fullQuery: sourceCode.getText(node),
106+
prevQuery,
107+
waitForMethodName,
99108
},
100109
fix,
101110
});
@@ -105,7 +114,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
105114
'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) {
106115
if (
107116
!ASTUtils.isIdentifier(node.callee) ||
108-
!WAIT_METHODS.includes(node.callee.name)
117+
!helpers.isAsyncUtil(node.callee, WAIT_METHODS)
109118
) {
110119
return;
111120
}
@@ -118,6 +127,9 @@ export default createTestingLibraryRule<Options, MessageIds>({
118127
if (!isCallExpression(argument.body)) {
119128
return;
120129
}
130+
131+
const waitForMethodName = node.callee.name;
132+
121133
// ensure here it's one of the sync methods that we are calling
122134
if (
123135
isMemberExpression(argument.body.callee) &&
@@ -135,6 +147,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
135147
reportInvalidUsage(node, {
136148
queryMethod,
137149
queryVariant,
150+
prevQuery: fullQueryMethod,
151+
waitForMethodName,
138152
fix(fixer) {
139153
const property = ((argument.body as TSESTree.CallExpression)
140154
.callee as TSESTree.MemberExpression).property;
@@ -164,6 +178,8 @@ export default createTestingLibraryRule<Options, MessageIds>({
164178
reportInvalidUsage(node, {
165179
queryMethod,
166180
queryVariant,
181+
prevQuery: fullQueryMethod,
182+
waitForMethodName,
167183
fix(fixer) {
168184
// we know from above callee is an Identifier
169185
if (

0 commit comments

Comments
 (0)