From db61b1032bef5c225be087c491e084837e6b0e3b Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 26 Jul 2020 15:34:00 -0300 Subject: [PATCH] feat: allow baseElement and container in prefer-screen-queries --- docs/rules/prefer-screen-queries.md | 5 ++ lib/node-utils.ts | 6 +- lib/rules/prefer-screen-queries.ts | 22 +++++- tests/lib/rules/prefer-screen-queries.test.ts | 77 ++++++++++++++++++- 4 files changed, 101 insertions(+), 9 deletions(-) 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()