Skip to content

Commit 9576462

Browse files
authoredOct 20, 2022
feat: support handling promises with jest-extended .toResolve & .toRejects (#612)
* Support jest-extended toResolve and toReject as handling promises * add test cases for toResolve and toReject matchers * feat(await-async-query): support handling promises with jest-extendeds toResolve and toRejects * docs(await-async-query): add docs for toResolve/toReject * Add jest-extended docs * fireEvent.blur(getByLabelText('username')) * run format * add tests for await-async-utils and await-fire-event
1 parent e2ea687 commit 9576462

7 files changed

+104
-2
lines changed
 

‎docs/rules/await-async-query.md

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ problems in the tests. The promise will be considered as handled when:
1919
- wrapped within `Promise.all` or `Promise.allSettled` methods
2020
- chaining the `then` method
2121
- chaining `resolves` or `rejects` from jest
22+
- chaining `toResolve()` or `toReject()` from [jest-extended](https://github.com/jest-community/jest-extended#promise)
2223
- it's returned from a function (in this case, that particular function will be analyzed by this rule too)
2324

2425
Examples of **incorrect** code for this rule:
@@ -90,6 +91,12 @@ expect(findByTestId('alert')).resolves.toBe('Success');
9091
expect(findByTestId('alert')).rejects.toBe('Error');
9192
```
9293

94+
```js
95+
// using a toResolve/toReject matcher is also correct
96+
expect(findByTestId('alert')).toResolve();
97+
expect(findByTestId('alert')).toReject();
98+
```
99+
93100
```js
94101
// sync queries don't need to handle any promise
95102
const element = getByRole('role');

‎docs/rules/await-async-utils.md

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ problems in the tests. The promise will be considered as handled when:
2020
- wrapped within `Promise.all` or `Promise.allSettled` methods
2121
- chaining the `then` method
2222
- chaining `resolves` or `rejects` from jest
23+
- chaining `toResolve()` or `toReject()` from [jest-extended](https://github.com/jest-community/jest-extended#promise)
2324
- it's returned from a function (in this case, that particular function will be analyzed by this rule too)
2425

2526
Examples of **incorrect** code for this rule:
@@ -85,6 +86,12 @@ test('something correctly', async () => {
8586
waitFor(() => getByLabelText('email')),
8687
waitForElementToBeRemoved(() => document.querySelector('div.getOuttaHere')),
8788
]);
89+
90+
// Using jest resolves or rejects
91+
expect(waitFor(() => getByLabelText('email'))).resolves.toBeUndefined();
92+
93+
// Using jest-extended a toResolve/toReject matcher is also correct
94+
expect(waitFor(() => getByLabelText('email'))).toResolve();
8895
});
8996
```
9097

‎docs/rules/await-fire-event.md

+14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ properly.
77

88
This rule aims to prevent users from forgetting to handle promise returned from `fireEvent`
99
methods.
10+
The promise will be considered as handled when:
11+
12+
- using the `await` operator
13+
- wrapped within `Promise.all` or `Promise.allSettled` methods
14+
- chaining the `then` method
15+
- chaining `resolves` or `rejects` from jest
16+
- chaining `toResolve()` or `toReject()` from [jest-extended](https://github.com/jest-community/jest-extended#promise)
17+
- it's returned from a function (in this case, that particular function will be analyzed by this rule too)
1018

1119
> ⚠️ `fireEvent` methods are async only on following Testing Library packages:
1220
>
@@ -55,6 +63,12 @@ await Promise.all([
5563
fireEvent.focus(getByLabelText('username')),
5664
fireEvent.blur(getByLabelText('username')),
5765
]);
66+
67+
// Using jest resolves or rejects
68+
expect(fireEvent.focus(getByLabelText('username'))).resolves.toBeUndefined();
69+
70+
// Using jest-extended a toResolve/toReject matcher is also correct
71+
expect(waitFor(() => getByLabelText('email'))).toResolve();
5872
```
5973

6074
## When Not To Use It

‎lib/node-utils/index.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export function isPromisesArrayResolved(node: TSESTree.Node): boolean {
191191
* - it's chained with the `then` method
192192
* - it's returned from a function
193193
* - has `resolves` or `rejects` jest methods
194+
* - has `toResolve` or `toReject` jest-extended matchers
194195
*/
195196
export function isPromiseHandled(nodeIdentifier: TSESTree.Identifier): boolean {
196197
const closestCallExpressionNode = findClosestCallExpressionNode(
@@ -462,9 +463,17 @@ export function getAssertNodeInfo(
462463
return { matcher, isNegated };
463464
}
464465

466+
const matcherNamesHandlePromise = [
467+
'resolves',
468+
'rejects',
469+
'toResolve',
470+
'toReject',
471+
];
472+
465473
/**
466474
* Determines whether a node belongs to an async assertion
467-
* fulfilled by `resolves` or `rejects` properties.
475+
* fulfilled by `resolves` or `rejects` properties or
476+
* by `toResolve` or `toReject` jest-extended matchers
468477
*
469478
*/
470479
export function hasClosestExpectResolvesRejects(node: TSESTree.Node): boolean {
@@ -478,7 +487,7 @@ export function hasClosestExpectResolvesRejects(node: TSESTree.Node): boolean {
478487
const expectMatcher = node.parent.property;
479488
return (
480489
ASTUtils.isIdentifier(expectMatcher) &&
481-
(expectMatcher.name === 'resolves' || expectMatcher.name === 'rejects')
490+
matcherNamesHandlePromise.includes(expectMatcher.name)
482491
);
483492
}
484493

‎tests/lib/rules/await-async-query.test.ts

+15
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,13 @@ ruleTester.run(RULE_NAME, rule, {
222222
expect(wrappedQuery(${query}("foo"))).resolves.toBe("bar")
223223
`
224224
),
225+
// async queries with toResolve matchers are valid
226+
...createTestCase(
227+
(query) => `
228+
expect(${query}("foo")).toResolve()
229+
expect(wrappedQuery(${query}("foo"))).toResolve()
230+
`
231+
),
225232

226233
// async queries with rejects matchers are valid
227234
...createTestCase(
@@ -231,6 +238,14 @@ ruleTester.run(RULE_NAME, rule, {
231238
`
232239
),
233240

241+
// async queries with toReject matchers are valid
242+
...createTestCase(
243+
(query) => `
244+
expect(${query}("foo")).toReject()
245+
expect(wrappedQuery(${query}("foo"))).toReject()
246+
`
247+
),
248+
234249
// unresolved async queries with aggressive reporting opted-out are valid
235250
...ALL_ASYNC_COMBINATIONS_TO_TEST.map((query) => ({
236251
settings: { 'testing-library/utils-module': 'test-utils' },

‎tests/lib/rules/await-async-utils.test.ts

+18
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,24 @@ ruleTester.run(RULE_NAME, rule, {
4040
doSomethingElse();
4141
${asyncUtil}(() => getByLabelText('email')).then(() => { console.log('done') });
4242
});
43+
`,
44+
})),
45+
...ASYNC_UTILS.map((asyncUtil) => ({
46+
code: `
47+
import { ${asyncUtil} } from '${testingFramework}';
48+
test('${asyncUtil} util expect chained with .resolves is valid', () => {
49+
doSomethingElse();
50+
expect(${asyncUtil}(() => getByLabelText('email'))).resolves.toBe("foo");
51+
});
52+
`,
53+
})),
54+
...ASYNC_UTILS.map((asyncUtil) => ({
55+
code: `
56+
import { ${asyncUtil} } from '${testingFramework}';
57+
test('${asyncUtil} util expect chained with .toResolve is valid', () => {
58+
doSomethingElse();
59+
expect(${asyncUtil}(() => getByLabelText('email'))).toResolve();
60+
});
4361
`,
4462
})),
4563
...ASYNC_UTILS.map((asyncUtil) => ({

‎tests/lib/rules/await-fire-event.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,38 @@ ruleTester.run(RULE_NAME, rule, {
3636
...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({
3737
code: `
3838
import { fireEvent } from '${testingFramework}'
39+
test('promise .resolves from fire event method is valid', async () => {
40+
expect(fireEvent.${fireEventMethod}(getByLabelText('username'))).resolves.toBe("bar")
41+
})
42+
`,
43+
})),
44+
...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({
45+
code: `
46+
import { fireEvent } from '${testingFramework}'
47+
test('wrapped promise .resolves from fire event method is valid', async () => {
48+
expect(wrapper(fireEvent.${fireEventMethod}(getByLabelText('username')))).resolves.toBe("bar")
49+
})
50+
`,
51+
})),
52+
...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({
53+
code: `
54+
import { fireEvent } from '${testingFramework}'
55+
test('promise .toResolve() from fire event method is valid', async () => {
56+
expect(fireEvent.${fireEventMethod}(getByLabelText('username'))).toResolve()
57+
})
58+
`,
59+
})),
60+
...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({
61+
code: `
62+
import { fireEvent } from '${testingFramework}'
63+
test('promise .toResolve() from fire event method is valid', async () => {
64+
expect(wrapper(fireEvent.${fireEventMethod}(getByLabelText('username')))).toResolve()
65+
})
66+
`,
67+
})),
68+
...COMMON_FIRE_EVENT_METHODS.map((fireEventMethod) => ({
69+
code: `
70+
import { fireEvent } from '${testingFramework}'
3971
test('await several promises from fire event methods is valid', async () => {
4072
await fireEvent.${fireEventMethod}(getByLabelText('username'))
4173
await fireEvent.${fireEventMethod}(getByLabelText('username'))

0 commit comments

Comments
 (0)
Please sign in to comment.