Skip to content

Commit 1c4391c

Browse files
authored
fix(prefer-screen-queries): avoid reporting custom queries (#342)
* fix(prefer-screen-queries): avoid reporting custom queries * docs(prefer-screen-queries): update examples, exceptions and reading Closes #340
1 parent 445adc8 commit 1c4391c

File tree

4 files changed

+94
-66
lines changed

4 files changed

+94
-66
lines changed

docs/rules/prefer-screen-queries.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
# Suggest using `screen` while using queries (`testing-library/prefer-screen-queries`)
1+
# Suggest using `screen` while querying (`testing-library/prefer-screen-queries`)
22

33
## Rule Details
44

5-
DOM Testing Library (and other Testing Library frameworks built on top of it) exports a `screen` object which has every query (and a `debug` method). This works better with autocomplete and makes each test a little simpler to write and maintain.
6-
This rule aims to force writing tests using queries directly from `screen` object rather than destructuring them from `render` result. Given the screen component does not expose utility methods such as `rerender()` or the `container` property, it is correct to use the `render` response in those scenarios.
5+
DOM Testing Library (and other Testing Library frameworks built on top of it) exports a `screen` object which has every query (and a `debug` method). This works better with autocomplete and makes each test a little simpler to write and maintain.
6+
7+
This rule aims to force writing tests using built-in queries directly from `screen` object rather than destructuring them from `render` result. Given the screen component does not expose utility methods such as `rerender()` or the `container` property, it is correct to use the `render` returned value in those scenarios.
8+
9+
However, there are 3 exceptions when this rule won't suggest using `screen` for querying:
10+
11+
1. You are using a query chained to `within`
12+
2. You are using custom queries, so you can't access them through `screen`
13+
3. You are setting the `container` or `baseElement`, so you need to use the queries returned from `render`
714

815
Examples of **incorrect** code for this rule:
916

@@ -65,8 +72,19 @@ unmount();
6572
const { getByText } = render(<Foo />, { baseElement: treeA });
6673
// using container
6774
const { getAllByText } = render(<Foo />, { container: treeA });
75+
76+
// querying with a custom query imported from its own module
77+
import { getByIcon } from 'custom-queries';
78+
const element = getByIcon('search');
79+
80+
// querying with a custom query returned from `render`
81+
const { getByIcon } = render(<Foo />);
82+
const element = getByIcon('search');
6883
```
6984

7085
## Further Reading
7186

72-
- [`screen` documentation](https://testing-library.com/docs/dom-testing-library/api-queries#screen)
87+
- [Common mistakes with React Testing Library - Not using `screen`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen)
88+
- [`screen` documentation](https://testing-library.com/docs/queries/about#screen)
89+
- [Advanced - Custom Queries](https://testing-library.com/docs/dom-testing-library/api-custom-queries/)
90+
- [React Testing Library - Add custom queries](https://testing-library.com/docs/react-testing-library/setup/#add-custom-queries)

lib/detect-testing-library-utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ type IsSyncQueryFn = (node: TSESTree.Identifier) => boolean;
6464
type IsAsyncQueryFn = (node: TSESTree.Identifier) => boolean;
6565
type IsQueryFn = (node: TSESTree.Identifier) => boolean;
6666
type IsCustomQueryFn = (node: TSESTree.Identifier) => boolean;
67+
type IsBuiltInQueryFn = (node: TSESTree.Identifier) => boolean;
6768
type IsAsyncUtilFn = (
6869
node: TSESTree.Identifier,
6970
validNames?: readonly typeof ASYNC_UTILS[number][]
@@ -98,6 +99,7 @@ export interface DetectionHelpers {
9899
isAsyncQuery: IsAsyncQueryFn;
99100
isQuery: IsQueryFn;
100101
isCustomQuery: IsCustomQueryFn;
102+
isBuiltInQuery: IsBuiltInQueryFn;
101103
isAsyncUtil: IsAsyncUtilFn;
102104
isFireEventUtil: (node: TSESTree.Identifier) => boolean;
103105
isUserEventUtil: (node: TSESTree.Identifier) => boolean;
@@ -301,6 +303,10 @@ export function detectTestingLibraryUtils<
301303
return isQuery(node) && !ALL_QUERIES_COMBINATIONS.includes(node.name);
302304
};
303305

306+
const isBuiltInQuery = (node: TSESTree.Identifier): boolean => {
307+
return ALL_QUERIES_COMBINATIONS.includes(node.name);
308+
};
309+
304310
/**
305311
* Determines whether a given node is a valid async util or not.
306312
*
@@ -704,6 +710,7 @@ export function detectTestingLibraryUtils<
704710
isAsyncQuery,
705711
isQuery,
706712
isCustomQuery,
713+
isBuiltInQuery,
707714
isAsyncUtil,
708715
isFireEventUtil,
709716
isUserEventUtil,

lib/rules/prefer-screen-queries.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
6464
if (
6565
isProperty(property) &&
6666
ASTUtils.isIdentifier(property.key) &&
67-
helpers.isQuery(property.key)
67+
helpers.isBuiltInQuery(property.key)
6868
) {
6969
safeDestructuredQueries.push(property.key.name);
7070
}
@@ -115,7 +115,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
115115
}
116116
},
117117
'CallExpression > Identifier'(node: TSESTree.Identifier) {
118-
if (!helpers.isQuery(node)) {
118+
if (!helpers.isBuiltInQuery(node)) {
119119
return;
120120
}
121121

@@ -130,7 +130,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
130130
return ['screen', ...withinDeclaredVariables].includes(name);
131131
}
132132

133-
if (!helpers.isQuery(node)) {
133+
if (!helpers.isBuiltInQuery(node)) {
134134
return;
135135
}
136136

tests/lib/rules/prefer-screen-queries.test.ts

Lines changed: 62 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,13 @@ const ruleTester = createRuleTester();
1111
const CUSTOM_QUERY_COMBINATIONS = combineQueries(ALL_QUERIES_VARIANTS, [
1212
'ByIcon',
1313
]);
14-
const ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS = [
15-
...ALL_QUERIES_COMBINATIONS,
16-
...CUSTOM_QUERY_COMBINATIONS,
17-
];
1814

1915
ruleTester.run(RULE_NAME, rule, {
2016
valid: [
2117
{
2218
code: `const baz = () => 'foo'`,
2319
},
24-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
20+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
2521
code: `screen.${queryMethod}()`,
2622
})),
2723
{
@@ -30,24 +26,45 @@ ruleTester.run(RULE_NAME, rule, {
3026
{
3127
code: `component.otherFunctionShouldNotThrow()`,
3228
},
33-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
29+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
3430
code: `within(component).${queryMethod}()`,
3531
})),
36-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
32+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
3733
code: `within(screen.${queryMethod}()).${queryMethod}()`,
3834
})),
39-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
35+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
4036
code: `
4137
const { ${queryMethod} } = within(screen.getByText('foo'))
4238
${queryMethod}(baz)
4339
`,
4440
})),
45-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
41+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
4642
code: `
4743
const myWithinVariable = within(foo)
4844
myWithinVariable.${queryMethod}('baz')
4945
`,
5046
})),
47+
...CUSTOM_QUERY_COMBINATIONS.map(
48+
(query) => `
49+
import { render } from '@testing-library/react'
50+
import { ${query} } from 'custom-queries'
51+
52+
test("imported custom queries, since they can't be used through screen", () => {
53+
render(foo)
54+
${query}('bar')
55+
})
56+
`
57+
),
58+
...CUSTOM_QUERY_COMBINATIONS.map(
59+
(query) => `
60+
import { render } from '@testing-library/react'
61+
62+
test("render-returned custom queries, since they can't be used through screen", () => {
63+
const { ${query} } = render(foo)
64+
${query}('bar')
65+
})
66+
`
67+
),
5168
{
5269
code: `
5370
const screen = render(baz);
@@ -96,70 +113,56 @@ ruleTester.run(RULE_NAME, rule, {
96113
utils.unmount();
97114
`,
98115
},
99-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
100-
(queryMethod: string) => ({
101-
code: `
116+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
117+
code: `
102118
const { ${queryMethod} } = render(baz, { baseElement: treeA })
103119
expect(${queryMethod}(baz)).toBeDefined()
104120
`,
105-
})
106-
),
107-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
108-
(queryMethod: string) => ({
109-
code: `
121+
})),
122+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
123+
code: `
110124
const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeA })
111125
expect(aliasMethod(baz)).toBeDefined()
112126
`,
113-
})
114-
),
115-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
116-
(queryMethod: string) => ({
117-
code: `
127+
})),
128+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
129+
code: `
118130
const { ${queryMethod} } = render(baz, { container: treeA })
119131
expect(${queryMethod}(baz)).toBeDefined()
120132
`,
121-
})
122-
),
123-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
124-
(queryMethod: string) => ({
125-
code: `
133+
})),
134+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
135+
code: `
126136
const { ${queryMethod}: aliasMethod } = render(baz, { container: treeA })
127137
expect(aliasMethod(baz)).toBeDefined()
128138
`,
129-
})
130-
),
131-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
132-
(queryMethod: string) => ({
133-
code: `
139+
})),
140+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
141+
code: `
134142
const { ${queryMethod} } = render(baz, { baseElement: treeB, container: treeA })
135143
expect(${queryMethod}(baz)).toBeDefined()
136144
`,
137-
})
138-
),
139-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
140-
(queryMethod: string) => ({
141-
code: `
145+
})),
146+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
147+
code: `
142148
const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeB, container: treeA })
143149
expect(aliasMethod(baz)).toBeDefined()
144150
`,
145-
})
146-
),
147-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
148-
(queryMethod: string) => ({
149-
code: `
151+
})),
152+
...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
153+
code: `
150154
render(foo, { baseElement: treeA }).${queryMethod}()
151155
`,
152-
})
153-
),
154-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
156+
})),
157+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
155158
settings: { 'testing-library/utils-module': 'test-utils' },
156159
code: `
157160
import { render as testUtilRender } from 'test-utils'
158161
import { render } from 'somewhere-else'
159162
const { ${queryMethod} } = render(foo)
160163
${queryMethod}()`,
161164
})),
162-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
165+
...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
163166
settings: {
164167
'testing-library/custom-renders': ['customRender'],
165168
},
@@ -171,7 +174,7 @@ ruleTester.run(RULE_NAME, rule, {
171174
],
172175

173176
invalid: [
174-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
177+
...ALL_QUERIES_COMBINATIONS.map(
175178
(queryMethod) =>
176179
({
177180
code: `
@@ -187,7 +190,7 @@ ruleTester.run(RULE_NAME, rule, {
187190
],
188191
} as const)
189192
),
190-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
193+
...ALL_QUERIES_COMBINATIONS.map(
191194
(queryMethod) =>
192195
({
193196
settings: { 'testing-library/utils-module': 'test-utils' },
@@ -208,7 +211,7 @@ ruleTester.run(RULE_NAME, rule, {
208211
} as const)
209212
),
210213

211-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
214+
...ALL_QUERIES_COMBINATIONS.map(
212215
(queryMethod) =>
213216
({
214217
settings: {
@@ -230,7 +233,7 @@ ruleTester.run(RULE_NAME, rule, {
230233
],
231234
} as const)
232235
),
233-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
236+
...ALL_QUERIES_COMBINATIONS.map(
234237
(queryMethod) =>
235238
({
236239
settings: { 'testing-library/utils-module': 'test-utils' },
@@ -250,7 +253,7 @@ ruleTester.run(RULE_NAME, rule, {
250253
],
251254
} as const)
252255
),
253-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
256+
...ALL_QUERIES_COMBINATIONS.map(
254257
(queryMethod) =>
255258
({
256259
settings: { 'testing-library/utils-module': 'test-utils' },
@@ -270,7 +273,7 @@ ruleTester.run(RULE_NAME, rule, {
270273
],
271274
} as const)
272275
),
273-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
276+
...ALL_QUERIES_COMBINATIONS.map(
274277
(queryMethod) =>
275278
({
276279
code: `render().${queryMethod}()`,
@@ -284,7 +287,7 @@ ruleTester.run(RULE_NAME, rule, {
284287
],
285288
} as const)
286289
),
287-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
290+
...ALL_QUERIES_COMBINATIONS.map(
288291
(queryMethod) =>
289292
({
290293
code: `render(foo, { hydrate: true }).${queryMethod}()`,
@@ -298,7 +301,7 @@ ruleTester.run(RULE_NAME, rule, {
298301
],
299302
} as const)
300303
),
301-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
304+
...ALL_QUERIES_COMBINATIONS.map(
302305
(queryMethod) =>
303306
({
304307
code: `component.${queryMethod}()`,
@@ -312,7 +315,7 @@ ruleTester.run(RULE_NAME, rule, {
312315
],
313316
} as const)
314317
),
315-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
318+
...ALL_QUERIES_COMBINATIONS.map(
316319
(queryMethod) =>
317320
({
318321
code: `
@@ -329,7 +332,7 @@ ruleTester.run(RULE_NAME, rule, {
329332
],
330333
} as const)
331334
),
332-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
335+
...ALL_QUERIES_COMBINATIONS.map(
333336
(queryMethod) =>
334337
({
335338
code: `
@@ -346,7 +349,7 @@ ruleTester.run(RULE_NAME, rule, {
346349
],
347350
} as const)
348351
),
349-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
352+
...ALL_QUERIES_COMBINATIONS.map(
350353
(queryMethod) =>
351354
({
352355
code: `
@@ -363,7 +366,7 @@ ruleTester.run(RULE_NAME, rule, {
363366
],
364367
} as const)
365368
),
366-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
369+
...ALL_QUERIES_COMBINATIONS.map(
367370
(queryMethod) =>
368371
({
369372
code: `
@@ -380,7 +383,7 @@ ruleTester.run(RULE_NAME, rule, {
380383
],
381384
} as const)
382385
),
383-
...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
386+
...ALL_QUERIES_COMBINATIONS.map(
384387
(queryMethod) =>
385388
({
386389
code: `

0 commit comments

Comments
 (0)