@samitouri / QOS-React-2 / commits / 72c890e312

Convert more Suspense tests to use act (2/n) (#26610)

Many of our Suspense-related tests were written before the `act` API was introduced, and use the lower level `waitFor` helpers instead. So they are less resilient to changes in implementation details than they could be. This converts some of our test suite to use `act` in more places. I found these while working on a PR to expand our fallback throttling mechanism to include all renders that result from a promise resolving, even if there are no more fallbacks in the tree. I think this covers all the remaining tests that are affected.

Andrew Clark committed Apr 12, 2023 at 13:36 UTC 72c890e3123d3ea48b5d7f51bca301d7da16a8b1
13 files changed +422 -429
packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js
+36 -24
@@ -21,6 +21,7 @@ let textResourceShouldFail;
21 let waitForAll;
22 let assertLog;
23 let waitForThrow;
24 +let act;
25
26 describe('ReactCache', () => {
27 beforeEach(() => {
@@ -40,6 +41,7 @@ describe('ReactCache', () => {
41 waitForAll = InternalTestUtils.waitForAll;
42 assertLog = InternalTestUtils.assertLog;
43 waitForThrow = InternalTestUtils.waitForThrow;
44 + act = InternalTestUtils.act;
45
46 TextResource = createResource(
47 ([text, ms = 0]) => {
@@ -145,11 +147,14 @@ describe('ReactCache', () => {
147 await waitForAll(['Suspend! [Hi]', 'Loading...']);
148
149 textResourceShouldFail = true;
148 - jest.advanceTimersByTime(100);
149 - assertLog(['Promise rejected [Hi]']);
150 -
151 - await waitForThrow('Failed to load: Hi');
152 - assertLog(['Error! [Hi]', 'Error! [Hi]']);
150 + let error;
151 + try {
152 + await act(() => jest.advanceTimersByTime(100));
153 + } catch (e) {
154 + error = e;
155 + }
156 + expect(error.message).toMatch('Failed to load: Hi');
157 + assertLog(['Promise rejected [Hi]', 'Error! [Hi]', 'Error! [Hi]']);
158
159 // Should throw again on a subsequent read
160 root.update(<App />);
@@ -217,9 +222,8 @@ describe('ReactCache', () => {
222 assertLog(['Promise resolved [2]']);
223 await waitForAll([1, 2, 'Suspend! [3]']);
224
220 - jest.advanceTimersByTime(100);
221 - assertLog(['Promise resolved [3]']);
222 - await waitForAll([1, 2, 3]);
225 + await act(() => jest.advanceTimersByTime(100));
226 + assertLog(['Promise resolved [3]', 1, 2, 3]);
227
228 expect(root).toMatchRenderedOutput('123');
229
@@ -234,13 +238,17 @@ describe('ReactCache', () => {
238
239 await waitForAll([1, 'Suspend! [4]', 'Loading...']);
240
237 - jest.advanceTimersByTime(100);
238 - assertLog(['Promise resolved [4]']);
239 - await waitForAll([1, 4, 'Suspend! [5]']);
240 -
241 - jest.advanceTimersByTime(100);
242 - assertLog(['Promise resolved [5]']);
243 - await waitForAll([1, 4, 5]);
241 + await act(() => jest.advanceTimersByTime(100));
242 + assertLog([
243 + 'Promise resolved [4]',
244 + 1,
245 + 4,
246 + 'Suspend! [5]',
247 + 'Promise resolved [5]',
248 + 1,
249 + 4,
250 + 5,
251 + ]);
252
253 expect(root).toMatchRenderedOutput('145');
254
@@ -262,13 +270,18 @@ describe('ReactCache', () => {
270 'Suspend! [2]',
271 'Loading...',
272 ]);
265 - jest.advanceTimersByTime(100);
266 - assertLog(['Promise resolved [2]']);
267 - await waitForAll([1, 2, 'Suspend! [3]']);
273
269 - jest.advanceTimersByTime(100);
270 - assertLog(['Promise resolved [3]']);
271 - await waitForAll([1, 2, 3]);
274 + await act(() => jest.advanceTimersByTime(100));
275 + assertLog([
276 + 'Promise resolved [2]',
277 + 1,
278 + 2,
279 + 'Suspend! [3]',
280 + 'Promise resolved [3]',
281 + 1,
282 + 2,
283 + 3,
284 + ]);
285 expect(root).toMatchRenderedOutput('123');
286 });
287
@@ -291,9 +304,8 @@ describe('ReactCache', () => {
304
305 await waitForAll(['Loading...']);
306
294 - jest.advanceTimersByTime(1000);
295 - assertLog(['Promise resolved [B]', 'Promise resolved [A]']);
296 - await waitForAll(['Result']);
307 + await act(() => jest.advanceTimersByTime(1000));
308 + assertLog(['Promise resolved [B]', 'Promise resolved [A]', 'Result']);
309 expect(root).toMatchRenderedOutput('Result');
310 });
311
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+8 -6
@@ -42,6 +42,7 @@ let waitFor;
42 let waitForAll;
43 let assertLog;
44 let waitForPaint;
45 +let clientAct;
46
47 function resetJSDOM(markup) {
48 // Test Environment
@@ -74,6 +75,7 @@ describe('ReactDOMFizzServer', () => {
75 waitFor = InternalTestUtils.waitFor;
76 waitForPaint = InternalTestUtils.waitForPaint;
77 assertLog = InternalTestUtils.assertLog;
78 + clientAct = InternalTestUtils.act;
79
80 if (gate(flags => flags.source)) {
81 // The `with-selector` module composes the main `use-sync-external-store`
@@ -1191,8 +1193,8 @@ describe('ReactDOMFizzServer', () => {
1193 expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
1194
1195 // We now resolve it on the client.
1194 - resolveText('Hello');
1195 - await waitForAll([]);
1196 + await clientAct(() => resolveText('Hello'));
1197 + assertLog([]);
1198
1199 // The client rendered HTML is now in place.
1200 expect(getVisibleChildren(container)).toEqual(
@@ -2884,10 +2886,10 @@ describe('ReactDOMFizzServer', () => {
2886 </div>,
2887 );
2888
2887 - await act(() => {
2889 + await clientAct(() => {
2890 resolveText('Yay!');
2891 });
2890 - await waitForAll(['Yay!']);
2892 + assertLog(['Yay!']);
2893 expect(getVisibleChildren(container)).toEqual(
2894 <div>
2895 <span />
@@ -4310,10 +4312,10 @@ describe('ReactDOMFizzServer', () => {
4312 <h1>Loading...</h1>
4313 </div>,
4314 );
4313 - await unsuspend();
4315 + await clientAct(() => unsuspend());
4316 // Since our client components only throw on the very first render there are no
4317 // new throws in this pass
4316 - await waitForAll([]);
4318 + assertLog([]);
4319 expect(mockError.mock.calls).toEqual([]);
4320
4321 expect(getVisibleChildren(container)).toEqual(
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+3 -1
@@ -37,6 +37,7 @@ let waitForAll;
37 let waitForThrow;
38 let assertLog;
39 let Scheduler;
40 +let clientAct;
41
42 function resetJSDOM(markup) {
43 // Test Environment
@@ -71,6 +72,7 @@ describe('ReactDOMFloat', () => {
72 waitForAll = InternalTestUtils.waitForAll;
73 waitForThrow = InternalTestUtils.waitForThrow;
74 assertLog = InternalTestUtils.assertLog;
75 + clientAct = InternalTestUtils.act;
76
77 textCache = new Map();
78 loadCache = new Set();
@@ -1186,7 +1188,7 @@ body {
1188 // events have already fired. This requires the load to be awaited for the commit to have a chance to flush
1189 // We could change this by tracking the loadingState's fulfilled status directly on the loadingState similar
1190 // to thenables however this slightly increases the fizz runtime code size.
1189 - await loadStylesheets();
1191 + await clientAct(() => loadStylesheets());
1192 assertLog(['load stylesheet: foo']);
1193 expect(getMeaningfulChildren(document)).toEqual(
1194 <html>
packages/react-reconciler/src/__tests__/ReactBatching-test.internal.js
+4 -3
@@ -7,6 +7,7 @@ let assertLog;
7 let ReactCache;
8 let Suspense;
9 let TextResource;
10 +let act;
11
12 describe('ReactBlockingMode', () => {
13 beforeEach(() => {
@@ -23,6 +24,7 @@ describe('ReactBlockingMode', () => {
24 const InternalTestUtils = require('internal-test-utils');
25 waitForAll = InternalTestUtils.waitForAll;
26 assertLog = InternalTestUtils.assertLog;
27 + act = InternalTestUtils.act;
28
29 TextResource = ReactCache.unstable_createResource(
30 ([text, ms = 0]) => {
@@ -117,9 +119,8 @@ describe('ReactBlockingMode', () => {
119 // fallback should mount immediately.
120 expect(root).toMatchRenderedOutput('Loading...');
121
120 - await jest.advanceTimersByTime(1000);
121 - assertLog(['Promise resolved [B]']);
122 - await waitForAll(['A', 'B', 'C']);
122 + await act(() => jest.advanceTimersByTime(1000));
123 + assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
124 expect(root).toMatchRenderedOutput(
125 <>
126 <span>A</span>
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+2 -3
@@ -3623,9 +3623,8 @@ describe('ReactHooksWithNoopRenderer', () => {
3623 </>,
3624 );
3625
3626 - await resolveText('A');
3627 - assertLog(['Promise resolved [A]']);
3628 - await waitForAll(['A']);
3626 + await act(() => resolveText('A'));
3627 + assertLog(['Promise resolved [A]', 'A']);
3628 expect(ReactNoop).toMatchRenderedOutput(
3629 <>
3630 <span prop="A" />
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+96 -85
@@ -9,6 +9,7 @@ let waitFor;
9 let waitForAll;
10 let waitForThrow;
11 let assertLog;
12 +let act;
13
14 let fakeModuleCache;
15
@@ -39,6 +40,7 @@ describe('ReactLazy', () => {
40 waitForAll = InternalTestUtils.waitForAll;
41 waitForThrow = InternalTestUtils.waitForThrow;
42 assertLog = InternalTestUtils.assertLog;
43 + act = InternalTestUtils.act;
44
45 fakeModuleCache = new Map();
46 });
@@ -104,9 +106,8 @@ describe('ReactLazy', () => {
106 await waitForAll(['Loading...']);
107 expect(root).not.toMatchRenderedOutput('Hi');
108
107 - await resolveFakeImport(Text);
108 -
109 - await waitForAll(['Hi']);
109 + await act(() => resolveFakeImport(Text));
110 + assertLog(['Hi']);
111 expect(root).toMatchRenderedOutput('Hi');
112
113 // Should not suspend on update
@@ -196,9 +197,8 @@ describe('ReactLazy', () => {
197 await waitForAll(['Foo']);
198 expect(root).not.toMatchRenderedOutput('FooBar');
199
199 - await resolveFakeImport(Bar);
200 -
201 - await waitForAll(['Foo', 'Bar']);
200 + await act(() => resolveFakeImport(Bar));
201 + assertLog(['Foo', 'Bar']);
202 expect(root).toMatchRenderedOutput('FooBar');
203 });
204
@@ -207,15 +207,24 @@ describe('ReactLazy', () => {
207
208 const LazyText = lazy(async () => Text);
209
210 - const root = ReactTestRenderer.create(
211 - <Suspense fallback={<Text text="Loading..." />}>
212 - <LazyText text="Hi" />
213 - </Suspense>,
214 - {
215 - unstable_isConcurrent: true,
216 - },
217 - );
218 - await waitForThrow('Element type is invalid');
210 + const root = ReactTestRenderer.create(null, {
211 + unstable_isConcurrent: true,
212 + });
213 +
214 + let error;
215 + try {
216 + await act(() => {
217 + root.update(
218 + <Suspense fallback={<Text text="Loading..." />}>
219 + <LazyText text="Hi" />
220 + </Suspense>,
221 + );
222 + });
223 + } catch (e) {
224 + error = e;
225 + }
226 +
227 + expect(error.message).toMatch('Element type is invalid');
228 assertLog(['Loading...']);
229 expect(root).not.toMatchRenderedOutput('Hi');
230 if (__DEV__) {
@@ -227,20 +236,29 @@ describe('ReactLazy', () => {
236 });
237
238 it('throws if promise rejects', async () => {
239 + const networkError = new Error('Bad network');
240 const LazyText = lazy(async () => {
231 - throw new Error('Bad network');
241 + throw networkError;
242 });
243
234 - const root = ReactTestRenderer.create(
235 - <Suspense fallback={<Text text="Loading..." />}>
236 - <LazyText text="Hi" />
237 - </Suspense>,
238 - {
239 - unstable_isConcurrent: true,
240 - },
241 - );
244 + const root = ReactTestRenderer.create(null, {
245 + unstable_isConcurrent: true,
246 + });
247
243 - await waitForThrow('Bad network');
248 + let error;
249 + try {
250 + await act(() => {
251 + root.update(
252 + <Suspense fallback={<Text text="Loading..." />}>
253 + <LazyText text="Hi" />
254 + </Suspense>,
255 + );
256 + });
257 + } catch (e) {
258 + error = e;
259 + }
260 +
261 + expect(error).toBe(networkError);
262 assertLog(['Loading...']);
263 expect(root).not.toMatchRenderedOutput('Hi');
264 });
@@ -290,14 +308,15 @@ describe('ReactLazy', () => {
308 await waitForAll(['Suspend! [LazyChildA]', 'Loading...']);
309 expect(root).not.toMatchRenderedOutput('AB');
310
293 - await resolveFakeImport(Child);
294 -
295 - // B suspends even though it happens to share the same import as A.
296 - // TODO: React.lazy should implement the `status` and `value` fields, so
297 - // we can unwrap the result synchronously if it already loaded. Like `use`.
298 - await waitFor(['A', 'Suspend! [LazyChildB]']);
311 + await act(async () => {
312 + await resolveFakeImport(Child);
313
300 - await waitForAll(['A', 'B', 'Did mount: A', 'Did mount: B']);
314 + // B suspends even though it happens to share the same import as A.
315 + // TODO: React.lazy should implement the `status` and `value` fields, so
316 + // we can unwrap the result synchronously if it already loaded. Like `use`.
317 + await waitFor(['A', 'Suspend! [LazyChildB]']);
318 + });
319 + assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
320 expect(root).toMatchRenderedOutput('AB');
321
322 // Swap the position of A and B
@@ -325,9 +344,10 @@ describe('ReactLazy', () => {
344 await waitForAll(['Loading...']);
345 expect(root).not.toMatchRenderedOutput('Hi');
346
328 - await resolveFakeImport(T);
329 -
330 - await expect(async () => await waitForAll(['Hi'])).toErrorDev(
347 + await expect(async () => {
348 + await act(() => resolveFakeImport(T));
349 + assertLog(['Hi']);
350 + }).toErrorDev(
351 'Warning: T: Support for defaultProps ' +
352 'will be removed from function components in a future major ' +
353 'release. Use JavaScript default parameters instead.',
@@ -380,11 +400,10 @@ describe('ReactLazy', () => {
400 await waitForAll(['Loading...']);
401 expect(root).not.toMatchRenderedOutput('SiblingA');
402
383 - await resolveFakeImport(LazyImpl);
384 -
385 - await expect(
386 - async () => await waitForAll(['Lazy', 'Sibling', 'A']),
387 - ).toErrorDev(
403 + await expect(async () => {
404 + await act(() => resolveFakeImport(LazyImpl));
405 + assertLog(['Lazy', 'Sibling', 'A']);
406 + }).toErrorDev(
407 'Warning: LazyImpl: Support for defaultProps ' +
408 'will be removed from function components in a future major ' +
409 'release. Use JavaScript default parameters instead.',
@@ -427,9 +446,8 @@ describe('ReactLazy', () => {
446 await waitForAll(['Not lazy: 0', 'Loading...']);
447 expect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0');
448
430 - await resolveFakeImport(LazyImpl);
431 -
432 - await waitForAll(['Lazy: 0']);
449 + await act(() => resolveFakeImport(LazyImpl));
450 + assertLog(['Lazy: 0']);
451 expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
452
453 // Should bailout due to unchanged props and state
@@ -473,9 +491,8 @@ describe('ReactLazy', () => {
491 await waitForAll(['Not lazy: 0', 'Loading...']);
492 expect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0');
493
476 - await resolveFakeImport(LazyImpl);
477 -
478 - await waitForAll(['Lazy: 0']);
494 + await act(() => resolveFakeImport(LazyImpl));
495 + assertLog(['Lazy: 0']);
496 expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
497
498 // Should bailout due to shallow equal props and state
@@ -551,9 +568,8 @@ describe('ReactLazy', () => {
568 await waitForAll(['Loading...']);
569 expect(root).not.toMatchRenderedOutput('A1');
570
554 - await resolveFakeImport(C);
555 -
556 - await waitForAll([
571 + await act(() => resolveFakeImport(C));
572 + assertLog([
573 'constructor: A',
574 'getDerivedStateFromProps: A',
575 'A1',
@@ -682,8 +698,10 @@ describe('ReactLazy', () => {
698 await waitForAll(['Loading...']);
699 expect(root).not.toMatchRenderedOutput('Hi Bye');
700
685 - await resolveFakeImport(T);
686 - await expect(async () => await waitForAll(['Hi Bye'])).toErrorDev(
701 + await expect(async () => {
702 + await act(() => resolveFakeImport(T));
703 + assertLog(['Hi Bye']);
704 + }).toErrorDev(
705 'Warning: T: Support for defaultProps ' +
706 'will be removed from function components in a future major ' +
707 'release. Use JavaScript default parameters instead.',
@@ -806,9 +824,8 @@ describe('ReactLazy', () => {
824 expect(root).not.toMatchRenderedOutput('22');
825
826 // Mount
809 - await resolveFakeImport(Add);
827 await expect(async () => {
811 - await waitForAll([]);
828 + await act(() => resolveFakeImport(Add));
829 }).toErrorDev(
830 shouldWarnAboutFunctionDefaultProps
831 ? [
@@ -1003,9 +1020,9 @@ describe('ReactLazy', () => {
1020 expect(root).not.toMatchRenderedOutput('Inner default text');
1021
1022 // Mount
1006 - await resolveFakeImport(T);
1023 await expect(async () => {
1008 - await waitForAll(['Inner default text']);
1024 + await act(() => resolveFakeImport(T));
1025 + assertLog(['Inner default text']);
1026 }).toErrorDev([
1027 'T: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
1028 'The prop `text` is marked as required in `T`, but its value is `undefined`',
@@ -1045,10 +1062,9 @@ describe('ReactLazy', () => {
1062 await waitForAll(['Started loading', 'Loading...']);
1063 expect(root).not.toMatchRenderedOutput(<div>AB</div>);
1064
1048 - await resolveFakeImport(Foo);
1049 -
1065 await expect(async () => {
1051 - await waitForAll(['A', 'B']);
1066 + await act(() => resolveFakeImport(Foo));
1067 + assertLog(['A', 'B']);
1068 }).toErrorDev(' in Text (at **)\n' + ' in Foo (at **)');
1069 expect(root).toMatchRenderedOutput(<div>AB</div>);
1070 });
@@ -1092,12 +1108,11 @@ describe('ReactLazy', () => {
1108 expect(root).not.toMatchRenderedOutput('FooBar');
1109 expect(ref.current).toBe(null);
1110
1095 - await resolveFakeImport(Foo);
1096 - await waitForAll(['Foo']);
1097 -
1098 - await resolveFakeImport(ForwardRefBar);
1111 + await act(() => resolveFakeImport(Foo));
1112 + assertLog(['Foo']);
1113
1100 - await waitForAll(['Foo', 'forwardRef', 'Bar']);
1114 + await act(() => resolveFakeImport(ForwardRefBar));
1115 + assertLog(['Foo', 'forwardRef', 'Bar']);
1116 expect(root).toMatchRenderedOutput('FooBar');
1117 expect(ref.current).not.toBe(null);
1118 });
@@ -1123,9 +1138,8 @@ describe('ReactLazy', () => {
1138 expect(root).not.toMatchRenderedOutput('4');
1139
1140 // Mount
1126 - await resolveFakeImport(Add);
1141 await expect(async () => {
1128 - await waitForAll([]);
1142 + await act(() => resolveFakeImport(Add));
1143 }).toErrorDev(
1144 'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
1145 );
@@ -1211,9 +1225,8 @@ describe('ReactLazy', () => {
1225 expect(root).not.toMatchRenderedOutput('4');
1226
1227 // Mount
1214 - await resolveFakeImport(Add);
1228 await expect(async () => {
1216 - await waitForAll([]);
1229 + await act(() => resolveFakeImport(Add));
1230 }).toErrorDev([
1231 'Memo: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
1232 'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
@@ -1296,8 +1309,8 @@ describe('ReactLazy', () => {
1309
1310 await waitForAll(['Loading...']);
1311
1299 - await resolveFakeImport(ResolvedText);
1300 - await waitForAll([]);
1312 + await act(() => resolveFakeImport(ResolvedText));
1313 + assertLog([]);
1314
1315 expect(componentStackMessage).toContain('in ResolvedText');
1316 });
@@ -1405,11 +1418,11 @@ describe('ReactLazy', () => {
1418 await waitForAll(['Init A', 'Loading...']);
1419 expect(root).not.toMatchRenderedOutput('AB');
1420
1408 - await resolveFakeImport(ChildA);
1409 - await waitForAll(['A', 'Init B']);
1421 + await act(() => resolveFakeImport(ChildA));
1422 + assertLog(['A', 'Init B']);
1423
1411 - await resolveFakeImport(ChildB);
1412 - await waitForAll(['A', 'B', 'Did mount: A', 'Did mount: B']);
1424 + await act(() => resolveFakeImport(ChildB));
1425 + assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
1426 expect(root).toMatchRenderedOutput('AB');
1427
1428 // Swap the position of A and B
@@ -1423,10 +1436,10 @@ describe('ReactLazy', () => {
1436
1437 // The suspense boundary should've triggered now.
1438 expect(root).toMatchRenderedOutput('Loading...');
1426 - await resolveB2({default: ChildB});
1439 + await act(() => resolveB2({default: ChildB}));
1440
1441 // We need to flush to trigger the second one to load.
1429 - await waitForAll(['Init A2', 'b', 'a', 'Did mount: b', 'Did mount: a']);
1442 + assertLog(['Init A2', 'b', 'a', 'Did mount: b', 'Did mount: a']);
1443 expect(root).toMatchRenderedOutput('ba');
1444 });
1445
@@ -1552,12 +1565,11 @@ describe('ReactLazy', () => {
1565 await waitForAll(['Init A', 'Loading...']);
1566 expect(root).not.toMatchRenderedOutput('AB');
1567
1555 - await resolveFakeImport(ChildA);
1568 + await act(() => resolveFakeImport(ChildA));
1569 // We need to flush to trigger the B to load.
1557 - await waitForAll(['Init B']);
1558 - await resolveFakeImport(ChildB);
1559 -
1560 - await waitForAll(['A', 'B', 'Did mount: A', 'Did mount: B']);
1570 + await assertLog(['Init B']);
1571 + await act(() => resolveFakeImport(ChildB));
1572 + assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
1573 expect(root).toMatchRenderedOutput('AB');
1574
1575 // Swap the position of A and B
@@ -1565,12 +1577,11 @@ describe('ReactLazy', () => {
1577 root.update(<Parent swap={true} />);
1578 });
1579 await waitForAll(['Init B2', 'Loading...']);
1568 - await resolveFakeImport(ChildB2);
1580 + await act(() => resolveFakeImport(ChildB2));
1581 // We need to flush to trigger the second one to load.
1570 - await waitForAll(['Init A2', 'Loading...']);
1571 - await resolveFakeImport(ChildA2);
1572 -
1573 - await waitForAll(['b', 'a', 'Did update: b', 'Did update: a']);
1582 + assertLog(['Init A2', 'Loading...']);
1583 + await act(() => resolveFakeImport(ChildA2));
1584 + assertLog(['b', 'a', 'Did update: b', 'Did update: a']);
1585 expect(root).toMatchRenderedOutput('ba');
1586 });
1587
packages/react-reconciler/src/__tests__/ReactMemo-test.js
+32 -23
@@ -106,12 +106,14 @@ describe('memo', () => {
106 }
107 Counter = memo(Counter);
108
109 - ReactNoop.render(
110 - <Suspense fallback={<Text text="Loading..." />}>
111 - <Counter count={0} />
112 - </Suspense>,
109 + await act(() =>
110 + ReactNoop.render(
111 + <Suspense fallback={<Text text="Loading..." />}>
112 + <Counter count={0} />
113 + </Suspense>,
114 + ),
115 );
114 - await waitForAll(['Loading...', 0]);
116 + assertLog(['Loading...', 0]);
117 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
118
119 // Should bail out because props have not changed
@@ -163,8 +165,8 @@ describe('memo', () => {
165 }
166
167 const parent = React.createRef(null);
166 - ReactNoop.render(<Parent ref={parent} />);
167 - await waitForAll(['Loading...', 'Count: 0']);
168 + await act(() => ReactNoop.render(<Parent ref={parent} />));
169 + assertLog(['Loading...', 'Count: 0']);
170 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
171
172 // Should bail out because props have not changed
@@ -340,12 +342,14 @@ describe('memo', () => {
342 return oldProps.count === newProps.count;
343 });
344
343 - ReactNoop.render(
344 - <Suspense fallback={<Text text="Loading..." />}>
345 - <Counter count={0} />
346 - </Suspense>,
345 + await act(() =>
346 + ReactNoop.render(
347 + <Suspense fallback={<Text text="Loading..." />}>
348 + <Counter count={0} />
349 + </Suspense>,
350 + ),
351 );
348 - await waitForAll(['Loading...', 0]);
352 + assertLog(['Loading...', 0]);
353 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
354
355 // Should bail out because props have not changed
@@ -376,12 +380,14 @@ describe('memo', () => {
380 }
381 const Counter = memo(CounterInner);
382
379 - ReactNoop.render(
380 - <Suspense fallback={<Text text="Loading..." />}>
381 - <Counter count={0} />
382 - </Suspense>,
383 + await act(() =>
384 + ReactNoop.render(
385 + <Suspense fallback={<Text text="Loading..." />}>
386 + <Counter count={0} />
387 + </Suspense>,
388 + ),
389 );
384 - await waitForAll(['Loading...', '0!']);
390 + assertLog(['Loading...', '0!']);
391 expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
392
393 // Should bail out because props have not changed
@@ -427,13 +433,16 @@ describe('memo', () => {
433 };
434 // The final layer uses memo() from test fixture (which might be lazy).
435 Counter = memo(Counter);
430 - ReactNoop.render(
431 - <Suspense fallback={<Text text="Loading..." />}>
432 - <Counter e={5} />
433 - </Suspense>,
434 - );
436 +
437 await expect(async () => {
436 - await waitForAll(['Loading...', 15]);
438 + await act(() => {
439 + ReactNoop.render(
440 + <Suspense fallback={<Text text="Loading..." />}>
441 + <Counter e={5} />
442 + </Suspense>,
443 + );
444 + });
445 + assertLog(['Loading...', 15]);
446 }).toErrorDev([
447 'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
448 ]);
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+38 -39
@@ -172,14 +172,14 @@ describe('ReactSuspense', () => {
172
173 // Resolve first Suspense's promise and switch back to the normal view. The
174 // second Suspense should still show the placeholder
175 - await resolveText('A');
176 - await waitForAll(['A']);
175 + await act(() => resolveText('A'));
176 + assertLog(['A']);
177 expect(root).toMatchRenderedOutput('ALoading B...');
178
179 // Resolve the second Suspense's promise resolves and switche back to the
180 // normal view
181 - await resolveText('B');
182 - await waitForAll(['B']);
181 + await act(() => resolveText('B'));
182 + assertLog(['B']);
183 expect(root).toMatchRenderedOutput('AB');
184 });
185
@@ -294,13 +294,10 @@ describe('ReactSuspense', () => {
294 // showing the inner fallback hoping that B will resolve soon enough.
295 expect(root).toMatchRenderedOutput('Loading...');
296
297 + await act(() => resolveText('B'));
298 // By this point, B has resolved.
298 - // We're still showing the outer fallback.
299 - await resolveText('B');
300 - expect(root).toMatchRenderedOutput('Loading...');
301 - await waitForAll(['A', 'B']);
302 -
303 - // Then contents of both should pop in together.
299 + // The contents of both should pop in together.
300 + assertLog(['A', 'B']);
301 expect(root).toMatchRenderedOutput('AB');
302 });
303
@@ -337,8 +334,8 @@ describe('ReactSuspense', () => {
334 jest.advanceTimersByTime(500);
335 expect(root).toMatchRenderedOutput('ALoading more...');
336
340 - await resolveText('B');
341 - await waitForAll(['B']);
337 + await act(() => resolveText('B'));
338 + assertLog(['B']);
339 expect(root).toMatchRenderedOutput('AB');
340 });
341
@@ -475,15 +472,15 @@ describe('ReactSuspense', () => {
472 });
473 await waitForAll(['Suspend! [default]', 'Loading...']);
474
478 - await resolveText('default');
479 - await waitForAll(['default']);
475 + await act(() => resolveText('default'));
476 + assertLog(['default']);
477 expect(root).toMatchRenderedOutput('default');
478
479 await act(() => setValue('new value'));
480 assertLog(['Suspend! [new value]', 'Loading...']);
481
485 - await resolveText('new value');
486 - await waitForAll(['new value']);
482 + await act(() => resolveText('new value'));
483 + assertLog(['new value']);
484 expect(root).toMatchRenderedOutput('new value');
485 });
486
@@ -521,15 +518,15 @@ describe('ReactSuspense', () => {
518 });
519 await waitForAll(['Suspend! [default]', 'Loading...']);
520
524 - await resolveText('default');
525 - await waitForAll(['default']);
521 + await act(() => resolveText('default'));
522 + assertLog(['default']);
523 expect(root).toMatchRenderedOutput('default');
524
525 await act(() => setValue('new value'));
526 assertLog(['Suspend! [new value]', 'Loading...']);
527
531 - await resolveText('new value');
532 - await waitForAll(['new value']);
528 + await act(() => resolveText('new value'));
529 + assertLog(['new value']);
530 expect(root).toMatchRenderedOutput('new value');
531 });
532
@@ -565,15 +562,15 @@ describe('ReactSuspense', () => {
562 );
563 await waitForAll(['Suspend! [default]', 'Loading...']);
564
568 - await resolveText('default');
569 - await waitForAll(['default']);
565 + await act(() => resolveText('default'));
566 + assertLog(['default']);
567 expect(root).toMatchRenderedOutput('default');
568
569 await act(() => setValue('new value'));
570 assertLog(['Suspend! [new value]', 'Loading...']);
571
575 - await resolveText('new value');
576 - await waitForAll(['new value']);
572 + await act(() => resolveText('new value'));
573 + assertLog(['new value']);
574 expect(root).toMatchRenderedOutput('new value');
575 });
576
@@ -609,15 +606,15 @@ describe('ReactSuspense', () => {
606 );
607 await waitForAll(['Suspend! [default]', 'Loading...']);
608
612 - await resolveText('default');
613 - await waitForAll(['default']);
609 + await act(() => resolveText('default'));
610 + assertLog(['default']);
611 expect(root).toMatchRenderedOutput('default');
612
613 await act(() => setValue('new value'));
614 assertLog(['Suspend! [new value]', 'Loading...']);
615
619 - await resolveText('new value');
620 - await waitForAll(['new value']);
616 + await act(() => resolveText('new value'));
617 + assertLog(['new value']);
618 expect(root).toMatchRenderedOutput('new value');
619 });
620
@@ -662,8 +659,8 @@ describe('ReactSuspense', () => {
659 'destroy layout',
660 ]);
661
665 - await resolveText('Child 2');
666 - await waitForAll(['Child 1', 'Child 2', 'create layout']);
662 + await act(() => resolveText('Child 2'));
663 + assertLog(['Child 1', 'Child 2', 'create layout']);
664 expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
665 });
666
@@ -920,8 +917,8 @@ describe('ReactSuspense', () => {
917 // Initial render
918 await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
919
923 - await resolveText('Step: 1');
924 - await waitForAll(['Step: 1']);
920 + await act(() => resolveText('Step: 1'));
921 + assertLog(['Step: 1']);
922 expect(root).toMatchRenderedOutput('Step: 1');
923
924 // Update that suspends
@@ -936,9 +933,11 @@ describe('ReactSuspense', () => {
933 await waitForAll(['Suspend! [Step: 3]']);
934 expect(root).toMatchRenderedOutput('Loading...');
935
939 - await resolveText('Step: 2');
940 - await resolveText('Step: 3');
941 - await waitForAll(['Step: 3']);
936 + await act(() => {
937 + resolveText('Step: 2');
938 + resolveText('Step: 3');
939 + });
940 + assertLog(['Step: 3']);
941 expect(root).toMatchRenderedOutput('Step: 3');
942 });
943
@@ -996,8 +995,8 @@ describe('ReactSuspense', () => {
995 function App(props) {
996 return (
997 <Suspense fallback={<Text text="Loading..." />}>
999 - <AsyncText ms={1000} text="Child 1" />
1000 - <AsyncText ms={7000} text="Child 2" />
998 + <AsyncText text="Child 1" />
999 + <AsyncText text="Child 2" />
1000 </Suspense>
1001 );
1002 }
@@ -1012,8 +1011,8 @@ describe('ReactSuspense', () => {
1011
1012 jest.advanceTimersByTime(6000);
1013
1015 - await resolveText('Child 2');
1016 - await waitForAll(['Child 1', 'Child 2']);
1014 + await act(() => resolveText('Child 2'));
1015 + assertLog(['Child 1', 'Child 2']);
1016 expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
1017 });
1018
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+7 -14
@@ -12,6 +12,7 @@
12 let React;
13 let ReactNoop;
14 let waitForAll;
15 +let act;
16
17 describe('ReactSuspense', () => {
18 beforeEach(() => {
@@ -22,6 +23,7 @@ describe('ReactSuspense', () => {
23
24 const InternalTestUtils = require('internal-test-utils');
25 waitForAll = InternalTestUtils.waitForAll;
26 + act = InternalTestUtils.act;
27 });
28
29 function createThenable() {
@@ -90,7 +92,7 @@ describe('ReactSuspense', () => {
92 expect(ops).toEqual([new Set([promise])]);
93 ops = [];
94
93 - await resolve();
95 + await act(() => resolve());
96 await waitForAll([]);
97 expect(ReactNoop).toMatchRenderedOutput('Done');
98 expect(ops).toEqual([]);
@@ -129,14 +131,14 @@ describe('ReactSuspense', () => {
131 expect(ops).toEqual([new Set([promise1])]);
132 ops = [];
133
132 - await resolve1();
134 + await act(() => resolve1());
135 ReactNoop.render(element);
136 await waitForAll([]);
137 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
138 expect(ops).toEqual([new Set([promise2])]);
139 ops = [];
140
139 - await resolve2();
141 + await act(() => resolve2());
142 ReactNoop.render(element);
143 await waitForAll([]);
144 expect(ReactNoop).toMatchRenderedOutput('DoneDone');
@@ -218,23 +220,14 @@ describe('ReactSuspense', () => {
220 ops1 = [];
221 ops2 = [];
222
221 - await resolve1();
222 - ReactNoop.render(element);
223 - await waitForAll([]);
224 -
225 - // Force fallback to commit.
226 - // TODO: Should be able to use `act` here.
227 - jest.runAllTimers();
228 -
223 + await act(() => resolve1());
224 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2Done');
225 expect(ops1).toEqual([]);
226 expect(ops2).toEqual([new Set([promise2])]);
227 ops1 = [];
228 ops2 = [];
229
235 - await resolve2();
236 - ReactNoop.render(element);
237 - await waitForAll([]);
230 + await act(() => resolve2());
231 expect(ReactNoop).toMatchRenderedOutput('DoneDone');
232 expect(ops1).toEqual([]);
233 expect(ops2).toEqual([]);
packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemanticsDOM-test.js
+14 -14
@@ -187,8 +187,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
187 });
188 assertLog(['Loading...']);
189
190 - await resolveFakeImport(ChildA);
191 - await waitForAll(['A', 'Ref mount: A']);
190 + await act(() => resolveFakeImport(ChildA));
191 + assertLog(['A', 'Ref mount: A']);
192 expect(container.innerHTML).toBe('<span>A</span>');
193
194 // Swap the position of A and B
@@ -200,8 +200,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
200 '<span style="display: none;">A</span>Loading...',
201 );
202
203 - await resolveFakeImport(ChildB);
204 - await waitForAll(['B', 'Ref mount: B']);
203 + await act(() => resolveFakeImport(ChildB));
204 + assertLog(['B', 'Ref mount: B']);
205 expect(container.innerHTML).toBe('<span>B</span>');
206 });
207
@@ -247,8 +247,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
247 });
248 assertLog(['Loading...']);
249
250 - await resolveFakeImport(ChildA);
251 - await waitForAll(['A', 'Did mount: A']);
250 + await act(() => resolveFakeImport(ChildA));
251 + assertLog(['A', 'Did mount: A']);
252 expect(container.innerHTML).toBe('A');
253
254 // Swap the position of A and B
@@ -258,8 +258,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
258 assertLog(['Loading...', 'Will unmount: A']);
259 expect(container.innerHTML).toBe('Loading...');
260
261 - await resolveFakeImport(ChildB);
262 - await waitForAll(['B', 'Did mount: B']);
261 + await act(() => resolveFakeImport(ChildB));
262 + assertLog(['B', 'Did mount: B']);
263 expect(container.innerHTML).toBe('B');
264 });
265
@@ -299,8 +299,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
299 });
300 assertLog(['Loading...']);
301
302 - await resolveFakeImport(ChildA);
303 - await waitForAll(['A', 'Did mount: A']);
302 + await act(() => resolveFakeImport(ChildA));
303 + assertLog(['A', 'Did mount: A']);
304 expect(container.innerHTML).toBe('A');
305
306 // Swap the position of A and B
@@ -366,8 +366,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
366 });
367 assertLog(['Loading...']);
368
369 - await resolveFakeImport(ChildA);
370 - await waitForAll(['A', 'Ref mount: A']);
369 + await act(() => resolveFakeImport(ChildA));
370 + assertLog(['A', 'Ref mount: A']);
371 expect(container.innerHTML).toBe('<span>A</span>');
372
373 // Swap the position of A and B
@@ -429,8 +429,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
429 });
430 assertLog(['Loading...']);
431
432 - await resolveFakeImport(ChildA);
433 - await waitForAll(['A', 'Did mount: A']);
432 + await act(() => resolveFakeImport(ChildA));
433 + assertLog(['A', 'Did mount: A']);
434 expect(container.innerHTML).toBe('A');
435
436 // Swap the position of A and B
packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+140 -175
@@ -249,9 +249,8 @@ describe('ReactSuspenseList', () => {
249 </>,
250 );
251
252 - await C.resolve();
253 -
254 - await waitForAll(['C']);
252 + await act(() => C.resolve());
253 + assertLog(['C']);
254
255 expect(ReactNoop).toMatchRenderedOutput(
256 <>
@@ -261,9 +260,8 @@ describe('ReactSuspenseList', () => {
260 </>,
261 );
262
264 - await B.resolve();
265 -
266 - await waitForAll(['B']);
263 + await act(() => B.resolve());
264 + assertLog(['B']);
265
266 expect(ReactNoop).toMatchRenderedOutput(
267 <>
@@ -384,9 +382,8 @@ describe('ReactSuspenseList', () => {
382 </>,
383 );
384
387 - await B.resolve();
388 -
389 - await waitForAll(['A', 'B', 'Suspend! [C]']);
385 + await act(() => B.resolve());
386 + assertLog(['A', 'B', 'Suspend! [C]']);
387
388 expect(ReactNoop).toMatchRenderedOutput(
389 <>
@@ -396,9 +393,8 @@ describe('ReactSuspenseList', () => {
393 </>,
394 );
395
399 - await C.resolve();
400 -
401 - await waitForAll(['A', 'B', 'C']);
396 + await act(() => C.resolve());
397 + assertLog(['A', 'B', 'C']);
398
399 expect(ReactNoop).toMatchRenderedOutput(
400 <>
@@ -462,9 +458,8 @@ describe('ReactSuspenseList', () => {
458 </>,
459 );
460
465 - await B.resolve();
466 -
467 - await waitForAll(['A', 'B', 'Suspend! [C]']);
461 + await act(() => B.resolve());
462 + assertLog(['A', 'B', 'Suspend! [C]']);
463
464 expect(ReactNoop).toMatchRenderedOutput(
465 <>
@@ -478,9 +473,8 @@ describe('ReactSuspenseList', () => {
473 </>,
474 );
475
481 - await C.resolve();
482 -
483 - await waitForAll(['A', 'B', 'C']);
476 + await act(() => C.resolve());
477 + assertLog(['A', 'B', 'C']);
478
479 expect(ReactNoop).toMatchRenderedOutput(
480 <>
@@ -544,9 +538,8 @@ describe('ReactSuspenseList', () => {
538 </>,
539 );
540
547 - await C.resolve();
548 -
549 - await waitForAll(['A', 'B', 'C']);
541 + await act(() => C.resolve());
542 + assertLog(['A', 'B', 'C']);
543
544 expect(ReactNoop).toMatchRenderedOutput(
545 <>
@@ -604,9 +597,8 @@ describe('ReactSuspenseList', () => {
597 </>,
598 );
599
607 - await C.resolve();
608 -
609 - await waitForAll(['A', 'B', 'C']);
600 + await act(() => C.resolve());
601 + assertLog(['A', 'B', 'C']);
602
603 expect(ReactNoop).toMatchRenderedOutput(
604 <>
@@ -667,8 +659,8 @@ describe('ReactSuspenseList', () => {
659 <span>Loading B</span>
660 </>,
661 );
670 - await B.resolve();
671 - await waitForAll(['A', 'B']);
662 + await act(() => B.resolve());
663 + assertLog(['A', 'B']);
664 expect(ReactNoop).toMatchRenderedOutput(
665 <>
666 <span>A</span>
@@ -692,8 +684,8 @@ describe('ReactSuspenseList', () => {
684 <span>Loading D</span>
685 </>,
686 );
695 - await D.resolve();
696 - await waitForAll(['C', 'D']);
687 + await act(() => D.resolve());
688 + assertLog(['C', 'D']);
689 expect(ReactNoop).toMatchRenderedOutput(
690 <>
691 <span>C</span>
@@ -742,9 +734,8 @@ describe('ReactSuspenseList', () => {
734
735 expect(ReactNoop).toMatchRenderedOutput(<span>Loading</span>);
736
745 - await A.resolve();
746 -
747 - await waitForAll(['A']);
737 + await act(() => A.resolve());
738 + assertLog(['A']);
739
740 expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
741
@@ -775,9 +766,8 @@ describe('ReactSuspenseList', () => {
766 </>,
767 );
768
778 - await B.resolve();
779 -
780 - await waitForAll(['B', 'Suspend! [C]']);
769 + await act(() => B.resolve());
770 + assertLog(['B', 'Suspend! [C]']);
771
772 // Even though we could now show B, we're still waiting on C.
773 expect(ReactNoop).toMatchRenderedOutput(
@@ -788,9 +778,8 @@ describe('ReactSuspenseList', () => {
778 </>,
779 );
780
791 - await C.resolve();
792 -
793 - await waitForAll(['B', 'C']);
781 + await act(() => C.resolve());
782 + assertLog(['B', 'C']);
783
784 expect(ReactNoop).toMatchRenderedOutput(
785 <>
@@ -838,9 +827,8 @@ describe('ReactSuspenseList', () => {
827
828 expect(ReactNoop).toMatchRenderedOutput(null);
829
841 - await A.resolve();
842 -
843 - await waitForAll(['A']);
830 + await act(() => A.resolve());
831 + assertLog(['A']);
832
833 expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
834
@@ -865,16 +853,14 @@ describe('ReactSuspenseList', () => {
853 // A is already showing content so it doesn't turn into a fallback.
854 expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
855
868 - await B.resolve();
869 -
870 - await waitForAll(['B', 'Suspend! [C]']);
856 + await act(() => B.resolve());
857 + assertLog(['B', 'Suspend! [C]']);
858
859 // Even though we could now show B, we're still waiting on C.
860 expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
861
875 - await C.resolve();
876 -
877 - await waitForAll(['B', 'C']);
862 + await act(() => C.resolve());
863 + assertLog(['B', 'C']);
864
865 expect(ReactNoop).toMatchRenderedOutput(
866 <>
@@ -921,9 +907,8 @@ describe('ReactSuspenseList', () => {
907 </>,
908 );
909
924 - await A.resolve();
925 -
926 - await waitForAll(['A', 'Suspend! [B]']);
910 + await act(() => A.resolve());
911 + assertLog(['A', 'Suspend! [B]']);
912
913 expect(ReactNoop).toMatchRenderedOutput(
914 <>
@@ -933,9 +918,8 @@ describe('ReactSuspenseList', () => {
918 </>,
919 );
920
936 - await B.resolve();
937 -
938 - await waitForAll(['B', 'C']);
921 + await act(() => B.resolve());
922 + assertLog(['B', 'C']);
923
924 expect(ReactNoop).toMatchRenderedOutput(
925 <>
@@ -982,9 +966,8 @@ describe('ReactSuspenseList', () => {
966 </>,
967 );
968
985 - await C.resolve();
986 -
987 - await waitForAll(['C', 'Suspend! [B]']);
969 + await act(() => C.resolve());
970 + assertLog(['C', 'Suspend! [B]']);
971
972 expect(ReactNoop).toMatchRenderedOutput(
973 <>
@@ -994,9 +977,8 @@ describe('ReactSuspenseList', () => {
977 </>,
978 );
979
997 - await B.resolve();
998 -
999 - await waitForAll(['B', 'A']);
980 + await act(() => B.resolve());
981 + assertLog(['B', 'A']);
982
983 expect(ReactNoop).toMatchRenderedOutput(
984 <>
@@ -1089,9 +1071,8 @@ describe('ReactSuspenseList', () => {
1071 </>,
1072 );
1073
1092 - await A.resolve();
1093 -
1094 - await waitForAll(['A', 'Suspend! [C]']);
1074 + await act(() => A.resolve());
1075 + assertLog(['A', 'Suspend! [C]']);
1076
1077 // Even though we could show A, it is still in a fallback state because
1078 // C is not yet resolved. We need to resolve everything in the head first.
@@ -1106,9 +1087,8 @@ describe('ReactSuspenseList', () => {
1087 </>,
1088 );
1089
1109 - await C.resolve();
1110 -
1111 - await waitForAll(['A', 'C', 'Suspend! [E]']);
1090 + await act(() => C.resolve());
1091 + assertLog(['A', 'C', 'Suspend! [E]']);
1092
1093 // We can now resolve the full head.
1094 expect(ReactNoop).toMatchRenderedOutput(
@@ -1122,9 +1102,8 @@ describe('ReactSuspenseList', () => {
1102 </>,
1103 );
1104
1125 - await E.resolve();
1126 -
1127 - await waitForAll(['E', 'Suspend! [F]']);
1105 + await act(() => E.resolve());
1106 + assertLog(['E', 'Suspend! [F]']);
1107
1108 // In the tail we can resolve one-by-one.
1109 expect(ReactNoop).toMatchRenderedOutput(
@@ -1138,7 +1117,18 @@ describe('ReactSuspenseList', () => {
1117 </>,
1118 );
1119
1141 - await F.resolve();
1120 + await act(() => F.resolve());
1121 + assertLog(['F']);
1122 + expect(ReactNoop).toMatchRenderedOutput(
1123 + <>
1124 + <span>A</span>
1125 + <span>B</span>
1126 + <span>C</span>
1127 + <span>D</span>
1128 + <span>E</span>
1129 + <span>F</span>
1130 + </>,
1131 + );
1132
1133 // We can also delete some items.
1134 ReactNoop.render(
@@ -1296,9 +1286,8 @@ describe('ReactSuspenseList', () => {
1286 </>,
1287 );
1288
1299 - await D.resolve();
1300 -
1301 - await waitForAll(['D', 'F', 'Suspend! [B]']);
1289 + await act(() => D.resolve());
1290 + assertLog(['D', 'F', 'Suspend! [B]']);
1291
1292 // We can now resolve the full head.
1293 expect(ReactNoop).toMatchRenderedOutput(
@@ -1314,9 +1303,8 @@ describe('ReactSuspenseList', () => {
1303 </>,
1304 );
1305
1317 - await B.resolve();
1318 -
1319 - await waitForAll(['B', 'Suspend! [A]']);
1306 + await act(() => B.resolve());
1307 + assertLog(['B', 'Suspend! [A]']);
1308
1309 // In the tail we can resolve one-by-one.
1310 expect(ReactNoop).toMatchRenderedOutput(
@@ -1331,9 +1319,8 @@ describe('ReactSuspenseList', () => {
1319 </>,
1320 );
1321
1334 - await A.resolve();
1335 -
1336 - await waitForAll(['A']);
1322 + await act(() => A.resolve());
1323 + assertLog(['A']);
1324
1325 expect(ReactNoop).toMatchRenderedOutput(
1326 <>
@@ -1366,47 +1353,43 @@ describe('ReactSuspenseList', () => {
1353 }
1354
1355 // This render is only CPU bound. Nothing suspends.
1369 - if (gate(flags => flags.enableSyncDefaultUpdates)) {
1356 + await act(async () => {
1357 React.startTransition(() => {
1358 ReactNoop.render(<Foo />);
1359 });
1373 - } else {
1374 - ReactNoop.render(<Foo />);
1375 - }
1360
1377 - await waitFor(['A']);
1361 + await waitFor(['A']);
1362
1379 - Scheduler.unstable_advanceTime(200);
1380 - jest.advanceTimersByTime(200);
1363 + Scheduler.unstable_advanceTime(200);
1364 + jest.advanceTimersByTime(200);
1365
1382 - await waitFor(['B']);
1366 + await waitFor(['B']);
1367
1384 - Scheduler.unstable_advanceTime(300);
1385 - jest.advanceTimersByTime(300);
1368 + Scheduler.unstable_advanceTime(300);
1369 + jest.advanceTimersByTime(300);
1370
1387 - // We've still not been able to show anything on the screen even though
1388 - // we have two items ready.
1389 - expect(ReactNoop).toMatchRenderedOutput(null);
1371 + // We've still not been able to show anything on the screen even though
1372 + // we have two items ready.
1373 + expect(ReactNoop).toMatchRenderedOutput(null);
1374
1391 - // Time has now elapsed for so long that we're just going to give up
1392 - // rendering the rest of the content. So that we can at least show
1393 - // something.
1394 - await waitFor([
1395 - 'Loading C',
1396 - 'C', // I'll flush through into the next render so that the first commits.
1397 - ]);
1398 -
1399 - expect(ReactNoop).toMatchRenderedOutput(
1400 - <>
1401 - <span>A</span>
1402 - <span>B</span>
1403 - <span>Loading C</span>
1404 - </>,
1405 - );
1375 + // Time has now elapsed for so long that we're just going to give up
1376 + // rendering the rest of the content. So that we can at least show
1377 + // something.
1378 + await waitFor([
1379 + 'Loading C',
1380 + 'C', // I'll flush through into the next render so that the first commits.
1381 + ]);
1382
1383 + expect(ReactNoop).toMatchRenderedOutput(
1384 + <>
1385 + <span>A</span>
1386 + <span>B</span>
1387 + <span>Loading C</span>
1388 + </>,
1389 + );
1390 + });
1391 // Then we do a second pass to commit the last item.
1408 - await waitForAll([]);
1409 -
1392 + assertLog([]);
1393 expect(ReactNoop).toMatchRenderedOutput(
1394 <>
1395 <span>A</span>
@@ -1473,9 +1456,8 @@ describe('ReactSuspenseList', () => {
1456 </>,
1457 );
1458
1476 - await C.resolve();
1477 -
1478 - await waitForAll(['C']);
1459 + await act(() => C.resolve());
1460 + await assertLog(['C']);
1461
1462 expect(ReactNoop).toMatchRenderedOutput(
1463 <>
@@ -1554,47 +1536,43 @@ describe('ReactSuspenseList', () => {
1536 }
1537
1538 // This render is only CPU bound. Nothing suspends.
1557 - if (gate(flags => flags.enableSyncDefaultUpdates)) {
1539 + await act(async () => {
1540 React.startTransition(() => {
1541 ReactNoop.render(<Foo />);
1542 });
1561 - } else {
1562 - ReactNoop.render(<Foo />);
1563 - }
1543
1565 - await waitFor(['A']);
1544 + await waitFor(['A']);
1545
1567 - Scheduler.unstable_advanceTime(200);
1568 - jest.advanceTimersByTime(200);
1546 + Scheduler.unstable_advanceTime(200);
1547 + jest.advanceTimersByTime(200);
1548
1570 - await waitFor(['B']);
1549 + await waitFor(['B']);
1550
1572 - Scheduler.unstable_advanceTime(300);
1573 - jest.advanceTimersByTime(300);
1551 + Scheduler.unstable_advanceTime(300);
1552 + jest.advanceTimersByTime(300);
1553
1575 - // We've still not been able to show anything on the screen even though
1576 - // we have two items ready.
1577 - expect(ReactNoop).toMatchRenderedOutput(null);
1554 + // We've still not been able to show anything on the screen even though
1555 + // we have two items ready.
1556 + expect(ReactNoop).toMatchRenderedOutput(null);
1557
1579 - // Time has now elapsed for so long that we're just going to give up
1580 - // rendering the rest of the content. So that we can at least show
1581 - // something.
1582 - await waitFor([
1583 - 'Loading C',
1584 - 'C', // I'll flush through into the next render so that the first commits.
1585 - ]);
1586 -
1587 - expect(ReactNoop).toMatchRenderedOutput(
1588 - <>
1589 - <span>A</span>
1590 - <span>B</span>
1591 - <span>Loading C</span>
1592 - </>,
1593 - );
1558 + // Time has now elapsed for so long that we're just going to give up
1559 + // rendering the rest of the content. So that we can at least show
1560 + // something.
1561 + await waitFor([
1562 + 'Loading C',
1563 + 'C', // I'll flush through into the next render so that the first commits.
1564 + ]);
1565
1566 + expect(ReactNoop).toMatchRenderedOutput(
1567 + <>
1568 + <span>A</span>
1569 + <span>B</span>
1570 + <span>Loading C</span>
1571 + </>,
1572 + );
1573 + });
1574 // Then we do a second pass to commit the last two items.
1596 - await waitForAll(['D']);
1597 -
1575 + assertLog(['D']);
1576 expect(ReactNoop).toMatchRenderedOutput(
1577 <>
1578 <span>A</span>
@@ -2073,9 +2051,8 @@ describe('ReactSuspenseList', () => {
2051
2052 expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
2053
2076 - await B.resolve();
2077 -
2078 - await waitForAll(['B', 'Suspend! [C]', 'Loading C']);
2054 + await act(() => B.resolve());
2055 + assertLog(['B', 'Suspend! [C]', 'Loading C']);
2056
2057 // Incremental loading is suspended.
2058 jest.advanceTimersByTime(500);
@@ -2087,9 +2064,8 @@ describe('ReactSuspenseList', () => {
2064 </>,
2065 );
2066
2090 - await C.resolve();
2091 -
2092 - await waitForAll(['C']);
2067 + await act(() => C.resolve());
2068 + assertLog(['C']);
2069
2070 expect(ReactNoop).toMatchRenderedOutput(
2071 <>
@@ -2149,9 +2125,8 @@ describe('ReactSuspenseList', () => {
2125 </>,
2126 );
2127
2152 - await B.resolve();
2153 -
2154 - await waitForAll(['A', 'B', 'C', 'D']);
2128 + await act(() => B.resolve());
2129 + assertLog(['A', 'B', 'C', 'D']);
2130
2131 expect(ReactNoop).toMatchRenderedOutput(
2132 <>
@@ -2191,9 +2166,8 @@ describe('ReactSuspenseList', () => {
2166
2167 expect(ReactNoop).toMatchRenderedOutput(<span>Loading C</span>);
2168
2194 - await B.resolve();
2195 -
2196 - await waitForAll(['A', 'B', 'C']);
2169 + await act(() => B.resolve());
2170 + assertLog(['A', 'B', 'C']);
2171
2172 expect(ReactNoop).toMatchRenderedOutput(
2173 <>
@@ -2252,9 +2226,8 @@ describe('ReactSuspenseList', () => {
2226 </>,
2227 );
2228
2255 - await B.resolve();
2256 -
2257 - await waitForAll(['B']);
2229 + await act(() => B.resolve());
2230 + assertLog(['B']);
2231
2232 expect(ReactNoop).toMatchRenderedOutput(
2233 <>
@@ -2385,9 +2358,8 @@ describe('ReactSuspenseList', () => {
2358 </>,
2359 );
2360
2388 - await AsyncB.resolve();
2389 -
2390 - await waitForAll(['B']);
2361 + await act(() => AsyncB.resolve());
2362 + assertLog(['B']);
2363
2364 expect(ReactNoop).toMatchRenderedOutput(
2365 <>
@@ -2474,9 +2446,8 @@ describe('ReactSuspenseList', () => {
2446 </>,
2447 );
2448
2477 - await AsyncB.resolve();
2478 -
2479 - await waitForAll(['B']);
2449 + await act(() => AsyncB.resolve());
2450 + assertLog(['B']);
2451
2452 expect(ReactNoop).toMatchRenderedOutput(
2453 <>
@@ -2555,9 +2526,8 @@ describe('ReactSuspenseList', () => {
2526 expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
2527 });
2528
2558 - await AsyncA.resolve();
2559 -
2560 - await waitForAll(['A', 'B', 'C', 'D']);
2529 + await act(() => AsyncA.resolve());
2530 + assertLog(['A', 'B', 'C', 'D']);
2531
2532 expect(ReactNoop).toMatchRenderedOutput(
2533 <>
@@ -2916,9 +2886,8 @@ describe('ReactSuspenseList', () => {
2886 // treeBaseDuration
2887 expect(onRender.mock.calls[2][3]).toBe(1 + 4 + 3 + 3);
2888
2919 - await C.resolve();
2920 -
2921 - await waitForAll(['C', 'Suspend! [D]']);
2889 + await act(() => C.resolve());
2890 + assertLog(['C', 'Suspend! [D]']);
2891 expect(ReactNoop).toMatchRenderedOutput(
2892 <>
2893 <span>A</span>
@@ -3003,10 +2972,8 @@ describe('ReactSuspenseList', () => {
2972 </>,
2973 );
2974
3006 - await A.resolve();
3007 -
3008 - await waitForAll(['A', 'Suspend! [B]']);
3009 -
2975 + await act(() => A.resolve());
2976 + assertLog(['A', 'Suspend! [B]']);
2977 expect(ReactNoop).toMatchRenderedOutput(
2978 <>
2979 <span>A</span>
@@ -3015,10 +2982,8 @@ describe('ReactSuspenseList', () => {
2982 </>,
2983 );
2984
3018 - await B.resolve();
3019 -
3020 - await waitForAll(['B', 'C']);
3021 -
2985 + await act(() => B.resolve());
2986 + assertLog(['B', 'C']);
2987 expect(ReactNoop).toMatchRenderedOutput(
2988 <>
2989 <span>A</span>
packages/react-reconciler/src/__tests__/ReactSuspensePlaceholder-test.internal.js
+29 -25
@@ -19,6 +19,7 @@ let TextResource;
19 let textResourceShouldFail;
20 let waitForAll;
21 let assertLog;
22 +let act;
23
24 describe('ReactSuspensePlaceholder', () => {
25 beforeEach(() => {
@@ -39,6 +40,7 @@ describe('ReactSuspensePlaceholder', () => {
40 const InternalTestUtils = require('internal-test-utils');
41 waitForAll = InternalTestUtils.waitForAll;
42 assertLog = InternalTestUtils.assertLog;
43 + act = InternalTestUtils.act;
44
45 TextResource = ReactCache.unstable_createResource(
46 ([text, ms = 0]) => {
@@ -137,10 +139,8 @@ describe('ReactSuspensePlaceholder', () => {
139 await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
140 expect(ReactNoop).toMatchRenderedOutput('Loading...');
141
140 - jest.advanceTimersByTime(1000);
141 - assertLog(['Promise resolved [B]']);
142 -
143 - await waitForAll(['A', 'B', 'C']);
142 + await act(() => jest.advanceTimersByTime(1000));
143 + assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
144
145 expect(ReactNoop).toMatchRenderedOutput(
146 <>
@@ -167,9 +167,8 @@ describe('ReactSuspensePlaceholder', () => {
167 );
168
169 // Resolve the promise
170 - jest.advanceTimersByTime(1000);
171 - assertLog(['Promise resolved [B2]']);
172 - await waitForAll(['B2', 'C']);
170 + await act(() => jest.advanceTimersByTime(1000));
171 + assertLog(['Promise resolved [B2]', 'B2', 'C']);
172
173 // Render the final update. A should still be hidden, because it was
174 // given a `hidden` prop.
@@ -200,9 +199,8 @@ describe('ReactSuspensePlaceholder', () => {
199
200 expect(ReactNoop).not.toMatchRenderedOutput('ABC');
201
203 - jest.advanceTimersByTime(1000);
204 - assertLog(['Promise resolved [B]']);
205 - await waitForAll(['A', 'B', 'C']);
202 + await act(() => jest.advanceTimersByTime(1000));
203 + assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
204 expect(ReactNoop).toMatchRenderedOutput('ABC');
205
206 // Update
@@ -214,9 +212,8 @@ describe('ReactSuspensePlaceholder', () => {
212 expect(ReactNoop).toMatchRenderedOutput('Loading...');
213
214 // Resolve the promise
217 - jest.advanceTimersByTime(1000);
218 - assertLog(['Promise resolved [B2]']);
219 - await waitForAll(['A', 'B2', 'C']);
215 + await act(() => jest.advanceTimersByTime(1000));
216 + assertLog(['Promise resolved [B2]', 'A', 'B2', 'C']);
217
218 // Render the final update. A should still be hidden, because it was
219 // given a `hidden` prop.
@@ -245,9 +242,8 @@ describe('ReactSuspensePlaceholder', () => {
242
243 expect(ReactNoop).toMatchRenderedOutput(<uppercase>LOADING...</uppercase>);
244
248 - jest.advanceTimersByTime(1000);
249 - assertLog(['Promise resolved [b]']);
250 - await waitForAll(['a', 'b', 'c']);
245 + await act(() => jest.advanceTimersByTime(1000));
246 + assertLog(['Promise resolved [b]', 'a', 'b', 'c']);
247 expect(ReactNoop).toMatchRenderedOutput(<uppercase>ABC</uppercase>);
248
249 // Update
@@ -259,9 +255,8 @@ describe('ReactSuspensePlaceholder', () => {
255 expect(ReactNoop).toMatchRenderedOutput(<uppercase>LOADING...</uppercase>);
256
257 // Resolve the promise
262 - jest.advanceTimersByTime(1000);
263 - assertLog(['Promise resolved [b2]']);
264 - await waitForAll(['a', 'b2', 'c']);
258 + await act(() => jest.advanceTimersByTime(1000));
259 + assertLog(['Promise resolved [b2]', 'a', 'b2', 'c']);
260
261 // Render the final update. A should still be hidden, because it was
262 // given a `hidden` prop.
@@ -358,9 +353,13 @@ describe('ReactSuspensePlaceholder', () => {
353 expect(onRender.mock.calls[0][3]).toBe(10);
354
355 // Resolve the pending promise.
361 - jest.advanceTimersByTime(1000);
362 - assertLog(['Promise resolved [Loaded]']);
363 - await waitForAll(['Suspending', 'Loaded', 'Text']);
356 + await act(() => jest.advanceTimersByTime(1000));
357 + assertLog([
358 + 'Promise resolved [Loaded]',
359 + 'Suspending',
360 + 'Loaded',
361 + 'Text',
362 + ]);
363 expect(ReactNoop).toMatchRenderedOutput('LoadedText');
364 expect(onRender).toHaveBeenCalledTimes(2);
365
@@ -531,9 +530,14 @@ describe('ReactSuspensePlaceholder', () => {
530 expect(onRender).toHaveBeenCalledTimes(3);
531
532 // Resolve the pending promise.
534 - jest.advanceTimersByTime(100);
535 - assertLog(['Promise resolved [Loaded]', 'Promise resolved [Sibling]']);
536 - await waitForAll(['Suspending', 'Loaded', 'New', 'Sibling']);
533 + await act(async () => {
534 + jest.advanceTimersByTime(100);
535 + assertLog([
536 + 'Promise resolved [Loaded]',
537 + 'Promise resolved [Sibling]',
538 + ]);
539 + await waitForAll(['Suspending', 'Loaded', 'New', 'Sibling']);
540 + });
541 expect(onRender).toHaveBeenCalledTimes(4);
542
543 // When the suspending data is resolved and our final UI is rendered,
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+13 -17
@@ -481,7 +481,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
481 return (
482 <Suspense fallback={<Text text="Loading..." />}>
483 <ErrorBoundary ref={errorBoundary}>
484 - <AsyncText text="Result" ms={3000} />
484 + <AsyncText text="Result" />
485 </ErrorBoundary>
486 </Suspense>
487 );
@@ -491,9 +491,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
491 await waitForAll(['Suspend! [Result]', 'Loading...']);
492 expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
493
494 - await rejectText('Result', new Error('Failed to load: Result'));
495 -
496 - await waitForAll([
494 + await act(() => rejectText('Result', new Error('Failed to load: Result')));
495 + assertLog([
496 'Error! [Result]',
497
498 // React retries one more time
@@ -3565,20 +3564,17 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3564 );
3565
3566 await resolveText('A1');
3568 - await waitFor([
3569 - 'A1',
3570 - 'Suspend! [A2]',
3571 - 'Loading...',
3572 - 'Suspend! [B2]',
3573 - 'Loading...',
3574 - ]);
3575 - expect(root).toMatchRenderedOutput(
3576 - <>
3577 - <span prop="A1" />
3578 - <span prop="B" />
3579 - </>,
3580 - );
3567 + await waitFor(['A1']);
3568 + });
3569 + assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
3570 + expect(root).toMatchRenderedOutput(
3571 + <>
3572 + <span prop="A1" />
3573 + <span prop="B" />
3574 + </>,
3575 + );
3576
3577 + await act(async () => {
3578 await resolveText('A2');
3579 await resolveText('B2');
3580 });