Update more tests to not rely on sync queuing (#26358)
This fixes a handful of tests that were accidentally relying on React synchronously queuing work in the Scheduler after a setState. Usually this is because they use a lower level SchedulerMock method instead of either `act` or one of the `waitFor` helpers. In some cases, the solution is to switch to those APIs. In other cases, if we're intentionally testing some lower level behavior, we might have to be a bit more clever. Co-authored-by: Tianyu Yao <skyyao@fb.com>
Andrew Clark committed
Mar 10, 2023 at 11:06 UTC
a8875eab7f78a453d22370d1061a8bb3cd672b9d
25 files changed
+550
-555
packages/react-debug-tools/src/__tests__/ReactDevToolsHooksIntegration-test.js
+24
-27
@@ -14,7 +14,6 @@ describe('React hooks DevTools integration', () => {
14
let React;
15
let ReactDebugTools;
16
let ReactTestRenderer;
17
- let Scheduler;
17
let act;
18
let overrideHookState;
19
let scheduleUpdate;
@@ -40,7 +39,6 @@ describe('React hooks DevTools integration', () => {
39
React = require('react');
40
ReactDebugTools = require('react-debug-tools');
41
ReactTestRenderer = require('react-test-renderer');
43
- Scheduler = require('scheduler');
42
43
const InternalTestUtils = require('internal-test-utils');
44
waitForAll = InternalTestUtils.waitForAll;
@@ -48,7 +46,7 @@ describe('React hooks DevTools integration', () => {
46
act = ReactTestRenderer.act;
47
});
48
51
- it('should support editing useState hooks', () => {
49
+ it('should support editing useState hooks', async () => {
50
let setCountFn;
51
52
function MyComponent() {
@@ -70,14 +68,14 @@ describe('React hooks DevTools integration', () => {
68
expect(stateHook.isStateEditable).toBe(true);
69
70
if (__DEV__) {
73
- act(() => overrideHookState(fiber, stateHook.id, [], 10));
71
+ await act(() => overrideHookState(fiber, stateHook.id, [], 10));
72
expect(renderer.toJSON()).toEqual({
73
type: 'div',
74
props: {},
75
children: ['count:', '10'],
76
});
77
80
- act(() => setCountFn(count => count + 1));
78
+ await act(() => setCountFn(count => count + 1));
79
expect(renderer.toJSON()).toEqual({
80
type: 'div',
81
props: {},
@@ -86,7 +84,7 @@ describe('React hooks DevTools integration', () => {
84
}
85
});
86
89
- it('should support editable useReducer hooks', () => {
87
+ it('should support editable useReducer hooks', async () => {
88
const initialData = {foo: 'abc', bar: 123};
89
90
function reducer(state, action) {
@@ -122,14 +120,14 @@ describe('React hooks DevTools integration', () => {
120
expect(reducerHook.isStateEditable).toBe(true);
121
122
if (__DEV__) {
125
- act(() => overrideHookState(fiber, reducerHook.id, ['foo'], 'def'));
123
+ await act(() => overrideHookState(fiber, reducerHook.id, ['foo'], 'def'));
124
expect(renderer.toJSON()).toEqual({
125
type: 'div',
126
props: {},
127
children: ['foo:', 'def', ', bar:', '123'],
128
});
129
132
- act(() => dispatchFn({type: 'swap'}));
130
+ await act(() => dispatchFn({type: 'swap'}));
131
expect(renderer.toJSON()).toEqual({
132
type: 'div',
133
props: {},
@@ -140,7 +138,7 @@ describe('React hooks DevTools integration', () => {
138
139
// This test case is based on an open source bug report:
140
// https://github.com/facebookincubator/redux-react-hook/issues/34#issuecomment-466693787
143
- it('should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)', () => {
141
+ it('should handle interleaved stateful hooks (e.g. useState) and non-stateful hooks (e.g. useContext)', async () => {
142
const MyContext = React.createContext(1);
143
144
let setStateFn;
@@ -170,13 +168,13 @@ describe('React hooks DevTools integration', () => {
168
expect(stateHook.isStateEditable).toBe(true);
169
170
if (__DEV__) {
173
- act(() => overrideHookState(fiber, stateHook.id, ['count'], 10));
171
+ await act(() => overrideHookState(fiber, stateHook.id, ['count'], 10));
172
expect(renderer.toJSON()).toEqual({
173
type: 'div',
174
props: {},
175
children: ['count:', '10'],
176
});
179
- act(() => setStateFn(state => ({count: state.count + 1})));
177
+ await act(() => setStateFn(state => ({count: state.count + 1})));
178
expect(renderer.toJSON()).toEqual({
179
type: 'div',
180
props: {},
@@ -185,7 +183,7 @@ describe('React hooks DevTools integration', () => {
183
}
184
});
185
188
- it('should support overriding suspense in legacy mode', () => {
186
+ it('should support overriding suspense in legacy mode', async () => {
187
if (__DEV__) {
188
// Lock the first render
189
setSuspenseHandler(() => true);
@@ -206,32 +204,32 @@ describe('React hooks DevTools integration', () => {
204
if (__DEV__) {
205
// First render was locked
206
expect(renderer.toJSON().children).toEqual(['Loading']);
209
- act(() => scheduleUpdate(fiber)); // Re-render
207
+ await act(() => scheduleUpdate(fiber)); // Re-render
208
expect(renderer.toJSON().children).toEqual(['Loading']);
209
210
// Release the lock
211
setSuspenseHandler(() => false);
214
- act(() => scheduleUpdate(fiber)); // Re-render
212
+ await act(() => scheduleUpdate(fiber)); // Re-render
213
expect(renderer.toJSON().children).toEqual(['Done']);
216
- act(() => scheduleUpdate(fiber)); // Re-render
214
+ await act(() => scheduleUpdate(fiber)); // Re-render
215
expect(renderer.toJSON().children).toEqual(['Done']);
216
217
// Lock again
218
setSuspenseHandler(() => true);
221
- act(() => scheduleUpdate(fiber)); // Re-render
219
+ await act(() => scheduleUpdate(fiber)); // Re-render
220
expect(renderer.toJSON().children).toEqual(['Loading']);
221
222
// Release the lock again
223
setSuspenseHandler(() => false);
226
- act(() => scheduleUpdate(fiber)); // Re-render
224
+ await act(() => scheduleUpdate(fiber)); // Re-render
225
expect(renderer.toJSON().children).toEqual(['Done']);
226
227
// Ensure it checks specific fibers.
228
setSuspenseHandler(f => f === fiber || f === fiber.alternate);
231
- act(() => scheduleUpdate(fiber)); // Re-render
229
+ await act(() => scheduleUpdate(fiber)); // Re-render
230
expect(renderer.toJSON().children).toEqual(['Loading']);
231
setSuspenseHandler(f => f !== fiber && f !== fiber.alternate);
234
- act(() => scheduleUpdate(fiber)); // Re-render
232
+ await act(() => scheduleUpdate(fiber)); // Re-render
233
expect(renderer.toJSON().children).toEqual(['Done']);
234
} else {
235
expect(renderer.toJSON().children).toEqual(['Done']);
@@ -267,33 +265,32 @@ describe('React hooks DevTools integration', () => {
265
if (__DEV__) {
266
// First render was locked
267
expect(renderer.toJSON().children).toEqual(['Loading']);
270
- act(() => scheduleUpdate(fiber)); // Re-render
268
+ await act(() => scheduleUpdate(fiber)); // Re-render
269
expect(renderer.toJSON().children).toEqual(['Loading']);
270
271
// Release the lock
272
setSuspenseHandler(() => false);
275
- act(() => scheduleUpdate(fiber)); // Re-render
276
- Scheduler.unstable_flushAll();
273
+ await act(() => scheduleUpdate(fiber)); // Re-render
274
expect(renderer.toJSON().children).toEqual(['Done']);
278
- act(() => scheduleUpdate(fiber)); // Re-render
275
+ await act(() => scheduleUpdate(fiber)); // Re-render
276
expect(renderer.toJSON().children).toEqual(['Done']);
277
278
// Lock again
279
setSuspenseHandler(() => true);
283
- act(() => scheduleUpdate(fiber)); // Re-render
280
+ await act(() => scheduleUpdate(fiber)); // Re-render
281
expect(renderer.toJSON().children).toEqual(['Loading']);
282
283
// Release the lock again
284
setSuspenseHandler(() => false);
288
- act(() => scheduleUpdate(fiber)); // Re-render
285
+ await act(() => scheduleUpdate(fiber)); // Re-render
286
expect(renderer.toJSON().children).toEqual(['Done']);
287
288
// Ensure it checks specific fibers.
289
setSuspenseHandler(f => f === fiber || f === fiber.alternate);
293
- act(() => scheduleUpdate(fiber)); // Re-render
290
+ await act(() => scheduleUpdate(fiber)); // Re-render
291
expect(renderer.toJSON().children).toEqual(['Loading']);
292
setSuspenseHandler(f => f !== fiber && f !== fiber.alternate);
296
- act(() => scheduleUpdate(fiber)); // Re-render
293
+ await act(() => scheduleUpdate(fiber)); // Re-render
294
expect(renderer.toJSON().children).toEqual(['Done']);
295
} else {
296
expect(renderer.toJSON().children).toEqual(['Done']);
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+2
-6
@@ -12,7 +12,6 @@
12
13
let React;
14
let ReactTestRenderer;
15
-let Scheduler;
15
let ReactDebugTools;
16
let act;
17
@@ -21,7 +20,6 @@ describe('ReactHooksInspectionIntegration', () => {
20
jest.resetModules();
21
React = require('react');
22
ReactTestRenderer = require('react-test-renderer');
24
- Scheduler = require('scheduler');
23
act = require('internal-test-utils').act;
24
ReactDebugTools = require('react-debug-tools');
25
});
@@ -890,10 +888,8 @@ describe('ReactHooksInspectionIntegration', () => {
888
</Suspense>,
889
);
890
893
- await LazyFoo;
894
-
895
- expect(() => {
896
- Scheduler.unstable_flushAll();
891
+ await expect(async () => {
892
+ await act(async () => await LazyFoo);
893
}).toErrorDev([
894
'Foo: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
895
]);
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+61
-62
@@ -16,6 +16,7 @@ let ReactDOMClient;
16
let Scheduler;
17
let act;
18
let waitForAll;
19
+let assertLog;
20
21
const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
22
HTMLInputElement.prototype,
@@ -36,6 +37,7 @@ describe('ReactDOMFiberAsync', () => {
37
38
const InternalTestUtils = require('internal-test-utils');
39
waitForAll = InternalTestUtils.waitForAll;
40
+ assertLog = InternalTestUtils.assertLog;
41
42
document.body.appendChild(container);
43
});
@@ -154,7 +156,7 @@ describe('ReactDOMFiberAsync', () => {
156
});
157
158
describe('concurrent mode', () => {
157
- it('does not perform deferred updates synchronously', () => {
159
+ it('does not perform deferred updates synchronously', async () => {
160
const inputRef = React.createRef();
161
const asyncValueRef = React.createRef();
162
const syncValueRef = React.createRef();
@@ -164,7 +166,7 @@ describe('ReactDOMFiberAsync', () => {
166
167
handleChange = e => {
168
const nextValue = e.target.value;
167
- requestIdleCallback(() => {
169
+ React.startTransition(() => {
170
this.setState({
171
asyncValue: nextValue,
172
});
@@ -191,38 +193,41 @@ describe('ReactDOMFiberAsync', () => {
193
}
194
}
195
const root = ReactDOMClient.createRoot(container);
194
- root.render(<Counter />);
195
- Scheduler.unstable_flushAll();
196
+ await act(() => root.render(<Counter />));
197
expect(asyncValueRef.current.textContent).toBe('');
198
expect(syncValueRef.current.textContent).toBe('');
199
199
- setUntrackedInputValue.call(inputRef.current, 'hello');
200
- inputRef.current.dispatchEvent(new MouseEvent('input', {bubbles: true}));
201
- // Should only flush non-deferred update.
202
- expect(asyncValueRef.current.textContent).toBe('');
203
- expect(syncValueRef.current.textContent).toBe('hello');
200
+ await act(() => {
201
+ setUntrackedInputValue.call(inputRef.current, 'hello');
202
+ inputRef.current.dispatchEvent(
203
+ new MouseEvent('input', {bubbles: true}),
204
+ );
205
+ // Should only flush non-deferred update.
206
+ expect(asyncValueRef.current.textContent).toBe('');
207
+ expect(syncValueRef.current.textContent).toBe('hello');
208
+ });
209
210
// Should flush both updates now.
206
- jest.runAllTimers();
207
- Scheduler.unstable_flushAll();
211
expect(asyncValueRef.current.textContent).toBe('hello');
212
expect(syncValueRef.current.textContent).toBe('hello');
213
});
214
212
- it('top-level updates are concurrent', () => {
215
+ it('top-level updates are concurrent', async () => {
216
const root = ReactDOMClient.createRoot(container);
214
- root.render(<div>Hi</div>);
215
- expect(container.textContent).toEqual('');
216
- Scheduler.unstable_flushAll();
217
+ await act(() => {
218
+ root.render(<div>Hi</div>);
219
+ expect(container.textContent).toEqual('');
220
+ });
221
expect(container.textContent).toEqual('Hi');
222
219
- root.render(<div>Bye</div>);
220
- expect(container.textContent).toEqual('Hi');
221
- Scheduler.unstable_flushAll();
223
+ await act(() => {
224
+ root.render(<div>Bye</div>);
225
+ expect(container.textContent).toEqual('Hi');
226
+ });
227
expect(container.textContent).toEqual('Bye');
228
});
229
225
- it('deep updates (setState) are concurrent', () => {
230
+ it('deep updates (setState) are concurrent', async () => {
231
let instance;
232
class Component extends React.Component {
233
state = {step: 0};
@@ -233,19 +238,21 @@ describe('ReactDOMFiberAsync', () => {
238
}
239
240
const root = ReactDOMClient.createRoot(container);
236
- root.render(<Component />);
237
- expect(container.textContent).toEqual('');
238
- Scheduler.unstable_flushAll();
239
- expect(container.textContent).toEqual('0');
241
241
- instance.setState({step: 1});
242
+ await act(() => {
243
+ root.render(<Component />);
244
+ expect(container.textContent).toEqual('');
245
+ });
246
expect(container.textContent).toEqual('0');
243
- Scheduler.unstable_flushAll();
247
+
248
+ await act(() => {
249
+ instance.setState({step: 1});
250
+ expect(container.textContent).toEqual('0');
251
+ });
252
expect(container.textContent).toEqual('1');
253
});
254
247
- it('flushSync flushes updates before end of the tick', () => {
248
- const ops = [];
255
+ it('flushSync flushes updates before end of the tick', async () => {
256
let instance;
257
258
class Component extends React.Component {
@@ -254,7 +261,7 @@ describe('ReactDOMFiberAsync', () => {
261
this.setState(state => ({text: state.text + val}));
262
}
263
componentDidUpdate() {
257
- ops.push(this.state.text);
264
+ Scheduler.log(this.state.text);
265
}
266
render() {
267
instance = this;
@@ -263,12 +270,11 @@ describe('ReactDOMFiberAsync', () => {
270
}
271
272
const root = ReactDOMClient.createRoot(container);
266
- root.render(<Component />);
267
- Scheduler.unstable_flushAll();
273
+ await act(() => root.render(<Component />));
274
275
// Updates are async by default
276
instance.push('A');
271
- expect(ops).toEqual([]);
277
+ assertLog([]);
278
expect(container.textContent).toEqual('');
279
280
ReactDOM.flushSync(() => {
@@ -276,39 +282,32 @@ describe('ReactDOMFiberAsync', () => {
282
instance.push('C');
283
// Not flushed yet
284
expect(container.textContent).toEqual('');
279
- expect(ops).toEqual([]);
285
+ assertLog([]);
286
});
287
// Only the active updates have flushed
288
if (gate(flags => flags.enableUnifiedSyncLane)) {
289
expect(container.textContent).toEqual('ABC');
284
- expect(ops).toEqual(['ABC']);
290
+ assertLog(['ABC']);
291
} else {
292
expect(container.textContent).toEqual('BC');
287
- expect(ops).toEqual(['BC']);
293
+ assertLog(['BC']);
294
}
295
290
- if (gate(flags => flags.enableUnifiedSyncLane)) {
291
- instance.push('D');
292
- expect(container.textContent).toEqual('ABC');
293
- expect(ops).toEqual(['ABC']);
294
- } else {
296
+ await act(() => {
297
instance.push('D');
296
- expect(container.textContent).toEqual('BC');
297
- expect(ops).toEqual(['BC']);
298
- }
299
-
300
- // Flush the async updates
301
- Scheduler.unstable_flushAll();
298
+ if (gate(flags => flags.enableUnifiedSyncLane)) {
299
+ expect(container.textContent).toEqual('ABC');
300
+ } else {
301
+ expect(container.textContent).toEqual('BC');
302
+ }
303
+ assertLog([]);
304
+ });
305
+ assertLog(['ABCD']);
306
expect(container.textContent).toEqual('ABCD');
303
- if (gate(flags => flags.enableUnifiedSyncLane)) {
304
- expect(ops).toEqual(['ABC', 'ABCD']);
305
- } else {
306
- expect(ops).toEqual(['BC', 'ABCD']);
307
- }
307
});
308
309
// @gate www
311
- it('flushControlled flushes updates before yielding to browser', () => {
310
+ it('flushControlled flushes updates before yielding to browser', async () => {
311
let inst;
312
class Counter extends React.Component {
313
state = {counter: 0};
@@ -320,14 +319,14 @@ describe('ReactDOMFiberAsync', () => {
319
}
320
}
321
const root = ReactDOMClient.createRoot(container);
323
- root.render(<Counter />);
324
- Scheduler.unstable_flushAll();
322
+ await act(() => root.render(<Counter />));
323
expect(container.textContent).toEqual('0');
324
325
// Test that a normal update is async
328
- inst.increment();
329
- expect(container.textContent).toEqual('0');
330
- Scheduler.unstable_flushAll();
326
+ await act(() => {
327
+ inst.increment();
328
+ expect(container.textContent).toEqual('0');
329
+ });
330
expect(container.textContent).toEqual('1');
331
332
const ops = [];
@@ -566,7 +565,7 @@ describe('ReactDOMFiberAsync', () => {
565
});
566
});
567
569
- it('regression test: does not drop passive effects across roots (#17066)', () => {
568
+ it('regression test: does not drop passive effects across roots (#17066)', async () => {
569
const {useState, useEffect} = React;
570
571
function App({label}) {
@@ -585,11 +584,11 @@ describe('ReactDOMFiberAsync', () => {
584
const containerB = document.createElement('div');
585
const containerC = document.createElement('div');
586
588
- ReactDOM.render(<App label="A" />, containerA);
589
- ReactDOM.render(<App label="B" />, containerB);
590
- ReactDOM.render(<App label="C" />, containerC);
591
-
592
- Scheduler.unstable_flushAll();
587
+ await act(() => {
588
+ ReactDOM.render(<App label="A" />, containerA);
589
+ ReactDOM.render(<App label="B" />, containerB);
590
+ ReactDOM.render(<App label="C" />, containerC);
591
+ });
592
593
expect(containerA.textContent).toEqual('Finished');
594
expect(containerB.textContent).toEqual('Finished');
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+21
-21
@@ -555,7 +555,7 @@ describe('ReactDOMFizzServer', () => {
555
expect(loggedErrors).toEqual([]);
556
expect(bootstrapped).toBe(true);
557
558
- Scheduler.unstable_flushAll();
558
+ await waitForAll([]);
559
560
// We're still loading because we're waiting for the server to stream more content.
561
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
@@ -677,7 +677,7 @@ describe('ReactDOMFizzServer', () => {
677
errors.push({error, errorInfo});
678
},
679
});
680
- Scheduler.unstable_flushAll();
680
+ await waitForAll([]);
681
682
// We're still loading because we're waiting for the server to stream more content.
683
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
@@ -773,7 +773,7 @@ describe('ReactDOMFizzServer', () => {
773
errors.push({error, errorInfo});
774
},
775
});
776
- Scheduler.unstable_flushAll();
776
+ await waitForAll([]);
777
778
expect(getVisibleChildren(container)).toEqual(<div>Hello World</div>);
779
@@ -841,7 +841,7 @@ describe('ReactDOMFizzServer', () => {
841
errors.push({error, errorInfo});
842
},
843
});
844
- Scheduler.unstable_flushAll();
844
+ await waitForAll([]);
845
846
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
847
@@ -931,7 +931,7 @@ describe('ReactDOMFizzServer', () => {
931
expect(bootstrapped).toBe(true);
932
933
// Attempt to hydrate the content.
934
- Scheduler.unstable_flushAll();
934
+ await waitForAll([]);
935
936
// We're still loading because we're waiting for the server to stream more content.
937
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
@@ -952,7 +952,7 @@ describe('ReactDOMFizzServer', () => {
952
// But it is not yet hydrated.
953
expect(ref.current).toBe(null);
954
955
- Scheduler.unstable_flushAll();
955
+ await waitForAll([]);
956
957
// Now it's hydrated.
958
expect(ref.current).toBe(h1);
@@ -1011,7 +1011,7 @@ describe('ReactDOMFizzServer', () => {
1011
1012
// Attempt to hydrate the content.
1013
ReactDOMClient.hydrateRoot(container, <App />);
1014
- Scheduler.unstable_flushAll();
1014
+ await waitForAll([]);
1015
1016
// We're still loading because we're waiting for the server to stream more content.
1017
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
@@ -1029,7 +1029,7 @@ describe('ReactDOMFizzServer', () => {
1029
expect(ref.current).toBe(null);
1030
1031
// Flush the hydration.
1032
- Scheduler.unstable_flushAll();
1032
+ await waitForAll([]);
1033
1034
// Hydrating should've generated an error and replaced the suspense boundary.
1035
expect(getVisibleChildren(container)).toEqual(<b>Error Message</b>);
@@ -1082,7 +1082,7 @@ describe('ReactDOMFizzServer', () => {
1082
container,
1083
<App showMore={false} />,
1084
);
1085
- Scheduler.unstable_flushAll();
1085
+ await waitForAll([]);
1086
1087
// We're not hydrated yet.
1088
expect(ref.current).toBe(null);
@@ -1095,7 +1095,7 @@ describe('ReactDOMFizzServer', () => {
1095
1096
// Add more rows before we've hydrated the first two.
1097
root.render(<App showMore={true} />);
1098
- Scheduler.unstable_flushAll();
1098
+ await waitForAll([]);
1099
1100
// We're not hydrated yet.
1101
expect(ref.current).toBe(null);
@@ -1113,7 +1113,7 @@ describe('ReactDOMFizzServer', () => {
1113
await resolveText('A');
1114
});
1115
1116
- Scheduler.unstable_flushAll();
1116
+ await waitForAll([]);
1117
1118
expect(getVisibleChildren(container)).toEqual([
1119
<span>A</span>,
@@ -1160,7 +1160,7 @@ describe('ReactDOMFizzServer', () => {
1160
errors.push({error, errorInfo});
1161
},
1162
});
1163
- Scheduler.unstable_flushAll();
1163
+ await waitForAll([]);
1164
1165
// We're still loading because we're waiting for the server to stream more content.
1166
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
@@ -1192,7 +1192,7 @@ describe('ReactDOMFizzServer', () => {
1192
1193
// We now resolve it on the client.
1194
resolveText('Hello');
1195
- Scheduler.unstable_flushAll();
1195
+ await waitForAll([]);
1196
1197
// The client rendered HTML is now in place.
1198
expect(getVisibleChildren(container)).toEqual(
@@ -1866,7 +1866,7 @@ describe('ReactDOMFizzServer', () => {
1866
errors.push({error, errorInfo});
1867
},
1868
});
1869
- Scheduler.unstable_flushAll();
1869
+ await waitForAll([]);
1870
1871
// We're still loading because we're waiting for the server to stream more content.
1872
expect(getVisibleChildren(container)).toEqual('Loading root...');
@@ -1881,7 +1881,7 @@ describe('ReactDOMFizzServer', () => {
1881
expect(loggedErrors).toEqual([theError]);
1882
1883
// We still can't render it on the client because we haven't unblocked the parent.
1884
- Scheduler.unstable_flushAll();
1884
+ await waitForAll([]);
1885
expect(getVisibleChildren(container)).toEqual('Loading root...');
1886
1887
// Unblock the loading state
@@ -2067,7 +2067,7 @@ describe('ReactDOMFizzServer', () => {
2067
let root;
2068
await act(async () => {
2069
root = ReactDOMClient.hydrateRoot(container, <App isClient={false} />);
2070
- Scheduler.unstable_flushAll();
2070
+ await waitForAll([]);
2071
await jest.runAllTimers();
2072
});
2073
@@ -2085,7 +2085,7 @@ describe('ReactDOMFizzServer', () => {
2085
await act(async () => {
2086
// Trigger update by changing isClient to true
2087
root.render(<App isClient={true} />);
2088
- Scheduler.unstable_flushAll();
2088
+ await waitForAll([]);
2089
await jest.runAllTimers();
2090
});
2091
@@ -2664,7 +2664,7 @@ describe('ReactDOMFizzServer', () => {
2664
React.startTransition(() => {
2665
root.render(<App color="blue" />);
2666
});
2667
- Scheduler.unstable_flushAll();
2667
+ await waitForAll([]);
2668
jest.runAllTimers();
2669
const clientFallback2 = container.getElementsByTagName('p')[0];
2670
expect(clientFallback2).toBe(serverFallback);
@@ -2767,7 +2767,7 @@ describe('ReactDOMFizzServer', () => {
2767
// However, an update may have changed the fallback props. In that case we have to
2768
// actually force it to re-render on the client and throw away the server one.
2769
root.render(<App fallbackText="More loading..." />);
2770
- Scheduler.unstable_flushAll();
2770
+ await waitForAll([]);
2771
jest.runAllTimers();
2772
assertLog([
2773
'[c!] The server could not finish this Suspense boundary, ' +
@@ -3717,7 +3717,7 @@ describe('ReactDOMFizzServer', () => {
3717
Scheduler.log('Logged recoverable error: ' + error.message);
3718
},
3719
});
3720
- Scheduler.unstable_flushAll();
3720
+ await waitForAll([]);
3721
3722
expect(getVisibleChildren(container)).toEqual(
3723
<div>
@@ -3794,7 +3794,7 @@ describe('ReactDOMFizzServer', () => {
3794
Scheduler.log('Logged recoverable error: ' + error.message);
3795
},
3796
});
3797
- Scheduler.unstable_flushAll();
3797
+ await waitForAll([]);
3798
3799
expect(getVisibleChildren(container)).toEqual(
3800
<div>
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+6
-6
@@ -12,8 +12,8 @@
12
let React;
13
let ReactDOM;
14
let ReactDOMClient;
15
-let Scheduler;
15
let act;
16
+let waitForAll;
17
18
describe('ReactDOMHooks', () => {
19
let container;
@@ -24,8 +24,8 @@ describe('ReactDOMHooks', () => {
24
React = require('react');
25
ReactDOM = require('react-dom');
26
ReactDOMClient = require('react-dom/client');
27
- Scheduler = require('scheduler');
27
act = require('internal-test-utils').act;
28
+ waitForAll = require('internal-test-utils').waitForAll;
29
30
container = document.createElement('div');
31
document.body.appendChild(container);
@@ -35,7 +35,7 @@ describe('ReactDOMHooks', () => {
35
document.body.removeChild(container);
36
});
37
38
- it('can ReactDOM.render() from useEffect', () => {
38
+ it('can ReactDOM.render() from useEffect', async () => {
39
const container2 = document.createElement('div');
40
const container3 = document.createElement('div');
41
@@ -61,7 +61,7 @@ describe('ReactDOMHooks', () => {
61
expect(container.textContent).toBe('1');
62
expect(container2.textContent).toBe('');
63
expect(container3.textContent).toBe('');
64
- Scheduler.unstable_flushAll();
64
+ await waitForAll([]);
65
expect(container.textContent).toBe('1');
66
expect(container2.textContent).toBe('2');
67
expect(container3.textContent).toBe('3');
@@ -70,7 +70,7 @@ describe('ReactDOMHooks', () => {
70
expect(container.textContent).toBe('2');
71
expect(container2.textContent).toBe('2'); // Not flushed yet
72
expect(container3.textContent).toBe('3'); // Not flushed yet
73
- Scheduler.unstable_flushAll();
73
+ await waitForAll([]);
74
expect(container.textContent).toBe('2');
75
expect(container2.textContent).toBe('4');
76
expect(container3.textContent).toBe('6');
@@ -132,7 +132,7 @@ describe('ReactDOMHooks', () => {
132
const root = ReactDOMClient.createRoot(container);
133
root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
134
135
- Scheduler.unstable_flushAll();
135
+ await waitForAll([]);
136
137
inputRef.current.value = 'abc';
138
await act(() => {
packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
+47
-51
@@ -82,13 +82,13 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
82
expect(disableButton.tagName).toBe('BUTTON');
83
84
// Dispatch a click event on the Disable-button.
85
- const firstEvent = document.createEvent('Event');
86
- firstEvent.initEvent('click', true, true);
87
- dispatchAndSetCurrentEvent(disableButton, firstEvent);
88
-
85
+ await act(async () => {
86
+ const firstEvent = document.createEvent('Event');
87
+ firstEvent.initEvent('click', true, true);
88
+ dispatchAndSetCurrentEvent(disableButton, firstEvent);
89
+ });
90
// Discrete events should be flushed in a microtask.
91
// Verify that the second button was removed.
91
- await null;
92
expect(submitButtonRef.current).toBe(null);
93
// We'll assume that the browser won't let the user click it.
94
});
@@ -130,9 +130,8 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
130
}
131
132
const root = ReactDOMClient.createRoot(container);
133
- root.render(<Form />);
133
// Flush
135
- Scheduler.unstable_flushAll();
134
+ await act(() => root.render(<Form />));
135
136
const disableButton = disableButtonRef.current;
137
expect(disableButton.tagName).toBe('BUTTON');
@@ -140,20 +139,22 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
139
// Dispatch a click event on the Disable-button.
140
const firstEvent = document.createEvent('Event');
141
firstEvent.initEvent('click', true, true);
143
- dispatchAndSetCurrentEvent(disableButton, firstEvent);
142
+ await act(() => {
143
+ dispatchAndSetCurrentEvent(disableButton, firstEvent);
144
145
- // There should now be a pending update to disable the form.
146
- // This should not have flushed yet since it's in concurrent mode.
147
- const submitButton = submitButtonRef.current;
148
- expect(submitButton.tagName).toBe('BUTTON');
145
+ // There should now be a pending update to disable the form.
146
+ // This should not have flushed yet since it's in concurrent mode.
147
+ const submitButton = submitButtonRef.current;
148
+ expect(submitButton.tagName).toBe('BUTTON');
149
150
- // Discrete events should be flushed in a microtask.
151
- await null;
150
+ // Flush the discrete event
151
+ ReactDOM.flushSync();
152
153
- // Now let's dispatch an event on the submit button.
154
- const secondEvent = document.createEvent('Event');
155
- secondEvent.initEvent('click', true, true);
156
- dispatchAndSetCurrentEvent(submitButton, secondEvent);
153
+ // Now let's dispatch an event on the submit button.
154
+ const secondEvent = document.createEvent('Event');
155
+ secondEvent.initEvent('click', true, true);
156
+ dispatchAndSetCurrentEvent(submitButton, secondEvent);
157
+ });
158
159
// Therefore the form should never have been submitted.
160
expect(formSubmitted).toBe(false);
@@ -190,30 +191,30 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
191
}
192
193
const root = ReactDOMClient.createRoot(container);
193
- root.render(<Form />);
194
- // Flush
195
- Scheduler.unstable_flushAll();
194
+ await act(() => root.render(<Form />));
195
196
const enableButton = enableButtonRef.current;
197
expect(enableButton.tagName).toBe('BUTTON');
198
199
// Dispatch a click event on the Enable-button.
201
- const firstEvent = document.createEvent('Event');
202
- firstEvent.initEvent('click', true, true);
203
- dispatchAndSetCurrentEvent(enableButton, firstEvent);
200
+ await act(() => {
201
+ const firstEvent = document.createEvent('Event');
202
+ firstEvent.initEvent('click', true, true);
203
+ dispatchAndSetCurrentEvent(enableButton, firstEvent);
204
205
- // There should now be a pending update to enable the form.
206
- // This should not have flushed yet since it's in concurrent mode.
207
- const submitButton = submitButtonRef.current;
208
- expect(submitButton.tagName).toBe('BUTTON');
205
+ // There should now be a pending update to enable the form.
206
+ // This should not have flushed yet since it's in concurrent mode.
207
+ const submitButton = submitButtonRef.current;
208
+ expect(submitButton.tagName).toBe('BUTTON');
209
210
- // Discrete events should be flushed in a microtask.
211
- await null;
210
+ // Flush discrete updates
211
+ ReactDOM.flushSync();
212
213
- // Now let's dispatch an event on the submit button.
214
- const secondEvent = document.createEvent('Event');
215
- secondEvent.initEvent('click', true, true);
216
- dispatchAndSetCurrentEvent(submitButton, secondEvent);
213
+ // Now let's dispatch an event on the submit button.
214
+ const secondEvent = document.createEvent('Event');
215
+ secondEvent.initEvent('click', true, true);
216
+ dispatchAndSetCurrentEvent(submitButton, secondEvent);
217
+ });
218
219
// Therefore the form should have been submitted.
220
expect(formSubmitted).toBe(true);
@@ -342,12 +343,11 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
343
});
344
expect(container.textContent).toEqual('Count: 0');
345
345
- const pressEvent = document.createEvent('Event');
346
- pressEvent.initEvent('click', true, true);
347
- dispatchAndSetCurrentEvent(target.current, pressEvent);
348
- // Intentionally not using `act` so we can observe in between the press
349
- // event and the microtask, without batching.
350
- await null;
346
+ await act(async () => {
347
+ const pressEvent = document.createEvent('Event');
348
+ pressEvent.initEvent('click', true, true);
349
+ dispatchAndSetCurrentEvent(target.current, pressEvent);
350
+ });
351
// If this is 2, that means the `setCount` calls were not batched.
352
expect(container.textContent).toEqual('Count: 1');
353
});
@@ -383,17 +383,13 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
383
});
384
expect(container.textContent).toEqual('Count: 0');
385
386
- const pressEvent = document.createEvent('Event');
387
- pressEvent.initEvent('click', true, true);
388
- dispatchAndSetCurrentEvent(target, pressEvent);
389
-
390
- assertLog(['Count: 0 [after batchedUpdates]']);
391
- expect(container.textContent).toEqual('Count: 0');
392
-
393
- // Intentionally not using `act` so we can observe in between the click
394
- // event and the microtask, without batching.
395
- await null;
396
-
386
+ await act(async () => {
387
+ const pressEvent = document.createEvent('Event');
388
+ pressEvent.initEvent('click', true, true);
389
+ dispatchAndSetCurrentEvent(target, pressEvent);
390
+ assertLog(['Count: 0 [after batchedUpdates]']);
391
+ expect(container.textContent).toEqual('Count: 0');
392
+ });
393
expect(container.textContent).toEqual('Count: 1');
394
});
395
});
packages/react-dom/src/__tests__/ReactDOMRoot-test.js
+34
-30
@@ -18,6 +18,7 @@ let act;
18
let useEffect;
19
let assertLog;
20
let waitFor;
21
+let waitForAll;
22
23
describe('ReactDOMRoot', () => {
24
let container;
@@ -36,12 +37,13 @@ describe('ReactDOMRoot', () => {
37
const InternalTestUtils = require('internal-test-utils');
38
assertLog = InternalTestUtils.assertLog;
39
waitFor = InternalTestUtils.waitFor;
40
+ waitForAll = InternalTestUtils.waitForAll;
41
});
42
41
- it('renders children', () => {
43
+ it('renders children', async () => {
44
const root = ReactDOMClient.createRoot(container);
45
root.render(<div>Hi</div>);
44
- Scheduler.unstable_flushAll();
46
+ await waitForAll([]);
47
expect(container.textContent).toEqual('Hi');
48
});
49
@@ -65,7 +67,7 @@ describe('ReactDOMRoot', () => {
67
);
68
});
69
68
- it('warns if a callback parameter is provided to render', () => {
70
+ it('warns if a callback parameter is provided to render', async () => {
71
const callback = jest.fn();
72
const root = ReactDOMClient.createRoot(container);
73
expect(() => root.render(<div>Hi</div>, callback)).toErrorDev(
@@ -73,7 +75,7 @@ describe('ReactDOMRoot', () => {
75
'To execute a side effect after rendering, declare it in a component body with useEffect().',
76
{withoutStack: true},
77
);
76
- Scheduler.unstable_flushAll();
78
+ await waitForAll([]);
79
expect(callback).not.toHaveBeenCalled();
80
});
81
@@ -108,7 +110,7 @@ describe('ReactDOMRoot', () => {
110
);
111
});
112
111
- it('warns if a callback parameter is provided to unmount', () => {
113
+ it('warns if a callback parameter is provided to unmount', async () => {
114
const callback = jest.fn();
115
const root = ReactDOMClient.createRoot(container);
116
root.render(<div>Hi</div>);
@@ -117,17 +119,17 @@ describe('ReactDOMRoot', () => {
119
'To execute a side effect after rendering, declare it in a component body with useEffect().',
120
{withoutStack: true},
121
);
120
- Scheduler.unstable_flushAll();
122
+ await waitForAll([]);
123
expect(callback).not.toHaveBeenCalled();
124
});
125
124
- it('unmounts children', () => {
126
+ it('unmounts children', async () => {
127
const root = ReactDOMClient.createRoot(container);
128
root.render(<div>Hi</div>);
127
- Scheduler.unstable_flushAll();
129
+ await waitForAll([]);
130
expect(container.textContent).toEqual('Hi');
131
root.unmount();
130
- Scheduler.unstable_flushAll();
132
+ await waitForAll([]);
133
expect(container.textContent).toEqual('');
134
});
135
@@ -151,7 +153,7 @@ describe('ReactDOMRoot', () => {
153
<span />
154
</div>,
155
);
154
- Scheduler.unstable_flushAll();
156
+ await waitForAll([]);
157
158
const container2 = document.createElement('div');
159
container2.innerHTML = markup;
@@ -161,7 +163,9 @@ describe('ReactDOMRoot', () => {
163
<span />
164
</div>,
165
);
164
- expect(() => Scheduler.unstable_flushAll()).toErrorDev('Extra attributes');
166
+ await expect(async () => await waitForAll([])).toErrorDev(
167
+ 'Extra attributes',
168
+ );
169
});
170
171
it('clears existing children with legacy API', async () => {
@@ -181,7 +185,7 @@ describe('ReactDOMRoot', () => {
185
</div>,
186
container,
187
);
184
- Scheduler.unstable_flushAll();
188
+ await waitForAll([]);
189
expect(container.textContent).toEqual('dc');
190
});
191
@@ -194,7 +198,7 @@ describe('ReactDOMRoot', () => {
198
<span>d</span>
199
</div>,
200
);
197
- Scheduler.unstable_flushAll();
201
+ await waitForAll([]);
202
expect(container.textContent).toEqual('cd');
203
root.render(
204
<div>
@@ -202,7 +206,7 @@ describe('ReactDOMRoot', () => {
206
<span>c</span>
207
</div>,
208
);
205
- Scheduler.unstable_flushAll();
209
+ await waitForAll([]);
210
expect(container.textContent).toEqual('dc');
211
});
212
@@ -212,10 +216,10 @@ describe('ReactDOMRoot', () => {
216
}).toThrow('createRoot(...): Target container is not a DOM element.');
217
});
218
215
- it('warns when rendering with legacy API into createRoot() container', () => {
219
+ it('warns when rendering with legacy API into createRoot() container', async () => {
220
const root = ReactDOMClient.createRoot(container);
221
root.render(<div>Hi</div>);
218
- Scheduler.unstable_flushAll();
222
+ await waitForAll([]);
223
expect(container.textContent).toEqual('Hi');
224
expect(() => {
225
ReactDOM.render(<div>Bye</div>, container);
@@ -230,15 +234,15 @@ describe('ReactDOMRoot', () => {
234
],
235
{withoutStack: true},
236
);
233
- Scheduler.unstable_flushAll();
237
+ await waitForAll([]);
238
// This works now but we could disallow it:
239
expect(container.textContent).toEqual('Bye');
240
});
241
238
- it('warns when hydrating with legacy API into createRoot() container', () => {
242
+ it('warns when hydrating with legacy API into createRoot() container', async () => {
243
const root = ReactDOMClient.createRoot(container);
244
root.render(<div>Hi</div>);
241
- Scheduler.unstable_flushAll();
245
+ await waitForAll([]);
246
expect(container.textContent).toEqual('Hi');
247
expect(() => {
248
ReactDOM.hydrate(<div>Hi</div>, container);
@@ -264,10 +268,10 @@ describe('ReactDOMRoot', () => {
268
assertLog(['callback']);
269
});
270
267
- it('warns when unmounting with legacy API (no previous content)', () => {
271
+ it('warns when unmounting with legacy API (no previous content)', async () => {
272
const root = ReactDOMClient.createRoot(container);
273
root.render(<div>Hi</div>);
270
- Scheduler.unstable_flushAll();
274
+ await waitForAll([]);
275
expect(container.textContent).toEqual('Hi');
276
let unmounted = false;
277
expect(() => {
@@ -283,20 +287,20 @@ describe('ReactDOMRoot', () => {
287
{withoutStack: true},
288
);
289
expect(unmounted).toBe(false);
286
- Scheduler.unstable_flushAll();
290
+ await waitForAll([]);
291
expect(container.textContent).toEqual('Hi');
292
root.unmount();
289
- Scheduler.unstable_flushAll();
293
+ await waitForAll([]);
294
expect(container.textContent).toEqual('');
295
});
296
293
- it('warns when unmounting with legacy API (has previous content)', () => {
297
+ it('warns when unmounting with legacy API (has previous content)', async () => {
298
// Currently createRoot().render() doesn't clear this.
299
container.appendChild(document.createElement('div'));
300
// The rest is the same as test above.
301
const root = ReactDOMClient.createRoot(container);
302
root.render(<div>Hi</div>);
299
- Scheduler.unstable_flushAll();
303
+ await waitForAll([]);
304
expect(container.textContent).toEqual('Hi');
305
let unmounted = false;
306
expect(() => {
@@ -310,10 +314,10 @@ describe('ReactDOMRoot', () => {
314
{withoutStack: true},
315
);
316
expect(unmounted).toBe(false);
313
- Scheduler.unstable_flushAll();
317
+ await waitForAll([]);
318
expect(container.textContent).toEqual('Hi');
319
root.unmount();
316
- Scheduler.unstable_flushAll();
320
+ await waitForAll([]);
321
expect(container.textContent).toEqual('');
322
});
323
@@ -340,10 +344,10 @@ describe('ReactDOMRoot', () => {
344
);
345
});
346
343
- it('does not warn when creating second root after first one is unmounted', () => {
347
+ it('does not warn when creating second root after first one is unmounted', async () => {
348
const root = ReactDOMClient.createRoot(container);
349
root.unmount();
346
- Scheduler.unstable_flushAll();
350
+ await waitForAll([]);
351
ReactDOMClient.createRoot(container); // No warning
352
});
353
@@ -368,7 +372,7 @@ describe('ReactDOMRoot', () => {
372
it('warns if updating a root that has had its contents removed', async () => {
373
const root = ReactDOMClient.createRoot(container);
374
root.render(<div>Hi</div>);
371
- Scheduler.unstable_flushAll();
375
+ await waitForAll([]);
376
container.innerHTML = '';
377
378
if (gate(flags => flags.enableFloat || flags.enableHostSingletons)) {
packages/react-dom/src/__tests__/ReactDOMServerPartialHydration-test.internal.js
+58
-104
@@ -180,8 +180,7 @@ describe('ReactDOMServerPartialHydration', () => {
180
// hydrating anyway.
181
suspend = true;
182
ReactDOMClient.hydrateRoot(container, <App />);
183
- Scheduler.unstable_flushAll();
184
- jest.runAllTimers();
183
+ await waitForAll([]);
184
185
expect(ref.current).toBe(null);
186
@@ -189,8 +188,7 @@ describe('ReactDOMServerPartialHydration', () => {
188
suspend = false;
189
resolve();
190
await promise;
192
- Scheduler.unstable_flushAll();
193
- jest.runAllTimers();
191
+ await waitForAll([]);
192
193
// We should now have hydrated with a ref on the existing span.
194
expect(ref.current).toBe(span);
@@ -237,8 +235,7 @@ describe('ReactDOMServerPartialHydration', () => {
235
Scheduler.log(error.message);
236
},
237
});
240
- Scheduler.unstable_flushAll();
241
- jest.runAllTimers();
238
+ await waitForAll([]);
239
240
// Expect the server-generated HTML to stay intact.
241
expect(container.textContent).toBe('HelloHello');
@@ -247,8 +244,7 @@ describe('ReactDOMServerPartialHydration', () => {
244
suspend = false;
245
resolve();
246
await promise;
250
- Scheduler.unstable_flushAll();
251
- jest.runAllTimers();
247
+ await waitForAll([]);
248
// Hydration should not change anything.
249
expect(container.textContent).toBe('HelloHello');
250
});
@@ -478,8 +474,7 @@ describe('ReactDOMServerPartialHydration', () => {
474
container.innerHTML = finalHTML;
475
476
ReactDOMClient.hydrateRoot(container, <App />);
481
- Scheduler.unstable_flushAll();
482
- jest.runAllTimers();
477
+ await waitForAll([]);
478
479
expect(container.innerHTML).toContain('<div>Sibling</div>');
480
});
@@ -583,11 +578,9 @@ describe('ReactDOMServerPartialHydration', () => {
578
ReactDOMClient.hydrateRoot(container, <App hasB={false} />);
579
});
580
586
- resolve();
587
- await promise;
588
- Scheduler.unstable_flushAll();
589
- await null;
590
- jest.runAllTimers();
581
+ await act(() => {
582
+ resolve();
583
+ });
584
585
expect(container.innerHTML).toContain('<span>A</span>');
586
expect(container.innerHTML).not.toContain('<span>B</span>');
@@ -689,12 +682,13 @@ describe('ReactDOMServerPartialHydration', () => {
682
// On the client we don't have all data yet but we want to start
683
// hydrating anyway.
684
suspend = true;
692
- const root = ReactDOMClient.hydrateRoot(container, <App />, {
693
- onDeleted(node) {
694
- deleted.push(node);
695
- },
685
+ const root = await act(() => {
686
+ return ReactDOMClient.hydrateRoot(container, <App />, {
687
+ onDeleted(node) {
688
+ deleted.push(node);
689
+ },
690
+ });
691
});
697
- Scheduler.unstable_flushAll();
692
693
expect(deleted.length).toBe(0);
694
@@ -775,11 +769,7 @@ describe('ReactDOMServerPartialHydration', () => {
769
770
// Resolving the promise should render the final content.
771
suspend = false;
778
- resolve();
779
- await promise;
780
- Scheduler.unstable_flushAll();
781
- await null;
782
- jest.runAllTimers();
772
+ await act(() => resolve());
773
774
// We should now have hydrated with a ref on the existing span.
775
expect(container.textContent).toBe('Hello');
@@ -939,8 +929,7 @@ describe('ReactDOMServerPartialHydration', () => {
929
container,
930
<App text="Hello" className="hello" />,
931
);
942
- Scheduler.unstable_flushAll();
943
- jest.runAllTimers();
932
+ await waitForAll([]);
933
934
expect(ref.current).toBe(null);
935
expect(span.textContent).toBe('Hello');
@@ -1012,8 +1001,7 @@ describe('ReactDOMServerPartialHydration', () => {
1001
container,
1002
<App text="Hello" className="hello" />,
1003
);
1015
- Scheduler.unstable_flushAll();
1016
- jest.runAllTimers();
1004
+ await waitForAll([]);
1005
1006
expect(ref.current).toBe(null);
1007
expect(span.textContent).toBe('Hello');
@@ -1029,8 +1017,7 @@ describe('ReactDOMServerPartialHydration', () => {
1017
await promise;
1018
1019
// This should first complete the hydration and then flush the update onto the hydrated state.
1032
- Scheduler.unstable_flushAll();
1033
- jest.runAllTimers();
1020
+ await waitForAll([]);
1021
1022
// The new span should be the same since we should have successfully hydrated
1023
// before changing it.
@@ -1090,8 +1077,7 @@ describe('ReactDOMServerPartialHydration', () => {
1077
},
1078
},
1079
);
1093
- Scheduler.unstable_flushAll();
1094
- jest.runAllTimers();
1080
+ await waitForAll([]);
1081
1082
expect(ref.current).toBe(null);
1083
@@ -1169,8 +1155,7 @@ describe('ReactDOMServerPartialHydration', () => {
1155
},
1156
},
1157
);
1172
- Scheduler.unstable_flushAll();
1173
- jest.runAllTimers();
1158
+ await waitForAll([]);
1159
1160
const span = container.getElementsByTagName('span')[0];
1161
expect(ref.current).toBe(span);
@@ -1246,8 +1231,7 @@ describe('ReactDOMServerPartialHydration', () => {
1231
},
1232
},
1233
);
1249
- Scheduler.unstable_flushAll();
1250
- jest.runAllTimers();
1234
+ await waitForAll([]);
1235
1236
expect(ref.current).toBe(null);
1237
@@ -1268,8 +1252,7 @@ describe('ReactDOMServerPartialHydration', () => {
1252
await promise;
1253
});
1254
1271
- Scheduler.unstable_flushAll();
1272
- jest.runAllTimers();
1255
+ await waitForAll([]);
1256
1257
const span = container.getElementsByTagName('span')[0];
1258
expect(span.textContent).toBe('Hi');
@@ -1320,29 +1303,23 @@ describe('ReactDOMServerPartialHydration', () => {
1303
container,
1304
<App text="Hello" className="hello" />,
1305
);
1323
- Scheduler.unstable_flushAll();
1324
- jest.runAllTimers();
1306
+ await waitForAll([]);
1307
1308
expect(ref.current).toBe(null);
1309
expect(container.textContent).toBe('Hello');
1310
1311
// Render an update with a long timeout.
1312
React.startTransition(() => root.render(<App text="Hi" className="hi" />));
1331
-
1313
// This shouldn't force the fallback yet.
1333
- Scheduler.unstable_flushAll();
1314
+ await waitForAll([]);
1315
1316
expect(ref.current).toBe(null);
1317
expect(container.textContent).toBe('Hello');
1318
1319
// Resolving the promise so that rendering can complete.
1339
- suspend = false;
1340
- resolve();
1341
- await promise;
1342
-
1320
// This should first complete the hydration and then flush the update onto the hydrated state.
1344
- Scheduler.unstable_flushAll();
1345
- jest.runAllTimers();
1321
+ suspend = false;
1322
+ await act(() => resolve());
1323
1324
// The new span should be the same since we should have successfully hydrated
1325
// before changing it.
@@ -1477,8 +1454,7 @@ describe('ReactDOMServerPartialHydration', () => {
1454
<App />
1455
</Context.Provider>,
1456
);
1480
- Scheduler.unstable_flushAll();
1481
- jest.runAllTimers();
1457
+ await waitForAll([]);
1458
1459
expect(ref.current).toBe(null);
1460
expect(span.textContent).toBe('Hello');
@@ -1563,8 +1539,7 @@ describe('ReactDOMServerPartialHydration', () => {
1539
},
1540
},
1541
);
1566
- Scheduler.unstable_flushAll();
1567
- jest.runAllTimers();
1542
+ await waitForAll([]);
1543
1544
expect(ref.current).toBe(null);
1545
@@ -1868,12 +1843,10 @@ describe('ReactDOMServerPartialHydration', () => {
1843
1844
const spanB = container.getElementsByTagName('span')[1];
1845
1871
- const root = ReactDOMClient.hydrateRoot(
1872
- container,
1873
- <App showMore={false} />,
1874
- );
1846
suspend = true;
1876
- Scheduler.unstable_flushAll();
1847
+ const root = await act(() =>
1848
+ ReactDOMClient.hydrateRoot(container, <App showMore={false} />),
1849
+ );
1850
1851
// We're not hydrated yet.
1852
expect(ref.current).toBe(null);
@@ -1951,13 +1924,10 @@ describe('ReactDOMServerPartialHydration', () => {
1924
1925
const spanA = container.getElementsByTagName('span')[0];
1926
1954
- const root = ReactDOMClient.hydrateRoot(
1955
- container,
1956
- <App showMore={false} />,
1957
- );
1958
-
1927
suspend = true;
1960
- Scheduler.unstable_flushAll();
1928
+ const root = await act(() =>
1929
+ ReactDOMClient.hydrateRoot(container, <App showMore={false} />),
1930
+ );
1931
1932
// We're not hydrated yet.
1933
expect(ref.current).toBe(null);
@@ -2241,8 +2211,7 @@ describe('ReactDOMServerPartialHydration', () => {
2211
// On the client we don't have all data yet but we want to start
2212
// hydrating anyway.
2213
ReactDOMClient.hydrateRoot(container, <App />);
2244
- Scheduler.unstable_flushAll();
2245
- jest.runAllTimers();
2214
+ await waitForAll([]);
2215
2216
expect(ref1.current).toBe(span1);
2217
expect(ref2.current).toBe(span2);
@@ -2303,8 +2272,7 @@ describe('ReactDOMServerPartialHydration', () => {
2272
},
2273
},
2274
);
2306
- Scheduler.unstable_flushAll();
2307
- jest.runAllTimers();
2275
+ await waitForAll([]);
2276
2277
expect(ref.current).toBe(null);
2278
expect(span.textContent).toBe('Hello');
@@ -2389,8 +2357,7 @@ describe('ReactDOMServerPartialHydration', () => {
2357
// hydrating anyway.
2358
suspend = true;
2359
ReactDOMClient.hydrateRoot(container, <App />);
2392
- Scheduler.unstable_flushAll();
2393
- jest.runAllTimers();
2360
+ await waitForAll([]);
2361
2362
expect(container.textContent).toBe('Click meHello');
2363
@@ -2482,8 +2449,7 @@ describe('ReactDOMServerPartialHydration', () => {
2449
// This should be delayed.
2450
expect(onEvent).toHaveBeenCalledTimes(0);
2451
2485
- Scheduler.unstable_flushAll();
2486
- jest.runAllTimers();
2452
+ await waitForAll([]);
2453
2454
// We're now partially hydrated.
2455
await act(() => {
@@ -2574,8 +2540,7 @@ describe('ReactDOMServerPartialHydration', () => {
2540
// This should be delayed.
2541
expect(clicks).toBe(0);
2542
2577
- Scheduler.unstable_flushAll();
2578
- jest.runAllTimers();
2543
+ await waitForAll([]);
2544
2545
// We're now partially hydrated.
2546
await act(() => {
@@ -2667,8 +2632,7 @@ describe('ReactDOMServerPartialHydration', () => {
2632
// This should be delayed.
2633
expect(onEvent).toHaveBeenCalledTimes(0);
2634
2670
- Scheduler.unstable_flushAll();
2671
- jest.runAllTimers();
2635
+ await waitForAll([]);
2636
2637
// We're now partially hydrated.
2638
await act(() => {
@@ -2748,8 +2712,7 @@ describe('ReactDOMServerPartialHydration', () => {
2712
// hydrating anyway.
2713
suspend = true;
2714
ReactDOMClient.hydrateRoot(container, <App />);
2751
- Scheduler.unstable_flushAll();
2752
- jest.runAllTimers();
2715
+ await waitForAll([]);
2716
2717
// We're now partially hydrated.
2718
await act(() => {
@@ -2823,8 +2786,7 @@ describe('ReactDOMServerPartialHydration', () => {
2786
// We're going to use a different root as a parent.
2787
// This lets us detect whether an event goes through React's event system.
2788
const parentRoot = ReactDOMClient.createRoot(parentContainer);
2826
- parentRoot.render(<Parent />);
2827
- Scheduler.unstable_flushAll();
2789
+ await act(() => parentRoot.render(<Parent />));
2790
2791
childSlotRef.current.appendChild(childContainer);
2792
@@ -2835,9 +2797,7 @@ describe('ReactDOMServerPartialHydration', () => {
2797
suspend = true;
2798
2799
// Hydrate asynchronously.
2838
- ReactDOMClient.hydrateRoot(childContainer, <App />);
2839
- jest.runAllTimers();
2840
- Scheduler.unstable_flushAll();
2800
+ await act(() => ReactDOMClient.hydrateRoot(childContainer, <App />));
2801
2802
// The Suspense boundary is not yet hydrated.
2803
await act(() => {
@@ -2935,8 +2895,7 @@ describe('ReactDOMServerPartialHydration', () => {
2895
suspend2 = true;
2896
ReactDOMClient.hydrateRoot(container, <App />);
2897
2938
- Scheduler.unstable_flushAll();
2939
- jest.runAllTimers();
2898
+ await waitForAll([]);
2899
2900
dispatchMouseEvent(appDiv, null);
2901
dispatchMouseEvent(firstSpan, appDiv);
@@ -2950,8 +2909,7 @@ describe('ReactDOMServerPartialHydration', () => {
2909
resolve2();
2910
await promise2;
2911
2953
- Scheduler.unstable_flushAll();
2954
- jest.runAllTimers();
2912
+ await waitForAll([]);
2913
2914
// We've unblocked the current hover target so we should be
2915
// able to replay it now.
@@ -2962,8 +2920,7 @@ describe('ReactDOMServerPartialHydration', () => {
2920
resolve1();
2921
await promise1;
2922
2965
- Scheduler.unstable_flushAll();
2966
- jest.runAllTimers();
2923
+ await waitForAll([]);
2924
2925
expect(ops).toEqual(['Mouse Enter Second']);
2926
@@ -3109,8 +3066,7 @@ describe('ReactDOMServerPartialHydration', () => {
3066
// hydrating anyway.
3067
suspend = true;
3068
ReactDOMClient.hydrateRoot(container, <App />);
3112
- Scheduler.unstable_flushAll();
3113
- jest.runAllTimers();
3069
+ await waitForAll([]);
3070
3071
expect(container.textContent).toBe('Click meHello');
3072
@@ -3173,13 +3129,14 @@ describe('ReactDOMServerPartialHydration', () => {
3129
const span = container.getElementsByTagName('span')[0];
3130
expect(span.innerHTML).toBe('Hidden child');
3131
3176
- ReactDOMClient.hydrateRoot(container, <App />, {
3177
- onRecoverableError(error) {
3178
- Scheduler.log('Log recoverable error: ' + error.message);
3179
- },
3180
- });
3132
+ await act(() =>
3133
+ ReactDOMClient.hydrateRoot(container, <App />, {
3134
+ onRecoverableError(error) {
3135
+ Scheduler.log('Log recoverable error: ' + error.message);
3136
+ },
3137
+ }),
3138
+ );
3139
3182
- Scheduler.unstable_flushAll();
3140
expect(ref.current).toBe(span);
3141
expect(span.innerHTML).toBe('Hidden child');
3142
});
@@ -3206,8 +3163,7 @@ describe('ReactDOMServerPartialHydration', () => {
3163
const span = container.getElementsByTagName('span')[0];
3164
expect(span.innerHTML).toBe('Hidden child');
3165
3209
- ReactDOMClient.hydrateRoot(container, <App />);
3210
- Scheduler.unstable_flushAll();
3166
+ await act(() => ReactDOMClient.hydrateRoot(container, <App />));
3167
expect(ref.current).toBe(span);
3168
expect(span.innerHTML).toBe('Hidden child');
3169
});
@@ -3231,8 +3187,7 @@ describe('ReactDOMServerPartialHydration', () => {
3187
3188
const span = container.getElementsByTagName('span')[0];
3189
3234
- ReactDOMClient.hydrateRoot(container, <App />);
3235
- Scheduler.unstable_flushAll();
3190
+ await act(() => ReactDOMClient.hydrateRoot(container, <App />));
3191
expect(ref.current).toBe(span);
3192
expect(ref.current.innerHTML).toBe('Hidden child');
3193
});
@@ -3335,14 +3290,13 @@ describe('ReactDOMServerPartialHydration', () => {
3290
});
3291
3292
function itHydratesWithoutMismatch(msg, App) {
3338
- it('hydrates without mismatch ' + msg, () => {
3293
+ it('hydrates without mismatch ' + msg, async () => {
3294
const container = document.createElement('div');
3295
document.body.appendChild(container);
3296
const finalHTML = ReactDOMServer.renderToString(<App />);
3297
container.innerHTML = finalHTML;
3298
3344
- ReactDOMClient.hydrateRoot(container, <App />);
3345
- Scheduler.unstable_flushAll();
3299
+ await act(() => ReactDOMClient.hydrateRoot(container, <App />));
3300
});
3301
}
3302
packages/react-dom/src/__tests__/ReactDOMServerSelectiveHydration-test.internal.js
+78
-81
@@ -763,18 +763,22 @@ describe('ReactDOMServerSelectiveHydration', () => {
763
764
// Nothing has been hydrated so far.
765
assertLog([]);
766
- // Click D
767
- dispatchMouseHoverEvent(spanD, null);
768
- dispatchClickEvent(spanD);
769
- // Hover over B and then C.
770
- dispatchMouseHoverEvent(spanB, spanD);
771
- dispatchMouseHoverEvent(spanC, spanB);
772
- assertLog(['App']);
773
- await act(async () => {
766
+
767
+ await act(() => {
768
+ // Click D
769
+ dispatchMouseHoverEvent(spanD, null);
770
+ dispatchClickEvent(spanD);
771
+
772
+ // Hover over B and then C.
773
+ dispatchMouseHoverEvent(spanB, spanD);
774
+ dispatchMouseHoverEvent(spanC, spanB);
775
+
776
+ assertLog(['App']);
777
+
778
suspend = false;
779
resolve();
776
- await promise;
780
});
781
+
782
if (
783
gate(
784
flags =>
@@ -914,19 +918,18 @@ describe('ReactDOMServerSelectiveHydration', () => {
918
// Nothing has been hydrated so far.
919
assertLog([]);
920
917
- // Click D
918
- dispatchMouseHoverEvent(spanD, null);
919
- dispatchClickEvent(spanD);
920
- // Hover over B and then C.
921
- dispatchMouseHoverEvent(spanB, spanD);
922
- dispatchMouseHoverEvent(spanC, spanB);
921
+ await act(async () => {
922
+ // Click D
923
+ dispatchMouseHoverEvent(spanD, null);
924
+ dispatchClickEvent(spanD);
925
+ // Hover over B and then C.
926
+ dispatchMouseHoverEvent(spanB, spanD);
927
+ dispatchMouseHoverEvent(spanC, spanB);
928
924
- assertLog(['App']);
929
+ assertLog(['App']);
930
926
- await act(async () => {
931
suspend = false;
932
resolve();
929
- await promise;
933
});
934
935
if (
@@ -998,18 +1001,24 @@ describe('ReactDOMServerSelectiveHydration', () => {
1001
let InnerScheduler;
1002
let innerDiv;
1003
1004
+ let OuterTestUtils;
1005
+ let InnerTestUtils;
1006
+
1007
beforeEach(async () => {
1008
document.body.innerHTML = '';
1009
jest.resetModules();
1010
let OuterReactDOMClient;
1011
let InnerReactDOMClient;
1012
+
1013
jest.isolateModules(() => {
1014
OuterReactDOMClient = require('react-dom/client');
1015
OuterScheduler = require('scheduler');
1016
+ OuterTestUtils = require('internal-test-utils');
1017
});
1018
jest.isolateModules(() => {
1019
InnerReactDOMClient = require('react-dom/client');
1020
InnerScheduler = require('scheduler');
1021
+ InnerTestUtils = require('internal-test-utils');
1022
});
1023
1024
expect(OuterReactDOMClient).not.toBe(InnerReactDOMClient);
@@ -1092,19 +1101,21 @@ describe('ReactDOMServerSelectiveHydration', () => {
1101
const innerHTML = ReactDOMServer.renderToString(<InnerApp />);
1102
innerContainer.innerHTML = innerHTML;
1103
1095
- expect(OuterScheduler.unstable_clearLog()).toEqual(['Outer']);
1096
- expect(InnerScheduler.unstable_clearLog()).toEqual(['Inner']);
1104
+ OuterTestUtils.assertLog(['Outer']);
1105
+ InnerTestUtils.assertLog(['Inner']);
1106
1107
suspendOuter = true;
1108
suspendInner = true;
1109
1101
- OuterReactDOMClient.hydrateRoot(outerContainer, <OuterApp />);
1102
- InnerReactDOMClient.hydrateRoot(innerContainer, <InnerApp />);
1110
+ await OuterTestUtils.act(() =>
1111
+ OuterReactDOMClient.hydrateRoot(outerContainer, <OuterApp />),
1112
+ );
1113
+ await InnerTestUtils.act(() =>
1114
+ InnerReactDOMClient.hydrateRoot(innerContainer, <InnerApp />),
1115
+ );
1116
1104
- OuterScheduler.unstable_flushAllWithoutAsserting();
1105
- InnerScheduler.unstable_flushAllWithoutAsserting();
1106
- expect(OuterScheduler.unstable_clearLog()).toEqual(['Suspend Outer']);
1107
- expect(InnerScheduler.unstable_clearLog()).toEqual(['Suspend Inner']);
1117
+ OuterTestUtils.assertLog(['Suspend Outer']);
1118
+ InnerTestUtils.assertLog(['Suspend Inner']);
1119
1120
innerDiv = document.querySelector('#inner');
1121
@@ -1117,7 +1128,7 @@ describe('ReactDOMServerSelectiveHydration', () => {
1128
InnerScheduler.unstable_flushAllWithoutAsserting();
1129
});
1130
1120
- expect(OuterScheduler.unstable_clearLog()).toEqual(['Suspend Outer']);
1131
+ OuterTestUtils.assertLog(['Suspend Outer']);
1132
if (
1133
gate(
1134
flags =>
@@ -1126,10 +1137,10 @@ describe('ReactDOMServerSelectiveHydration', () => {
1137
) {
1138
// InnerApp doesn't see the event because OuterApp calls stopPropagation in
1139
// capture phase since the event is blocked on suspended component
1129
- expect(InnerScheduler.unstable_clearLog()).toEqual([]);
1140
+ InnerTestUtils.assertLog([]);
1141
} else {
1142
// no stopPropagation
1132
- expect(InnerScheduler.unstable_clearLog()).toEqual(['Suspend Inner']);
1143
+ InnerTestUtils.assertLog(['Suspend Inner']);
1144
}
1145
1146
assertLog([]);
@@ -1142,51 +1153,39 @@ describe('ReactDOMServerSelectiveHydration', () => {
1153
it('Inner hydrates first then Outer', async () => {
1154
dispatchMouseHoverEvent(innerDiv);
1155
1145
- await act(async () => {
1146
- resolveInner();
1147
- await innerPromise;
1148
- jest.runAllTimers();
1149
- Scheduler.unstable_flushAllWithoutAsserting();
1150
- OuterScheduler.unstable_flushAllWithoutAsserting();
1151
- InnerScheduler.unstable_flushAllWithoutAsserting();
1156
+ await InnerTestUtils.act(async () => {
1157
+ await OuterTestUtils.act(() => {
1158
+ resolveInner();
1159
+ });
1160
});
1161
1154
- expect(OuterScheduler.unstable_clearLog()).toEqual(['Suspend Outer']);
1162
+ OuterTestUtils.assertLog(['Suspend Outer']);
1163
// Inner App renders because it is unblocked
1156
- expect(InnerScheduler.unstable_clearLog()).toEqual(['Inner']);
1164
+ InnerTestUtils.assertLog(['Inner']);
1165
// No event is replayed yet
1166
assertLog([]);
1167
1168
dispatchMouseHoverEvent(innerDiv);
1161
- expect(OuterScheduler.unstable_clearLog()).toEqual([]);
1162
- expect(InnerScheduler.unstable_clearLog()).toEqual([]);
1169
+ OuterTestUtils.assertLog([]);
1170
+ InnerTestUtils.assertLog([]);
1171
// No event is replayed yet
1172
assertLog([]);
1173
1166
- await act(async () => {
1167
- resolveOuter();
1168
- await outerPromise;
1169
- jest.runAllTimers();
1170
- Scheduler.unstable_flushAllWithoutAsserting();
1171
- OuterScheduler.unstable_flushAllWithoutAsserting();
1172
- InnerScheduler.unstable_flushAllWithoutAsserting();
1174
+ await InnerTestUtils.act(async () => {
1175
+ await OuterTestUtils.act(() => {
1176
+ resolveOuter();
1177
+
1178
+ // Nothing happens to inner app yet.
1179
+ // Its blocked on the outer app replaying the event
1180
+ InnerTestUtils.assertLog([]);
1181
+ // Outer hydrates and schedules Replay
1182
+ OuterTestUtils.waitFor(['Outer']);
1183
+ // No event is replayed yet
1184
+ assertLog([]);
1185
+ });
1186
});
1187
1175
- // Nothing happens to inner app yet.
1176
- // Its blocked on the outer app replaying the event
1177
- expect(InnerScheduler.unstable_clearLog()).toEqual([]);
1178
- // Outer hydrates and schedules Replay
1179
- expect(OuterScheduler.unstable_clearLog()).toEqual(['Outer']);
1180
- // No event is replayed yet
1181
- assertLog([]);
1182
-
1188
// fire scheduled Replay
1184
- await act(() => {
1185
- jest.runAllTimers();
1186
- Scheduler.unstable_flushAllWithoutAsserting();
1187
- OuterScheduler.unstable_flushAllWithoutAsserting();
1188
- InnerScheduler.unstable_flushAllWithoutAsserting();
1189
- });
1189
1190
// First Inner Mouse Enter fires then Outer Mouse Enter
1191
assertLog(['Inner Mouse Enter', 'Outer Mouse Enter']);
@@ -1205,9 +1204,9 @@ describe('ReactDOMServerSelectiveHydration', () => {
1204
});
1205
1206
// Outer resolves and scheduled replay
1208
- expect(OuterScheduler.unstable_clearLog()).toEqual(['Outer']);
1207
+ OuterTestUtils.assertLog(['Outer']);
1208
// Inner App is still blocked
1210
- expect(InnerScheduler.unstable_clearLog()).toEqual([]);
1209
+ InnerTestUtils.assertLog([]);
1210
1211
// Replay outer event
1212
await act(() => {
@@ -1219,12 +1218,12 @@ describe('ReactDOMServerSelectiveHydration', () => {
1218
// Inner is still blocked so when Outer replays the event in capture phase
1219
// inner ends up caling stopPropagation
1220
assertLog([]);
1222
- expect(OuterScheduler.unstable_clearLog()).toEqual([]);
1223
- expect(InnerScheduler.unstable_clearLog()).toEqual(['Suspend Inner']);
1221
+ OuterTestUtils.assertLog([]);
1222
+ InnerTestUtils.assertLog(['Suspend Inner']);
1223
1224
dispatchMouseHoverEvent(innerDiv);
1226
- expect(OuterScheduler.unstable_clearLog()).toEqual([]);
1227
- expect(InnerScheduler.unstable_clearLog()).toEqual([]);
1225
+ OuterTestUtils.assertLog([]);
1226
+ InnerTestUtils.assertLog([]);
1227
assertLog([]);
1228
1229
await act(async () => {
@@ -1236,9 +1235,9 @@ describe('ReactDOMServerSelectiveHydration', () => {
1235
});
1236
1237
// Inner hydrates
1239
- expect(InnerScheduler.unstable_clearLog()).toEqual(['Inner']);
1238
+ InnerTestUtils.assertLog(['Inner']);
1239
// Outer was hydrated earlier
1241
- expect(OuterScheduler.unstable_clearLog()).toEqual([]);
1240
+ OuterTestUtils.assertLog([]);
1241
1242
await act(() => {
1243
Scheduler.unstable_flushAllWithoutAsserting();
@@ -1297,18 +1296,17 @@ describe('ReactDOMServerSelectiveHydration', () => {
1296
ReactDOMClient.hydrateRoot(container, <App />);
1297
1298
const childDiv = container.firstElementChild;
1300
- dispatchMouseHoverEvent(childDiv);
1299
1302
- // Not hydrated so event is saved for replay and stopPropagation is called
1303
- assertLog([]);
1300
+ await act(async () => {
1301
+ dispatchMouseHoverEvent(childDiv);
1302
1305
- resolve();
1306
- Scheduler.unstable_flushNumberOfYields(1);
1307
- assertLog(['Child']);
1303
+ // Not hydrated so event is saved for replay and stopPropagation is called
1304
+ assertLog([]);
1305
+
1306
+ resolve();
1307
+ await waitFor(['Child']);
1308
1309
- Scheduler.unstable_scheduleCallback(
1310
- Scheduler.unstable_ImmediatePriority,
1311
- () => {
1309
+ ReactDOM.flushSync(() => {
1310
container.removeChild(childDiv);
1311
1312
const container2 = document.createElement('div');
@@ -1316,9 +1314,8 @@ describe('ReactDOMServerSelectiveHydration', () => {
1314
Scheduler.log('container2 mouse over');
1315
});
1316
container2.appendChild(childDiv);
1319
- },
1320
- );
1321
- Scheduler.unstable_flushAllWithoutAsserting();
1317
+ });
1318
+ });
1319
1320
// Even though the tree is remove the event is still dispatched with native event handler
1321
// on the container firing.
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+10
-13
@@ -15,8 +15,8 @@ let ReactDOM;
15
let ReactDOMClient;
16
let ReactDOMServer;
17
let ReactDOMServerBrowser;
18
-let Scheduler;
18
let waitForAll;
19
+let act;
20
21
// These tests rely both on ReactDOMServer and ReactDOM.
22
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
@@ -28,10 +28,10 @@ describe('ReactDOMServerHydration', () => {
28
ReactDOMClient = require('react-dom/client');
29
ReactDOMServer = require('react-dom/server');
30
ReactDOMServerBrowser = require('react-dom/server.browser');
31
- Scheduler = require('scheduler');
31
32
const InternalTestUtils = require('internal-test-utils');
33
waitForAll = InternalTestUtils.waitForAll;
34
+ act = InternalTestUtils.act;
35
});
36
37
it('should have the correct mounting behavior (new hydrate API)', () => {
@@ -403,25 +403,22 @@ describe('ReactDOMServerHydration', () => {
403
ReactDOM.hydrate(<HelloWorld />, element);
404
expect(element.textContent).toBe('Hello loading');
405
406
- jest.runAllTimers();
407
- await Promise.resolve();
408
- Scheduler.unstable_flushAll();
409
- await null;
406
+ // Resolve Lazy component
407
+ await act(() => jest.runAllTimers());
408
expect(element.textContent).toBe('Hello world');
409
});
410
413
- it('does not re-enter hydration after committing the first one', () => {
411
+ it('does not re-enter hydration after committing the first one', async () => {
412
const finalHTML = ReactDOMServer.renderToString(<div />);
413
const container = document.createElement('div');
414
container.innerHTML = finalHTML;
417
- const root = ReactDOMClient.hydrateRoot(container, <div />);
418
- Scheduler.unstable_flushAll();
419
- root.render(null);
420
- Scheduler.unstable_flushAll();
415
+ const root = await act(() =>
416
+ ReactDOMClient.hydrateRoot(container, <div />),
417
+ );
418
+ await act(() => root.render(null));
419
// This should not reenter hydration state and therefore not trigger hydration
420
// warnings.
423
- root.render(<div />);
424
- Scheduler.unstable_flushAll();
421
+ await act(() => root.render(<div />));
422
});
423
424
it('Suspense + hydration in legacy mode', () => {
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
-1
@@ -108,7 +108,6 @@ describe('ReactTestUtils.act()', () => {
108
it('does not warn in concurrent mode', () => {
109
const root = ReactDOMClient.createRoot(document.createElement('div'));
110
act(() => root.render(<App />));
111
- Scheduler.unstable_flushAll();
111
});
112
});
113
});
packages/react-noop-renderer/src/ReactNoop.js
+2
-1
@@ -35,7 +35,8 @@ export const {
35
unmountRootWithID,
36
findInstance,
37
flushNextYield,
38
- flushWithHostCounters,
38
+ startTrackingHostCounters,
39
+ stopTrackingHostCounters,
40
expire,
41
flushExpired,
42
batchedUpdates,
packages/react-noop-renderer/src/ReactNoopPersistent.js
+2
-1
@@ -35,7 +35,8 @@ export const {
35
unmountRootWithID,
36
findInstance,
37
flushNextYield,
38
- flushWithHostCounters,
38
+ startTrackingHostCounters,
39
+ stopTrackingHostCounters,
40
expire,
41
flushExpired,
42
batchedUpdates,
packages/react-noop-renderer/src/createReactNoop.js
+18
-17
@@ -983,7 +983,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
983
return Scheduler.unstable_clearLog();
984
},
985
986
- flushWithHostCounters(fn: () => void):
986
+ startTrackingHostCounters(): void {
987
+ hostDiffCounter = 0;
988
+ hostUpdateCounter = 0;
989
+ hostCloneCounter = 0;
990
+ },
991
+
992
+ stopTrackingHostCounters():
993
| {
994
hostDiffCounter: number,
995
hostUpdateCounter: number,
@@ -992,25 +998,20 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
998
hostDiffCounter: number,
999
hostCloneCounter: number,
1000
} {
1001
+ const result = useMutation
1002
+ ? {
1003
+ hostDiffCounter,
1004
+ hostUpdateCounter,
1005
+ }
1006
+ : {
1007
+ hostDiffCounter,
1008
+ hostCloneCounter,
1009
+ };
1010
hostDiffCounter = 0;
1011
hostUpdateCounter = 0;
1012
hostCloneCounter = 0;
998
- try {
999
- Scheduler.unstable_flushAll();
1000
- return useMutation
1001
- ? {
1002
- hostDiffCounter,
1003
- hostUpdateCounter,
1004
- }
1005
- : {
1006
- hostDiffCounter,
1007
- hostCloneCounter,
1008
- };
1009
- } finally {
1010
- hostDiffCounter = 0;
1011
- hostUpdateCounter = 0;
1012
- hostCloneCounter = 0;
1013
- }
1013
+
1014
+ return result;
1015
},
1016
1017
expire: Scheduler.unstable_advanceTime,
packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js
+20
-4
@@ -13,6 +13,7 @@ describe('DebugTracing', () => {
13
let React;
14
let ReactTestRenderer;
15
let waitForPaint;
16
+ let waitForAll;
17
18
let logs;
19
@@ -29,6 +30,7 @@ describe('DebugTracing', () => {
30
ReactTestRenderer = require('react-test-renderer');
31
const InternalTestUtils = require('internal-test-utils');
32
waitForPaint = InternalTestUtils.waitForPaint;
33
+ waitForAll = InternalTestUtils.waitForAll;
34
35
logs = [];
36
@@ -73,9 +75,20 @@ describe('DebugTracing', () => {
75
76
// @gate experimental && build === 'development' && enableDebugTracing
77
it('should log sync render with suspense', async () => {
76
- const fakeSuspensePromise = Promise.resolve(true);
78
+ let resolveFakeSuspensePromise;
79
+ let didResolve = false;
80
+ const fakeSuspensePromise = new Promise(resolve => {
81
+ resolveFakeSuspensePromise = () => {
82
+ didResolve = true;
83
+ resolve();
84
+ };
85
+ });
86
+
87
function Example() {
78
- throw fakeSuspensePromise;
88
+ if (!didResolve) {
89
+ throw fakeSuspensePromise;
90
+ }
91
+ return null;
92
}
93
94
ReactTestRenderer.act(() =>
@@ -96,7 +109,9 @@ describe('DebugTracing', () => {
109
110
logs.splice(0);
111
99
- await fakeSuspensePromise;
112
+ resolveFakeSuspensePromise();
113
+ await waitForAll([]);
114
+
115
expect(logs).toEqual(['log: ⚛️ Example resolved']);
116
});
117
@@ -387,7 +402,8 @@ describe('DebugTracing', () => {
402
return didMount;
403
}
404
390
- const fakeSuspensePromise = new Promise(() => {});
405
+ const fakeSuspensePromise = {then() {}};
406
+
407
function ExampleThatSuspends() {
408
throw fakeSuspensePromise;
409
}
packages/react-reconciler/src/__tests__/ReactExpiration-test.js
+32
-30
@@ -113,35 +113,30 @@ describe('ReactExpiration', () => {
113
}
114
}
115
116
- function flushNextRenderIfExpired() {
117
- // This will start rendering the next level of work. If the work hasn't
118
- // expired yet, React will exit without doing anything. If it has expired,
119
- // it will schedule a sync task.
120
- Scheduler.unstable_flushExpired();
121
- // Flush the sync task.
122
- ReactNoop.flushSync();
123
- }
124
-
125
- it('increases priority of updates as time progresses', () => {
116
+ it('increases priority of updates as time progresses', async () => {
117
+ ReactNoop.render(<Text text="Step 1" />);
118
React.startTransition(() => {
127
- ReactNoop.render(<span prop="done" />);
119
+ ReactNoop.render(<Text text="Step 2" />);
120
});
121
130
- expect(ReactNoop).toMatchRenderedOutput(null);
122
+ await waitFor(['Step 1']);
123
+
124
+ expect(ReactNoop).toMatchRenderedOutput('Step 1');
125
126
// Nothing has expired yet because time hasn't advanced.
133
- flushNextRenderIfExpired();
134
- expect(ReactNoop).toMatchRenderedOutput(null);
127
+ Scheduler.unstable_flushExpired();
128
+ expect(ReactNoop).toMatchRenderedOutput('Step 1');
129
130
// Advance time a bit, but not enough to expire the low pri update.
131
ReactNoop.expire(4500);
138
- flushNextRenderIfExpired();
139
- expect(ReactNoop).toMatchRenderedOutput(null);
132
+ Scheduler.unstable_flushExpired();
133
+ expect(ReactNoop).toMatchRenderedOutput('Step 1');
134
141
- // Advance by another second. Now the update should expire and flush.
135
+ // Advance by a little bit more. Now the update should expire and flush.
136
ReactNoop.expire(500);
143
- flushNextRenderIfExpired();
144
- expect(ReactNoop).toMatchRenderedOutput(<span prop="done" />);
137
+ Scheduler.unstable_flushExpired();
138
+ assertLog(['Step 2']);
139
+ expect(ReactNoop).toMatchRenderedOutput('Step 2');
140
});
141
142
it('two updates of like priority in the same event always flush within the same batch', async () => {
@@ -344,7 +339,7 @@ describe('ReactExpiration', () => {
339
340
Scheduler.unstable_advanceTime(10000);
341
347
- flushNextRenderIfExpired();
342
+ Scheduler.unstable_flushExpired();
343
assertLog(['D', 'E']);
344
expect(root).toMatchRenderedOutput('ABCDE');
345
});
@@ -374,17 +369,21 @@ describe('ReactExpiration', () => {
369
370
Scheduler.unstable_advanceTime(10000);
371
377
- flushNextRenderIfExpired();
372
+ Scheduler.unstable_flushExpired();
373
assertLog(['D', 'E']);
374
expect(root).toMatchRenderedOutput('ABCDE');
375
});
376
382
- it('should measure expiration times relative to module initialization', () => {
377
+ it('should measure expiration times relative to module initialization', async () => {
378
// Tests an implementation detail where expiration times are computed using
379
// bitwise operations.
380
381
jest.resetModules();
382
Scheduler = require('scheduler');
383
+ const InternalTestUtils = require('internal-test-utils');
384
+ waitFor = InternalTestUtils.waitFor;
385
+ assertLog = InternalTestUtils.assertLog;
386
+
387
// Before importing the renderer, advance the current time by a number
388
// larger than the maximum allowed for bitwise operations.
389
const maxSigned31BitInt = 1073741823;
@@ -393,22 +392,25 @@ describe('ReactExpiration', () => {
392
// Now import the renderer. On module initialization, it will read the
393
// current time.
394
ReactNoop = require('react-noop-renderer');
395
+ React = require('react');
396
397
+ ReactNoop.render(<Text text="Step 1" />);
398
React.startTransition(() => {
398
- ReactNoop.render('Hi');
399
+ ReactNoop.render(<Text text="Step 2" />);
400
});
401
+ await waitFor(['Step 1']);
402
403
// The update should not have expired yet.
402
- flushNextRenderIfExpired();
404
+ Scheduler.unstable_flushExpired();
405
assertLog([]);
406
405
- expect(ReactNoop).toMatchRenderedOutput(null);
407
+ expect(ReactNoop).toMatchRenderedOutput('Step 1');
408
409
// Advance the time some more to expire the update.
410
Scheduler.unstable_advanceTime(10000);
409
- flushNextRenderIfExpired();
410
- assertLog([]);
411
- expect(ReactNoop).toMatchRenderedOutput('Hi');
411
+ Scheduler.unstable_flushExpired();
412
+ assertLog(['Step 2']);
413
+ expect(ReactNoop).toMatchRenderedOutput('Step 2');
414
});
415
416
it('should measure callback timeout relative to current time, not start-up time', () => {
@@ -422,14 +424,14 @@ describe('ReactExpiration', () => {
424
React.startTransition(() => {
425
ReactNoop.render('Hi');
426
});
425
- flushNextRenderIfExpired();
427
+ Scheduler.unstable_flushExpired();
428
assertLog([]);
429
expect(ReactNoop).toMatchRenderedOutput(null);
430
431
// Advancing by ~5 seconds should be sufficient to expire the update. (I
432
// used a slightly larger number to allow for possible rounding.)
433
Scheduler.unstable_advanceTime(6000);
432
- flushNextRenderIfExpired();
434
+ Scheduler.unstable_flushExpired();
435
assertLog([]);
436
expect(ReactNoop).toMatchRenderedOutput('Hi');
437
});
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+1
-1
@@ -79,7 +79,7 @@ describe('ReactIncremental', () => {
79
ReactNoop.render(<Foo />, () => Scheduler.log('callback'));
80
});
81
// Do one step of work.
82
- expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
82
+ await waitFor(['Foo']);
83
84
// Do the rest of the work.
85
await waitForAll(['Bar', 'Bar', 'callback']);
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js
+8
-6
@@ -15,6 +15,7 @@ let ReactNoop;
15
let Scheduler;
16
let waitForAll;
17
let waitFor;
18
+let waitForPaint;
19
20
describe('ReactIncrementalSideEffects', () => {
21
beforeEach(() => {
@@ -27,6 +28,7 @@ describe('ReactIncrementalSideEffects', () => {
28
const InternalTestUtils = require('internal-test-utils');
29
waitForAll = InternalTestUtils.waitForAll;
30
waitFor = InternalTestUtils.waitFor;
31
+ waitForPaint = InternalTestUtils.waitForPaint;
32
});
33
34
// Note: This is based on a similar component we use in www. We can delete
@@ -694,25 +696,25 @@ describe('ReactIncrementalSideEffects', () => {
696
697
it('can update a completed tree before it has a chance to commit', async () => {
698
function Foo(props) {
697
- Scheduler.log('Foo');
699
+ Scheduler.log('Foo ' + props.step);
700
return <span prop={props.step} />;
701
}
702
React.startTransition(() => {
703
ReactNoop.render(<Foo step={1} />);
704
});
705
// This should be just enough to complete the tree without committing it
704
- await waitFor(['Foo']);
706
+ await waitFor(['Foo 1']);
707
expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
708
// To confirm, perform one more unit of work. The tree should now
709
// be flushed.
708
- ReactNoop.flushNextYield();
710
+ await waitForPaint([]);
711
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
712
713
React.startTransition(() => {
714
ReactNoop.render(<Foo step={2} />);
715
});
716
// This should be just enough to complete the tree without committing it
715
- await waitFor(['Foo']);
717
+ await waitFor(['Foo 2']);
718
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
719
// This time, before we commit the tree, we update the root component with
720
// new props
@@ -723,11 +725,11 @@ describe('ReactIncrementalSideEffects', () => {
725
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
726
// Now let's commit. We already had a commit that was pending, which will
727
// render 2.
726
- ReactNoop.flushNextYield();
728
+ await waitForPaint([]);
729
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
730
// If we flush the rest of the work, we should get another commit that
731
// renders 3. If it renders 2 again, that means an update was dropped.
730
- await waitForAll([]);
732
+ await waitForAll(['Foo 3']);
733
expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
734
});
735
packages/react-reconciler/src/__tests__/ReactIncrementalUpdatesMinimalism-test.js
+27
-17
@@ -12,15 +12,18 @@
12
13
let React;
14
let ReactNoop;
15
+let act;
16
17
describe('ReactIncrementalUpdatesMinimalism', () => {
18
beforeEach(() => {
19
jest.resetModules();
20
React = require('react');
21
ReactNoop = require('react-noop-renderer');
22
+
23
+ act = require('internal-test-utils').act;
24
});
25
23
- it('should render a simple component', () => {
26
+ it('should render a simple component', async () => {
27
function Child() {
28
return <div>Hello World</div>;
29
}
@@ -29,20 +32,22 @@ describe('ReactIncrementalUpdatesMinimalism', () => {
32
return <Child />;
33
}
34
32
- ReactNoop.render(<Parent />);
33
- expect(ReactNoop.flushWithHostCounters()).toEqual({
35
+ ReactNoop.startTrackingHostCounters();
36
+ await act(() => ReactNoop.render(<Parent />));
37
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
38
hostDiffCounter: 0,
39
hostUpdateCounter: 0,
40
});
41
38
- ReactNoop.render(<Parent />);
39
- expect(ReactNoop.flushWithHostCounters()).toEqual({
42
+ ReactNoop.startTrackingHostCounters();
43
+ await act(() => ReactNoop.render(<Parent />));
44
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
45
hostDiffCounter: 1,
46
hostUpdateCounter: 1,
47
});
48
});
49
45
- it('should not diff referentially equal host elements', () => {
50
+ it('should not diff referentially equal host elements', async () => {
51
function Leaf(props) {
52
return (
53
<span>
@@ -67,20 +72,22 @@ describe('ReactIncrementalUpdatesMinimalism', () => {
72
return <Child />;
73
}
74
70
- ReactNoop.render(<Parent />);
71
- expect(ReactNoop.flushWithHostCounters()).toEqual({
75
+ ReactNoop.startTrackingHostCounters();
76
+ await act(() => ReactNoop.render(<Parent />));
77
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
78
hostDiffCounter: 0,
79
hostUpdateCounter: 0,
80
});
81
76
- ReactNoop.render(<Parent />);
77
- expect(ReactNoop.flushWithHostCounters()).toEqual({
82
+ ReactNoop.startTrackingHostCounters();
83
+ await act(() => ReactNoop.render(<Parent />));
84
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
85
hostDiffCounter: 0,
86
hostUpdateCounter: 0,
87
});
88
});
89
83
- it('should not diff parents of setState targets', () => {
90
+ it('should not diff parents of setState targets', async () => {
91
let childInst;
92
93
function Leaf(props) {
@@ -118,14 +125,16 @@ describe('ReactIncrementalUpdatesMinimalism', () => {
125
);
126
}
127
121
- ReactNoop.render(<Parent />);
122
- expect(ReactNoop.flushWithHostCounters()).toEqual({
128
+ ReactNoop.startTrackingHostCounters();
129
+ await act(() => ReactNoop.render(<Parent />));
130
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
131
hostDiffCounter: 0,
132
hostUpdateCounter: 0,
133
});
134
127
- childInst.setState({name: 'Robin'});
128
- expect(ReactNoop.flushWithHostCounters()).toEqual({
135
+ ReactNoop.startTrackingHostCounters();
136
+ await act(() => childInst.setState({name: 'Robin'}));
137
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
138
// Child > div
139
// Child > Leaf > span
140
// Child > Leaf > span > b
@@ -137,8 +146,9 @@ describe('ReactIncrementalUpdatesMinimalism', () => {
146
hostUpdateCounter: 4,
147
});
148
140
- ReactNoop.render(<Parent />);
141
- expect(ReactNoop.flushWithHostCounters()).toEqual({
149
+ ReactNoop.startTrackingHostCounters();
150
+ await act(() => ReactNoop.render(<Parent />));
151
+ expect(ReactNoop.stopTrackingHostCounters()).toEqual({
152
// Parent > section
153
// Parent > section > div
154
// Parent > section > div > Leaf > span
packages/react-reconciler/src/__tests__/ReactPersistentUpdatesMinimalism-test.js
+26
-17
@@ -12,15 +12,17 @@
12
13
let React;
14
let ReactNoopPersistent;
15
+let act;
16
17
describe('ReactPersistentUpdatesMinimalism', () => {
18
beforeEach(() => {
19
jest.resetModules();
20
React = require('react');
21
ReactNoopPersistent = require('react-noop-renderer/persistent');
22
+ act = require('internal-test-utils').act;
23
});
24
23
- it('should render a simple component', () => {
25
+ it('should render a simple component', async () => {
26
function Child() {
27
return <div>Hello World</div>;
28
}
@@ -29,20 +31,22 @@ describe('ReactPersistentUpdatesMinimalism', () => {
31
return <Child />;
32
}
33
32
- ReactNoopPersistent.render(<Parent />);
33
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
34
+ ReactNoopPersistent.startTrackingHostCounters();
35
+ await act(() => ReactNoopPersistent.render(<Parent />));
36
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
37
hostDiffCounter: 0,
38
hostCloneCounter: 0,
39
});
40
38
- ReactNoopPersistent.render(<Parent />);
39
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
41
+ ReactNoopPersistent.startTrackingHostCounters();
42
+ await act(() => ReactNoopPersistent.render(<Parent />));
43
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
44
hostDiffCounter: 1,
45
hostCloneCounter: 1,
46
});
47
});
48
45
- it('should not diff referentially equal host elements', () => {
49
+ it('should not diff referentially equal host elements', async () => {
50
function Leaf(props) {
51
return (
52
<span>
@@ -67,20 +71,22 @@ describe('ReactPersistentUpdatesMinimalism', () => {
71
return <Child />;
72
}
73
70
- ReactNoopPersistent.render(<Parent />);
71
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
74
+ ReactNoopPersistent.startTrackingHostCounters();
75
+ await act(() => ReactNoopPersistent.render(<Parent />));
76
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
77
hostDiffCounter: 0,
78
hostCloneCounter: 0,
79
});
80
76
- ReactNoopPersistent.render(<Parent />);
77
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
81
+ ReactNoopPersistent.startTrackingHostCounters();
82
+ await act(() => ReactNoopPersistent.render(<Parent />));
83
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
84
hostDiffCounter: 0,
85
hostCloneCounter: 0,
86
});
87
});
88
83
- it('should not diff parents of setState targets', () => {
89
+ it('should not diff parents of setState targets', async () => {
90
let childInst;
91
92
function Leaf(props) {
@@ -118,14 +124,16 @@ describe('ReactPersistentUpdatesMinimalism', () => {
124
);
125
}
126
121
- ReactNoopPersistent.render(<Parent />);
122
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
127
+ ReactNoopPersistent.startTrackingHostCounters();
128
+ await act(() => ReactNoopPersistent.render(<Parent />));
129
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
130
hostDiffCounter: 0,
131
hostCloneCounter: 0,
132
});
133
127
- childInst.setState({name: 'Robin'});
128
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
134
+ ReactNoopPersistent.startTrackingHostCounters();
135
+ await act(() => childInst.setState({name: 'Robin'}));
136
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
137
// section > div > Child > div
138
// section > div > Child > Leaf > span
139
// section > div > Child > Leaf > span > b
@@ -138,8 +146,9 @@ describe('ReactPersistentUpdatesMinimalism', () => {
146
hostCloneCounter: 5,
147
});
148
141
- ReactNoopPersistent.render(<Parent />);
142
- expect(ReactNoopPersistent.flushWithHostCounters()).toEqual({
149
+ ReactNoopPersistent.startTrackingHostCounters();
150
+ await act(() => ReactNoopPersistent.render(<Parent />));
151
+ expect(ReactNoopPersistent.stopTrackingHostCounters()).toEqual({
152
// Parent > section
153
// Parent > section > div
154
// Parent > section > div > Leaf > span
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+9
-9
@@ -12,7 +12,7 @@
12
let React;
13
let ReactFeatureFlags;
14
let ReactDOMServer;
15
-let Scheduler;
15
+let act;
16
17
describe('ReactScope', () => {
18
beforeEach(() => {
@@ -20,7 +20,9 @@ describe('ReactScope', () => {
20
ReactFeatureFlags = require('shared/ReactFeatureFlags');
21
ReactFeatureFlags.enableScopeAPI = true;
22
React = require('react');
23
- Scheduler = require('scheduler');
23
+
24
+ const InternalTestUtils = require('internal-test-utils');
25
+ act = InternalTestUtils.act;
26
});
27
28
describe('ReactDOM', () => {
@@ -314,9 +316,7 @@ describe('ReactScope', () => {
316
// On the client we don't have all data yet but we want to start
317
// hydrating anyway.
318
suspend = true;
317
- ReactDOMClient.hydrateRoot(container2, <App />);
318
- Scheduler.unstable_flushAll();
319
- jest.runAllTimers();
319
+ await act(() => ReactDOMClient.hydrateRoot(container2, <App />));
320
321
// This should not cause a runtime exception, see:
322
// https://github.com/facebook/react/pull/18184
@@ -325,10 +325,10 @@ describe('ReactScope', () => {
325
326
// Resolving the promise should continue hydration
327
suspend = false;
328
- resolve();
329
- await promise;
330
- Scheduler.unstable_flushAll();
331
- jest.runAllTimers();
328
+ await act(async () => {
329
+ resolve();
330
+ await promise;
331
+ });
332
333
// We should now have hydrated with a ref on the existing span.
334
expect(ref.current).toBe(span);
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+22
-24
@@ -11,7 +11,6 @@
11
12
let React;
13
let ReactNoop;
14
-let Scheduler;
14
let waitForAll;
15
16
describe('ReactSuspense', () => {
@@ -20,7 +19,6 @@ describe('ReactSuspense', () => {
19
20
React = require('react');
21
ReactNoop = require('react-noop-renderer');
23
- Scheduler = require('scheduler');
22
23
const InternalTestUtils = require('internal-test-utils');
24
waitForAll = InternalTestUtils.waitForAll;
@@ -44,32 +42,32 @@ describe('ReactSuspense', () => {
42
return {promise, resolve, PromiseComp};
43
}
44
47
- if (__DEV__) {
48
- // @gate www
49
- it('check type', () => {
50
- const {PromiseComp} = createThenable();
45
+ // Warning don't fire in production, so this test passes in prod even if
46
+ // the suspenseCallback feature is not enabled
47
+ // @gate www || !__DEV__
48
+ it('check type', async () => {
49
+ const {PromiseComp} = createThenable();
50
52
- const elementBadType = (
53
- <React.Suspense suspenseCallback={1} fallback={'Waiting'}>
54
- <PromiseComp />
55
- </React.Suspense>
56
- );
51
+ const elementBadType = (
52
+ <React.Suspense suspenseCallback={1} fallback={'Waiting'}>
53
+ <PromiseComp />
54
+ </React.Suspense>
55
+ );
56
58
- ReactNoop.render(elementBadType);
59
- expect(() => Scheduler.unstable_flushAll()).toErrorDev([
60
- 'Warning: Unexpected type for suspenseCallback.',
61
- ]);
57
+ ReactNoop.render(elementBadType);
58
+ await expect(async () => await waitForAll([])).toErrorDev([
59
+ 'Warning: Unexpected type for suspenseCallback.',
60
+ ]);
61
63
- const elementMissingCallback = (
64
- <React.Suspense fallback={'Waiting'}>
65
- <PromiseComp />
66
- </React.Suspense>
67
- );
62
+ const elementMissingCallback = (
63
+ <React.Suspense fallback={'Waiting'}>
64
+ <PromiseComp />
65
+ </React.Suspense>
66
+ );
67
69
- ReactNoop.render(elementMissingCallback);
70
- expect(() => Scheduler.unstable_flushAll()).toErrorDev([]);
71
- });
72
- }
68
+ ReactNoop.render(elementMissingCallback);
69
+ await expect(async () => await waitForAll([])).toErrorDev([]);
70
+ });
71
72
// @gate www
73
it('1 then 0 suspense callback', async () => {
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+10
-2
@@ -8,6 +8,7 @@ let waitForAll;
8
let assertLog;
9
let waitForPaint;
10
let Suspense;
11
+let startTransition;
12
let getCacheForType;
13
14
let caches;
@@ -23,6 +24,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
24
Scheduler = require('scheduler');
25
act = require('internal-test-utils').act;
26
Suspense = React.Suspense;
27
+ startTransition = React.startTransition;
28
const InternalTestUtils = require('internal-test-utils');
29
waitFor = InternalTestUtils.waitFor;
30
waitForAll = InternalTestUtils.waitForAll;
@@ -3224,9 +3226,15 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3226
ReactNoop.discreteUpdates(() => {
3227
setText('B');
3228
});
3227
- // Update to a value that has already resolved
3229
+ startTransition(() => {
3230
+ setText('C');
3231
+ });
3232
+ // Assert that neither update has happened yet. Both the high pri and
3233
+ // low pri updates are in the queue.
3234
+ assertLog([]);
3235
+
3236
+ // Resolve this before starting to render so that C doesn't suspend.
3237
await resolveText('C');
3229
- setText('C');
3238
});
3239
assertLog([
3240
// First we attempt the high pri update. It suspends.
packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js
+11
-2
@@ -31,9 +31,18 @@ describe('ReactTestRenderer', () => {
31
it('should warn if used to render a ReactDOM portal', () => {
32
const container = document.createElement('div');
33
expect(() => {
34
- expect(() => {
34
+ try {
35
ReactTestRenderer.create(ReactDOM.createPortal('foo', container));
36
- }).toThrow();
36
+ } catch (e) {
37
+ // TODO: After the update throws, a subsequent render is scheduled to
38
+ // unmount the whole tree. This update also causes an error, and this
39
+ // happens in a separate task. Flush this error now and capture it, to
40
+ // prevent it from firing asynchronously and causing the Jest test
41
+ // to fail.
42
+ expect(() => Scheduler.unstable_flushAll()).toThrow(
43
+ '.children.indexOf is not a function',
44
+ );
45
+ }
46
}).toErrorDev('An invalid container has been provided.', {
47
withoutStack: true,
48
});
packages/react/src/__tests__/ReactStrictMode-test.js
+21
-22
@@ -13,7 +13,6 @@ let React;
13
let ReactDOM;
14
let ReactDOMClient;
15
let ReactDOMServer;
16
-let Scheduler;
16
let PropTypes;
17
let act;
18
let useMemo;
@@ -525,10 +524,10 @@ describe('Concurrent Mode', () => {
524
React = require('react');
525
ReactDOM = require('react-dom');
526
ReactDOMClient = require('react-dom/client');
528
- Scheduler = require('scheduler');
527
+ act = require('internal-test-utils').act;
528
});
529
531
- it('should warn about unsafe legacy lifecycle methods anywhere in a StrictMode tree', () => {
530
+ it('should warn about unsafe legacy lifecycle methods anywhere in a StrictMode tree', async () => {
531
function StrictRoot() {
532
return (
533
<React.StrictMode>
@@ -571,8 +570,9 @@ describe('Concurrent Mode', () => {
570
571
const container = document.createElement('div');
572
const root = ReactDOMClient.createRoot(container);
574
- root.render(<StrictRoot />);
575
- expect(() => Scheduler.unstable_flushAll()).toErrorDev(
573
+ await expect(
574
+ async () => await act(() => root.render(<StrictRoot />)),
575
+ ).toErrorDev(
576
[
577
/* eslint-disable max-len */
578
`Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://reactjs.org/link/unsafe-component-lifecycles for details.
@@ -597,11 +597,10 @@ Please update the following components: App`,
597
);
598
599
// Dedupe
600
- root.render(<App />);
601
- Scheduler.unstable_flushAll();
600
+ await act(() => root.render(<App />));
601
});
602
604
- it('should coalesce warnings by lifecycle name', () => {
603
+ it('should coalesce warnings by lifecycle name', async () => {
604
function StrictRoot() {
605
return (
606
<React.StrictMode>
@@ -633,10 +632,11 @@ Please update the following components: App`,
632
633
const container = document.createElement('div');
634
const root = ReactDOMClient.createRoot(container);
636
- root.render(<StrictRoot />);
635
638
- expect(() => {
639
- expect(() => Scheduler.unstable_flushAll()).toErrorDev(
636
+ await expect(async () => {
637
+ await expect(
638
+ async () => await act(() => root.render(<StrictRoot />)),
639
+ ).toErrorDev(
640
[
641
/* eslint-disable max-len */
642
`Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://reactjs.org/link/unsafe-component-lifecycles for details.
@@ -686,11 +686,10 @@ Please update the following components: Parent`,
686
{withoutStack: true},
687
);
688
// Dedupe
689
- root.render(<StrictRoot />);
690
- Scheduler.unstable_flushAll();
689
+ await act(() => root.render(<StrictRoot />));
690
});
691
693
- it('should warn about components not present during the initial render', () => {
692
+ it('should warn about components not present during the initial render', async () => {
693
function StrictRoot({foo}) {
694
return <React.StrictMode>{foo ? <Foo /> : <Bar />}</React.StrictMode>;
695
}
@@ -709,23 +708,23 @@ Please update the following components: Parent`,
708
709
const container = document.createElement('div');
710
const root = ReactDOMClient.createRoot(container);
712
- root.render(<StrictRoot foo={true} />);
713
- expect(() => Scheduler.unstable_flushAll()).toErrorDev(
711
+ await expect(async () => {
712
+ await act(() => root.render(<StrictRoot foo={true} />));
713
+ }).toErrorDev(
714
'Using UNSAFE_componentWillMount in strict mode is not recommended',
715
{withoutStack: true},
716
);
717
718
- root.render(<StrictRoot foo={false} />);
719
- expect(() => Scheduler.unstable_flushAll()).toErrorDev(
718
+ await expect(async () => {
719
+ await act(() => root.render(<StrictRoot foo={false} />));
720
+ }).toErrorDev(
721
'Using UNSAFE_componentWillMount in strict mode is not recommended',
722
{withoutStack: true},
723
);
724
725
// Dedupe
725
- root.render(<StrictRoot foo={true} />);
726
- Scheduler.unstable_flushAll();
727
- root.render(<StrictRoot foo={false} />);
728
- Scheduler.unstable_flushAll();
726
+ await act(() => root.render(<StrictRoot foo={true} />));
727
+ await act(() => root.render(<StrictRoot foo={false} />));
728
});
729
730
it('should also warn inside of "strict" mode trees', () => {