Skip to content

Commit eadf277

Browse files
committed
act should work without mock Scheduler
Updates `act` so that it works in Concurrent and Blocking Mode without a mock Scheduler.
1 parent e7b2553 commit eadf277

20 files changed

+345
-356
lines changed

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

+74-44
Large diffs are not rendered by default.

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

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ afterEach(() => {
4747
document.body.removeChild(container);
4848
});
4949

50+
// @gate __DEV__
5051
it('can use act to flush effects', () => {
5152
function App() {
5253
React.useEffect(() => {
@@ -62,6 +63,7 @@ it('can use act to flush effects', () => {
6263
expect(clearYields()).toEqual([100]);
6364
});
6465

66+
// @gate __DEV__
6567
it('flushes effects on every call', () => {
6668
function App() {
6769
const [ctr, setCtr] = React.useState(0);
@@ -100,6 +102,7 @@ it('flushes effects on every call', () => {
100102
expect(button.innerHTML).toEqual('5');
101103
});
102104

105+
// @gate __DEV__
103106
it("should keep flushing effects until they're done", () => {
104107
function App() {
105108
const [ctr, setCtr] = React.useState(0);
@@ -118,6 +121,7 @@ it("should keep flushing effects until they're done", () => {
118121
expect(container.innerHTML).toEqual('5');
119122
});
120123

124+
// @gate __DEV__
121125
it('should flush effects only on exiting the outermost act', () => {
122126
function App() {
123127
React.useEffect(() => {
@@ -138,6 +142,7 @@ it('should flush effects only on exiting the outermost act', () => {
138142
expect(clearYields()).toEqual([0]);
139143
});
140144

145+
// @gate __DEV__
141146
it('can handle cascading promises', async () => {
142147
// this component triggers an effect, that waits a tick,
143148
// then sets state. repeats this 5 times.

packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js

-56
This file was deleted.

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

+128-42
Original file line numberDiff line numberDiff line change
@@ -7,56 +7,142 @@
77
* @emails react-core
88
*/
99

10-
let React;
11-
let ReactDOM;
12-
13-
function App() {
14-
return null;
15-
}
16-
1710
beforeEach(() => {
1811
jest.resetModules();
19-
jest.unmock('scheduler');
20-
React = require('react');
21-
ReactDOM = require('react-dom');
12+
13+
// Unmock the Scheduler, which is mocked by default in our test setup
14+
jest.mock('scheduler', () => require.requireActual('scheduler'));
15+
jest.mock('scheduler/src/SchedulerHostConfig', () =>
16+
require.requireActual('scheduler/src/forks/SchedulerHostConfig.default.js'),
17+
);
18+
});
19+
20+
// @gate __DEV__
21+
it('public implementation of `act` works without mocking scheduler (DOM)', async () => {
22+
const React = require('react');
23+
const ReactDOM = require('react-dom');
24+
const TestUtils = require('react-dom/test-utils');
25+
26+
const log = [];
27+
28+
function App() {
29+
React.useEffect(() => {
30+
log.push('Did mount');
31+
}, []);
32+
return 'App';
33+
}
34+
35+
const container = document.createElement('div');
36+
await TestUtils.act(async () => {
37+
ReactDOM.render(<App />, container);
38+
});
39+
expect(container.textContent).toEqual('App');
40+
expect(log).toEqual(['Did mount']);
2241
});
2342

24-
it('does not warn when rendering in legacy mode', () => {
25-
expect(() => {
26-
ReactDOM.render(<App />, document.createElement('div'));
27-
}).toErrorDev([]);
43+
it('internal implementation of `act` throws if Scheduler is not mocked (DOM)', async () => {
44+
const React = require('react');
45+
const ReactDOM = require('react-dom');
46+
const TestUtils = require('react-dom/test-utils');
47+
48+
const log = [];
49+
50+
function App() {
51+
React.useEffect(() => {
52+
log.push('Did mount');
53+
}, []);
54+
return 'App';
55+
}
56+
57+
const container = document.createElement('div');
58+
let error = null;
59+
try {
60+
await TestUtils.unstable_concurrentAct(async () => {
61+
ReactDOM.render(<App />, container);
62+
});
63+
} catch (e) {
64+
error = e;
65+
}
66+
expect(error).not.toBe(null);
67+
expect(error.message).toEqual(
68+
'This version of `act` requires a special mock build of Scheduler.',
69+
);
2870
});
2971

30-
// @gate experimental
31-
it('should warn when rendering in concurrent mode', () => {
32-
expect(() => {
33-
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
34-
}).toErrorDev(
35-
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
36-
'to guarantee consistent behaviour across tests and browsers.',
37-
{withoutStack: true},
72+
it('internal implementation of `act` throws if Scheduler is not mocked (noop)', async () => {
73+
const React = require('react');
74+
const ReactNoop = require('react-noop-renderer');
75+
76+
const log = [];
77+
78+
function App() {
79+
React.useEffect(() => {
80+
log.push('Did mount');
81+
}, []);
82+
return 'App';
83+
}
84+
85+
const root = ReactNoop.createRoot();
86+
let error = null;
87+
try {
88+
await ReactNoop.act(async () => {
89+
root.render(<App />);
90+
});
91+
} catch (e) {
92+
error = e;
93+
}
94+
expect(error).not.toBe(null);
95+
expect(error.message).toEqual(
96+
'This version of `act` requires a special mock build of Scheduler.',
3897
);
39-
// does not warn twice
40-
expect(() => {
41-
ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
42-
}).toErrorDev([]);
4398
});
4499

45-
// @gate experimental
46-
it('should warn when rendering in blocking mode', () => {
47-
expect(() => {
48-
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
49-
<App />,
50-
);
51-
}).toErrorDev(
52-
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
53-
'to guarantee consistent behaviour across tests and browsers.',
54-
{withoutStack: true},
100+
// @gate __DEV__
101+
it('public implementation of `act` works without mocking scheduler (test renderer)', async () => {
102+
const React = require('react');
103+
const ReactTestRenderer = require('react-test-renderer');
104+
105+
const log = [];
106+
107+
function App() {
108+
React.useEffect(() => {
109+
log.push('Did mount');
110+
}, []);
111+
return 'App';
112+
}
113+
114+
const root = ReactTestRenderer.create(null);
115+
await ReactTestRenderer.act(async () => {
116+
root.update(<App />);
117+
});
118+
expect(root.toJSON()).toEqual('App');
119+
expect(log).toEqual(['Did mount']);
120+
});
121+
122+
it('internal implementation of `act` throws if Scheduler is not mocked (test renderer)', async () => {
123+
const React = require('react');
124+
const ReactTestRenderer = require('react-test-renderer');
125+
126+
const log = [];
127+
128+
function App() {
129+
React.useEffect(() => {
130+
log.push('Did mount');
131+
}, []);
132+
return 'App';
133+
}
134+
135+
const root = ReactTestRenderer.create(null);
136+
let error = null;
137+
try {
138+
await ReactTestRenderer.unstable_concurrentAct(async () => {
139+
root.update(<App />);
140+
});
141+
} catch (e) {
142+
error = e;
143+
}
144+
expect(error).not.toBe(null);
145+
expect(error.message).toEqual(
146+
'This version of `act` requires a special mock build of Scheduler.',
55147
);
56-
// does not warn twice
57-
expect(() => {
58-
ReactDOM.unstable_createBlockingRoot(document.createElement('div')).render(
59-
<App />,
60-
);
61-
}).toErrorDev([]);
62148
});

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

-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import {
6161
flushDiscreteUpdates,
6262
flushPassiveEffects,
6363
warnIfNotScopedWithMatchingAct,
64-
warnIfUnmockedScheduler,
6564
IsThisRendererActing,
6665
act,
6766
} from './ReactFiberWorkLoop.new';
@@ -261,7 +260,6 @@ export function updateContainer(
261260
if (__DEV__) {
262261
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
263262
if ('undefined' !== typeof jest) {
264-
warnIfUnmockedScheduler(current);
265263
warnIfNotScopedWithMatchingAct(current);
266264
}
267265
}

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

-2
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import {
6161
flushDiscreteUpdates,
6262
flushPassiveEffects,
6363
warnIfNotScopedWithMatchingAct,
64-
warnIfUnmockedScheduler,
6564
IsThisRendererActing,
6665
act,
6766
} from './ReactFiberWorkLoop.old';
@@ -261,7 +260,6 @@ export function updateContainer(
261260
if (__DEV__) {
262261
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
263262
if ('undefined' !== typeof jest) {
264-
warnIfUnmockedScheduler(current);
265263
warnIfNotScopedWithMatchingAct(current);
266264
}
267265
}

0 commit comments

Comments
 (0)