Skip to content

Commit f8050b9

Browse files
committed
feat: make prefer-find-by rule fixable
1 parent c030508 commit f8050b9

File tree

3 files changed

+30
-17
lines changed

3 files changed

+30
-17
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ To enable this configuration use the `extends` property in your
143143
| [no-manual-cleanup](docs/rules/no-manual-cleanup.md) | Disallow the use of `cleanup` | | |
144144
| [no-wait-for-empty-callback](docs/rules/no-wait-for-empty-callback.md) | Disallow empty callbacks for `waitFor` and `waitForElementToBeRemoved` | | |
145145
| [prefer-explicit-assert](docs/rules/prefer-explicit-assert.md) | Suggest using explicit assertions rather than just `getBy*` queries | | |
146-
| [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][] | |
146+
| [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][] |
147147
| [prefer-presence-queries](docs/rules/prefer-presence-queries.md) | Enforce specific queries when checking element is present or not | | |
148148
| [prefer-screen-queries](docs/rules/prefer-screen-queries.md) | Suggest using screen while using queries | | |
149149
| [prefer-wait-for](docs/rules/prefer-wait-for.md) | Use `waitFor` instead of deprecated wait methods | | ![fixable-badge][] |

lib/rules/prefer-find-by.ts

+22-7
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,35 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
2626
messages: {
2727
preferFindBy: 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}'
2828
},
29-
fixable: null,
29+
fixable: 'code',
3030
schema: []
3131
},
3232
defaultOptions: [],
3333

3434
create(context) {
35+
const sourceCode = context.getSourceCode();
3536

36-
function reportInvalidUsage(node: TSESTree.CallExpression, { queryVariant, queryMethod, fullQuery }: { queryVariant: string, queryMethod: string, fullQuery: string}) {
37+
/**
38+
* Reports the invalid usage of wait* plus getBy/QueryBy methods and automatically fixes the scenario
39+
* @param {TSESTree.CallExpression} node - The CallExpresion node that contains the wait* method
40+
* @param {'findBy' | 'findAllBy'} replacementParams.queryVariant - The variant method used to query: findBy/findByAll.
41+
* @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.
42+
* @param {Array<TSESTree.Expression>} replacementParams.callArguments - Array of argument nodes which contain the parameters of the query inside the wait* method.
43+
* @param {string=} replacementParams.caller - the variable name that targets screen or the value returned from `render` function.
44+
*/
45+
function reportInvalidUsage(node: TSESTree.CallExpression, { queryVariant, queryMethod, callArguments, caller }: { queryVariant: 'findBy' | 'findAllBy', queryMethod: string, callArguments: TSESTree.Expression[], caller?: string }) {
46+
3747
context.report({
3848
node,
3949
messageId: "preferFindBy",
40-
data: { queryVariant, queryMethod, fullQuery },
50+
data: { queryVariant, queryMethod, fullQuery: sourceCode.getText(node) },
51+
fix(fixer) {
52+
const newCode = `${caller ? `${caller}.` : ''}${queryVariant}${queryMethod}(${callArguments.map((node) => sourceCode.getText(node)).join(', ')})`
53+
return fixer.replaceText(node, newCode)
54+
}
4155
});
4256
}
4357

44-
const sourceCode = context.getSourceCode();
45-
4658
return {
4759
'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) {
4860
if (!isIdentifier(node.callee) || !WAIT_METHODS.includes(node.callee.name)) {
@@ -61,10 +73,13 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
6173
if (isMemberExpression(argument.body.callee) && isIdentifier(argument.body.callee.property) && isIdentifier(argument.body.callee.object) && SYNC_QUERIES_COMBINATIONS.includes(argument.body.callee.property.name)) {
6274
// shape of () => screen.getByText
6375
const queryMethod = argument.body.callee.property.name
76+
const caller = argument.body.callee.object.name
77+
6478
reportInvalidUsage(node, {
6579
queryMethod: queryMethod.split('By')[1],
6680
queryVariant: getFindByQueryVariant(queryMethod),
67-
fullQuery: sourceCode.getText(node)
81+
callArguments: argument.body.arguments,
82+
caller,
6883
})
6984
return
7085
}
@@ -74,7 +89,7 @@ export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({
7489
reportInvalidUsage(node, {
7590
queryMethod: queryMethod.split('By')[1],
7691
queryVariant: getFindByQueryVariant(queryMethod),
77-
fullQuery: sourceCode.getText(node)
92+
callArguments: argument.body.arguments,
7893
})
7994
return
8095
}

tests/lib/rules/prefer-find-by.test.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -64,31 +64,29 @@ ruleTester.run(RULE_NAME, rule, {
6464
...WAIT_METHODS.reduce((acc: InvalidTestCase<'preferFindBy', []>[], waitMethod) => acc
6565
.concat(
6666
SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
67-
code: `
68-
const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))
69-
`,
67+
code: `const submitButton = await ${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`,
7068
errors: [{
7169
messageId: 'preferFindBy',
7270
data: {
7371
queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy',
7472
queryMethod: queryMethod.split('By')[1],
7573
fullQuery: `${waitMethod}(() => ${queryMethod}('foo', { name: 'baz' }))`,
76-
}
77-
}]
74+
},
75+
}],
76+
output: `const submitButton = await ${queryMethod.includes('All') ? 'findAllBy': 'findBy'}${queryMethod.split('By')[1]}('foo', { name: 'baz' })`
7877
}))
7978
).concat(
8079
SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({
81-
code: `
82-
const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))
83-
`,
80+
code: `const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`,
8481
errors: [{
8582
messageId: 'preferFindBy',
8683
data: {
8784
queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy',
8885
queryMethod: queryMethod.split('By')[1],
8986
fullQuery: `${waitMethod}(() => screen.${queryMethod}('foo', { name: 'baz' }))`,
9087
}
91-
}]
88+
}],
89+
output: `const submitButton = await screen.${queryMethod.includes('All') ? 'findAllBy': 'findBy'}${queryMethod.split('By')[1]}('foo', { name: 'baz' })`
9290
}))
9391
),
9492
[])

0 commit comments

Comments
 (0)