Skip to content

Commit 6d90153

Browse files
authored
refactor(prefer-screen-queries): migrate to v4 (#285)
* refactor(prefer-screen-queries): use new rule creator * refactor(prefer-screen-queries): detect render methods with helper * refactor(prefer-screen-queries): detect queries with helper * fix(prefer-screen-queries): detect queries coming from proper render
1 parent b0b8c48 commit 6d90153

File tree

3 files changed

+212
-79
lines changed

3 files changed

+212
-79
lines changed

lib/detect-testing-library-utils.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type IsQueryQueryVariantFn = (node: TSESTree.Identifier) => boolean;
6161
type IsFindQueryVariantFn = (node: TSESTree.Identifier) => boolean;
6262
type IsSyncQueryFn = (node: TSESTree.Identifier) => boolean;
6363
type IsAsyncQueryFn = (node: TSESTree.Identifier) => boolean;
64+
type IsQueryFn = (node: TSESTree.Identifier) => boolean;
6465
type IsCustomQueryFn = (node: TSESTree.Identifier) => boolean;
6566
type IsAsyncUtilFn = (node: TSESTree.Identifier) => boolean;
6667
type IsFireEventMethodFn = (node: TSESTree.Identifier) => boolean;
@@ -87,6 +88,7 @@ export interface DetectionHelpers {
8788
isFindQueryVariant: IsFindQueryVariantFn;
8889
isSyncQuery: IsSyncQueryFn;
8990
isAsyncQuery: IsAsyncQueryFn;
91+
isQuery: IsQueryFn;
9092
isCustomQuery: IsCustomQueryFn;
9193
isAsyncUtil: IsAsyncUtilFn;
9294
isFireEventMethod: IsFireEventMethodFn;
@@ -271,11 +273,16 @@ export function detectTestingLibraryUtils<
271273
return isFindQueryVariant(node);
272274
};
273275

276+
/**
277+
* Determines whether a given node is a valid query,
278+
* either built-in or custom
279+
*/
280+
const isQuery: IsQueryFn = (node) => {
281+
return isSyncQuery(node) || isAsyncQuery(node);
282+
};
283+
274284
const isCustomQuery: IsCustomQueryFn = (node) => {
275-
return (
276-
(isSyncQuery(node) || isAsyncQuery(node)) &&
277-
!ALL_QUERIES_COMBINATIONS.includes(node.name)
278-
);
285+
return isQuery(node) && !ALL_QUERIES_COMBINATIONS.includes(node.name);
279286
};
280287

281288
/**
@@ -528,6 +535,7 @@ export function detectTestingLibraryUtils<
528535
isFindQueryVariant,
529536
isSyncQuery,
530537
isAsyncQuery,
538+
isQuery,
531539
isCustomQuery,
532540
isAsyncUtil,
533541
isFireEventMethod,

lib/rules/prefer-screen-queries.ts

+51-40
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1+
import { ASTUtils, TSESTree } from '@typescript-eslint/experimental-utils';
12
import {
2-
ESLintUtils,
3-
TSESTree,
4-
ASTUtils,
5-
} from '@typescript-eslint/experimental-utils';
6-
import { getDocsUrl, ALL_QUERIES_COMBINATIONS } from '../utils';
7-
import {
3+
isCallExpression,
84
isMemberExpression,
5+
isObjectExpression,
96
isObjectPattern,
10-
isCallExpression,
117
isProperty,
12-
isObjectExpression,
138
} from '../node-utils';
9+
import { createTestingLibraryRule } from '../create-testing-library-rule';
1410

1511
export const RULE_NAME = 'prefer-screen-queries';
1612
export type MessageIds = 'preferScreenQueries';
@@ -20,7 +16,6 @@ const ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING = [
2016
'container',
2117
'baseElement',
2218
];
23-
const ALL_QUERIES_COMBINATIONS_REGEXP = ALL_QUERIES_COMBINATIONS.join('|');
2419

2520
function usesContainerOrBaseElement(node: TSESTree.CallExpression) {
2621
const secondArgument = node.arguments[1];
@@ -35,7 +30,7 @@ function usesContainerOrBaseElement(node: TSESTree.CallExpression) {
3530
);
3631
}
3732

38-
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
33+
export default createTestingLibraryRule<Options, MessageIds>({
3934
name: RULE_NAME,
4035
meta: {
4136
type: 'suggestion',
@@ -53,7 +48,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
5348
},
5449
defaultOptions: [],
5550

56-
create(context) {
51+
create(context, _, helpers) {
5752
function reportInvalidUsage(node: TSESTree.Identifier) {
5853
context.report({
5954
node,
@@ -64,8 +59,26 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
6459
});
6560
}
6661

67-
const queriesRegex = new RegExp(ALL_QUERIES_COMBINATIONS_REGEXP);
68-
const queriesDestructuredInWithinDeclaration: string[] = [];
62+
function saveSafeDestructuredQueries(node: TSESTree.VariableDeclarator) {
63+
if (isObjectPattern(node.id)) {
64+
const identifiers = node.id.properties
65+
.filter(
66+
(property) =>
67+
isProperty(property) &&
68+
ASTUtils.isIdentifier(property.key) &&
69+
helpers.isQuery(property.key)
70+
)
71+
.map(
72+
(property: TSESTree.Property) =>
73+
(property.key as TSESTree.Identifier).name
74+
);
75+
safeDestructuredQueries.push(...identifiers);
76+
}
77+
}
78+
79+
// keep here those queries which are safe and shouldn't be reported
80+
// (from within, from render + container/base element, not related to TL, etc)
81+
const safeDestructuredQueries: string[] = [];
6982
// use an array as within might be used more than once in a test
7083
const withinDeclaredVariables: string[] = [];
7184

@@ -77,63 +90,61 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
7790
) {
7891
return;
7992
}
93+
94+
const isComingFromValidRender = helpers.isRenderUtil(node.init.callee);
95+
96+
if (!isComingFromValidRender) {
97+
// save the destructured query methods as safe since they are coming
98+
// from render not related to TL
99+
saveSafeDestructuredQueries(node);
100+
}
101+
80102
const isWithinFunction = node.init.callee.name === 'within';
81-
// TODO add the custom render option #198
82103
const usesRenderOptions =
83-
node.init.callee.name === 'render' &&
84-
usesContainerOrBaseElement(node.init);
104+
isComingFromValidRender && usesContainerOrBaseElement(node.init);
85105

86106
if (!isWithinFunction && !usesRenderOptions) {
87107
return;
88108
}
89109

90110
if (isObjectPattern(node.id)) {
91-
// save the destructured query methods
92-
const identifiers = node.id.properties
93-
.filter(
94-
(property) =>
95-
isProperty(property) &&
96-
ASTUtils.isIdentifier(property.key) &&
97-
queriesRegex.test(property.key.name)
98-
)
99-
.map(
100-
(property: TSESTree.Property) =>
101-
(property.key as TSESTree.Identifier).name
102-
);
103-
104-
queriesDestructuredInWithinDeclaration.push(...identifiers);
111+
// save the destructured query methods as safe since they are coming
112+
// from within or render + base/container options
113+
saveSafeDestructuredQueries(node);
105114
return;
106115
}
107116

108117
if (ASTUtils.isIdentifier(node.id)) {
109118
withinDeclaredVariables.push(node.id.name);
110119
}
111120
},
112-
[`CallExpression > Identifier[name=/^${ALL_QUERIES_COMBINATIONS_REGEXP}$/]`](
113-
node: TSESTree.Identifier
114-
) {
121+
'CallExpression > Identifier'(node: TSESTree.Identifier) {
122+
if (!helpers.isQuery(node)) {
123+
return;
124+
}
125+
115126
if (
116-
!queriesDestructuredInWithinDeclaration.some(
117-
(queryName) => queryName === node.name
118-
)
127+
!safeDestructuredQueries.some((queryName) => queryName === node.name)
119128
) {
120129
reportInvalidUsage(node);
121130
}
122131
},
123-
[`MemberExpression > Identifier[name=/^${ALL_QUERIES_COMBINATIONS_REGEXP}$/]`](
124-
node: TSESTree.Identifier
125-
) {
132+
'MemberExpression > Identifier'(node: TSESTree.Identifier) {
126133
function isIdentifierAllowed(name: string) {
127134
return ['screen', ...withinDeclaredVariables].includes(name);
128135
}
129136

137+
if (!helpers.isQuery(node)) {
138+
return;
139+
}
140+
130141
if (
131142
ASTUtils.isIdentifier(node) &&
132143
isMemberExpression(node.parent) &&
133144
isCallExpression(node.parent.object) &&
134145
ASTUtils.isIdentifier(node.parent.object.callee) &&
135146
node.parent.object.callee.name !== 'within' &&
136-
node.parent.object.callee.name === 'render' &&
147+
helpers.isRenderUtil(node.parent.object.callee) &&
137148
!usesContainerOrBaseElement(node.parent.object)
138149
) {
139150
reportInvalidUsage(node);

0 commit comments

Comments
 (0)