@samitouri / QOS-React-2 / commits / a8875eab7f

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', () => {