diff --git a/docs/rules/prefer-screen-queries.md b/docs/rules/prefer-screen-queries.md
index 081d7283..fa8efe52 100644
--- a/docs/rules/prefer-screen-queries.md
+++ b/docs/rules/prefer-screen-queries.md
@@ -1,9 +1,16 @@
-# Suggest using `screen` while using queries (`testing-library/prefer-screen-queries`)
+# Suggest using `screen` while querying (`testing-library/prefer-screen-queries`)
## Rule Details
-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.
-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.
+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.
+
+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.
+
+However, there are 3 exceptions when this rule won't suggest using `screen` for querying:
+
+1. You are using a query chained to `within`
+2. You are using custom queries, so you can't access them through `screen`
+3. You are setting the `container` or `baseElement`, so you need to use the queries returned from `render`
Examples of **incorrect** code for this rule:
@@ -65,8 +72,19 @@ unmount();
const { getByText } = render(, { baseElement: treeA });
// using container
const { getAllByText } = render(, { container: treeA });
+
+// querying with a custom query imported from its own module
+import { getByIcon } from 'custom-queries';
+const element = getByIcon('search');
+
+// querying with a custom query returned from `render`
+const { getByIcon } = render();
+const element = getByIcon('search');
```
## Further Reading
-- [`screen` documentation](https://testing-library.com/docs/dom-testing-library/api-queries#screen)
+- [Common mistakes with React Testing Library - Not using `screen`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen)
+- [`screen` documentation](https://testing-library.com/docs/queries/about#screen)
+- [Advanced - Custom Queries](https://testing-library.com/docs/dom-testing-library/api-custom-queries/)
+- [React Testing Library - Add custom queries](https://testing-library.com/docs/react-testing-library/setup/#add-custom-queries)
diff --git a/lib/detect-testing-library-utils.ts b/lib/detect-testing-library-utils.ts
index 261e2609..f4324e1a 100644
--- a/lib/detect-testing-library-utils.ts
+++ b/lib/detect-testing-library-utils.ts
@@ -64,6 +64,7 @@ type IsSyncQueryFn = (node: TSESTree.Identifier) => boolean;
type IsAsyncQueryFn = (node: TSESTree.Identifier) => boolean;
type IsQueryFn = (node: TSESTree.Identifier) => boolean;
type IsCustomQueryFn = (node: TSESTree.Identifier) => boolean;
+type IsBuiltInQueryFn = (node: TSESTree.Identifier) => boolean;
type IsAsyncUtilFn = (
node: TSESTree.Identifier,
validNames?: readonly typeof ASYNC_UTILS[number][]
@@ -98,6 +99,7 @@ export interface DetectionHelpers {
isAsyncQuery: IsAsyncQueryFn;
isQuery: IsQueryFn;
isCustomQuery: IsCustomQueryFn;
+ isBuiltInQuery: IsBuiltInQueryFn;
isAsyncUtil: IsAsyncUtilFn;
isFireEventUtil: (node: TSESTree.Identifier) => boolean;
isUserEventUtil: (node: TSESTree.Identifier) => boolean;
@@ -301,6 +303,10 @@ export function detectTestingLibraryUtils<
return isQuery(node) && !ALL_QUERIES_COMBINATIONS.includes(node.name);
};
+ const isBuiltInQuery = (node: TSESTree.Identifier): boolean => {
+ return ALL_QUERIES_COMBINATIONS.includes(node.name);
+ };
+
/**
* Determines whether a given node is a valid async util or not.
*
@@ -704,6 +710,7 @@ export function detectTestingLibraryUtils<
isAsyncQuery,
isQuery,
isCustomQuery,
+ isBuiltInQuery,
isAsyncUtil,
isFireEventUtil,
isUserEventUtil,
diff --git a/lib/rules/prefer-screen-queries.ts b/lib/rules/prefer-screen-queries.ts
index 650e5986..b8158bb7 100644
--- a/lib/rules/prefer-screen-queries.ts
+++ b/lib/rules/prefer-screen-queries.ts
@@ -64,7 +64,7 @@ export default createTestingLibraryRule({
if (
isProperty(property) &&
ASTUtils.isIdentifier(property.key) &&
- helpers.isQuery(property.key)
+ helpers.isBuiltInQuery(property.key)
) {
safeDestructuredQueries.push(property.key.name);
}
@@ -115,7 +115,7 @@ export default createTestingLibraryRule({
}
},
'CallExpression > Identifier'(node: TSESTree.Identifier) {
- if (!helpers.isQuery(node)) {
+ if (!helpers.isBuiltInQuery(node)) {
return;
}
@@ -130,7 +130,7 @@ export default createTestingLibraryRule({
return ['screen', ...withinDeclaredVariables].includes(name);
}
- if (!helpers.isQuery(node)) {
+ if (!helpers.isBuiltInQuery(node)) {
return;
}
diff --git a/tests/lib/rules/prefer-screen-queries.test.ts b/tests/lib/rules/prefer-screen-queries.test.ts
index 23bb8135..8a8dcc1f 100644
--- a/tests/lib/rules/prefer-screen-queries.test.ts
+++ b/tests/lib/rules/prefer-screen-queries.test.ts
@@ -11,17 +11,13 @@ const ruleTester = createRuleTester();
const CUSTOM_QUERY_COMBINATIONS = combineQueries(ALL_QUERIES_VARIANTS, [
'ByIcon',
]);
-const ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS = [
- ...ALL_QUERIES_COMBINATIONS,
- ...CUSTOM_QUERY_COMBINATIONS,
-];
ruleTester.run(RULE_NAME, rule, {
valid: [
{
code: `const baz = () => 'foo'`,
},
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
code: `screen.${queryMethod}()`,
})),
{
@@ -30,24 +26,45 @@ ruleTester.run(RULE_NAME, rule, {
{
code: `component.otherFunctionShouldNotThrow()`,
},
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
code: `within(component).${queryMethod}()`,
})),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
code: `within(screen.${queryMethod}()).${queryMethod}()`,
})),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
code: `
const { ${queryMethod} } = within(screen.getByText('foo'))
${queryMethod}(baz)
`,
})),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
code: `
const myWithinVariable = within(foo)
myWithinVariable.${queryMethod}('baz')
`,
})),
+ ...CUSTOM_QUERY_COMBINATIONS.map(
+ (query) => `
+ import { render } from '@testing-library/react'
+ import { ${query} } from 'custom-queries'
+
+ test("imported custom queries, since they can't be used through screen", () => {
+ render(foo)
+ ${query}('bar')
+ })
+ `
+ ),
+ ...CUSTOM_QUERY_COMBINATIONS.map(
+ (query) => `
+ import { render } from '@testing-library/react'
+
+ test("render-returned custom queries, since they can't be used through screen", () => {
+ const { ${query} } = render(foo)
+ ${query}('bar')
+ })
+ `
+ ),
{
code: `
const screen = render(baz);
@@ -96,62 +113,48 @@ ruleTester.run(RULE_NAME, rule, {
utils.unmount();
`,
},
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
const { ${queryMethod} } = render(baz, { baseElement: treeA })
expect(${queryMethod}(baz)).toBeDefined()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeA })
expect(aliasMethod(baz)).toBeDefined()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
const { ${queryMethod} } = render(baz, { container: treeA })
expect(${queryMethod}(baz)).toBeDefined()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
const { ${queryMethod}: aliasMethod } = render(baz, { container: treeA })
expect(aliasMethod(baz)).toBeDefined()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
const { ${queryMethod} } = render(baz, { baseElement: treeB, container: treeA })
expect(${queryMethod}(baz)).toBeDefined()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeB, container: treeA })
expect(aliasMethod(baz)).toBeDefined()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
- (queryMethod: string) => ({
- code: `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
render(foo, { baseElement: treeA }).${queryMethod}()
`,
- })
- ),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
settings: { 'testing-library/utils-module': 'test-utils' },
code: `
import { render as testUtilRender } from 'test-utils'
@@ -159,7 +162,7 @@ ruleTester.run(RULE_NAME, rule, {
const { ${queryMethod} } = render(foo)
${queryMethod}()`,
})),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map((queryMethod) => ({
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod) => ({
settings: {
'testing-library/custom-renders': ['customRender'],
},
@@ -171,7 +174,7 @@ ruleTester.run(RULE_NAME, rule, {
],
invalid: [
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `
@@ -187,7 +190,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
settings: { 'testing-library/utils-module': 'test-utils' },
@@ -208,7 +211,7 @@ ruleTester.run(RULE_NAME, rule, {
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
settings: {
@@ -230,7 +233,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
settings: { 'testing-library/utils-module': 'test-utils' },
@@ -250,7 +253,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
settings: { 'testing-library/utils-module': 'test-utils' },
@@ -270,7 +273,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `render().${queryMethod}()`,
@@ -284,7 +287,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `render(foo, { hydrate: true }).${queryMethod}()`,
@@ -298,7 +301,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `component.${queryMethod}()`,
@@ -312,7 +315,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `
@@ -329,7 +332,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `
@@ -346,7 +349,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `
@@ -363,7 +366,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `
@@ -380,7 +383,7 @@ ruleTester.run(RULE_NAME, rule, {
],
} as const)
),
- ...ALL_BUILTIN_AND_CUSTOM_QUERIES_COMBINATIONS.map(
+ ...ALL_QUERIES_COMBINATIONS.map(
(queryMethod) =>
({
code: `