Skip to content

Commit dcc0693

Browse files
authored
refactor: use custom rule creator for promise-queries rules (#260)
* refactor(no-await-sync-query): use custom rule creator * refactor(no-await-sync-query): improve error message * test(no-await-sync-query): check error location in invalid cases * refactor(no-await-sync-query): catch sync queries with detection helper * test(no-await-sync-query): use more realistic scenarios * test(no-await-sync-query): add more cases for custom queries and settings * refactor(await-async-query): use custom rule creator * refactor(await-async-query): improve error message * feat: new detection helpers for findBy queries * refactor(await-async-query): detection helpers + aggressive reporting * test(await-async-query): add cases for custom queries * test(await-async-query): add more cases for custom queries * test(await-async-query): check errors locations * test(await-async-query): mix built-in and custom queries * test(await-async-query): non-matching query case * feat(await-async-query): report query wrappers * refactor(await-async-query): extract ast utils for functions * test(await-async-query): cases for arrow functions * refactor(await-async-query): extract ast util for promise handled * test(await-async-query): increase coverage * refactor(await-async-query): rename isPromiseResolved to hasChainedThen * docs(await-async-query): update rule description and examples * docs(await-async-query): minor improvements * refactor: minor type fix * docs(await-async-query): more fixes * docs(await-async-query): wrong return type * refactor(await-async-query): check regex more efficiently
1 parent 5ce0ba0 commit dcc0693

13 files changed

+755
-204
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ To enable this configuration use the `extends` property in your
127127

128128
| Rule | Description | Configurations | Fixable |
129129
| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------ |
130-
| [await-async-query](docs/rules/await-async-query.md) | Enforce async queries to have proper `await` | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
130+
| [await-async-query](docs/rules/await-async-query.md) | Enforce promises from async queries to be handled | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
131131
| [await-async-utils](docs/rules/await-async-utils.md) | Enforce async utils to be awaited properly | ![dom-badge][] ![angular-badge][] ![react-badge][] ![vue-badge][] | |
132132
| [await-fire-event](docs/rules/await-fire-event.md) | Enforce async fire event methods to be awaited | ![vue-badge][] | |
133133
| [consistent-data-testid](docs/rules/consistent-data-testid.md) | Ensure `data-testid` values match a provided regex. | | |

docs/rules/await-async-query.md

+50-39
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,86 @@
1-
# Enforce async queries to have proper `await` (await-async-query)
1+
# Enforce promises from async queries to be handled (await-async-query)
22

33
Ensure that promises returned by async queries are handled properly.
44

55
## Rule Details
66

7-
Some of the queries variants that Testing Library provides are
7+
Some queries variants that Testing Library provides are
88
asynchronous as they return a promise which resolves when elements are
99
found. Those queries variants are:
1010

1111
- `findBy*`
1212
- `findAllBy*`
1313

14-
This rule aims to prevent users from forgetting to await the returned
14+
This rule aims to prevent users from forgetting to handle the returned
1515
promise from those async queries to be fulfilled, which could lead to
16-
errors in the tests. The promises can be handled by using either `await`
17-
operator or `then` method.
16+
errors in the tests. The promise will be considered as handled when:
17+
18+
- using the `await` operator
19+
- chaining the `then` method
20+
- chaining `resolves` or `rejects` from jest
21+
- it's returned from a function (in this case, that particular function will be analyzed by this rule too)
1822

1923
Examples of **incorrect** code for this rule:
2024

2125
```js
22-
const foo = () => {
23-
// ...
24-
const rows = findAllByRole('row');
25-
// ...
26-
};
27-
28-
const bar = () => {
29-
// ...
30-
findByText('submit');
31-
// ...
32-
};
33-
34-
const baz = () => {
35-
// ...
36-
screen.findAllByPlaceholderText('name');
37-
// ...
38-
};
26+
// async query without handling promise
27+
const rows = findAllByRole('row');
28+
29+
findByIcon('search');
30+
31+
screen.findAllByPlaceholderText('name');
32+
```
33+
34+
```js
35+
// promise from async query returned within wrapper function without being handled
36+
const findMyButton = () => findByText('my button');
37+
38+
const someButton = findMyButton(); // promise unhandled here
3939
```
4040

4141
Examples of **correct** code for this rule:
4242

4343
```js
4444
// `await` operator is correct
45-
const foo = async () => {
46-
// ...
47-
const rows = await findAllByRole('row');
48-
// ...
49-
};
45+
const rows = await findAllByRole('row');
46+
47+
await screen.findAllByPlaceholderText('name');
48+
49+
const promise = findByIcon('search');
50+
const element = await promise;
51+
```
5052

53+
```js
5154
// `then` method is correct
52-
const bar = () => {
53-
// ...
54-
findByText('submit').then(() => {
55-
// ...
56-
});
57-
};
58-
59-
const baz = () => {
60-
// ...
61-
await screen.findAllByPlaceholderText('name');
62-
// ...
63-
};
55+
findByText('submit').then(() => {});
6456

57+
const promise = findByRole('button');
58+
promise.then(() => {});
59+
```
60+
61+
```js
6562
// return the promise within a function is correct too!
6663
const findMyButton = () => findByText('my button');
64+
```
65+
66+
```js
67+
// promise from async query returned within wrapper function being handled
68+
const findMyButton = () => findByText('my button');
6769

70+
const someButton = await findMyButton();
71+
```
72+
73+
```js
6874
// using a resolves/rejects matcher is also correct
6975
expect(findByTestId('alert')).resolves.toBe('Success');
7076
expect(findByTestId('alert')).rejects.toBe('Error');
7177
```
7278

79+
```js
80+
// sync queries don't need to handle any promise
81+
const element = getByRole('role');
82+
```
83+
7384
## Further Reading
7485

7586
- [Async queries variants](https://testing-library.com/docs/dom-testing-library/api-queries#findby)

lib/detect-testing-library-utils.ts

+20-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export type DetectionHelpers = {
5050
isValidFilename: () => boolean;
5151
isGetByQuery: (node: TSESTree.Identifier) => boolean;
5252
isQueryByQuery: (node: TSESTree.Identifier) => boolean;
53+
isFindByQuery: (node: TSESTree.Identifier) => boolean;
5354
isSyncQuery: (node: TSESTree.Identifier) => boolean;
55+
isAsyncQuery: (node: TSESTree.Identifier) => boolean;
5456
isPresenceAssert: (node: TSESTree.MemberExpression) => boolean;
5557
isAbsenceAssert: (node: TSESTree.MemberExpression) => boolean;
5658
canReportErrors: () => boolean;
@@ -135,14 +137,21 @@ export function detectTestingLibraryUtils<
135137
* Determines whether a given node is `getBy*` or `getAllBy*` query variant or not.
136138
*/
137139
const isGetByQuery: DetectionHelpers['isGetByQuery'] = (node) => {
138-
return !!node.name.match(/^get(All)?By.+$/);
140+
return /^get(All)?By.+$/.test(node.name);
139141
};
140142

141143
/**
142144
* Determines whether a given node is `queryBy*` or `queryAllBy*` query variant or not.
143145
*/
144146
const isQueryByQuery: DetectionHelpers['isQueryByQuery'] = (node) => {
145-
return !!node.name.match(/^query(All)?By.+$/);
147+
return /^query(All)?By.+$/.test(node.name);
148+
};
149+
150+
/**
151+
* Determines whether a given node is `findBy*` or `findAllBy*` query variant or not.
152+
*/
153+
const isFindByQuery: DetectionHelpers['isFindByQuery'] = (node) => {
154+
return /^find(All)?By.+$/.test(node.name);
146155
};
147156

148157
/**
@@ -152,6 +161,13 @@ export function detectTestingLibraryUtils<
152161
return isGetByQuery(node) || isQueryByQuery(node);
153162
};
154163

164+
/**
165+
* Determines whether a given node is async query or not.
166+
*/
167+
const isAsyncQuery: DetectionHelpers['isAsyncQuery'] = (node) => {
168+
return isFindByQuery(node);
169+
};
170+
155171
/**
156172
* Determines whether a given MemberExpression node is a presence assert
157173
*
@@ -293,7 +309,9 @@ export function detectTestingLibraryUtils<
293309
isValidFilename,
294310
isGetByQuery,
295311
isQueryByQuery,
312+
isFindByQuery,
296313
isSyncQuery,
314+
isAsyncQuery,
297315
isPresenceAssert,
298316
isAbsenceAssert,
299317
canReportErrors,

0 commit comments

Comments
 (0)