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
});