Skip to content

Commit 29a0fd3

Browse files
committed
Remove jest global check in concurrent roots
In concurrent mode, instead of checking `jest`, we check the new `IS_REACT_ACT_ENVIRONMENT` global. The default behavior is `false`. Legacy mode behavior is unchanged. React's own internal test suite use a custom version of `act` that works by mocking the Scheduler — rather than the "real" act used publicly. So we don't enable the flag in our repo.
1 parent 5a0bbca commit 29a0fd3

File tree

5 files changed

+113
-47
lines changed

5 files changed

+113
-47
lines changed

packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,7 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
7676
// Dispatch a click event on the Disable-button.
7777
const firstEvent = document.createEvent('Event');
7878
firstEvent.initEvent('click', true, true);
79-
expect(() =>
80-
dispatchAndSetCurrentEvent(disableButton, firstEvent),
81-
).toErrorDev(['An update to Form inside a test was not wrapped in act']);
79+
dispatchAndSetCurrentEvent(disableButton, firstEvent);
8280

8381
// Discrete events should be flushed in a microtask.
8482
// Verify that the second button was removed.
@@ -134,9 +132,7 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
134132
// Dispatch a click event on the Disable-button.
135133
const firstEvent = document.createEvent('Event');
136134
firstEvent.initEvent('click', true, true);
137-
expect(() => {
138-
dispatchAndSetCurrentEvent(disableButton, firstEvent);
139-
}).toErrorDev(['An update to Form inside a test was not wrapped in act']);
135+
dispatchAndSetCurrentEvent(disableButton, firstEvent);
140136

141137
// There should now be a pending update to disable the form.
142138
// This should not have flushed yet since it's in concurrent mode.
@@ -196,9 +192,7 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
196192
// Dispatch a click event on the Enable-button.
197193
const firstEvent = document.createEvent('Event');
198194
firstEvent.initEvent('click', true, true);
199-
expect(() => {
200-
dispatchAndSetCurrentEvent(enableButton, firstEvent);
201-
}).toErrorDev(['An update to Form inside a test was not wrapped in act']);
195+
dispatchAndSetCurrentEvent(enableButton, firstEvent);
202196

203197
// There should now be a pending update to enable the form.
204198
// This should not have flushed yet since it's in concurrent mode.
@@ -344,9 +338,6 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
344338
});
345339
expect(container.textContent).toEqual('Count: 0');
346340

347-
// Ignore act warning. We can't use act because it forces batched updates.
348-
spyOnDev(console, 'error');
349-
350341
const pressEvent = document.createEvent('Event');
351342
pressEvent.initEvent('click', true, true);
352343
dispatchAndSetCurrentEvent(target.current, pressEvent);
@@ -355,17 +346,6 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
355346
await null;
356347
// If this is 2, that means the `setCount` calls were not batched.
357348
expect(container.textContent).toEqual('Count: 1');
358-
359-
// Assert that the `act` warnings were the only ones that fired.
360-
if (__DEV__) {
361-
expect(console.error).toHaveBeenCalledTimes(2);
362-
expect(console.error.calls.argsFor(0)[0]).toContain(
363-
'was not wrapped in act',
364-
);
365-
expect(console.error.calls.argsFor(1)[0]).toContain(
366-
'was not wrapped in act',
367-
);
368-
}
369349
});
370350

371351
it('should not flush discrete events at the end of outermost batchedUpdates', async () => {

packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ let container;
1616

1717
jest.useRealTimers();
1818

19+
global.IS_REACT_ACT_ENVIRONMENT = true;
20+
1921
function sleep(period) {
2022
return new Promise(resolve => {
2123
setTimeout(() => {

packages/react-reconciler/src/ReactFiberAct.new.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {Fiber} from './ReactFiber.new';
1111
import {warnsIfNotActing} from './ReactFiberHostConfig';
12+
import {ConcurrentMode} from './ReactTypeOfMode';
1213

1314
export function isActEnvironment(fiber: Fiber) {
1415
if (__DEV__) {
@@ -18,18 +19,21 @@ export function isActEnvironment(fiber: Fiber) {
1819
? IS_REACT_ACT_ENVIRONMENT
1920
: undefined;
2021

21-
// TODO: Only check `jest` in legacy mode. In concurrent mode, this
22-
// heuristic is replaced by IS_REACT_ACT_ENVIRONMENT.
23-
// $FlowExpectedError - Flow doesn't know about jest
24-
const jestIsDefined = typeof jest !== 'undefined';
25-
return (
26-
warnsIfNotActing &&
27-
jestIsDefined &&
28-
// Legacy mode assumes an act environment whenever `jest` is defined, but
29-
// you can still turn off spurious warnings by setting
30-
// IS_REACT_ACT_ENVIRONMENT explicitly to false.
31-
isReactActEnvironmentGlobal !== false
32-
);
22+
if (fiber.mode & ConcurrentMode) {
23+
return isReactActEnvironmentGlobal;
24+
} else {
25+
// Legacy mode. We preserve the behavior of React 17's act. It assumes an
26+
// act environment whenever `jest` is defined, but you can still turn off
27+
// spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly
28+
// to false.
29+
// $FlowExpectedError - Flow doesn't know about jest
30+
const jestIsDefined = typeof jest !== 'undefined';
31+
return (
32+
warnsIfNotActing &&
33+
jestIsDefined &&
34+
isReactActEnvironmentGlobal !== false
35+
);
36+
}
3337
}
3438
return false;
3539
}

packages/react-reconciler/src/ReactFiberAct.old.js

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import type {Fiber} from './ReactFiber.old';
1111
import {warnsIfNotActing} from './ReactFiberHostConfig';
12+
import {ConcurrentMode} from './ReactTypeOfMode';
1213

1314
export function isActEnvironment(fiber: Fiber) {
1415
if (__DEV__) {
@@ -18,18 +19,21 @@ export function isActEnvironment(fiber: Fiber) {
1819
? IS_REACT_ACT_ENVIRONMENT
1920
: undefined;
2021

21-
// TODO: Only check `jest` in legacy mode. In concurrent mode, this
22-
// heuristic is replaced by IS_REACT_ACT_ENVIRONMENT.
23-
// $FlowExpectedError - Flow doesn't know about jest
24-
const jestIsDefined = typeof jest !== 'undefined';
25-
return (
26-
warnsIfNotActing &&
27-
jestIsDefined &&
28-
// Legacy mode assumes an act environment whenever `jest` is defined, but
29-
// you can still turn off spurious warnings by setting
30-
// IS_REACT_ACT_ENVIRONMENT explicitly to false.
31-
isReactActEnvironmentGlobal !== false
32-
);
22+
if (fiber.mode & ConcurrentMode) {
23+
return isReactActEnvironmentGlobal;
24+
} else {
25+
// Legacy mode. We preserve the behavior of React 17's act. It assumes an
26+
// act environment whenever `jest` is defined, but you can still turn off
27+
// spurious warnings by setting IS_REACT_ACT_ENVIRONMENT explicitly
28+
// to false.
29+
// $FlowExpectedError - Flow doesn't know about jest
30+
const jestIsDefined = typeof jest !== 'undefined';
31+
return (
32+
warnsIfNotActing &&
33+
jestIsDefined &&
34+
isReactActEnvironmentGlobal !== false
35+
);
36+
}
3337
}
3438
return false;
3539
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @jest-environment node
8+
*/
9+
10+
let React;
11+
let Scheduler;
12+
let ReactNoop;
13+
let useState;
14+
15+
// These tests are mostly concerned with concurrent roots. The legacy root
16+
// behavior is covered by other older test suites and is unchanged from
17+
// React 17.
18+
describe('act warnings', () => {
19+
beforeEach(() => {
20+
React = require('react');
21+
Scheduler = require('scheduler');
22+
ReactNoop = require('react-noop-renderer');
23+
useState = React.useState;
24+
});
25+
26+
function Text(props) {
27+
Scheduler.unstable_yieldValue(props.text);
28+
return props.text;
29+
}
30+
31+
function withActEnvironment(value, scope) {
32+
const prevValue = global.IS_REACT_ACT_ENVIRONMENT;
33+
global.IS_REACT_ACT_ENVIRONMENT = value;
34+
try {
35+
return scope();
36+
} finally {
37+
global.IS_REACT_ACT_ENVIRONMENT = prevValue;
38+
}
39+
}
40+
41+
test('warns about unwrapped updates only if environment flag is enabled', () => {
42+
let setState;
43+
function App() {
44+
const [state, _setState] = useState(0);
45+
setState = _setState;
46+
return <Text text={state} />;
47+
}
48+
49+
const root = ReactNoop.createRoot();
50+
root.render(<App />);
51+
expect(Scheduler).toFlushAndYield([0]);
52+
expect(root).toMatchRenderedOutput('0');
53+
54+
// Default behavior. Flag is undefined. No warning.
55+
expect(global.IS_REACT_ACT_ENVIRONMENT).toBe(undefined);
56+
setState(1);
57+
expect(Scheduler).toFlushAndYield([1]);
58+
expect(root).toMatchRenderedOutput('1');
59+
60+
// Flag is true. Warn.
61+
withActEnvironment(true, () => {
62+
expect(() => setState(2)).toErrorDev(
63+
'An update to App inside a test was not wrapped in act',
64+
);
65+
expect(Scheduler).toFlushAndYield([2]);
66+
expect(root).toMatchRenderedOutput('2');
67+
});
68+
69+
// Flag is false. No warning.
70+
withActEnvironment(false, () => {
71+
setState(3);
72+
expect(Scheduler).toFlushAndYield([3]);
73+
expect(root).toMatchRenderedOutput('3');
74+
});
75+
});
76+
});

0 commit comments

Comments
 (0)