-
Notifications
You must be signed in to change notification settings - Fork 132
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
192 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Use `find*` query methods to wait for elements instead of waitFor (prefer-find-by) | ||
|
||
TBD | ||
|
||
## Rule details | ||
|
||
This rule aims to use `findBy*` or `findAllBy*` queries to wait for elements, rather than using `waitFor`, or the deprecated methods `waitForElement` and `wait` | ||
|
||
Examples of **incorrect** code for this rule | ||
|
||
```js | ||
const submitButton = await waitFor(() => | ||
screen.getByRole('button', { name: /submit/i }) | ||
); | ||
|
||
const submitButton = await waitFor(() => | ||
screen.getAllTestId('button', { name: /submit/i }) | ||
); | ||
|
||
const submitButton = await waitFor(() => | ||
queryByLabel('button', { name: /submit/i }) | ||
); | ||
|
||
const submitButton = await waitFor(() => | ||
queryAllByText('button', { name: /submit/i }) | ||
); | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
const submitButton = await findByText('foo'); | ||
|
||
const submitButton = await screen.findAllByRole('table'); | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
TBD | ||
|
||
## Further Reading | ||
|
||
TBD |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { ESLintUtils, TSESTree } from '@typescript-eslint/experimental-utils'; | ||
import { | ||
isIdentifier, | ||
isCallExpression, | ||
isMemberExpression, | ||
isArrowFunctionExpression, | ||
} from '../node-utils'; | ||
import { getDocsUrl, SYNC_QUERIES_COMBINATIONS } from '../utils'; | ||
|
||
export const RULE_NAME = 'prefer-find-by'; | ||
|
||
type Options = []; | ||
export type MessageIds = 'preferFindBy'; | ||
// TODO check if this should be under utils.ts - there are some async utils | ||
export const WAIT_METHODS = ['waitFor', 'waitForElement', 'wait'] | ||
|
||
export default ESLintUtils.RuleCreator(getDocsUrl)<Options, MessageIds>({ | ||
name: RULE_NAME, | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Suggest using find* instead of waitFor to wait for elements', | ||
category: 'Best Practices', | ||
recommended: false, | ||
}, | ||
messages: { | ||
preferFindBy: 'Prefer {{queryVariant}}{{queryMethod}} method over using await {{fullQuery}}' | ||
}, | ||
fixable: null, | ||
schema: [] | ||
}, | ||
defaultOptions: [], | ||
|
||
create(context) { | ||
|
||
function reportInvalidUsage(node: TSESTree.CallExpression, { queryVariant, queryMethod, fullQuery }: { queryVariant: string, queryMethod: string, fullQuery: string}) { | ||
context.report({ | ||
node, | ||
messageId: "preferFindBy", | ||
data: { queryVariant, queryMethod, fullQuery }, | ||
}); | ||
} | ||
|
||
const sourceCode = context.getSourceCode(); | ||
|
||
return { | ||
'AwaitExpression > CallExpression'(node: TSESTree.CallExpression) { | ||
if (!isIdentifier(node.callee) || !WAIT_METHODS.includes(node.callee.name)) { | ||
return | ||
} | ||
// ensure the only argument is an arrow function expression - if the arrow function is a block | ||
// we skip it | ||
const argument = node.arguments[0] | ||
if (!isArrowFunctionExpression(argument)) { | ||
return | ||
} | ||
if (!isCallExpression(argument.body)) { | ||
return | ||
} | ||
// ensure here it's one of the sync methods that we are calling | ||
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 | ||
reportInvalidUsage(node, { | ||
queryMethod: queryMethod.split('By')[1], | ||
queryVariant: getFindByQueryVariant(queryMethod), | ||
fullQuery: sourceCode.getText(node) | ||
}) | ||
return | ||
} | ||
if (isIdentifier(argument.body.callee) && SYNC_QUERIES_COMBINATIONS.includes(argument.body.callee.name)) { | ||
// shape of () => getByText | ||
const queryMethod = argument.body.callee.name | ||
reportInvalidUsage(node, { | ||
queryMethod: queryMethod.split('By')[1], | ||
queryVariant: getFindByQueryVariant(queryMethod), | ||
fullQuery: sourceCode.getText(node) | ||
}) | ||
return | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
|
||
function getFindByQueryVariant(queryMethod: string) { | ||
return queryMethod.includes('All') ? 'findAllBy' : 'findBy' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { InvalidTestCase } from '@typescript-eslint/experimental-utils/dist/ts-eslint' | ||
import { createRuleTester } from '../test-utils'; | ||
import { ASYNC_QUERIES_COMBINATIONS, SYNC_QUERIES_COMBINATIONS } from '../../../lib/utils'; | ||
import rule, { WAIT_METHODS, RULE_NAME } from '../../../lib/rules/prefer-find-by'; | ||
|
||
const ruleTester = createRuleTester({ | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}); | ||
|
||
ruleTester.run(RULE_NAME, rule, { | ||
valid: [ | ||
...ASYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ | ||
code: `const submitButton = await ${queryMethod}('foo')` | ||
})), | ||
...ASYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ | ||
code: `const submitButton = await screen.${queryMethod}('foo')` | ||
})) | ||
], | ||
invalid: [ | ||
// using reduce + concat 'cause flatMap is not available in node10.x | ||
...WAIT_METHODS.reduce((acc: InvalidTestCase<'preferFindBy', []>[], waitMethod) => acc | ||
.concat( | ||
SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ | ||
code: ` | ||
const submitButton = await ${waitMethod}(() => ${queryMethod}('foo')) | ||
`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy', | ||
queryMethod: queryMethod.split('By')[1], | ||
fullQuery: `${waitMethod}(() => ${queryMethod}('foo'))`, | ||
} | ||
}] | ||
})) | ||
).concat( | ||
SYNC_QUERIES_COMBINATIONS.map((queryMethod: string) => ({ | ||
code: ` | ||
const submitButton = await ${waitMethod}(() => screen.${queryMethod}('foo')) | ||
`, | ||
errors: [{ | ||
messageId: 'preferFindBy', | ||
data: { | ||
queryVariant: queryMethod.includes('All') ? 'findAllBy': 'findBy', | ||
queryMethod: queryMethod.split('By')[1], | ||
fullQuery: `${waitMethod}(() => screen.${queryMethod}('foo'))`, | ||
} | ||
}] | ||
})) | ||
), | ||
[]) | ||
], | ||
}) |