Skip to content

Commit b68d66b

Browse files
authored
feat(no-unnecessary-act): add isStrict option (#404)
* feat: isStrict option for no-unnecessary-act * refactor: rename and simplify isReported -> shouldBeReported * refactor: specify in docs that isStrict is disabled by default Closes #382
1 parent b94437a commit b68d66b

File tree

3 files changed

+128
-9
lines changed

3 files changed

+128
-9
lines changed

docs/rules/no-unnecessary-act.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,30 @@ await act(async () => {
8787
});
8888
```
8989

90+
## Options
91+
92+
This rule has one option:
93+
94+
- `isStrict`: **disabled by default**. Wrapping both things related and not related to Testing Library in `act` is reported
95+
96+
```js
97+
"testing-library/no-unnecessary-act": ["error", {"isStrict": true}]
98+
```
99+
100+
Incorrect:
101+
102+
```jsx
103+
// ❌ wrapping both things related and not related to Testing Library in `act` is NOT correct
104+
105+
import { act, screen } from '@testing-library/react';
106+
import { stuffThatDoesNotUseRTL } from 'somwhere-else';
107+
108+
await act(async () => {
109+
await screen.findByRole('button');
110+
stuffThatDoesNotUseRTL();
111+
});
112+
```
113+
90114
## Further Reading
91115

92116
- [Inspiration for this rule](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#wrapping-things-in-act-unnecessarily)

lib/rules/no-unnecessary-act.ts

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ export const RULE_NAME = 'no-unnecessary-act';
1111
export type MessageIds =
1212
| 'noUnnecessaryActEmptyFunction'
1313
| 'noUnnecessaryActTestingLibraryUtil';
14+
type Options = [{ isStrict: boolean }];
1415

15-
export default createTestingLibraryRule<[], MessageIds>({
16+
export default createTestingLibraryRule<Options, MessageIds>({
1617
name: RULE_NAME,
1718
meta: {
1819
type: 'problem',
@@ -32,37 +33,71 @@ export default createTestingLibraryRule<[], MessageIds>({
3233
'Avoid wrapping Testing Library util calls in `act`',
3334
noUnnecessaryActEmptyFunction: 'Avoid wrapping empty function in `act`',
3435
},
35-
schema: [],
36+
schema: [
37+
{
38+
type: 'object',
39+
properties: {
40+
isStrict: { type: 'boolean' },
41+
},
42+
},
43+
],
3644
},
37-
defaultOptions: [],
45+
defaultOptions: [
46+
{
47+
isStrict: false,
48+
},
49+
],
50+
51+
create(context, [options], helpers) {
52+
function getStatementIdentifier(statement: TSESTree.Statement) {
53+
const callExpression = getStatementCallExpression(statement);
54+
55+
if (!callExpression) {
56+
return null;
57+
}
58+
59+
const identifier = getDeepestIdentifierNode(callExpression);
60+
61+
if (!identifier) {
62+
return null;
63+
}
64+
65+
return identifier;
66+
}
3867

39-
create(context, _, helpers) {
4068
/**
4169
* Determines whether some call is non Testing Library related for a given list of statements.
4270
*/
4371
function hasSomeNonTestingLibraryCall(
4472
statements: TSESTree.Statement[]
4573
): boolean {
4674
return statements.some((statement) => {
47-
const callExpression = getStatementCallExpression(statement);
75+
const identifier = getStatementIdentifier(statement);
4876

49-
if (!callExpression) {
77+
if (!identifier) {
5078
return false;
5179
}
5280

53-
const identifier = getDeepestIdentifierNode(callExpression);
81+
return !helpers.isTestingLibraryUtil(identifier);
82+
});
83+
}
84+
85+
function hasTestingLibraryCall(statements: TSESTree.Statement[]) {
86+
return statements.some((statement) => {
87+
const identifier = getStatementIdentifier(statement);
5488

5589
if (!identifier) {
5690
return false;
5791
}
5892

59-
return !helpers.isTestingLibraryUtil(identifier);
93+
return helpers.isTestingLibraryUtil(identifier);
6094
});
6195
}
6296

6397
function checkNoUnnecessaryActFromBlockStatement(
6498
blockStatementNode: TSESTree.BlockStatement
6599
) {
100+
const { isStrict } = options;
66101
const functionNode = blockStatementNode.parent as
67102
| TSESTree.ArrowFunctionExpression
68103
| TSESTree.FunctionExpression
@@ -89,7 +124,15 @@ export default createTestingLibraryRule<[], MessageIds>({
89124
node: identifierNode,
90125
messageId: 'noUnnecessaryActEmptyFunction',
91126
});
92-
} else if (!hasSomeNonTestingLibraryCall(blockStatementNode.body)) {
127+
return;
128+
}
129+
130+
const shouldBeReported = isStrict
131+
? hasSomeNonTestingLibraryCall(blockStatementNode.body) &&
132+
hasTestingLibraryCall(blockStatementNode.body)
133+
: !hasSomeNonTestingLibraryCall(blockStatementNode.body);
134+
135+
if (shouldBeReported) {
93136
context.report({
94137
node: identifierNode,
95138
messageId: 'noUnnecessaryActTestingLibraryUtil',

tests/lib/rules/no-unnecessary-act.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ ruleTester.run(RULE_NAME, rule, {
156156
});
157157
`,
158158
},
159+
{
160+
options: [
161+
{
162+
isStrict: true,
163+
},
164+
],
165+
code: `// case: RTL act wrapping non-RTL calls with strict option
166+
import { act, render } from '@testing-library/react'
167+
168+
act(() => jest.advanceTimersByTime(1000))
169+
act(() => {
170+
jest.advanceTimersByTime(1000)
171+
})
172+
act(() => {
173+
return jest.advanceTimersByTime(1000)
174+
})
175+
act(function() {
176+
return jest.advanceTimersByTime(1000)
177+
})
178+
`,
179+
},
159180
],
160181
invalid: [
161182
// cases for act related to React Testing Library
@@ -799,5 +820,36 @@ ruleTester.run(RULE_NAME, rule, {
799820
},
800821
],
801822
},
823+
{
824+
options: [
825+
{
826+
isStrict: true,
827+
},
828+
],
829+
code: `// case: RTL act wrapping both RTL and non-RTL calls with strict option
830+
import { act, render } from '@testing-library/react'
831+
832+
await act(async () => {
833+
userEvent.click(screen.getByText("Submit"))
834+
await flushPromises()
835+
})
836+
act(function() {
837+
userEvent.click(screen.getByText("Submit"))
838+
flushPromises()
839+
})
840+
`,
841+
errors: [
842+
{
843+
messageId: 'noUnnecessaryActTestingLibraryUtil',
844+
line: 4,
845+
column: 13,
846+
},
847+
{
848+
messageId: 'noUnnecessaryActTestingLibraryUtil',
849+
line: 8,
850+
column: 7,
851+
},
852+
],
853+
},
802854
],
803855
});

0 commit comments

Comments
 (0)