Skip to content

Commit 88f6735

Browse files
authored
feat(no-await-sync-events): add eventModules option (#569)
* feat(no-await-sync-events): add eventModules to rule options * feat(no-await-sync-events): report only when event module enabled * docs(no-await-sync-events): add options section * test: remove "name" property Closes #567
1 parent 7f751e1 commit 88f6735

File tree

3 files changed

+142
-14
lines changed

3 files changed

+142
-14
lines changed

docs/rules/no-await-sync-events.md

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Ensure that sync simulated events are not awaited unnecessarily.
44

55
## Rule Details
66

7-
Methods for simulating events in Testing Library ecosystem -`fireEvent` and `userEvent`-
7+
Methods for simulating events in Testing Library ecosystem -`fireEvent` and `userEvent` prior to v14 -
88
do NOT return any Promise, with an exception of
99
`userEvent.type` and `userEvent.keyboard`, which delays the promise resolve only if [`delay`
1010
option](https://github.com/testing-library/user-event#typeelement-text-options) is specified.
@@ -13,8 +13,8 @@ Some examples of simulating events not returning any Promise are:
1313

1414
- `fireEvent.click`
1515
- `fireEvent.select`
16-
- `userEvent.tab`
17-
- `userEvent.hover`
16+
- `userEvent.tab` (prior to `user-event` v14)
17+
- `userEvent.hover` (prior to `user-event` v14)
1818

1919
This rule aims to prevent users from waiting for those function calls.
2020

@@ -29,12 +29,14 @@ const foo = async () => {
2929

3030
const bar = async () => {
3131
// ...
32+
// userEvent prior to v14
3233
await userEvent.tab();
3334
// ...
3435
};
3536

3637
const baz = async () => {
3738
// ...
39+
// userEvent prior to v14
3840
await userEvent.type(textInput, 'abc');
3941
await userEvent.keyboard('abc');
4042
// ...
@@ -66,9 +68,42 @@ const baz = async () => {
6668
userEvent.keyboard('123');
6769
// ...
6870
};
71+
72+
const qux = async () => {
73+
// userEvent v14
74+
await userEvent.tab();
75+
await userEvent.click(button);
76+
await userEvent.type(textInput, 'abc');
77+
await userEvent.keyboard('abc');
78+
// ...
79+
};
80+
```
81+
82+
## Options
83+
84+
This rule provides the following options:
85+
86+
- `eventModules`: array of strings. The possibilities are: `"fire-event"` and `"user-event"`. Defaults to `["fire-event", "user-event"]`
87+
88+
### `eventModules`
89+
90+
This option gives you more granular control of which event modules you want to report, so you can choose to only report methods from either `fire-event`, `user-event` or both.
91+
92+
Example:
93+
94+
```json
95+
{
96+
"testing-library/no-await-sync-events": [
97+
"error",
98+
{
99+
"eventModules": ["fire-event", "user-event"]
100+
}
101+
]
102+
}
69103
```
70104

71105
## Notes
72106

73-
There is another rule `await-fire-event`, which is only in Vue Testing
74-
Library. Please do not confuse with this rule.
107+
- Since `user-event` v14 all its methods are async, so you should disable reporting them by setting the `eventModules` to just `"fire-event"` so `user-event` methods are not reported.
108+
- There is another rule `await-fire-event`, which is only in Vue Testing
109+
Library. Please do not confuse with this rule.

lib/rules/no-await-sync-events.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ import {
99
isProperty,
1010
} from '../node-utils';
1111

12+
const USER_EVENT_ASYNC_EXCEPTIONS: string[] = ['type', 'keyboard'];
13+
const VALID_EVENT_MODULES = ['fire-event', 'user-event'] as const;
14+
1215
export const RULE_NAME = 'no-await-sync-events';
1316
export type MessageIds = 'noAwaitSyncEvents';
14-
type Options = [];
15-
16-
const USER_EVENT_ASYNC_EXCEPTIONS: string[] = ['type', 'keyboard'];
17+
type Options = [
18+
{ eventModules?: readonly typeof VALID_EVENT_MODULES[number][] }
19+
];
1720

1821
export default createTestingLibraryRule<Options, MessageIds>({
1922
name: RULE_NAME,
@@ -32,11 +35,23 @@ export default createTestingLibraryRule<Options, MessageIds>({
3235
noAwaitSyncEvents:
3336
'`{{ name }}` is sync and does not need `await` operator',
3437
},
35-
schema: [],
38+
schema: [
39+
{
40+
type: 'object',
41+
properties: {
42+
eventModules: {
43+
enum: VALID_EVENT_MODULES,
44+
},
45+
},
46+
additionalProperties: false,
47+
},
48+
],
3649
},
37-
defaultOptions: [],
50+
defaultOptions: [{ eventModules: VALID_EVENT_MODULES }],
51+
52+
create(context, [options], helpers) {
53+
const { eventModules = VALID_EVENT_MODULES } = options;
3854

39-
create(context, _, helpers) {
4055
// userEvent.type() and userEvent.keyboard() are exceptions, which returns a
4156
// Promise. But it is only necessary to wait when delay option other than 0
4257
// is specified. So this rule has a special exception for the case await:
@@ -50,14 +65,25 @@ export default createTestingLibraryRule<Options, MessageIds>({
5065
return;
5166
}
5267

53-
const isSimulateEventMethod =
54-
helpers.isUserEventMethod(simulateEventFunctionIdentifier) ||
55-
helpers.isFireEventMethod(simulateEventFunctionIdentifier);
68+
const isUserEventMethod = helpers.isUserEventMethod(
69+
simulateEventFunctionIdentifier
70+
);
71+
const isFireEventMethod = helpers.isFireEventMethod(
72+
simulateEventFunctionIdentifier
73+
);
74+
const isSimulateEventMethod = isUserEventMethod || isFireEventMethod;
5675

5776
if (!isSimulateEventMethod) {
5877
return;
5978
}
6079

80+
if (isFireEventMethod && !eventModules.includes('fire-event')) {
81+
return;
82+
}
83+
if (isUserEventMethod && !eventModules.includes('user-event')) {
84+
return;
85+
}
86+
6187
const lastArg = node.arguments[node.arguments.length - 1];
6288

6389
const hasDelay =

tests/lib/rules/no-await-sync-events.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,28 @@ ruleTester.run(RULE_NAME, rule, {
166166
});
167167
`,
168168
},
169+
170+
// valid tests for fire-event when only user-event set in eventModules
171+
...FIRE_EVENT_FUNCTIONS.map((func) => ({
172+
code: `
173+
import { fireEvent } from '@testing-library/framework';
174+
test('should not report fireEvent.${func} sync event awaited', async() => {
175+
await fireEvent.${func}('foo');
176+
});
177+
`,
178+
options: [{ eventModules: 'user-event' }],
179+
})),
180+
181+
// valid tests for user-event when only fire-event set in eventModules
182+
...USER_EVENT_SYNC_FUNCTIONS.map((func) => ({
183+
code: `
184+
import userEvent from '@testing-library/user-event';
185+
test('should not report userEvent.${func} sync event awaited', async() => {
186+
await userEvent.${func}('foo');
187+
});
188+
`,
189+
options: [{ eventModules: 'fire-event' }],
190+
})),
169191
],
170192

171193
invalid: [
@@ -210,6 +232,51 @@ ruleTester.run(RULE_NAME, rule, {
210232
} as const)
211233
),
212234

235+
// sync fireEvent methods with await operator are not valid
236+
// when only fire-event set in eventModules
237+
...FIRE_EVENT_FUNCTIONS.map(
238+
(func) =>
239+
({
240+
code: `
241+
import { fireEvent } from '@testing-library/framework';
242+
test('should report fireEvent.${func} sync event awaited', async() => {
243+
await fireEvent.${func}('foo');
244+
});
245+
`,
246+
options: [{ eventModules: 'fire-event' }],
247+
errors: [
248+
{
249+
line: 4,
250+
column: 17,
251+
messageId: 'noAwaitSyncEvents',
252+
data: { name: `fireEvent.${func}` },
253+
},
254+
],
255+
} as const)
256+
),
257+
// sync userEvent sync methods with await operator are not valid
258+
// when only fire-event set in eventModules
259+
...USER_EVENT_SYNC_FUNCTIONS.map(
260+
(func) =>
261+
({
262+
code: `
263+
import userEvent from '@testing-library/user-event';
264+
test('should report userEvent.${func} sync event awaited', async() => {
265+
await userEvent.${func}('foo');
266+
});
267+
`,
268+
options: [{ eventModules: 'user-event' }],
269+
errors: [
270+
{
271+
line: 4,
272+
column: 17,
273+
messageId: 'noAwaitSyncEvents',
274+
data: { name: `userEvent.${func}` },
275+
},
276+
],
277+
} as const)
278+
),
279+
213280
{
214281
code: `
215282
import userEvent from '@testing-library/user-event';

0 commit comments

Comments
 (0)