From f8050b90ad64e6622c688e9c04538ffa3e629de7 Mon Sep 17 00:00:00 2001 From: Gonzalo D'Elia Date: Tue, 9 Jun 2020 18:46:53 -0300 Subject: [PATCH] feat: make prefer-find-by rule fixable --- README.md | 2 +- lib/rules/prefer-find-by.ts | 29 +++++++++++++++++++------- tests/lib/rules/prefer-find-by.test.ts | 16 +++++++------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6773fd15..4bbd7abd 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ To enable this configuration use the `extends` property in your | [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | | | [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | | | | [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | | -| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | | +| [prefer-find-by](docs/rules/prefer-find-by.md) | Suggest using `findBy*` methods instead of the `waitFor` + `getBy` queries | ![recommended-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | ![fixable-badge][] | | [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | | | [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using screen while using queries | | | | [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] | diff --git a/lib/rules/prefer-find-by.ts b/lib/rules/prefer-find-by.ts index da1e627f..a606c164 100644 --- a/lib/rules/prefer-find-by.ts +++ b/lib/rules/prefer-find-by.ts @@ -26,23 +26,35 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ messages: { preferFindBy: 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}' }, - fixable: null, + fixable: 'code', schema: [] }, defaultOptions: [], create(context) { + const sourceCode = context.getSourceCode(); - function reportInvalidUsage(node: TSESTree.CallExpression, { queryVariant, queryMethod, fullQuery }: { queryVariant: string, queryMethod: string, fullQuery: string}) { + /** + * Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario + * @param {TSESTree.CallExpression} node - The CallExpresion node that contains the wait* method + * @param {'findBy' | 'findAllBy'} replacementParams.queryVariant - The variant method used to query: findBy/findByAll. + * @param {string} replacementParams.queryMethod - Suffix string to build the query method (the query-part that comes after the "By"): LabelText, Placeholder, Text, Role, Title, etc. + * @param {Array} replacementParams.callArguments - Array of argument nodes which contain the parameters of the query inside the wait* method. + * @param {string=} replacementParams.caller - the variable name that targets screen or the value returned from `render` function. + */ + function reportInvalidUsage(node: TSESTree.CallExpression, { queryVariant, queryMethod, callArguments, caller }: { queryVariant: 'findBy' | 'findAllBy', queryMethod: string, callArguments: TSESTree.Expression[], caller?: string }) { + context.report({ node, messageId: "preferFindBy", - data: { queryVariant, queryMethod, fullQuery }, + data: { queryVariant, queryMethod, fullQuery: sourceCode.getText(node) }, + fix(fixer) { + const newCode = `${caller ? `${caller}.` : ''}${queryVariant}${queryMethod}(${callArguments.map((node) => sourceCode.getText(node)).join(', ')})` + return fixer.replaceText(node, newCode) + } }); } - const sourceCode = context.getSourceCode(); - return { 'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { if (!isIdentifier(node.callee) || !WAIT_METHODS.includes(node.callee.name)) { @@ -61,10 +73,13 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ if (isMemberExpression(argument.body.callee) && isIdentifier(argument.body.callee.property) && isIdentifier(argument.body.callee.object) && SYNC_QUERIES_COMBINATIONS.includes(argument.body.callee.property.name)) { // shape of () => screen.getByText const queryMethod = argument.body.callee.property.name + const caller = argument.body.callee.object.name + reportInvalidUsage(node, { queryMethod: queryMethod.split('By')[1], queryVariant: getFindByQueryVariant(queryMethod), - fullQuery: sourceCode.getText(node) + callArguments: argument.body.arguments, + caller, }) return } @@ -74,7 +89,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)({ reportInvalidUsage(node, { queryMethod: queryMethod.split('By')[1], queryVariant: getFindByQueryVariant(queryMethod), - fullQuery: sourceCode.getText(node) + callArguments: argument.body.arguments, }) return } diff --git a/tests/lib/rules/prefer-find-by.test.ts b/tests/lib/rules/prefer-find-by.test.ts index 7a483c59..6dac97b5 100644 --- a/tests/lib/rules/prefer-find-by.test.ts +++ b/tests/lib/rules/prefer-find-by.test.ts @@ -64,23 +64,20 @@ ruleTester.run(RULE_NAME, rule, { ...WAIT_METHODS.reduce((acc: InvalidTestCase<'preferFindBy', []>[], waitMethod) => acc .concat( SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` - const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' })) - `, + code: `const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`, errors: [{ messageId: 'preferFindBy', data: { queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy', queryMethod: queryMethod.split('By')[1], fullQuery: `${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`, - } - }] + }, + }], + output: `const submitButton = await ${queryMethod.includes('All') ? 'findAllBy': 'findBy'}${queryMethod.split('By')[1]}('foo', { name: 'baz' })` })) ).concat( SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ - code: ` - const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' })) - `, + code: `const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`, errors: [{ messageId: 'preferFindBy', data: { @@ -88,7 +85,8 @@ ruleTester.run(RULE_NAME, rule, { queryMethod: queryMethod.split('By')[1], fullQuery: `${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`, } - }] + }], + output: `const submitButton = await screen.${queryMethod.includes('All') ? 'findAllBy': 'findBy'}${queryMethod.split('By')[1]}('foo', { name: 'baz' })` })) ), [])