diff --git a/docs/rules/prefer-screen-queries.md b/docs/rules/prefer-screen-queries.md
index 9de4a105..79978017 100644
--- a/docs/rules/prefer-screen-queries.md
+++ b/docs/rules/prefer-screen-queries.md
@@ -60,6 +60,11 @@ const { rerender, unmount, asFragment } = render();
rerender();
asFragment();
unmount();
+
+// using baseElement
+const { getByText } = render(, { baseElement: treeA });
+// using container
+const { getAllByText } = render(, { container: treeA });
```
## Further Reading
diff --git a/lib/node-utils.ts b/lib/node-utils.ts
index 53e788b8..64c8c028 100644
--- a/lib/node-utils.ts
+++ b/lib/node-utils.ts
@@ -1,4 +1,4 @@
-import { TSESTree } from '@typescript-eslint/experimental-utils';
+import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
export function isCallExpression(
node: TSESTree.Node
@@ -105,4 +105,8 @@ export function hasThenProperty(node: TSESTree.Node) {
export function isArrowFunctionExpression(node: TSESTree.Node): node is TSESTree.ArrowFunctionExpression {
return node && node.type === 'ArrowFunctionExpression'
+}
+
+export function isObjectExpression(node: TSESTree.Expression): node is TSESTree.ObjectExpression {
+ return node?.type === AST_NODE_TYPES.ObjectExpression
}
\ No newline at end of file
diff --git a/lib/rules/prefer-screen-queries.ts b/lib/rules/prefer-screen-queries.ts
index ce671366..471932bf 100644
--- a/lib/rules/prefer-screen-queries.ts
+++ b/lib/rules/prefer-screen-queries.ts
@@ -6,14 +6,21 @@ import {
isCallExpression,
isProperty,
isIdentifier,
+ isObjectExpression,
} from '../node-utils';
export const RULE_NAME = 'prefer-screen-queries';
export type MessageIds = 'preferScreenQueries';
type Options = [];
+const ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING = ['container', 'baseElement']
const ALL_QUERIES_COMBINATIONS_REGEXP = ALL_QUERIES_COMBINATIONS.join('|');
+function usesContainerOrBaseElement(node: TSESTree.CallExpression) {
+ const secondArgument = node.arguments[1]
+ return isObjectExpression(secondArgument) && secondArgument.properties.some((property) => isProperty(property) && isIdentifier(property.key) && ALLOWED_RENDER_PROPERTIES_FOR_DESTRUCTURING.includes(property.key.name))
+}
+
export default ESLintUtils.RuleCreator(getDocsUrl)({
name: RULE_NAME,
meta: {
@@ -50,9 +57,14 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
return {
VariableDeclarator(node) {
- const isWithinFunction = isCallExpression(node.init) && isIdentifier(node.init.callee) && node.init.callee.name === 'within';
+ if (!isCallExpression(node.init) || !isIdentifier(node.init.callee)) {
+ return
+ }
+ const isWithinFunction = node.init.callee.name === 'within';
+ // TODO add the custom render option #198
+ const usesRenderOptions = node.init.callee.name === 'render' && usesContainerOrBaseElement(node.init);
- if (!isWithinFunction) {
+ if (!isWithinFunction && !usesRenderOptions) {
return
}
@@ -94,11 +106,13 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
isMemberExpression(node.parent) &&
isCallExpression(node.parent.object) &&
isIdentifier(node.parent.object.callee) &&
- node.parent.object.callee.name !== 'within'
+ node.parent.object.callee.name !== 'within' &&
+ node.parent.object.callee.name === 'render' && !usesContainerOrBaseElement(node.parent.object)
) {
reportInvalidUsage(node);
return;
}
+
if (
isMemberExpression(node.parent) &&
isIdentifier(node.parent.object) &&
@@ -109,4 +123,4 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({
},
};
},
-});
+});
\ No newline at end of file
diff --git a/tests/lib/rules/prefer-screen-queries.test.ts b/tests/lib/rules/prefer-screen-queries.test.ts
index 66dbe315..1c9f5e9b 100644
--- a/tests/lib/rules/prefer-screen-queries.test.ts
+++ b/tests/lib/rules/prefer-screen-queries.test.ts
@@ -6,6 +6,9 @@ const ruleTester = createRuleTester();
ruleTester.run(RULE_NAME, rule, {
valid: [
+ {
+ code: `const baz = () => 'foo'`
+ },
...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
code: `screen.${queryMethod}()`,
})),
@@ -80,12 +83,55 @@ ruleTester.run(RULE_NAME, rule, {
const utils = render(baz);
utils.unmount();
`
- }
+ },
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ const { ${queryMethod} } = render(baz, { baseElement: treeA })
+ expect(${queryMethod}(baz)).toBeDefined()
+ `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeA })
+ expect(aliasMethod(baz)).toBeDefined()
+ `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ const { ${queryMethod} } = render(baz, { container: treeA })
+ expect(${queryMethod}(baz)).toBeDefined()
+ `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ const { ${queryMethod}: aliasMethod } = render(baz, { container: treeA })
+ expect(aliasMethod(baz)).toBeDefined()
+ `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ const { ${queryMethod} } = render(baz, { baseElement: treeB, container: treeA })
+ expect(${queryMethod}(baz)).toBeDefined()
+ `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ const { ${queryMethod}: aliasMethod } = render(baz, { baseElement: treeB, container: treeA })
+ expect(aliasMethod(baz)).toBeDefined()
+ `
+ })),
+ ...ALL_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
+ code: `
+ render(foo, { baseElement: treeA }).${queryMethod}()
+ `
+ }))
],
invalid: [
...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
- code: `${queryMethod}()`,
+ code: `
+ const { ${queryMethod} } = render(foo)
+ ${queryMethod}()`,
errors: [
{
messageId: 'preferScreenQueries',
@@ -95,7 +141,6 @@ ruleTester.run(RULE_NAME, rule, {
},
],
})),
-
...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
code: `render().${queryMethod}()`,
errors: [
@@ -107,7 +152,17 @@ ruleTester.run(RULE_NAME, rule, {
},
],
})),
-
+ ...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
+ code: `render(foo, { hydrate: true }).${queryMethod}()`,
+ errors: [
+ {
+ messageId: 'preferScreenQueries',
+ data: {
+ name: queryMethod,
+ },
+ },
+ ],
+ })),
...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
code: `component.${queryMethod}()`,
errors: [
@@ -161,6 +216,20 @@ ruleTester.run(RULE_NAME, rule, {
},
],
})),
+ ...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
+ code: `
+ const { ${queryMethod} } = render(baz, { hydrate: true })
+ ${queryMethod}(baz)
+ `,
+ errors: [
+ {
+ messageId: 'preferScreenQueries',
+ data: {
+ name: queryMethod,
+ },
+ },
+ ],
+ })),
...ALL_QUERIES_COMBINATIONS.map(queryMethod => ({
code: `
const [myVariable] = within()