Codemod tests to waitFor pattern (8/?) (#26308)
This converts some of our test suite to use the `waitFor` test pattern, instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of these changes are automated with jscodeshift, with some slight manual cleanup in certain cases. See #26285 for full context.
Andrew Clark committed
Mar 4, 2023 at 18:04 UTC
64dde70827117fbe15df2a05c618d01d81fcbbf6
17 files changed
+591
-557
packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+30
-35
@@ -17,6 +17,8 @@ describe('Timeline profiler', () => {
17
let ReactDOMClient;
18
let Scheduler;
19
let utils;
20
+ let assertLog;
21
+ let waitFor;
22
23
describe('User Timing API', () => {
24
let clearedMarks;
@@ -82,6 +84,10 @@ describe('Timeline profiler', () => {
84
ReactDOMClient = require('react-dom/client');
85
Scheduler = require('scheduler');
86
87
+ const InternalTestUtils = require('internal-test-utils');
88
+ assertLog = InternalTestUtils.assertLog;
89
+ waitFor = InternalTestUtils.waitFor;
90
+
91
setPerformanceMock =
92
require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
93
setPerformanceMock(createUserTimingPolyfill());
@@ -1372,28 +1378,29 @@ describe('Timeline profiler', () => {
1378
<Yield id="B" value={1} />
1379
</>,
1380
);
1375
- expect(Scheduler).toFlushAndYieldThrough(['A:1']);
1381
+ });
1382
1377
- testMarks.push(...createUserTimingData(clearedMarks));
1378
- clearPendingMarks();
1383
+ await waitFor(['A:1']);
1384
1380
- // Advance the clock some more to make the pending React update seem long.
1381
- startTime += 20000;
1385
+ testMarks.push(...createUserTimingData(clearedMarks));
1386
+ clearPendingMarks();
1387
1383
- // Fake a long "click" event in the middle
1384
- // and schedule a sync update that will also flush the previous work.
1385
- testMarks.push(createNativeEventEntry('click', 25000));
1386
- ReactDOM.flushSync(() => {
1387
- root.render(
1388
- <>
1389
- <Yield id="A" value={2} />
1390
- <Yield id="B" value={2} />
1391
- </>,
1392
- );
1393
- });
1388
+ // Advance the clock some more to make the pending React update seem long.
1389
+ startTime += 20000;
1390
+
1391
+ // Fake a long "click" event in the middle
1392
+ // and schedule a sync update that will also flush the previous work.
1393
+ testMarks.push(createNativeEventEntry('click', 25000));
1394
+ ReactDOM.flushSync(() => {
1395
+ root.render(
1396
+ <>
1397
+ <Yield id="A" value={2} />
1398
+ <Yield id="B" value={2} />
1399
+ </>,
1400
+ );
1401
});
1402
1396
- expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
1403
+ assertLog(['A:2', 'B:2']);
1404
1405
testMarks.push(...createUserTimingData(clearedMarks));
1406
@@ -1424,10 +1431,7 @@ describe('Timeline profiler', () => {
1431
root.render(<Component />);
1432
});
1433
1427
- expect(Scheduler).toHaveYielded([
1428
- 'Component mount',
1429
- 'Component update',
1430
- ]);
1434
+ assertLog(['Component mount', 'Component update']);
1435
1436
const data = await preprocessData([
1437
...createBoilerplateEntries(),
@@ -1463,10 +1467,7 @@ describe('Timeline profiler', () => {
1467
root.render(<Component />);
1468
});
1469
1466
- expect(Scheduler).toHaveYielded([
1467
- 'Component mount',
1468
- 'Component update',
1469
- ]);
1470
+ assertLog(['Component mount', 'Component update']);
1471
1472
const data = await preprocessData([
1473
...createBoilerplateEntries(),
@@ -1504,10 +1505,7 @@ describe('Timeline profiler', () => {
1505
root.render(<Component />);
1506
});
1507
1507
- expect(Scheduler).toHaveYielded([
1508
- 'Component mount',
1509
- 'Component update',
1510
- ]);
1508
+ assertLog(['Component mount', 'Component update']);
1509
1510
const testMarks = [];
1511
clearedMarks.forEach(markName => {
@@ -1567,10 +1565,7 @@ describe('Timeline profiler', () => {
1565
root.render(<Component />);
1566
});
1567
1570
- expect(Scheduler).toHaveYielded([
1571
- 'Component mount',
1572
- 'Component update',
1573
- ]);
1568
+ assertLog(['Component mount', 'Component update']);
1569
1570
const testMarks = [];
1571
clearedMarks.forEach(markName => {
@@ -1639,7 +1634,7 @@ describe('Timeline profiler', () => {
1634
root.render(<Component />);
1635
});
1636
1642
- expect(Scheduler).toHaveYielded([
1637
+ assertLog([
1638
'Component rendered with value 0',
1639
'Component rendered with value 0',
1640
'Component rendered with value 1',
@@ -1708,7 +1703,7 @@ describe('Timeline profiler', () => {
1703
root.render(<Component />);
1704
});
1705
1711
- expect(Scheduler).toHaveYielded([
1706
+ assertLog([
1707
'Component rendered with value 0 and deferredValue 0',
1708
'Component rendered with value 1 and deferredValue 0',
1709
'Component rendered with value 1 and deferredValue 1',
packages/react-dom/src/__tests__/ReactDOMFizzShellHydration-test.js
+14
-10
@@ -23,6 +23,7 @@ let buffer = '';
23
let hasErrored = false;
24
let fatalError = undefined;
25
let textCache;
26
+let assertLog;
27
28
describe('ReactDOMFizzShellHydration', () => {
29
beforeEach(() => {
@@ -35,6 +36,9 @@ describe('ReactDOMFizzShellHydration', () => {
36
ReactDOMFizzServer = require('react-dom/server');
37
Stream = require('stream');
38
39
+ const InternalTestUtils = require('internal-test-utils');
40
+ assertLog = InternalTestUtils.assertLog;
41
+
42
startTransition = React.startTransition;
43
44
textCache = new Map();
@@ -180,7 +184,7 @@ describe('ReactDOMFizzShellHydration', () => {
184
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
185
pipe(writable);
186
});
183
- expect(Scheduler).toHaveYielded(['Shell']);
187
+ assertLog(['Shell']);
188
const dehydratedDiv = container.getElementsByTagName('div')[0];
189
190
// Clear the cache and start rendering on the client
@@ -190,7 +194,7 @@ describe('ReactDOMFizzShellHydration', () => {
194
await clientAct(async () => {
195
ReactDOMClient.hydrateRoot(container, <App />);
196
});
193
- expect(Scheduler).toHaveYielded(['Suspend! [Shell]']);
197
+ assertLog(['Suspend! [Shell]']);
198
expect(div.current).toBe(null);
199
expect(container.textContent).toBe('Shell');
200
@@ -198,7 +202,7 @@ describe('ReactDOMFizzShellHydration', () => {
202
await clientAct(async () => {
203
await resolveText('Shell');
204
});
201
- expect(Scheduler).toHaveYielded(['Shell']);
205
+ assertLog(['Shell']);
206
expect(div.current).toBe(dehydratedDiv);
207
expect(container.textContent).toBe('Shell');
208
});
@@ -213,12 +217,12 @@ describe('ReactDOMFizzShellHydration', () => {
217
await clientAct(async () => {
218
root.render(<App />);
219
});
216
- expect(Scheduler).toHaveYielded(['Suspend! [Shell]']);
220
+ assertLog(['Suspend! [Shell]']);
221
222
await clientAct(async () => {
223
await resolveText('Shell');
224
});
221
- expect(Scheduler).toHaveYielded(['Shell']);
225
+ assertLog(['Shell']);
226
expect(container.textContent).toBe('Shell');
227
});
228
@@ -236,7 +240,7 @@ describe('ReactDOMFizzShellHydration', () => {
240
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
241
pipe(writable);
242
});
239
- expect(Scheduler).toHaveYielded(['Initial']);
243
+ assertLog(['Initial']);
244
245
await clientAct(async () => {
246
const root = ReactDOMClient.hydrateRoot(container, <App />);
@@ -246,7 +250,7 @@ describe('ReactDOMFizzShellHydration', () => {
250
root.render(<Text text="Updated" />);
251
});
252
});
249
- expect(Scheduler).toHaveYielded(['Initial', 'Updated']);
253
+ assertLog(['Initial', 'Updated']);
254
expect(container.textContent).toBe('Updated');
255
},
256
);
@@ -262,7 +266,7 @@ describe('ReactDOMFizzShellHydration', () => {
266
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
267
pipe(writable);
268
});
265
- expect(Scheduler).toHaveYielded(['Shell']);
269
+ assertLog(['Shell']);
270
271
// Clear the cache and start rendering on the client
272
resetTextCache();
@@ -275,13 +279,13 @@ describe('ReactDOMFizzShellHydration', () => {
279
},
280
});
281
});
278
- expect(Scheduler).toHaveYielded(['Suspend! [Shell]']);
282
+ assertLog(['Suspend! [Shell]']);
283
expect(container.textContent).toBe('Shell');
284
285
await clientAct(async () => {
286
root.render(<Text text="New screen" />);
287
});
284
- expect(Scheduler).toHaveYielded([
288
+ assertLog([
289
'New screen',
290
'This root received an early update, before anything was able ' +
291
'hydrate. Switched the entire root to client rendering.',
packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
+120
-119
@@ -16,6 +16,8 @@ let getCacheForType;
16
let caches;
17
let seededCache;
18
let ErrorBoundary;
19
+let waitForAll;
20
+let assertLog;
21
22
// TODO: These tests don't pass in persistent mode yet. Need to implement.
23
@@ -31,6 +33,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
33
34
getCacheForType = React.unstable_getCacheForType;
35
36
+ const InternalTestUtils = require('internal-test-utils');
37
+ waitForAll = InternalTestUtils.waitForAll;
38
+ assertLog = InternalTestUtils.assertLog;
39
+
40
caches = [];
41
seededCache = null;
42
@@ -256,7 +262,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
262
</App>,
263
);
264
});
259
- expect(Scheduler).toHaveYielded([
265
+ assertLog([
266
'App render',
267
'Text:Inside:Before render',
268
'Suspend:Async',
@@ -281,7 +287,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
287
await act(async () => {
288
await resolveText('Async');
289
});
284
- expect(Scheduler).toHaveYielded([
290
+ assertLog([
291
'Text:Inside:Before render',
292
'AsyncText:Async render',
293
'ClassText:Inside:After render',
@@ -305,7 +311,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
311
await act(async () => {
312
ReactNoop.render(null);
313
});
308
- expect(Scheduler).toHaveYielded([
314
+ assertLog([
315
'App destroy layout',
316
'Text:Inside:Before destroy layout',
317
'AsyncText:Async destroy layout',
@@ -377,7 +383,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
383
</App>,
384
);
385
});
380
- expect(Scheduler).toHaveYielded([
386
+ assertLog([
387
'App render',
388
'Text:Inside:Before render',
389
'Suspend:Async',
@@ -407,7 +413,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
413
await act(async () => {
414
await resolveText('Async');
415
});
410
- expect(Scheduler).toHaveYielded([
416
+ assertLog([
417
'AsyncText:Async render',
418
'Text:Fallback destroy layout',
419
'AsyncText:Async create layout',
@@ -426,7 +432,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
432
await act(async () => {
433
ReactNoop.renderLegacySyncRoot(null);
434
});
429
- expect(Scheduler).toHaveYielded([
435
+ assertLog([
436
'App destroy layout',
437
'Text:Inside:Before destroy layout',
438
'AsyncText:Async destroy layout',
@@ -474,7 +480,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
480
act(() => {
481
ReactNoop.renderLegacySyncRoot(<App />);
482
});
477
- expect(Scheduler).toHaveYielded([
483
+ assertLog([
484
'App render',
485
'Text:Inside:Before render',
486
'Text:Inside:After render',
@@ -504,7 +510,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
510
</App>,
511
);
512
});
507
- expect(Scheduler).toHaveYielded([
513
+ assertLog([
514
'App render',
515
'Text:Inside:Before render',
516
'Suspend:Async',
@@ -526,7 +532,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
532
await advanceTimers(1000);
533
534
// Noop since sync root has already committed
529
- expect(Scheduler).toHaveYielded([]);
535
+ assertLog([]);
536
expect(ReactNoop).toMatchRenderedOutput(
537
<>
538
<span prop="Inside:Before" hidden={true} />
@@ -540,7 +546,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
546
await act(async () => {
547
await resolveText('Async');
548
});
543
- expect(Scheduler).toHaveYielded([
549
+ assertLog([
550
'AsyncText:Async render',
551
'Text:Fallback destroy layout',
552
'AsyncText:Async create layout',
@@ -559,7 +565,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
565
await act(async () => {
566
ReactNoop.renderLegacySyncRoot(null);
567
});
562
- expect(Scheduler).toHaveYielded([
568
+ assertLog([
569
'App destroy layout',
570
'Text:Inside:Before destroy layout',
571
'AsyncText:Async destroy layout',
@@ -604,7 +610,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
610
await act(async () => {
611
ReactNoop.render(<App />);
612
});
607
- expect(Scheduler).toHaveYielded([
613
+ assertLog([
614
'App render',
615
'Text:Inside:Before render',
616
'Text:Inside:After render',
@@ -634,7 +640,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
640
</App>,
641
);
642
});
637
- expect(Scheduler).toHaveYielded([
643
+ assertLog([
644
'App render',
645
'Text:Inside:Before render',
646
'Suspend:Async',
@@ -653,12 +659,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
659
await advanceTimers(1000);
660
661
// Timing out should commit the fallback and destroy inner layout effects.
656
- expect(Scheduler).toHaveYielded([
662
+ assertLog([
663
'Text:Inside:Before destroy layout',
664
'Text:Inside:After destroy layout',
665
'Text:Fallback create layout',
666
]);
661
- expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
667
+ await waitForAll(['Text:Fallback create passive']);
668
expect(ReactNoop).toMatchRenderedOutput(
669
<>
670
<span prop="Inside:Before" hidden={true} />
@@ -672,7 +678,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
678
await act(async () => {
679
await resolveText('Async');
680
});
675
- expect(Scheduler).toHaveYielded([
681
+ assertLog([
682
'Text:Inside:Before render',
683
'AsyncText:Async render',
684
'Text:Inside:After render',
@@ -695,7 +701,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
701
await act(async () => {
702
ReactNoop.render(null);
703
});
698
- expect(Scheduler).toHaveYielded([
704
+ assertLog([
705
'App destroy layout',
706
'Text:Inside:Before destroy layout',
707
'AsyncText:Async destroy layout',
@@ -763,7 +769,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
769
await act(async () => {
770
ReactNoop.render(<App />);
771
});
766
- expect(Scheduler).toHaveYielded([
772
+ assertLog([
773
'App render',
774
'ClassText:Inside:Before render',
775
'ClassText:Inside:After render',
@@ -790,7 +796,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
796
</App>,
797
);
798
});
793
- expect(Scheduler).toHaveYielded([
799
+ assertLog([
800
'App render',
801
'ClassText:Inside:Before render',
802
'Suspend:Async',
@@ -809,7 +815,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
815
await advanceTimers(1000);
816
817
// Timing out should commit the fallback and destroy inner layout effects.
812
- expect(Scheduler).toHaveYielded([
818
+ assertLog([
819
'ClassText:Inside:Before componentWillUnmount',
820
'ClassText:Inside:After componentWillUnmount',
821
'ClassText:Fallback componentDidMount',
@@ -828,7 +834,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
834
await act(async () => {
835
await resolveText('Async');
836
});
831
- expect(Scheduler).toHaveYielded([
837
+ assertLog([
838
'ClassText:Inside:Before render',
839
'AsyncText:Async render',
840
'ClassText:Inside:After render',
@@ -849,7 +855,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
855
await act(async () => {
856
ReactNoop.render(null);
857
});
852
- expect(Scheduler).toHaveYielded([
858
+ assertLog([
859
'App destroy layout',
860
'ClassText:Inside:Before componentWillUnmount',
861
'AsyncText:Async destroy layout',
@@ -890,7 +896,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
896
await act(async () => {
897
ReactNoop.render(<App />);
898
});
893
- expect(Scheduler).toHaveYielded([
899
+ assertLog([
900
'App render',
901
'Text:Outer render',
902
'Text:Inner render',
@@ -915,7 +921,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
921
</App>,
922
);
923
});
918
- expect(Scheduler).toHaveYielded([
924
+ assertLog([
925
'App render',
926
'Suspend:Async',
927
'Text:Outer render',
@@ -931,12 +937,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
937
await advanceTimers(1000);
938
939
// Timing out should commit the fallback and destroy inner layout effects.
934
- expect(Scheduler).toHaveYielded([
940
+ assertLog([
941
'Text:Outer destroy layout',
942
'Text:Inner destroy layout',
943
'Text:Fallback create layout',
944
]);
939
- expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
945
+ await waitForAll(['Text:Fallback create passive']);
946
expect(ReactNoop).toMatchRenderedOutput(
947
<>
948
<span hidden={true} prop="Outer">
@@ -950,7 +956,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
956
await act(async () => {
957
await resolveText('Async');
958
});
953
- expect(Scheduler).toHaveYielded([
959
+ assertLog([
960
'AsyncText:Async render',
961
'Text:Outer render',
962
'Text:Inner render',
@@ -973,7 +979,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
979
await act(async () => {
980
ReactNoop.render(null);
981
});
976
- expect(Scheduler).toHaveYielded([
982
+ assertLog([
983
'App destroy layout',
984
'AsyncText:Async destroy layout',
985
'Text:Outer destroy layout',
@@ -1017,7 +1023,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1023
await act(async () => {
1024
ReactNoop.render(<App />);
1025
});
1020
- expect(Scheduler).toHaveYielded([
1026
+ assertLog([
1027
'App render',
1028
'Text:Outer render',
1029
'Text:MemoizedInner render',
@@ -1042,7 +1048,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1048
</App>,
1049
);
1050
});
1045
- expect(Scheduler).toHaveYielded([
1051
+ assertLog([
1052
'App render',
1053
'Suspend:Async',
1054
'Text:Outer render',
@@ -1059,12 +1065,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
1065
1066
// Timing out should commit the fallback and destroy inner layout effects.
1067
// Even though the innermost layout effects are beneath a hidden HostComponent.
1062
- expect(Scheduler).toHaveYielded([
1068
+ assertLog([
1069
'Text:Outer destroy layout',
1070
'Text:MemoizedInner destroy layout',
1071
'Text:Fallback create layout',
1072
]);
1067
- expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
1073
+ await waitForAll(['Text:Fallback create passive']);
1074
expect(ReactNoop).toMatchRenderedOutput(
1075
<>
1076
<span hidden={true} prop="Outer">
@@ -1078,7 +1084,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1084
await act(async () => {
1085
await resolveText('Async');
1086
});
1081
- expect(Scheduler).toHaveYielded([
1087
+ assertLog([
1088
'AsyncText:Async render',
1089
'Text:Outer render',
1090
'Text:Fallback destroy layout',
@@ -1100,7 +1106,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1106
await act(async () => {
1107
ReactNoop.render(null);
1108
});
1103
- expect(Scheduler).toHaveYielded([
1109
+ assertLog([
1110
'App destroy layout',
1111
'AsyncText:Async destroy layout',
1112
'Text:Outer destroy layout',
@@ -1131,7 +1137,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1137
await act(async () => {
1138
ReactNoop.render(<App />);
1139
});
1134
- expect(Scheduler).toHaveYielded([
1140
+ assertLog([
1141
'Text:Outer render',
1142
'Text:Inner render',
1143
'Text:Outer create layout',
@@ -1153,7 +1159,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1159
);
1160
});
1161
await advanceTimers(1000);
1156
- expect(Scheduler).toHaveYielded([
1162
+ assertLog([
1163
'Text:Outer render',
1164
'Text:Inner render',
1165
'Suspend:InnerAsync_1',
@@ -1161,7 +1167,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1167
'Text:Inner destroy layout',
1168
'Text:InnerFallback create layout',
1169
]);
1164
- expect(Scheduler).toFlushAndYield(['Text:InnerFallback create passive']);
1170
+ await waitForAll(['Text:InnerFallback create passive']);
1171
expect(ReactNoop).toMatchRenderedOutput(
1172
<>
1173
<span prop="Outer" />
@@ -1181,7 +1187,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1187
);
1188
});
1189
await advanceTimers(1000);
1184
- expect(Scheduler).toHaveYielded([
1190
+ assertLog([
1191
'Text:Outer render',
1192
'Suspend:OuterAsync_1',
1193
'Text:Inner render',
@@ -1192,7 +1198,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1198
'Text:InnerFallback destroy layout',
1199
'Text:OuterFallback create layout',
1200
]);
1195
- expect(Scheduler).toFlushAndYield(['Text:OuterFallback create passive']);
1201
+ await waitForAll(['Text:OuterFallback create passive']);
1202
expect(ReactNoop).toMatchRenderedOutput(
1203
<>
1204
<span prop="Outer" hidden={true} />
@@ -1206,7 +1212,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1212
await act(async () => {
1213
await resolveText('InnerAsync_1');
1214
});
1209
- expect(Scheduler).toHaveYielded([
1215
+ assertLog([
1216
'Text:Outer render',
1217
'Suspend:OuterAsync_1',
1218
'Text:Inner render',
@@ -1231,7 +1237,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1237
);
1238
});
1239
await advanceTimers(1000);
1234
- expect(Scheduler).toHaveYielded([
1240
+ assertLog([
1241
'Text:Outer render',
1242
'Suspend:OuterAsync_1',
1243
'Text:Inner render',
@@ -1252,7 +1258,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1258
await act(async () => {
1259
await resolveText('OuterAsync_1');
1260
});
1255
- expect(Scheduler).toHaveYielded([
1261
+ assertLog([
1262
'Text:Outer render',
1263
'AsyncText:OuterAsync_1 render',
1264
'Text:Inner render',
@@ -1278,7 +1284,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1284
await act(async () => {
1285
await resolveText('InnerAsync_2');
1286
});
1281
- expect(Scheduler).toHaveYielded([
1287
+ assertLog([
1288
'Text:Inner render',
1289
'AsyncText:InnerAsync_2 render',
1290
'Text:InnerFallback destroy layout',
@@ -1306,7 +1312,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1312
);
1313
});
1314
await advanceTimers(1000);
1309
- expect(Scheduler).toHaveYielded([
1315
+ assertLog([
1316
'Text:Outer render',
1317
'Suspend:OuterAsync_2',
1318
'Text:Inner render',
@@ -1332,7 +1338,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1338
await act(async () => {
1339
await resolveText('OuterAsync_2');
1340
});
1335
- expect(Scheduler).toHaveYielded([
1341
+ assertLog([
1342
'Text:OuterFallback create passive',
1343
'Text:Outer render',
1344
'AsyncText:OuterAsync_2 render',
@@ -1374,7 +1380,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1380
await act(async () => {
1381
ReactNoop.render(<App />);
1382
});
1377
- expect(Scheduler).toHaveYielded([
1383
+ assertLog([
1384
'Text:Outer render',
1385
'Text:Inner render',
1386
'Text:Outer create layout',
@@ -1396,7 +1402,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1402
);
1403
});
1404
await advanceTimers(1000);
1399
- expect(Scheduler).toHaveYielded([
1405
+ assertLog([
1406
'Text:Outer render',
1407
'Text:Inner render',
1408
'Suspend:InnerAsync_1',
@@ -1404,7 +1410,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1410
'Text:Inner destroy layout',
1411
'Text:InnerFallback create layout',
1412
]);
1407
- expect(Scheduler).toFlushAndYield(['Text:InnerFallback create passive']);
1413
+ await waitForAll(['Text:InnerFallback create passive']);
1414
expect(ReactNoop).toMatchRenderedOutput(
1415
<>
1416
<span prop="Outer" />
@@ -1424,7 +1430,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1430
);
1431
});
1432
await advanceTimers(1000);
1427
- expect(Scheduler).toHaveYielded([
1433
+ assertLog([
1434
'Text:Outer render',
1435
'Suspend:OuterAsync_1',
1436
'Text:Inner render',
@@ -1435,7 +1441,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1441
'Text:InnerFallback destroy layout',
1442
'Text:OuterFallback create layout',
1443
]);
1438
- expect(Scheduler).toFlushAndYield(['Text:OuterFallback create passive']);
1444
+ await waitForAll(['Text:OuterFallback create passive']);
1445
expect(ReactNoop).toMatchRenderedOutput(
1446
<>
1447
<span prop="Outer" hidden={true} />
@@ -1450,7 +1456,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1456
await resolveText('OuterAsync_1');
1457
await resolveText('InnerAsync_1');
1458
});
1453
- expect(Scheduler).toHaveYielded([
1459
+ assertLog([
1460
'Text:Outer render',
1461
'AsyncText:OuterAsync_1 render',
1462
'Text:Inner render',
@@ -1502,7 +1508,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1508
await act(async () => {
1509
ReactNoop.render(<App />);
1510
});
1505
- expect(Scheduler).toHaveYielded([
1511
+ assertLog([
1512
'Text:Inside render',
1513
'Text:Outside render',
1514
'Text:Inside create layout',
@@ -1523,7 +1529,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1529
<App outerChildren={<AsyncText text="OutsideAsync" ms={1000} />} />,
1530
);
1531
});
1526
- expect(Scheduler).toHaveYielded([
1532
+ assertLog([
1533
'Text:Inside render',
1534
'Suspend:OutsideAsync',
1535
'Text:Fallback:Inside render',
@@ -1539,12 +1545,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
1545
1546
// Timing out should commit the fallback and destroy inner layout effects.
1547
await advanceTimers(1000);
1542
- expect(Scheduler).toHaveYielded([
1548
+ assertLog([
1549
'Text:Inside destroy layout',
1550
'Text:Fallback:Inside create layout',
1551
'Text:Fallback:Outside create layout',
1552
]);
1547
- expect(Scheduler).toFlushAndYield([
1553
+ await waitForAll([
1554
'Text:Fallback:Inside create passive',
1555
'Text:Fallback:Outside create passive',
1556
]);
@@ -1566,7 +1572,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1572
/>,
1573
);
1574
});
1569
- expect(Scheduler).toHaveYielded([
1575
+ assertLog([
1576
'Text:Inside render',
1577
'Suspend:OutsideAsync',
1578
'Text:Fallback:Inside render',
@@ -1586,13 +1592,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
1592
1593
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
1594
await advanceTimers(1000);
1589
- expect(Scheduler).toHaveYielded([
1595
+ assertLog([
1596
'Text:Fallback:Inside destroy layout',
1597
'Text:Fallback:Fallback create layout',
1598
]);
1593
- expect(Scheduler).toFlushAndYield([
1594
- 'Text:Fallback:Fallback create passive',
1595
- ]);
1599
+ await waitForAll(['Text:Fallback:Fallback create passive']);
1600
expect(ReactNoop).toMatchRenderedOutput(
1601
<>
1602
<span prop="Inside" hidden={true} />
@@ -1608,7 +1612,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1612
await resolveText('FallbackAsync');
1613
await resolveText('OutsideAsync');
1614
});
1611
- expect(Scheduler).toHaveYielded([
1615
+ assertLog([
1616
'Text:Inside render',
1617
'AsyncText:OutsideAsync render',
1618
'Text:Fallback:Fallback destroy layout',
@@ -1656,7 +1660,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1660
await act(async () => {
1661
ReactNoop.render(<App />);
1662
});
1659
- expect(Scheduler).toHaveYielded([
1663
+ assertLog([
1664
'Text:Inside render',
1665
'Text:Outside render',
1666
'Text:Inside create layout',
@@ -1681,7 +1685,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1685
);
1686
});
1687
await advanceTimers(1000);
1684
- expect(Scheduler).toHaveYielded([
1688
+ assertLog([
1689
'Text:Inside render',
1690
'Suspend:OutsideAsync',
1691
'Text:Fallback:Inside render',
@@ -1693,7 +1697,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1697
'Text:Fallback:Fallback create layout',
1698
'Text:Fallback:Outside create layout',
1699
]);
1696
- expect(Scheduler).toFlushAndYield([
1700
+ await waitForAll([
1701
'Text:Fallback:Fallback create passive',
1702
'Text:Fallback:Outside create passive',
1703
]);
@@ -1710,7 +1714,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1714
await act(async () => {
1715
await resolveText('FallbackAsync');
1716
});
1713
- expect(Scheduler).toHaveYielded([
1717
+ assertLog([
1718
'Text:Fallback:Inside render',
1719
'AsyncText:FallbackAsync render',
1720
'Text:Fallback:Fallback destroy layout',
@@ -1734,7 +1738,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1738
await act(async () => {
1739
await resolveText('OutsideAsync');
1740
});
1737
- expect(Scheduler).toHaveYielded([
1741
+ assertLog([
1742
'Text:Inside render',
1743
'AsyncText:OutsideAsync render',
1744
'Text:Fallback:Inside destroy layout',
@@ -1780,7 +1784,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1784
await act(async () => {
1785
ReactNoop.render(<App shouldSuspend={false} />);
1786
});
1783
- expect(Scheduler).toHaveYielded([
1787
+ assertLog([
1788
'Text:Inside render',
1789
'Text:Outside render',
1790
'Text:Inside create layout',
@@ -1800,7 +1804,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1804
act(() => {
1805
ReactNoop.render(<App shouldSuspend={true} />);
1806
});
1803
- expect(Scheduler).toHaveYielded([
1807
+ assertLog([
1808
'Suspend:Suspend',
1809
'Text:Fallback render',
1810
'Text:Outside render',
@@ -1814,11 +1818,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
1818
1819
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
1820
await advanceTimers(1000);
1817
- expect(Scheduler).toHaveYielded([
1818
- 'Text:Inside destroy layout',
1819
- 'Text:Fallback create layout',
1820
- ]);
1821
- expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
1821
+ assertLog(['Text:Inside destroy layout', 'Text:Fallback create layout']);
1822
+ await waitForAll(['Text:Fallback create passive']);
1823
expect(ReactNoop).toMatchRenderedOutput(
1824
<>
1825
<span prop="Inside" hidden={true} />
@@ -1831,7 +1832,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1832
await act(async () => {
1833
await resolveText('Suspend');
1834
});
1834
- expect(Scheduler).toHaveYielded([
1835
+ assertLog([
1836
'Text:Inside render',
1837
'Text:Fallback destroy layout',
1838
'Text:Inside create layout',
@@ -1895,7 +1896,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1896
</ErrorBoundary>,
1897
);
1898
});
1898
- expect(Scheduler).toHaveYielded([
1899
+ assertLog([
1900
'ErrorBoundary render: try',
1901
'App render',
1902
'ThrowsInDidMount render',
@@ -1926,7 +1927,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1927
</ErrorBoundary>,
1928
);
1929
});
1929
- expect(Scheduler).toHaveYielded([
1930
+ assertLog([
1931
'ErrorBoundary render: try',
1932
'App render',
1933
'Suspend:Async',
@@ -1953,7 +1954,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
1954
await act(async () => {
1955
await resolveText('Async');
1956
});
1956
- expect(Scheduler).toHaveYielded([
1957
+ assertLog([
1958
'AsyncText:Async render',
1959
'ThrowsInDidMount render',
1960
'Text:Inside render',
@@ -2035,7 +2036,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2036
</ErrorBoundary>,
2037
);
2038
});
2038
- expect(Scheduler).toHaveYielded([
2039
+ assertLog([
2040
'ErrorBoundary render: try',
2041
'App render',
2042
'ThrowsInWillUnmount render',
@@ -2066,7 +2067,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2067
</ErrorBoundary>,
2068
);
2069
});
2069
- expect(Scheduler).toHaveYielded([
2070
+ assertLog([
2071
'ErrorBoundary render: try',
2072
'App render',
2073
'Suspend:Async',
@@ -2151,7 +2152,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2152
</ErrorBoundary>,
2153
);
2154
});
2154
- expect(Scheduler).toHaveYielded([
2155
+ assertLog([
2156
'ErrorBoundary render: try',
2157
'App render',
2158
'ThrowsInLayoutEffect render',
@@ -2182,7 +2183,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2183
</ErrorBoundary>,
2184
);
2185
});
2185
- expect(Scheduler).toHaveYielded([
2186
+ assertLog([
2187
'ErrorBoundary render: try',
2188
'App render',
2189
'Suspend:Async',
@@ -2209,7 +2210,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2210
await act(async () => {
2211
await resolveText('Async');
2212
});
2212
- expect(Scheduler).toHaveYielded([
2213
+ assertLog([
2214
'AsyncText:Async render',
2215
'ThrowsInLayoutEffect render',
2216
'Text:Inside render',
@@ -2290,7 +2291,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2291
</ErrorBoundary>,
2292
);
2293
});
2293
- expect(Scheduler).toHaveYielded([
2294
+ assertLog([
2295
'ErrorBoundary render: try',
2296
'App render',
2297
'ThrowsInLayoutEffectDestroy render',
@@ -2321,7 +2322,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2322
</ErrorBoundary>,
2323
);
2324
});
2324
- expect(Scheduler).toHaveYielded([
2325
+ assertLog([
2326
'ErrorBoundary render: try',
2327
'App render',
2328
'Suspend:Async',
@@ -2394,7 +2395,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2395
await act(async () => {
2396
ReactNoop.render(<App />);
2397
});
2397
- expect(Scheduler).toHaveYielded([
2398
+ assertLog([
2399
'Text:Function render',
2400
'ClassText:Class render',
2401
'Text:Function create layout',
@@ -2417,7 +2418,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2418
</App>,
2419
);
2420
});
2420
- expect(Scheduler).toHaveYielded([
2421
+ assertLog([
2422
'Text:Function render',
2423
'Suspend:Async_1',
2424
'Suspend:Async_2',
@@ -2434,7 +2435,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2435
await advanceTimers(1000);
2436
2437
// Timing out should commit the fallback and destroy inner layout effects.
2437
- expect(Scheduler).toHaveYielded([
2438
+ assertLog([
2439
'Text:Function destroy layout',
2440
'ClassText:Class componentWillUnmount',
2441
'ClassText:Fallback componentDidMount',
@@ -2451,7 +2452,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2452
await act(async () => {
2453
await resolveText('Async_1');
2454
});
2454
- expect(Scheduler).toHaveYielded([
2455
+ assertLog([
2456
'Text:Function render',
2457
'AsyncText:Async_1 render',
2458
'Suspend:Async_2',
@@ -2469,7 +2470,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2470
await act(async () => {
2471
await resolveText('Async_2');
2472
});
2472
- expect(Scheduler).toHaveYielded([
2473
+ assertLog([
2474
'Text:Function render',
2475
'AsyncText:Async_1 render',
2476
'AsyncText:Async_2 render',
@@ -2494,7 +2495,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2495
await act(async () => {
2496
ReactNoop.render(null);
2497
});
2497
- expect(Scheduler).toHaveYielded([
2498
+ assertLog([
2499
'Text:Function destroy layout',
2500
'AsyncText:Async_1 destroy layout',
2501
'AsyncText:Async_2 destroy layout',
@@ -2552,7 +2553,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2553
await act(async () => {
2554
ReactNoop.render(<App />);
2555
});
2555
- expect(Scheduler).toHaveYielded([
2556
+ assertLog([
2557
'Text:Function render',
2558
'Suspender "null" render',
2559
'ClassText:Class render',
@@ -2573,7 +2574,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2574
act(() => {
2575
ReactNoop.render(<App />);
2576
});
2576
- expect(Scheduler).toHaveYielded([
2577
+ assertLog([
2578
'Text:Function render',
2579
'Suspender "A" render',
2580
'Suspend:A',
@@ -2591,7 +2592,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2592
await advanceTimers(1000);
2593
2594
// Timing out should commit the fallback and destroy inner layout effects.
2594
- expect(Scheduler).toHaveYielded([
2595
+ assertLog([
2596
'Text:Function destroy layout',
2597
'ClassText:Class componentWillUnmount',
2598
'ClassText:Fallback componentDidMount',
@@ -2610,7 +2611,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2611
await act(async () => {
2612
await resolveText('A');
2613
});
2613
- expect(Scheduler).toHaveYielded([
2614
+ assertLog([
2615
'Text:Function render',
2616
'Suspender "B" render',
2617
'Suspend:B',
@@ -2629,7 +2630,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2630
await act(async () => {
2631
await resolveText('B');
2632
});
2632
- expect(Scheduler).toHaveYielded([
2633
+ assertLog([
2634
'Text:Function render',
2635
'Suspender "B" render',
2636
'ClassText:Class render',
@@ -2648,7 +2649,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2649
await act(async () => {
2650
ReactNoop.render(null);
2651
});
2651
- expect(Scheduler).toHaveYielded([
2652
+ assertLog([
2653
'Text:Function destroy layout',
2654
'ClassText:Class componentWillUnmount',
2655
'Text:Function destroy passive',
@@ -2740,7 +2741,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2741
act(() => {
2742
ReactNoop.renderLegacySyncRoot(<App />);
2743
});
2743
- expect(Scheduler).toHaveYielded([
2744
+ assertLog([
2745
'App render',
2746
'RefCheckerOuter render',
2747
'ClassComponent:refObject render',
@@ -2761,7 +2762,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2762
);
2763
});
2764
await advanceTimers(1000);
2764
- expect(Scheduler).toHaveYielded([
2765
+ assertLog([
2766
'App render',
2767
'Suspend:Async',
2768
'RefCheckerOuter render',
@@ -2779,7 +2780,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2780
await act(async () => {
2781
await resolveText('Async');
2782
});
2782
- expect(Scheduler).toHaveYielded([
2783
+ assertLog([
2784
'AsyncText:Async render',
2785
'Text:Fallback destroy layout',
2786
'AsyncText:Async create layout',
@@ -2791,7 +2792,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2792
await act(async () => {
2793
ReactNoop.renderLegacySyncRoot(null);
2794
});
2794
- expect(Scheduler).toHaveYielded([
2795
+ assertLog([
2796
'AsyncText:Async destroy layout',
2797
'RefCheckerOuter destroy layout refObject? true refCallback? true',
2798
'RefCheckerInner:refObject destroy layout ref? false',
@@ -2818,7 +2819,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2819
await act(async () => {
2820
ReactNoop.render(<App />);
2821
});
2821
- expect(Scheduler).toHaveYielded([
2822
+ assertLog([
2823
'App render',
2824
'RefCheckerOuter render',
2825
'RefCheckerInner:refObject render',
@@ -2842,7 +2843,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2843
);
2844
});
2845
await advanceTimers(1000);
2845
- expect(Scheduler).toHaveYielded([
2846
+ assertLog([
2847
'App render',
2848
'Suspend:Async',
2849
'RefCheckerOuter render',
@@ -2867,7 +2868,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2868
await act(async () => {
2869
await resolveText('Async');
2870
});
2870
- expect(Scheduler).toHaveYielded([
2871
+ assertLog([
2872
'Text:Fallback create passive',
2873
'AsyncText:Async render',
2874
'RefCheckerOuter render',
@@ -2893,7 +2894,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2894
await act(async () => {
2895
ReactNoop.render(null);
2896
});
2896
- expect(Scheduler).toHaveYielded([
2897
+ assertLog([
2898
'AsyncText:Async destroy layout',
2899
'RefCheckerOuter destroy layout refObject? true refCallback? true',
2900
'RefCheckerInner:refObject destroy layout ref? false',
@@ -2929,7 +2930,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2930
await act(async () => {
2931
ReactNoop.render(<App />);
2932
});
2932
- expect(Scheduler).toHaveYielded([
2933
+ assertLog([
2934
'App render',
2935
'RefCheckerOuter render',
2936
'ClassComponent:refObject render',
@@ -2950,7 +2951,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2951
);
2952
});
2953
await advanceTimers(1000);
2953
- expect(Scheduler).toHaveYielded([
2954
+ assertLog([
2955
'App render',
2956
'Suspend:Async',
2957
'RefCheckerOuter render',
@@ -2971,7 +2972,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2972
await act(async () => {
2973
await resolveText('Async');
2974
});
2974
- expect(Scheduler).toHaveYielded([
2975
+ assertLog([
2976
'Text:Fallback create passive',
2977
'AsyncText:Async render',
2978
'RefCheckerOuter render',
@@ -2993,7 +2994,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
2994
await act(async () => {
2995
ReactNoop.render(null);
2996
});
2996
- expect(Scheduler).toHaveYielded([
2997
+ assertLog([
2998
'AsyncText:Async destroy layout',
2999
'RefCheckerOuter destroy layout refObject? true refCallback? true',
3000
'RefCheckerInner:refObject destroy layout ref? false',
@@ -3033,7 +3034,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3034
await act(async () => {
3035
ReactNoop.render(<App />);
3036
});
3036
- expect(Scheduler).toHaveYielded([
3037
+ assertLog([
3038
'App render',
3039
'RefCheckerOuter render',
3040
'FunctionComponent render',
@@ -3054,7 +3055,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3055
);
3056
});
3057
await advanceTimers(1000);
3057
- expect(Scheduler).toHaveYielded([
3058
+ assertLog([
3059
'App render',
3060
'Suspend:Async',
3061
'RefCheckerOuter render',
@@ -3075,7 +3076,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3076
await act(async () => {
3077
await resolveText('Async');
3078
});
3078
- expect(Scheduler).toHaveYielded([
3079
+ assertLog([
3080
'Text:Fallback create passive',
3081
'AsyncText:Async render',
3082
'RefCheckerOuter render',
@@ -3097,7 +3098,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3098
await act(async () => {
3099
ReactNoop.render(null);
3100
});
3100
- expect(Scheduler).toHaveYielded([
3101
+ assertLog([
3102
'AsyncText:Async destroy layout',
3103
'RefCheckerOuter destroy layout refObject? true refCallback? true',
3104
'RefCheckerInner:refObject destroy layout ref? false',
@@ -3152,7 +3153,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3153
await act(async () => {
3154
ReactNoop.render(<App />);
3155
});
3155
- expect(Scheduler).toHaveYielded([
3156
+ assertLog([
3157
'App render',
3158
'RefChecker render',
3159
'RefChecker create layout ref? true',
@@ -3167,7 +3168,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3168
);
3169
});
3170
await advanceTimers(1000);
3170
- expect(Scheduler).toHaveYielded([
3171
+ assertLog([
3172
'App render',
3173
'Suspend:Async',
3174
'RefChecker render',
@@ -3181,7 +3182,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3182
await act(async () => {
3183
await resolveText('Async');
3184
});
3184
- expect(Scheduler).toHaveYielded([
3185
+ assertLog([
3186
'Text:Fallback create passive',
3187
'AsyncText:Async render',
3188
'RefChecker render',
@@ -3196,7 +3197,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3197
await act(async () => {
3198
ReactNoop.render(null);
3199
});
3199
- expect(Scheduler).toHaveYielded([
3200
+ assertLog([
3201
'App destroy layout ref? true',
3202
'AsyncText:Async destroy layout',
3203
'RefChecker destroy layout ref? true',
@@ -3251,7 +3252,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3252
</ErrorBoundary>,
3253
);
3254
});
3254
- expect(Scheduler).toHaveYielded([
3255
+ assertLog([
3256
'ErrorBoundary render: try',
3257
'App render',
3258
'ThrowsInRefCallback render',
@@ -3282,7 +3283,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3283
</ErrorBoundary>,
3284
);
3285
});
3285
- expect(Scheduler).toHaveYielded([
3286
+ assertLog([
3287
'ErrorBoundary render: try',
3288
'App render',
3289
'Suspend:Async',
@@ -3309,7 +3310,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
3310
await act(async () => {
3311
await resolveText('Async');
3312
});
3312
- expect(Scheduler).toHaveYielded([
3313
+ assertLog([
3314
'AsyncText:Async render',
3315
'ThrowsInRefCallback render',
3316
'Text:Inside render',
packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
+127
-140
@@ -19,6 +19,10 @@ let Scheduler;
19
let act;
20
let createMutableSource;
21
let useMutableSource;
22
+let waitFor;
23
+let waitForAll;
24
+let assertLog;
25
+let waitForPaint;
26
27
function loadModules() {
28
jest.resetModules();
@@ -32,6 +36,12 @@ function loadModules() {
36
Scheduler = require('scheduler');
37
act = require('jest-react').act;
38
39
+ const InternalTestUtils = require('internal-test-utils');
40
+ waitFor = InternalTestUtils.waitFor;
41
+ waitForAll = InternalTestUtils.waitForAll;
42
+ waitForPaint = InternalTestUtils.waitForPaint;
43
+ assertLog = InternalTestUtils.assertLog;
44
+
45
// Stable entrypoints export with "unstable_" prefix.
46
createMutableSource =
47
React.createMutableSource || React.unstable_createMutableSource;
@@ -142,11 +152,11 @@ describe('useMutableSource', () => {
152
beforeEach(loadModules);
153
154
// @gate enableUseMutableSource
145
- it('should subscribe to a source and schedule updates when it changes', () => {
155
+ it('should subscribe to a source and schedule updates when it changes', async () => {
156
const source = createSource('one');
157
const mutableSource = createMutableSource(source, param => param.version);
158
149
- act(() => {
159
+ await act(async () => {
160
ReactNoop.renderToRootWithID(
161
<>
162
<Component
@@ -165,11 +175,7 @@ describe('useMutableSource', () => {
175
'root',
176
() => Scheduler.unstable_yieldValue('Sync effect'),
177
);
168
- expect(Scheduler).toFlushAndYieldThrough([
169
- 'a:one',
170
- 'b:one',
171
- 'Sync effect',
172
- ]);
178
+ await waitFor(['a:one', 'b:one', 'Sync effect']);
179
180
// Subscriptions should be passive
181
expect(source.listenerCount).toBe(0);
@@ -178,7 +184,7 @@ describe('useMutableSource', () => {
184
185
// Changing values should schedule an update with React
186
source.value = 'two';
181
- expect(Scheduler).toFlushAndYieldThrough(['a:two', 'b:two']);
187
+ await waitFor(['a:two', 'b:two']);
188
189
// Unmounting a component should remove its subscription.
190
ReactNoop.renderToRootWithID(
@@ -193,28 +199,28 @@ describe('useMutableSource', () => {
199
'root',
200
() => Scheduler.unstable_yieldValue('Sync effect'),
201
);
196
- expect(Scheduler).toFlushAndYield(['a:two', 'Sync effect']);
202
+ await waitForAll(['a:two', 'Sync effect']);
203
ReactNoop.flushPassiveEffects();
204
expect(source.listenerCount).toBe(1);
205
206
// Unmounting a root should remove the remaining event listeners
207
ReactNoop.unmountRootWithID('root');
202
- expect(Scheduler).toFlushAndYield([]);
208
+ await waitForAll([]);
209
ReactNoop.flushPassiveEffects();
210
expect(source.listenerCount).toBe(0);
211
212
// Changes to source should not trigger an updates or warnings.
213
source.value = 'three';
208
- expect(Scheduler).toFlushAndYield([]);
214
+ await waitForAll([]);
215
});
216
});
217
218
// @gate enableUseMutableSource
213
- it('should restart work if a new source is mutated during render', () => {
219
+ it('should restart work if a new source is mutated during render', async () => {
220
const source = createSource('one');
221
const mutableSource = createMutableSource(source, param => param.version);
222
217
- act(() => {
223
+ await act(async () => {
224
React.startTransition(() => {
225
ReactNoop.render(
226
<>
@@ -235,22 +241,22 @@ describe('useMutableSource', () => {
241
);
242
});
243
// Do enough work to read from one component
238
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
244
+ await waitFor(['a:one']);
245
246
// Mutate source before continuing work
247
source.value = 'two';
248
249
// Render work should restart and the updated value should be used
244
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two', 'Sync effect']);
250
+ await waitForAll(['a:two', 'b:two', 'Sync effect']);
251
});
252
});
253
254
// @gate enableUseMutableSource
249
- it('should schedule an update if a new source is mutated between render and commit (subscription)', () => {
255
+ it('should schedule an update if a new source is mutated between render and commit (subscription)', async () => {
256
const source = createSource('one');
257
const mutableSource = createMutableSource(source, param => param.version);
258
253
- act(() => {
259
+ await act(async () => {
260
ReactNoop.render(
261
<>
262
<Component
@@ -270,23 +276,19 @@ describe('useMutableSource', () => {
276
);
277
278
// Finish rendering
273
- expect(Scheduler).toFlushAndYieldThrough([
274
- 'a:one',
275
- 'b:one',
276
- 'Sync effect',
277
- ]);
279
+ await waitFor(['a:one', 'b:one', 'Sync effect']);
280
281
// Mutate source before subscriptions are attached
282
expect(source.listenerCount).toBe(0);
283
source.value = 'two';
284
285
// Mutation should be detected, and a new render should be scheduled
284
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
286
+ await waitForAll(['a:two', 'b:two']);
287
});
288
});
289
290
// @gate enableUseMutableSource
289
- it('should unsubscribe and resubscribe if a new source is used', () => {
291
+ it('should unsubscribe and resubscribe if a new source is used', async () => {
292
const sourceA = createSource('a-one');
293
const mutableSourceA = createMutableSource(
294
sourceA,
@@ -299,7 +301,7 @@ describe('useMutableSource', () => {
301
param => param.versionB,
302
);
303
302
- act(() => {
304
+ await act(async () => {
305
ReactNoop.render(
306
<Component
307
label="only"
@@ -309,13 +311,13 @@ describe('useMutableSource', () => {
311
/>,
312
() => Scheduler.unstable_yieldValue('Sync effect'),
313
);
312
- expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
314
+ await waitForAll(['only:a-one', 'Sync effect']);
315
ReactNoop.flushPassiveEffects();
316
expect(sourceA.listenerCount).toBe(1);
317
318
// Changing values should schedule an update with React
319
sourceA.value = 'a-two';
318
- expect(Scheduler).toFlushAndYield(['only:a-two']);
320
+ await waitForAll(['only:a-two']);
321
322
// If we re-render with a new source, the old one should be unsubscribed.
323
ReactNoop.render(
@@ -327,23 +329,23 @@ describe('useMutableSource', () => {
329
/>,
330
() => Scheduler.unstable_yieldValue('Sync effect'),
331
);
330
- expect(Scheduler).toFlushAndYield(['only:b-one', 'Sync effect']);
332
+ await waitForAll(['only:b-one', 'Sync effect']);
333
ReactNoop.flushPassiveEffects();
334
expect(sourceA.listenerCount).toBe(0);
335
expect(sourceB.listenerCount).toBe(1);
336
337
// Changing to original source should not schedule updates with React
338
sourceA.value = 'a-three';
337
- expect(Scheduler).toFlushAndYield([]);
339
+ await waitForAll([]);
340
341
// Changing new source value should schedule an update with React
342
sourceB.value = 'b-two';
341
- expect(Scheduler).toFlushAndYield(['only:b-two']);
343
+ await waitForAll(['only:b-two']);
344
});
345
});
346
347
// @gate enableUseMutableSource
346
- it('should unsubscribe and resubscribe if a new subscribe function is provided', () => {
348
+ it('should unsubscribe and resubscribe if a new subscribe function is provided', async () => {
349
const source = createSource('a-one');
350
const mutableSource = createMutableSource(source, param => param.version);
351
@@ -364,7 +366,7 @@ describe('useMutableSource', () => {
366
};
367
});
368
367
- act(() => {
369
+ await act(async () => {
370
ReactNoop.renderToRootWithID(
371
<Component
372
label="only"
@@ -375,7 +377,7 @@ describe('useMutableSource', () => {
377
'root',
378
() => Scheduler.unstable_yieldValue('Sync effect'),
379
);
378
- expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
380
+ await waitForAll(['only:a-one', 'Sync effect']);
381
ReactNoop.flushPassiveEffects();
382
expect(source.listenerCount).toBe(1);
383
expect(subscribeA).toHaveBeenCalledTimes(1);
@@ -392,7 +394,7 @@ describe('useMutableSource', () => {
394
'root',
395
() => Scheduler.unstable_yieldValue('Sync effect'),
396
);
395
- expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
397
+ await waitForAll(['only:a-one', 'Sync effect']);
398
ReactNoop.flushPassiveEffects();
399
expect(source.listenerCount).toBe(1);
400
expect(unsubscribeA).toHaveBeenCalledTimes(1);
@@ -400,7 +402,7 @@ describe('useMutableSource', () => {
402
403
// Unmounting should call the newer unsubscribe.
404
ReactNoop.unmountRootWithID('root');
403
- expect(Scheduler).toFlushAndYield([]);
405
+ await waitForAll([]);
406
ReactNoop.flushPassiveEffects();
407
expect(source.listenerCount).toBe(0);
408
expect(unsubscribeB).toHaveBeenCalledTimes(1);
@@ -408,11 +410,11 @@ describe('useMutableSource', () => {
410
});
411
412
// @gate enableUseMutableSource
411
- it('should re-use previously read snapshot value when reading is unsafe', () => {
413
+ it('should re-use previously read snapshot value when reading is unsafe', async () => {
414
const source = createSource('one');
415
const mutableSource = createMutableSource(source, param => param.version);
416
415
- act(() => {
417
+ await act(async () => {
418
ReactNoop.render(
419
<>
420
<Component
@@ -430,14 +432,14 @@ describe('useMutableSource', () => {
432
</>,
433
() => Scheduler.unstable_yieldValue('Sync effect'),
434
);
433
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
435
+ await waitForAll(['a:one', 'b:one', 'Sync effect']);
436
437
// Changing values should schedule an update with React.
438
// Start working on this update but don't finish it.
439
React.startTransition(() => {
440
source.value = 'two';
441
});
440
- expect(Scheduler).toFlushAndYieldThrough(['a:two']);
442
+ await waitFor(['a:two']);
443
444
// Re-renders that occur before the update is processed
445
// should reuse snapshot so long as the config has not changed
@@ -460,18 +462,18 @@ describe('useMutableSource', () => {
462
() => Scheduler.unstable_yieldValue('Sync effect'),
463
);
464
});
463
- expect(Scheduler).toHaveYielded(['a:one', 'b:one', 'Sync effect']);
465
+ assertLog(['a:one', 'b:one', 'Sync effect']);
466
465
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
467
+ await waitForAll(['a:two', 'b:two']);
468
});
469
});
470
471
// @gate enableUseMutableSource
470
- it('should read from source on newly mounted subtree if no pending updates are scheduled for source', () => {
472
+ it('should read from source on newly mounted subtree if no pending updates are scheduled for source', async () => {
473
const source = createSource('one');
474
const mutableSource = createMutableSource(source, param => param.version);
475
474
- act(() => {
476
+ await act(async () => {
477
ReactNoop.render(
478
<>
479
<Component
@@ -483,7 +485,7 @@ describe('useMutableSource', () => {
485
</>,
486
() => Scheduler.unstable_yieldValue('Sync effect'),
487
);
486
- expect(Scheduler).toFlushAndYield(['a:one', 'Sync effect']);
488
+ await waitForAll(['a:one', 'Sync effect']);
489
490
ReactNoop.render(
491
<>
@@ -502,16 +504,16 @@ describe('useMutableSource', () => {
504
</>,
505
() => Scheduler.unstable_yieldValue('Sync effect'),
506
);
505
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
507
+ await waitForAll(['a:one', 'b:one', 'Sync effect']);
508
});
509
});
510
511
// @gate enableUseMutableSource
510
- it('should throw and restart render if source and snapshot are unavailable during an update', () => {
512
+ it('should throw and restart render if source and snapshot are unavailable during an update', async () => {
513
const source = createSource('one');
514
const mutableSource = createMutableSource(source, param => param.version);
515
514
- act(() => {
516
+ await act(async () => {
517
ReactNoop.render(
518
<>
519
<Component
@@ -529,16 +531,17 @@ describe('useMutableSource', () => {
531
</>,
532
() => Scheduler.unstable_yieldValue('Sync effect'),
533
);
532
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
534
+ await waitForAll(['a:one', 'b:one', 'Sync effect']);
535
ReactNoop.flushPassiveEffects();
536
537
// Changing values should schedule an update with React.
536
- // Start working on this update but don't finish it.
538
ReactNoop.idleUpdates(() => {
539
source.value = 'two';
539
- expect(Scheduler).toFlushAndYieldThrough(['a:two']);
540
});
541
542
+ // Start working on this update but don't finish it.
543
+ await waitFor(['a:two']);
544
+
545
const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
546
547
// Force a higher priority render with a new config.
@@ -562,20 +565,16 @@ describe('useMutableSource', () => {
565
() => Scheduler.unstable_yieldValue('Sync effect'),
566
);
567
});
565
- expect(Scheduler).toHaveYielded([
566
- 'a:new:two',
567
- 'b:new:two',
568
- 'Sync effect',
569
- ]);
568
+ assertLog(['a:new:two', 'b:new:two', 'Sync effect']);
569
});
570
});
571
572
// @gate enableUseMutableSource
574
- it('should throw and restart render if source and snapshot are unavailable during a sync update', () => {
573
+ it('should throw and restart render if source and snapshot are unavailable during a sync update', async () => {
574
const source = createSource('one');
575
const mutableSource = createMutableSource(source, param => param.version);
576
578
- act(() => {
577
+ await act(async () => {
578
ReactNoop.render(
579
<>
580
<Component
@@ -593,16 +592,17 @@ describe('useMutableSource', () => {
592
</>,
593
() => Scheduler.unstable_yieldValue('Sync effect'),
594
);
596
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
595
+ await waitForAll(['a:one', 'b:one', 'Sync effect']);
596
ReactNoop.flushPassiveEffects();
597
598
// Changing values should schedule an update with React.
600
- // Start working on this update but don't finish it.
599
ReactNoop.idleUpdates(() => {
600
source.value = 'two';
603
- expect(Scheduler).toFlushAndYieldThrough(['a:two']);
601
});
602
603
+ // Start working on this update but don't finish it.
604
+ await waitFor(['a:two']);
605
+
606
const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
607
608
// Force a higher priority render with a new config.
@@ -626,16 +626,12 @@ describe('useMutableSource', () => {
626
() => Scheduler.unstable_yieldValue('Sync effect'),
627
);
628
});
629
- expect(Scheduler).toHaveYielded([
630
- 'a:new:two',
631
- 'b:new:two',
632
- 'Sync effect',
633
- ]);
629
+ assertLog(['a:new:two', 'b:new:two', 'Sync effect']);
630
});
631
});
632
633
// @gate enableUseMutableSource
638
- it('should only update components whose subscriptions fire', () => {
634
+ it('should only update components whose subscriptions fire', async () => {
635
const source = createComplexSource('a:one', 'b:one');
636
const mutableSource = createMutableSource(source, param => param.version);
637
@@ -645,7 +641,7 @@ describe('useMutableSource', () => {
641
const getSnapshotB = s => s.valueB;
642
const subscribeB = (s, callback) => s.subscribeB(callback);
643
648
- act(() => {
644
+ await act(async () => {
645
ReactNoop.render(
646
<>
647
<Component
@@ -663,18 +659,18 @@ describe('useMutableSource', () => {
659
</>,
660
() => Scheduler.unstable_yieldValue('Sync effect'),
661
);
666
- expect(Scheduler).toFlushAndYield(['a:a:one', 'b:b:one', 'Sync effect']);
662
+ await waitForAll(['a:a:one', 'b:b:one', 'Sync effect']);
663
664
// Changes to part of the store (e.g. A) should not render other parts.
665
source.valueA = 'a:two';
670
- expect(Scheduler).toFlushAndYield(['a:a:two']);
666
+ await waitForAll(['a:a:two']);
667
source.valueB = 'b:two';
672
- expect(Scheduler).toFlushAndYield(['b:b:two']);
668
+ await waitForAll(['b:b:two']);
669
});
670
});
671
672
// @gate enableUseMutableSource
677
- it('should detect tearing in part of the store not yet subscribed to', () => {
673
+ it('should detect tearing in part of the store not yet subscribed to', async () => {
674
const source = createComplexSource('a:one', 'b:one');
675
const mutableSource = createMutableSource(source, param => param.version);
676
@@ -684,7 +680,7 @@ describe('useMutableSource', () => {
680
const getSnapshotB = s => s.valueB;
681
const subscribeB = (s, callback) => s.subscribeB(callback);
682
687
- act(() => {
683
+ await act(async () => {
684
ReactNoop.render(
685
<>
686
<Component
@@ -696,7 +692,7 @@ describe('useMutableSource', () => {
692
</>,
693
() => Scheduler.unstable_yieldValue('Sync effect'),
694
);
699
- expect(Scheduler).toFlushAndYield(['a:a:one', 'Sync effect']);
695
+ await waitForAll(['a:a:one', 'Sync effect']);
696
697
// Because the store has not changed yet, there are no pending updates,
698
// so it is considered safe to read from when we start this render.
@@ -725,29 +721,24 @@ describe('useMutableSource', () => {
721
() => Scheduler.unstable_yieldValue('Sync effect'),
722
);
723
});
728
- expect(Scheduler).toFlushAndYieldThrough(['a:a:one', 'b:b:one']);
724
+ await waitFor(['a:a:one', 'b:b:one']);
725
726
// Mutating the source should trigger a tear detection on the next read,
727
// which should throw and re-render the entire tree.
728
source.valueB = 'b:two';
729
734
- expect(Scheduler).toFlushAndYield([
735
- 'a:a:one',
736
- 'b:b:two',
737
- 'c:b:two',
738
- 'Sync effect',
739
- ]);
730
+ await waitForAll(['a:a:one', 'b:b:two', 'c:b:two', 'Sync effect']);
731
});
732
});
733
734
// @gate enableUseMutableSource
744
- it('does not schedule an update for subscriptions that fire with an unchanged snapshot', () => {
735
+ it('does not schedule an update for subscriptions that fire with an unchanged snapshot', async () => {
736
const MockComponent = jest.fn(Component);
737
738
const source = createSource('one');
739
const mutableSource = createMutableSource(source, param => param.version);
740
750
- act(() => {
741
+ await act(async () => {
742
ReactNoop.render(
743
<MockComponent
744
label="only"
@@ -757,18 +748,18 @@ describe('useMutableSource', () => {
748
/>,
749
() => Scheduler.unstable_yieldValue('Sync effect'),
750
);
760
- expect(Scheduler).toFlushAndYieldThrough(['only:one', 'Sync effect']);
751
+ await waitFor(['only:one', 'Sync effect']);
752
ReactNoop.flushPassiveEffects();
753
expect(source.listenerCount).toBe(1);
754
755
// Notify subscribe function but don't change the value
756
source.value = 'one';
766
- expect(Scheduler).toFlushWithoutYielding();
757
+ await waitForAll([]);
758
});
759
});
760
761
// @gate enableUseMutableSource
771
- it('should throw and restart if getSnapshot changes between scheduled update and re-render', () => {
762
+ it('should throw and restart if getSnapshot changes between scheduled update and re-render', async () => {
763
const source = createSource('one');
764
const mutableSource = createMutableSource(source, param => param.version);
765
@@ -789,11 +780,11 @@ describe('useMutableSource', () => {
780
);
781
}
782
792
- act(() => {
783
+ await act(async () => {
784
ReactNoop.render(<WrapperWithState />, () =>
785
Scheduler.unstable_yieldValue('Sync effect'),
786
);
796
- expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
787
+ await waitForAll(['only:one', 'Sync effect']);
788
ReactNoop.flushPassiveEffects();
789
790
// Change the source (and schedule an update).
@@ -804,16 +795,16 @@ describe('useMutableSource', () => {
795
updateGetSnapshot(() => newGetSnapshot);
796
});
797
807
- expect(Scheduler).toHaveYielded(['only:new:two']);
798
+ assertLog(['only:new:two']);
799
});
800
});
801
802
// @gate enableUseMutableSource
812
- it('should recover from a mutation during yield when other work is scheduled', () => {
803
+ it('should recover from a mutation during yield when other work is scheduled', async () => {
804
const source = createSource('one');
805
const mutableSource = createMutableSource(source, param => param.version);
806
816
- act(() => {
807
+ await act(async () => {
808
// Start a render that uses the mutable source.
809
React.startTransition(() => {
810
ReactNoop.render(
@@ -833,19 +824,19 @@ describe('useMutableSource', () => {
824
</>,
825
);
826
});
836
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
827
+ await waitFor(['a:one']);
828
829
// Mutate source
830
source.value = 'two';
831
832
// Now render something different.
833
ReactNoop.render(<div />);
843
- expect(Scheduler).toFlushAndYield([]);
834
+ await waitForAll([]);
835
});
836
});
837
838
// @gate enableUseMutableSource
848
- it('should not throw if the new getSnapshot returns the same snapshot value', () => {
839
+ it('should not throw if the new getSnapshot returns the same snapshot value', async () => {
840
const source = createSource('one');
841
const mutableSource = createMutableSource(source, param => param.version);
842
@@ -867,7 +858,7 @@ describe('useMutableSource', () => {
858
);
859
}
860
870
- act(() => {
861
+ await act(async () => {
862
ReactNoop.render(
863
<>
864
<React.Profiler id="a" onRender={onRenderA}>
@@ -884,7 +875,7 @@ describe('useMutableSource', () => {
875
</>,
876
() => Scheduler.unstable_yieldValue('Sync effect'),
877
);
887
- expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
878
+ await waitForAll(['a:one', 'b:one', 'Sync effect']);
879
ReactNoop.flushPassiveEffects();
880
expect(onRenderA).toHaveBeenCalledTimes(1);
881
expect(onRenderB).toHaveBeenCalledTimes(1);
@@ -892,7 +883,7 @@ describe('useMutableSource', () => {
883
// If B's getSnapshot function updates, but the snapshot it returns is the same,
884
// only B should re-render (to update its state).
885
updateGetSnapshot(() => s => defaultGetSnapshot(s));
895
- expect(Scheduler).toFlushAndYield(['b:one']);
886
+ await waitForAll(['b:one']);
887
ReactNoop.flushPassiveEffects();
888
expect(onRenderA).toHaveBeenCalledTimes(1);
889
expect(onRenderB).toHaveBeenCalledTimes(2);
@@ -900,7 +891,7 @@ describe('useMutableSource', () => {
891
});
892
893
// @gate enableUseMutableSource
903
- it('should not throw if getSnapshot changes but the source can be safely read from anyway', () => {
894
+ it('should not throw if getSnapshot changes but the source can be safely read from anyway', async () => {
895
const source = createSource('one');
896
const mutableSource = createMutableSource(source, param => param.version);
897
@@ -921,11 +912,11 @@ describe('useMutableSource', () => {
912
);
913
}
914
924
- act(() => {
915
+ await act(async () => {
916
ReactNoop.render(<WrapperWithState />, () =>
917
Scheduler.unstable_yieldValue('Sync effect'),
918
);
928
- expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
919
+ await waitForAll(['only:one', 'Sync effect']);
920
ReactNoop.flushPassiveEffects();
921
922
// Change the source (and schedule an update)
@@ -935,12 +926,12 @@ describe('useMutableSource', () => {
926
updateGetSnapshot(() => newGetSnapshot);
927
});
928
938
- expect(Scheduler).toFlushAndYield(['only:new:two']);
929
+ await waitForAll(['only:new:two']);
930
});
931
});
932
933
// @gate enableUseMutableSource
943
- it('should still schedule an update if an eager selector throws after a mutation', () => {
934
+ it('should still schedule an update if an eager selector throws after a mutation', async () => {
935
const source = createSource({
936
friends: [
937
{id: 1, name: 'Foo'},
@@ -986,11 +977,11 @@ describe('useMutableSource', () => {
977
return <li>{name}</li>;
978
}
979
989
- act(() => {
980
+ await act(async () => {
981
ReactNoop.render(<FriendsList />, () =>
982
Scheduler.unstable_yieldValue('Sync effect'),
983
);
993
- expect(Scheduler).toFlushAndYield(['1:Foo', '2:Bar', 'Sync effect']);
984
+ await waitForAll(['1:Foo', '2:Bar', 'Sync effect']);
985
986
// This mutation will cause the "Bar" component to throw,
987
// since its value will no longer be a part of the store.
@@ -1002,12 +993,12 @@ describe('useMutableSource', () => {
993
{id: 3, name: 'Baz'},
994
],
995
};
1005
- expect(Scheduler).toFlushAndYield(['1:Foo', '3:Baz']);
996
+ await waitForAll(['1:Foo', '3:Baz']);
997
});
998
});
999
1000
// @gate enableUseMutableSource
1010
- it('should not warn about updates that fire between unmount and passive unsubscribe', () => {
1001
+ it('should not warn about updates that fire between unmount and passive unsubscribe', async () => {
1002
const source = createSource('one');
1003
const mutableSource = createMutableSource(source, param => param.version);
1004
@@ -1025,21 +1016,21 @@ describe('useMutableSource', () => {
1016
);
1017
}
1018
1028
- act(() => {
1019
+ await act(async () => {
1020
ReactNoop.renderToRootWithID(<Wrapper />, 'root', () =>
1021
Scheduler.unstable_yieldValue('Sync effect'),
1022
);
1032
- expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
1023
+ await waitForAll(['only:one', 'Sync effect']);
1024
ReactNoop.flushPassiveEffects();
1025
1026
// Unmounting a root should remove the remaining event listeners in a passive effect
1027
ReactNoop.unmountRootWithID('root');
1037
- expect(Scheduler).toFlushAndYieldThrough(['layout unmount']);
1028
+ await waitFor(['layout unmount']);
1029
1030
// Changes to source should not cause a warning,
1031
// even though the unsubscribe hasn't run yet (since it's a pending passive effect).
1032
source.value = 'two';
1042
- expect(Scheduler).toFlushAndYield([]);
1033
+ await waitForAll([]);
1034
});
1035
});
1036
@@ -1247,7 +1238,7 @@ describe('useMutableSource', () => {
1238
);
1239
});
1240
// x and y start out reading from different parts of the store.
1250
- expect(Scheduler).toHaveYielded(['x: foo, y: bar']);
1241
+ assertLog(['x: foo, y: bar']);
1242
1243
await act(async () => {
1244
ReactNoop.discreteUpdates(() => {
@@ -1277,7 +1268,7 @@ describe('useMutableSource', () => {
1268
// The actual sequence of work will be:
1269
// 1. React renders the high-pri update, sees a new getSnapshot, detects the source has been further mutated, and throws
1270
// 2. React re-renders with all pending updates, including the second mutation, and renders "bar" and "bar".
1280
- expect(Scheduler).toHaveYielded(['x: bar, y: bar']);
1271
+ assertLog(['x: bar, y: bar']);
1272
});
1273
1274
// @gate enableUseMutableSource
@@ -1317,18 +1308,18 @@ describe('useMutableSource', () => {
1308
await act(async () => {
1309
root.render(<App getSnapshot={getSnapshotA} />);
1310
});
1320
- expect(Scheduler).toHaveYielded(['Render: foo', 'Commit: foo']);
1311
+ assertLog(['Render: foo', 'Commit: foo']);
1312
1313
await act(async () => {
1314
// Switch getSnapshot to read from B instead
1315
root.render(<App getSnapshot={getSnapshotB} />);
1316
// Render and finish the tree, but yield right after paint, before
1317
// the passive effects have fired.
1327
- expect(Scheduler).toFlushUntilNextPaint(['Render: bar']);
1318
+ await waitForPaint(['Render: bar']);
1319
// Then mutate B.
1320
mutateB('baz');
1321
});
1331
- expect(Scheduler).toHaveYielded([
1322
+ assertLog([
1323
// Fires the effect from the previous render
1324
'Commit: bar',
1325
// During that effect, it should detect that the snapshot has changed
@@ -1393,7 +1384,7 @@ describe('useMutableSource', () => {
1384
);
1385
// Render and finish the tree, but yield right after paint, before
1386
// the passive effects have fired.
1396
- expect(Scheduler).toFlushUntilNextPaint([]);
1387
+ await waitForPaint([]);
1388
1389
// Now mutate A. Both hooks should update.
1390
// This is at high priority so that it doesn't get batched with default
@@ -1462,7 +1453,7 @@ describe('useMutableSource', () => {
1453
</>,
1454
);
1455
});
1465
- expect(Scheduler).toHaveYielded(['a0']);
1456
+ assertLog(['a0']);
1457
expect(root).toMatchRenderedOutput('a0');
1458
1459
await act(async () => {
@@ -1476,7 +1467,7 @@ describe('useMutableSource', () => {
1467
);
1468
});
1469
1479
- expect(Scheduler).toFlushAndYieldThrough(['a0', 'b0']);
1470
+ await waitFor(['a0', 'b0']);
1471
// Mutate in an event. This schedules a subscription update on a, which
1472
// already mounted, but not b, which hasn't subscribed yet.
1473
if (gate(flags => flags.enableUnifiedSyncLane)) {
@@ -1499,14 +1490,14 @@ describe('useMutableSource', () => {
1490
mutateB('b0');
1491
});
1492
// Finish the current render
1502
- expect(Scheduler).toFlushUntilNextPaint(['c']);
1493
+ await waitForPaint(['c']);
1494
// a0 will re-render because of the mutation update. But it should show
1495
// the latest value, not the intermediate one, to avoid tearing with b.
1505
- expect(Scheduler).toFlushUntilNextPaint(['a0']);
1496
+ await waitForPaint(['a0']);
1497
1498
expect(root).toMatchRenderedOutput('a0b0c');
1499
// We should be done.
1509
- expect(Scheduler).toFlushAndYield([]);
1500
+ await waitForAll([]);
1501
expect(root).toMatchRenderedOutput('a0b0c');
1502
});
1503
},
@@ -1606,7 +1597,7 @@ describe('useMutableSource', () => {
1597
await act(async () => {
1598
root.render(<App parentConfig={configA} childConfig={configB} />);
1599
});
1609
- expect(Scheduler).toHaveYielded(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
1600
+ assertLog(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
1601
1602
await act(async () => {
1603
// Switch the parent and the child to read using the same config
@@ -1614,7 +1605,7 @@ describe('useMutableSource', () => {
1605
root.render(<App parentConfig={configB} childConfig={configB} />);
1606
});
1607
// Start rendering the parent, but yield before rendering the child
1617
- expect(Scheduler).toFlushAndYieldThrough(['Parent: 2']);
1608
+ await waitFor(['Parent: 2']);
1609
1610
// Mutate the config. This is at lower priority so that 1) to make sure
1611
// it doesn't happen to get batched with the in-progress render, and 2)
@@ -1624,7 +1615,7 @@ describe('useMutableSource', () => {
1615
});
1616
1617
// In default sync mode, all of the updates flush sync.
1627
- expect(Scheduler).toFlushAndYieldThrough([
1618
+ await waitFor([
1619
// The partial render completes
1620
'Child: 2',
1621
'Commit: 2, 2',
@@ -1632,7 +1623,7 @@ describe('useMutableSource', () => {
1623
'Child: 3',
1624
]);
1625
1635
- expect(Scheduler).toFlushAndYield([
1626
+ await waitForAll([
1627
// Now finish the rest of the update
1628
'Commit: 3, 3',
1629
]);
@@ -1687,11 +1678,11 @@ describe('useMutableSource', () => {
1678
() => Scheduler.unstable_yieldValue('Sync effect'),
1679
);
1680
});
1690
- expect(Scheduler).toHaveYielded(['a:one', 'Sync effect']);
1681
+ assertLog(['a:one', 'Sync effect']);
1682
expect(source.listenerCount).toBe(1);
1683
1684
// Mount ComponentB with version 1 (but don't commit it)
1694
- act(() => {
1685
+ await act(async () => {
1686
ReactNoop.render(
1687
<React.Profiler id="root" onRender={onRender}>
1688
<ComponentA />
@@ -1699,11 +1690,7 @@ describe('useMutableSource', () => {
1690
</React.Profiler>,
1691
() => Scheduler.unstable_yieldValue('Sync effect'),
1692
);
1702
- expect(Scheduler).toFlushAndYieldThrough([
1703
- 'a:one',
1704
- 'b:one',
1705
- 'Sync effect',
1706
- ]);
1693
+ await waitFor(['a:one', 'b:one', 'Sync effect']);
1694
expect(source.listenerCount).toBe(1);
1695
1696
// Mutate -> schedule update for ComponentA
@@ -1712,7 +1699,7 @@ describe('useMutableSource', () => {
1699
});
1700
1701
// Commit ComponentB -> notice the change and schedule an update for ComponentB
1715
- expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
1702
+ await waitForAll(['a:two', 'b:two']);
1703
expect(source.listenerCount).toBe(2);
1704
});
1705
});
@@ -1746,14 +1733,14 @@ describe('useMutableSource', () => {
1733
});
1734
1735
// @gate enableUseMutableSource
1749
- it('should error if multiple renderers of the same type use a mutable source at the same time', () => {
1736
+ it('should error if multiple renderers of the same type use a mutable source at the same time', async () => {
1737
const source = createSource('one');
1738
const mutableSource = createMutableSource(
1739
source,
1740
param => param.version,
1741
);
1742
1756
- act(() => {
1743
+ await act(async () => {
1744
// Start a render that uses the mutable source.
1745
React.startTransition(() => {
1746
ReactNoop.render(
@@ -1773,7 +1760,7 @@ describe('useMutableSource', () => {
1760
</>,
1761
);
1762
});
1776
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
1763
+ await waitFor(['a:one']);
1764
1765
const PrevScheduler = Scheduler;
1766
@@ -1791,7 +1778,7 @@ describe('useMutableSource', () => {
1778
subscribe={defaultSubscribe}
1779
/>,
1780
);
1794
- expect(Scheduler).toFlushAndYieldThrough(['c:one']);
1781
+ await waitFor(['c:one']);
1782
1783
expect(console.error.mock.calls[0][0]).toContain(
1784
'Detected multiple renderers concurrently rendering the ' +
@@ -1808,14 +1795,14 @@ describe('useMutableSource', () => {
1795
});
1796
1797
// @gate enableUseMutableSource
1811
- it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', () => {
1798
+ it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', async () => {
1799
const source = createSource('one');
1800
const mutableSource = createMutableSource(
1801
source,
1802
param => param.version,
1803
);
1804
1818
- act(() => {
1805
+ await act(async () => {
1806
// Start a render that uses the mutable source.
1807
React.startTransition(() => {
1808
ReactNoop.render(
@@ -1835,7 +1822,7 @@ describe('useMutableSource', () => {
1822
</>,
1823
);
1824
});
1838
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
1825
+ await waitFor(['a:one']);
1826
1827
const PrevScheduler = Scheduler;
1828
@@ -1856,7 +1843,7 @@ describe('useMutableSource', () => {
1843
subscribe={defaultSubscribe}
1844
/>,
1845
);
1859
- expect(Scheduler).toFlushAndYieldThrough(['c:two']);
1846
+ await waitFor(['c:two']);
1847
1848
expect(console.error.mock.calls[0][0]).toContain(
1849
'Detected multiple renderers concurrently rendering the ' +
packages/react-reconciler/src/__tests__/useMutableSourceHydration-test.js
+22
-16
@@ -16,6 +16,8 @@ let Scheduler;
16
let act;
17
let createMutableSource;
18
let useMutableSource;
19
+let waitFor;
20
+let assertLog;
21
22
describe('useMutableSourceHydration', () => {
23
beforeEach(() => {
@@ -33,6 +35,10 @@ describe('useMutableSourceHydration', () => {
35
React.createMutableSource || React.unstable_createMutableSource;
36
useMutableSource =
37
React.useMutableSource || React.unstable_useMutableSource;
38
+
39
+ const InternalTestUtils = require('internal-test-utils');
40
+ waitFor = InternalTestUtils.waitFor;
41
+ assertLog = InternalTestUtils.assertLog;
42
});
43
44
const defaultGetSnapshot = source => source.value;
@@ -156,7 +162,7 @@ describe('useMutableSourceHydration', () => {
162
163
const htmlString = ReactDOMServer.renderToString(<TestComponent />);
164
container.innerHTML = htmlString;
159
- expect(Scheduler).toHaveYielded(['only:one']);
165
+ assertLog(['only:one']);
166
expect(source.listenerCount).toBe(0);
167
168
act(() => {
@@ -164,7 +170,7 @@ describe('useMutableSourceHydration', () => {
170
mutableSources: [mutableSource],
171
});
172
});
167
- expect(Scheduler).toHaveYielded(['only:one']);
173
+ assertLog(['only:one']);
174
expect(source.listenerCount).toBe(1);
175
});
176
@@ -190,7 +196,7 @@ describe('useMutableSourceHydration', () => {
196
197
const htmlString = ReactDOMServer.renderToString(<TestComponent />);
198
container.innerHTML = htmlString;
193
- expect(Scheduler).toHaveYielded(['only:one']);
199
+ assertLog(['only:one']);
200
expect(source.listenerCount).toBe(0);
201
202
expect(() => {
@@ -211,7 +217,7 @@ describe('useMutableSourceHydration', () => {
217
],
218
{withoutStack: 1},
219
);
214
- expect(Scheduler).toHaveYielded([
220
+ assertLog([
221
'only:two',
222
'only:two',
223
'Log error: Text content does not match server-rendered HTML.',
@@ -221,7 +227,7 @@ describe('useMutableSourceHydration', () => {
227
});
228
229
// @gate enableUseMutableSource
224
- it('should detect a tear between hydrating components', () => {
230
+ it('should detect a tear between hydrating components', async () => {
231
const source = createSource('one');
232
const mutableSource = createMutableSource(source, param => param.version);
233
@@ -249,11 +255,11 @@ describe('useMutableSourceHydration', () => {
255
256
const htmlString = ReactDOMServer.renderToString(<TestComponent />);
257
container.innerHTML = htmlString;
252
- expect(Scheduler).toHaveYielded(['a:one', 'b:one']);
258
+ assertLog(['a:one', 'b:one']);
259
expect(source.listenerCount).toBe(0);
260
255
- expect(() => {
256
- act(() => {
261
+ await expect(async () => {
262
+ await act(async () => {
263
React.startTransition(() => {
264
ReactDOMClient.hydrateRoot(container, <TestComponent />, {
265
mutableSources: [mutableSource],
@@ -262,7 +268,7 @@ describe('useMutableSourceHydration', () => {
268
},
269
});
270
});
265
- expect(Scheduler).toFlushAndYieldThrough(['a:one']);
271
+ await waitFor(['a:one']);
272
source.value = 'two';
273
});
274
}).toErrorDev(
@@ -270,7 +276,7 @@ describe('useMutableSourceHydration', () => {
276
'The server HTML was replaced with client content in <div>.',
277
{withoutStack: true},
278
);
273
- expect(Scheduler).toHaveYielded([
279
+ assertLog([
280
'a:two',
281
'b:two',
282
// TODO: Before onRecoverableError, this error was never surfaced to the
@@ -288,7 +294,7 @@ describe('useMutableSourceHydration', () => {
294
});
295
296
// @gate enableUseMutableSource
291
- it('should detect a tear between hydrating components reading from different parts of a source', () => {
297
+ it('should detect a tear between hydrating components reading from different parts of a source', async () => {
298
const source = createComplexSource('a:one', 'b:one');
299
const mutableSource = createMutableSource(source, param => param.version);
300
@@ -318,10 +324,10 @@ describe('useMutableSourceHydration', () => {
324
</>,
325
);
326
container.innerHTML = htmlString;
321
- expect(Scheduler).toHaveYielded(['0:a:one', '1:b:one']);
327
+ assertLog(['0:a:one', '1:b:one']);
328
323
- expect(() => {
324
- act(() => {
329
+ await expect(async () => {
330
+ await act(async () => {
331
const fragment = (
332
<>
333
<Component
@@ -346,7 +352,7 @@ describe('useMutableSourceHydration', () => {
352
},
353
});
354
});
349
- expect(Scheduler).toFlushAndYieldThrough(['0:a:one']);
355
+ await waitFor(['0:a:one']);
356
source.valueB = 'b:two';
357
});
358
}).toErrorDev(
@@ -354,7 +360,7 @@ describe('useMutableSourceHydration', () => {
360
'The server HTML was replaced with client content in <div>.',
361
{withoutStack: true},
362
);
357
- expect(Scheduler).toHaveYielded([
363
+ assertLog([
364
'0:a:one',
365
'1:b:two',
366
// TODO: Before onRecoverableError, this error was never surfaced to the
packages/react-reconciler/src/__tests__/useRef-test.internal.js
+14
-8
@@ -22,6 +22,8 @@ describe('useRef', () => {
22
let useLayoutEffect;
23
let useRef;
24
let useState;
25
+ let waitForAll;
26
+ let assertLog;
27
28
beforeEach(() => {
29
React = require('react');
@@ -37,6 +39,10 @@ describe('useRef', () => {
39
useLayoutEffect = React.useLayoutEffect;
40
useRef = React.useRef;
41
useState = React.useState;
42
+
43
+ const InternalTestUtils = require('internal-test-utils');
44
+ waitForAll = InternalTestUtils.waitForAll;
45
+ assertLog = InternalTestUtils.assertLog;
46
});
47
48
function Text(props) {
@@ -79,17 +85,17 @@ describe('useRef', () => {
85
act(() => {
86
ReactNoop.render(<App />);
87
});
82
- expect(Scheduler).toHaveYielded([]);
88
+ assertLog([]);
89
90
ping(1);
91
ping(2);
92
ping(3);
93
88
- expect(Scheduler).toHaveYielded([]);
94
+ assertLog([]);
95
96
jest.advanceTimersByTime(100);
97
92
- expect(Scheduler).toHaveYielded(['ping: 3']);
98
+ assertLog(['ping: 3']);
99
100
ping(4);
101
jest.advanceTimersByTime(20);
@@ -97,13 +103,13 @@ describe('useRef', () => {
103
ping(6);
104
jest.advanceTimersByTime(80);
105
100
- expect(Scheduler).toHaveYielded([]);
106
+ assertLog([]);
107
108
jest.advanceTimersByTime(20);
103
- expect(Scheduler).toHaveYielded(['ping: 6']);
109
+ assertLog(['ping: 6']);
110
});
111
106
- it('should return the same ref during re-renders', () => {
112
+ it('should return the same ref during re-renders', async () => {
113
function Counter() {
114
const ref = useRef('val');
115
const [count, setCount] = useState(0);
@@ -121,10 +127,10 @@ describe('useRef', () => {
127
}
128
129
ReactNoop.render(<Counter />);
124
- expect(Scheduler).toFlushAndYield([3]);
130
+ await waitForAll([3]);
131
132
ReactNoop.render(<Counter />);
127
- expect(Scheduler).toFlushAndYield([3]);
133
+ await waitForAll([3]);
134
});
135
136
if (__DEV__) {
packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
+15
-7
@@ -20,6 +20,9 @@ let useImperativeHandle;
20
let useRef;
21
let useState;
22
let startTransition;
23
+let waitFor;
24
+let waitForAll;
25
+let assertLog;
26
27
// This tests the native useSyncExternalStore implementation, not the shim.
28
// Tests that apply to both the native implementation and the shim should go
@@ -41,6 +44,11 @@ describe('useSyncExternalStore', () => {
44
useSyncExternalStore = React.useSyncExternalStore;
45
startTransition = React.startTransition;
46
47
+ const InternalTestUtils = require('internal-test-utils');
48
+ waitFor = InternalTestUtils.waitFor;
49
+ waitForAll = InternalTestUtils.waitForAll;
50
+ assertLog = InternalTestUtils.assertLog;
51
+
52
act = require('jest-react').act;
53
});
54
@@ -122,13 +130,13 @@ describe('useSyncExternalStore', () => {
130
root.render(<App store={store1} />);
131
});
132
125
- expect(Scheduler).toFlushAndYieldThrough(['A0', 'B0']);
133
+ await waitFor(['A0', 'B0']);
134
135
// During an interleaved event, the store is mutated.
136
store1.set(1);
137
138
// Then we continue rendering.
131
- expect(Scheduler).toFlushAndYield([
139
+ await waitForAll([
140
// C reads a newer value from the store than A or B, which means they
141
// are inconsistent.
142
'C1',
@@ -152,13 +160,13 @@ describe('useSyncExternalStore', () => {
160
});
161
162
// Start a concurrent render that reads from the store, then yield.
155
- expect(Scheduler).toFlushAndYieldThrough(['A0', 'B0']);
163
+ await waitFor(['A0', 'B0']);
164
165
// During an interleaved event, the store is mutated.
166
store2.set(1);
167
168
// Then we continue rendering.
161
- expect(Scheduler).toFlushAndYield([
169
+ await waitForAll([
170
// C reads a newer value from the store than A or B, which means they
171
// are inconsistent.
172
'C1',
@@ -191,17 +199,17 @@ describe('useSyncExternalStore', () => {
199
// Start a render that reads from the store and yields value
200
root.render(<App />);
201
});
194
- expect(Scheduler).toHaveYielded(['value:initial']);
202
+ assertLog(['value:initial']);
203
204
await act(() => {
205
store.set('value:changed');
206
});
199
- expect(Scheduler).toHaveYielded(['value:changed']);
207
+ assertLog(['value:changed']);
208
209
// If cached value was updated, we expect a re-render
210
await act(() => {
211
store.set('value:initial');
212
});
205
- expect(Scheduler).toHaveYielded(['value:initial']);
213
+ assertLog(['value:initial']);
214
});
215
});
packages/react-refresh/src/__tests__/ReactFresh-test.js
+11
-4
@@ -18,6 +18,8 @@ let ReactFreshRuntime;
18
let Scheduler;
19
let act;
20
let createReactClass;
21
+let waitFor;
22
+let assertLog;
23
24
describe('ReactFresh', () => {
25
let container;
@@ -32,6 +34,11 @@ describe('ReactFresh', () => {
34
ReactDOMClient = require('react-dom/client');
35
Scheduler = require('scheduler');
36
act = require('jest-react').act;
37
+
38
+ const InternalTestUtils = require('internal-test-utils');
39
+ waitFor = InternalTestUtils.waitFor;
40
+ assertLog = InternalTestUtils.assertLog;
41
+
42
createReactClass = require('create-react-class/factory')(
43
React.Component,
44
React.isValidElement,
@@ -2441,7 +2448,7 @@ describe('ReactFresh', () => {
2448
2449
const root = ReactDOMClient.createRoot(container);
2450
root.render(<AppV1 offscreen={true} />);
2444
- expect(Scheduler).toFlushAndYieldThrough(['App#layout']);
2451
+ await waitFor(['App#layout']);
2452
const el = container.firstChild;
2453
expect(el.hidden).toBe(true);
2454
expect(el.firstChild).toBe(null); // Offscreen content not flushed yet.
@@ -2468,7 +2475,7 @@ describe('ReactFresh', () => {
2475
expect(el.firstChild).toBe(null);
2476
2477
// Process the offscreen updates.
2471
- expect(Scheduler).toFlushAndYieldThrough(['Hello#layout']);
2478
+ await waitFor(['Hello#layout']);
2479
expect(container.firstChild).toBe(el);
2480
expect(el.firstChild.textContent).toBe('0');
2481
expect(el.firstChild.style.color).toBe('red');
@@ -2481,7 +2488,7 @@ describe('ReactFresh', () => {
2488
);
2489
});
2490
2484
- expect(Scheduler).toHaveYielded(['Hello#layout']);
2491
+ assertLog(['Hello#layout']);
2492
expect(el.firstChild.textContent).toBe('1');
2493
expect(el.firstChild.style.color).toBe('red');
2494
@@ -2507,7 +2514,7 @@ describe('ReactFresh', () => {
2514
expect(el.firstChild.style.color).toBe('red');
2515
2516
// Process the offscreen updates.
2510
- expect(Scheduler).toFlushAndYieldThrough(['Hello#layout']);
2517
+ await waitFor(['Hello#layout']);
2518
expect(container.firstChild).toBe(el);
2519
expect(el.firstChild.textContent).toBe('1');
2520
expect(el.firstChild.style.color).toBe('orange');
packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.internal.js
+5
-3
@@ -19,7 +19,9 @@ const {format: prettyFormat} = require('pretty-format');
19
// Isolate noop renderer
20
jest.resetModules();
21
const ReactNoop = require('react-noop-renderer');
22
-const Scheduler = require('scheduler');
22
+
23
+const InternalTestUtils = require('internal-test-utils');
24
+const waitForAll = InternalTestUtils.waitForAll;
25
26
// Kind of hacky, but we nullify all the instances to test the tree structure
27
// with jasmine's deep equality function, and test the instances separate. We
@@ -1015,7 +1017,7 @@ describe('ReactTestRenderer', () => {
1017
);
1018
});
1019
1018
- it('can concurrently render context with a "primary" renderer', () => {
1020
+ it('can concurrently render context with a "primary" renderer', async () => {
1021
const Context = React.createContext(null);
1022
const Indirection = React.Fragment;
1023
const App = () => (
@@ -1026,7 +1028,7 @@ describe('ReactTestRenderer', () => {
1028
</Context.Provider>
1029
);
1030
ReactNoop.render(<App />);
1029
- expect(Scheduler).toFlushWithoutYielding();
1031
+ await waitForAll([]);
1032
ReactTestRenderer.create(<App />);
1033
});
1034
packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
+5
-1
@@ -4,6 +4,7 @@ let React;
4
let ReactTestRenderer;
5
let Scheduler;
6
let act;
7
+let assertLog;
8
9
describe('ReactTestRenderer.act()', () => {
10
beforeEach(() => {
@@ -12,6 +13,9 @@ describe('ReactTestRenderer.act()', () => {
13
ReactTestRenderer = require('react-test-renderer');
14
Scheduler = require('scheduler');
15
act = ReactTestRenderer.act;
16
+
17
+ const InternalTestUtils = require('internal-test-utils');
18
+ assertLog = InternalTestUtils.assertLog;
19
});
20
21
// @gate __DEV__
@@ -91,7 +95,7 @@ describe('ReactTestRenderer.act()', () => {
95
await act(async () => {
96
root.update(<App />);
97
});
94
- expect(Scheduler).toHaveYielded([
98
+ assertLog([
99
// Should not flush effects without also flushing microtasks
100
// First render:
101
'Effect',
packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js
+21
-13
@@ -13,6 +13,9 @@
13
let React;
14
let ReactTestRenderer;
15
let Scheduler;
16
+let waitForAll;
17
+let waitFor;
18
+let assertLog;
19
20
describe('ReactTestRendererAsync', () => {
21
beforeEach(() => {
@@ -21,9 +24,14 @@ describe('ReactTestRendererAsync', () => {
24
React = require('react');
25
ReactTestRenderer = require('react-test-renderer');
26
Scheduler = require('scheduler');
27
+
28
+ const InternalTestUtils = require('internal-test-utils');
29
+ waitForAll = InternalTestUtils.waitForAll;
30
+ waitFor = InternalTestUtils.waitFor;
31
+ assertLog = InternalTestUtils.assertLog;
32
});
33
26
- it('flushAll flushes all work', () => {
34
+ it('flushAll flushes all work', async () => {
35
function Foo(props) {
36
return props.children;
37
}
@@ -35,7 +43,7 @@ describe('ReactTestRendererAsync', () => {
43
expect(renderer.toJSON()).toEqual(null);
44
45
// Flush initial mount.
38
- expect(Scheduler).toFlushWithoutYielding();
46
+ await waitForAll([]);
47
expect(renderer.toJSON()).toEqual('Hi');
48
49
// Update
@@ -43,11 +51,11 @@ describe('ReactTestRendererAsync', () => {
51
// Not yet updated.
52
expect(renderer.toJSON()).toEqual('Hi');
53
// Flush update.
46
- expect(Scheduler).toFlushWithoutYielding();
54
+ await waitForAll([]);
55
expect(renderer.toJSON()).toEqual('Bye');
56
});
57
50
- it('flushAll returns array of yielded values', () => {
58
+ it('flushAll returns array of yielded values', async () => {
59
function Child(props) {
60
Scheduler.unstable_yieldValue(props.children);
61
return props.children;
@@ -65,15 +73,15 @@ describe('ReactTestRendererAsync', () => {
73
unstable_isConcurrent: true,
74
});
75
68
- expect(Scheduler).toFlushAndYield(['A:1', 'B:1', 'C:1']);
76
+ await waitForAll(['A:1', 'B:1', 'C:1']);
77
expect(renderer.toJSON()).toEqual(['A:1', 'B:1', 'C:1']);
78
79
renderer.update(<Parent step={2} />);
72
- expect(Scheduler).toFlushAndYield(['A:2', 'B:2', 'C:2']);
80
+ await waitForAll(['A:2', 'B:2', 'C:2']);
81
expect(renderer.toJSON()).toEqual(['A:2', 'B:2', 'C:2']);
82
});
83
76
- it('flushThrough flushes until the expected values is yielded', () => {
84
+ it('flushThrough flushes until the expected values is yielded', async () => {
85
function Child(props) {
86
Scheduler.unstable_yieldValue(props.children);
87
return props.children;
@@ -96,16 +104,16 @@ describe('ReactTestRendererAsync', () => {
104
});
105
106
// Flush the first two siblings
99
- expect(Scheduler).toFlushAndYieldThrough(['A:1', 'B:1']);
107
+ await waitFor(['A:1', 'B:1']);
108
// Did not commit yet.
109
expect(renderer.toJSON()).toEqual(null);
110
111
// Flush the remaining work
104
- expect(Scheduler).toFlushAndYield(['C:1']);
112
+ await waitForAll(['C:1']);
113
expect(renderer.toJSON()).toEqual(['A:1', 'B:1', 'C:1']);
114
});
115
108
- it('supports high priority interruptions', () => {
116
+ it('supports high priority interruptions', async () => {
117
function Child(props) {
118
Scheduler.unstable_yieldValue(props.children);
119
return props.children;
@@ -136,7 +144,7 @@ describe('ReactTestRendererAsync', () => {
144
});
145
146
// Flush the some of the changes, but don't commit
139
- expect(Scheduler).toFlushAndYieldThrough(['A:1']);
147
+ await waitFor(['A:1']);
148
expect(renderer.toJSON()).toEqual(null);
149
150
// Interrupt with higher priority properties
@@ -232,12 +240,12 @@ describe('ReactTestRendererAsync', () => {
240
});
241
242
expect(Scheduler).toFlushAndThrow('Oh no!');
235
- expect(Scheduler).toHaveYielded(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
243
+ assertLog(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
244
245
renderer.update(<App />);
246
247
expect(Scheduler).toFlushAndThrow('Oh no!');
240
- expect(Scheduler).toHaveYielded(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
248
+ assertLog(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
249
250
renderer.update(<App />);
251
expect(Scheduler).toFlushAndThrow('Oh no!');
packages/scheduler/src/__tests__/SchedulerMock-test.js
+75
-85
@@ -21,6 +21,10 @@ let cancelCallback;
21
let wrapCallback;
22
let getCurrentPriorityLevel;
23
let shouldYield;
24
+let waitForAll;
25
+let assertLog;
26
+let waitFor;
27
+let waitForPaint;
28
29
describe('Scheduler', () => {
30
beforeEach(() => {
@@ -40,20 +44,26 @@ describe('Scheduler', () => {
44
wrapCallback = Scheduler.unstable_wrapCallback;
45
getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
46
shouldYield = Scheduler.unstable_shouldYield;
47
+
48
+ const InternalTestUtils = require('internal-test-utils');
49
+ waitForAll = InternalTestUtils.waitForAll;
50
+ assertLog = InternalTestUtils.assertLog;
51
+ waitFor = InternalTestUtils.waitFor;
52
+ waitForPaint = InternalTestUtils.waitForPaint;
53
});
54
45
- it('flushes work incrementally', () => {
55
+ it('flushes work incrementally', async () => {
56
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
57
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
58
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('C'));
59
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('D'));
60
51
- expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
52
- expect(Scheduler).toFlushAndYieldThrough(['C']);
53
- expect(Scheduler).toFlushAndYield(['D']);
61
+ await waitFor(['A', 'B']);
62
+ await waitFor(['C']);
63
+ await waitForAll(['D']);
64
});
65
56
- it('cancels work', () => {
66
+ it('cancels work', async () => {
67
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
68
const callbackHandleB = scheduleCallback(NormalPriority, () =>
69
Scheduler.unstable_yieldValue('B'),
@@ -62,19 +72,19 @@ describe('Scheduler', () => {
72
73
cancelCallback(callbackHandleB);
74
65
- expect(Scheduler).toFlushAndYield([
75
+ await waitForAll([
76
'A',
77
// B should have been cancelled
78
'C',
79
]);
80
});
81
72
- it('executes the highest priority callbacks first', () => {
82
+ it('executes the highest priority callbacks first', async () => {
83
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
84
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
85
86
// Yield before B is flushed
77
- expect(Scheduler).toFlushAndYieldThrough(['A']);
87
+ await waitFor(['A']);
88
89
scheduleCallback(UserBlockingPriority, () =>
90
Scheduler.unstable_yieldValue('C'),
@@ -84,10 +94,10 @@ describe('Scheduler', () => {
94
);
95
96
// C and D should come first, because they are higher priority
87
- expect(Scheduler).toFlushAndYield(['C', 'D', 'B']);
97
+ await waitForAll(['C', 'D', 'B']);
98
});
99
90
- it('expires work', () => {
100
+ it('expires work', async () => {
101
scheduleCallback(NormalPriority, didTimeout => {
102
Scheduler.unstable_advanceTime(100);
103
Scheduler.unstable_yieldValue(`A (did timeout: ${didTimeout})`);
@@ -103,7 +113,7 @@ describe('Scheduler', () => {
113
114
// Advance time, but not by enough to expire any work
115
Scheduler.unstable_advanceTime(249);
106
- expect(Scheduler).toHaveYielded([]);
116
+ assertLog([]);
117
118
// Schedule a few more callbacks
119
scheduleCallback(NormalPriority, didTimeout => {
@@ -117,33 +127,27 @@ describe('Scheduler', () => {
127
128
// Advance by just a bit more to expire the user blocking callbacks
129
Scheduler.unstable_advanceTime(1);
120
- expect(Scheduler).toFlushAndYieldThrough([
121
- 'B (did timeout: true)',
122
- 'C (did timeout: true)',
123
- ]);
130
+ await waitFor(['B (did timeout: true)', 'C (did timeout: true)']);
131
132
// Expire A
133
Scheduler.unstable_advanceTime(4600);
127
- expect(Scheduler).toFlushAndYieldThrough(['A (did timeout: true)']);
134
+ await waitFor(['A (did timeout: true)']);
135
136
// Flush the rest without expiring
130
- expect(Scheduler).toFlushAndYield([
131
- 'D (did timeout: false)',
132
- 'E (did timeout: true)',
133
- ]);
137
+ await waitForAll(['D (did timeout: false)', 'E (did timeout: true)']);
138
});
139
140
it('has a default expiration of ~5 seconds', () => {
141
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
142
143
Scheduler.unstable_advanceTime(4999);
140
- expect(Scheduler).toHaveYielded([]);
144
+ assertLog([]);
145
146
Scheduler.unstable_advanceTime(1);
147
expect(Scheduler).toFlushExpired(['A']);
148
});
149
146
- it('continues working on same task after yielding', () => {
150
+ it('continues working on same task after yielding', async () => {
151
scheduleCallback(NormalPriority, () => {
152
Scheduler.unstable_advanceTime(100);
153
Scheduler.unstable_yieldValue('A');
@@ -184,14 +188,14 @@ describe('Scheduler', () => {
188
189
// Flush, then yield while in the middle of C.
190
expect(didYield).toBe(false);
187
- expect(Scheduler).toFlushAndYieldThrough(['A', 'B', 'C1']);
191
+ await waitFor(['A', 'B', 'C1']);
192
expect(didYield).toBe(true);
193
194
// When we resume, we should continue working on C.
191
- expect(Scheduler).toFlushAndYield(['C2', 'C3', 'D', 'E']);
195
+ await waitForAll(['C2', 'C3', 'D', 'E']);
196
});
197
194
- it('continuation callbacks inherit the expiration of the previous callback', () => {
198
+ it('continuation callbacks inherit the expiration of the previous callback', async () => {
199
const tasks = [
200
['A', 125],
201
['B', 124],
@@ -213,14 +217,14 @@ describe('Scheduler', () => {
217
scheduleCallback(UserBlockingPriority, work);
218
219
// Flush until just before the expiration time
216
- expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
220
+ await waitFor(['A', 'B']);
221
222
// Advance time by just a bit more. This should expire all the remaining work.
223
Scheduler.unstable_advanceTime(1);
224
expect(Scheduler).toFlushExpired(['C', 'D']);
225
});
226
223
- it('continuations are interrupted by higher priority work', () => {
227
+ it('continuations are interrupted by higher priority work', async () => {
228
const tasks = [
229
['A', 100],
230
['B', 100],
@@ -238,20 +242,20 @@ describe('Scheduler', () => {
242
}
243
};
244
scheduleCallback(NormalPriority, work);
241
- expect(Scheduler).toFlushAndYieldThrough(['A']);
245
+ await waitFor(['A']);
246
247
scheduleCallback(UserBlockingPriority, () => {
248
Scheduler.unstable_advanceTime(100);
249
Scheduler.unstable_yieldValue('High pri');
250
});
251
248
- expect(Scheduler).toFlushAndYield(['High pri', 'B', 'C', 'D']);
252
+ await waitForAll(['High pri', 'B', 'C', 'D']);
253
});
254
255
it(
256
'continuations do not block higher priority work scheduled ' +
257
'inside an executing callback',
254
- () => {
258
+ async () => {
259
const tasks = [
260
['A', 100],
261
['B', 100],
@@ -279,7 +283,7 @@ describe('Scheduler', () => {
283
}
284
};
285
scheduleCallback(NormalPriority, work);
282
- expect(Scheduler).toFlushAndYield([
286
+ await waitForAll([
287
'A',
288
'B',
289
'Schedule high pri',
@@ -293,7 +297,7 @@ describe('Scheduler', () => {
297
},
298
);
299
296
- it('cancelling a continuation', () => {
300
+ it('cancelling a continuation', async () => {
301
const task = scheduleCallback(NormalPriority, () => {
302
Scheduler.unstable_yieldValue('Yield');
303
return () => {
@@ -301,9 +305,9 @@ describe('Scheduler', () => {
305
};
306
});
307
304
- expect(Scheduler).toFlushAndYieldThrough(['Yield']);
308
+ await waitFor(['Yield']);
309
cancelCallback(task);
306
- expect(Scheduler).toFlushWithoutYielding();
310
+ await waitForAll([]);
311
});
312
313
it('top-level immediate callbacks fire in a subsequent task', () => {
@@ -320,7 +324,7 @@ describe('Scheduler', () => {
324
Scheduler.unstable_yieldValue('D'),
325
);
326
// Immediate callback hasn't fired, yet.
323
- expect(Scheduler).toHaveYielded([]);
327
+ assertLog([]);
328
// They all flush immediately within the subsequent task.
329
expect(Scheduler).toFlushExpired(['A', 'B', 'C', 'D']);
330
});
@@ -339,7 +343,7 @@ describe('Scheduler', () => {
343
scheduleCallback(ImmediatePriority, () =>
344
Scheduler.unstable_yieldValue('D'),
345
);
342
- expect(Scheduler).toHaveYielded([]);
346
+ assertLog([]);
347
// C should flush at the end
348
expect(Scheduler).toFlushExpired(['A', 'B', 'D', 'C']);
349
});
@@ -365,10 +369,10 @@ describe('Scheduler', () => {
369
);
370
371
wrappedCallback();
368
- expect(Scheduler).toHaveYielded([NormalPriority]);
372
+ assertLog([NormalPriority]);
373
374
wrappedUserBlockingCallback();
371
- expect(Scheduler).toHaveYielded([UserBlockingPriority]);
375
+ assertLog([UserBlockingPriority]);
376
});
377
378
it('wrapped callbacks inherit the current priority even when nested', () => {
@@ -387,10 +391,10 @@ describe('Scheduler', () => {
391
});
392
393
wrappedCallback();
390
- expect(Scheduler).toHaveYielded([NormalPriority]);
394
+ assertLog([NormalPriority]);
395
396
wrappedUserBlockingCallback();
393
- expect(Scheduler).toHaveYielded([UserBlockingPriority]);
397
+ assertLog([UserBlockingPriority]);
398
});
399
400
it("immediate callbacks fire even if there's an error", () => {
@@ -407,12 +411,12 @@ describe('Scheduler', () => {
411
});
412
413
expect(() => expect(Scheduler).toFlushExpired()).toThrow('Oops A');
410
- expect(Scheduler).toHaveYielded(['A']);
414
+ assertLog(['A']);
415
416
// B and C flush in a subsequent event. That way, the second error is not
417
// swallowed.
418
expect(() => expect(Scheduler).toFlushExpired()).toThrow('Oops C');
415
- expect(Scheduler).toHaveYielded(['B', 'C']);
419
+ assertLog(['B', 'C']);
420
});
421
422
it('multiple immediate callbacks can throw and there will be an error for each one', () => {
@@ -440,7 +444,7 @@ describe('Scheduler', () => {
444
Scheduler.unstable_yieldValue(getCurrentPriorityLevel());
445
});
446
443
- expect(Scheduler).toHaveYielded([
447
+ assertLog([
448
NormalPriority,
449
ImmediatePriority,
450
NormalPriority,
@@ -454,7 +458,7 @@ describe('Scheduler', () => {
458
// priority if you have sourcemaps.
459
// TODO: Feature temporarily disabled while we investigate a bug in one of
460
// our minifiers.
457
- it.skip('adds extra function to the JS stack whose name includes the priority level', () => {
461
+ it.skip('adds extra function to the JS stack whose name includes the priority level', async () => {
462
function inferPriorityFromCallstack() {
463
try {
464
throw Error();
@@ -508,7 +512,7 @@ describe('Scheduler', () => {
512
Scheduler.unstable_yieldValue('Idle: ' + inferPriorityFromCallstack()),
513
);
514
511
- expect(Scheduler).toFlushAndYield([
515
+ await waitForAll([
516
'Immediate: ' + ImmediatePriority,
517
'UserBlocking: ' + UserBlockingPriority,
518
'Normal: ' + NormalPriority,
@@ -519,7 +523,7 @@ describe('Scheduler', () => {
523
}
524
525
describe('delayed tasks', () => {
522
- it('schedules a delayed task', () => {
526
+ it('schedules a delayed task', async () => {
527
scheduleCallback(
528
NormalPriority,
529
() => Scheduler.unstable_yieldValue('A'),
@@ -529,21 +533,21 @@ describe('Scheduler', () => {
533
);
534
535
// Should flush nothing, because delay hasn't elapsed
532
- expect(Scheduler).toFlushAndYield([]);
536
+ await waitForAll([]);
537
538
// Advance time until right before the threshold
539
Scheduler.unstable_advanceTime(999);
540
// Still nothing
537
- expect(Scheduler).toFlushAndYield([]);
541
+ await waitForAll([]);
542
543
// Advance time past the threshold
544
Scheduler.unstable_advanceTime(1);
545
546
// Now it should flush like normal
543
- expect(Scheduler).toFlushAndYield(['A']);
547
+ await waitForAll(['A']);
548
});
549
546
- it('schedules multiple delayed tasks', () => {
550
+ it('schedules multiple delayed tasks', async () => {
551
scheduleCallback(
552
NormalPriority,
553
() => Scheduler.unstable_yieldValue('C'),
@@ -577,20 +581,20 @@ describe('Scheduler', () => {
581
);
582
583
// Should flush nothing, because delay hasn't elapsed
580
- expect(Scheduler).toFlushAndYield([]);
584
+ await waitForAll([]);
585
586
// Advance some time.
587
Scheduler.unstable_advanceTime(200);
588
// Both A and B are no longer delayed. They can now flush incrementally.
585
- expect(Scheduler).toFlushAndYieldThrough(['A']);
586
- expect(Scheduler).toFlushAndYield(['B']);
589
+ await waitFor(['A']);
590
+ await waitForAll(['B']);
591
592
// Advance the rest
593
Scheduler.unstable_advanceTime(200);
590
- expect(Scheduler).toFlushAndYield(['C', 'D']);
594
+ await waitForAll(['C', 'D']);
595
});
596
593
- it('interleaves normal tasks and delayed tasks', () => {
597
+ it('interleaves normal tasks and delayed tasks', async () => {
598
// Schedule some high priority callbacks with a delay. When their delay
599
// elapses, they will be the most important callback in the queue.
600
scheduleCallback(
@@ -624,17 +628,10 @@ describe('Scheduler', () => {
628
629
// Flush all the work. The timers should be interleaved with the
630
// other tasks.
627
- expect(Scheduler).toFlushAndYield([
628
- 'A',
629
- 'Timer 1',
630
- 'B',
631
- 'C',
632
- 'Timer 2',
633
- 'D',
634
- ]);
631
+ await waitForAll(['A', 'Timer 1', 'B', 'C', 'Timer 2', 'D']);
632
});
633
637
- it('interleaves delayed tasks with time-sliced tasks', () => {
634
+ it('interleaves delayed tasks with time-sliced tasks', async () => {
635
// Schedule some high priority callbacks with a delay. When their delay
636
// elapses, they will be the most important callback in the queue.
637
scheduleCallback(
@@ -670,17 +667,10 @@ describe('Scheduler', () => {
667
668
// Flush all the work. The timers should be interleaved with the
669
// other tasks.
673
- expect(Scheduler).toFlushAndYield([
674
- 'A',
675
- 'Timer 1',
676
- 'B',
677
- 'C',
678
- 'Timer 2',
679
- 'D',
680
- ]);
670
+ await waitForAll(['A', 'Timer 1', 'B', 'C', 'Timer 2', 'D']);
671
});
672
683
- it('cancels a delayed task', () => {
673
+ it('cancels a delayed task', async () => {
674
// Schedule several tasks with the same delay
675
const options = {delay: 100};
676
@@ -701,7 +691,7 @@ describe('Scheduler', () => {
691
);
692
693
// Cancel B before its delay has elapsed
704
- expect(Scheduler).toFlushAndYield([]);
694
+ await waitForAll([]);
695
cancelCallback(taskB);
696
697
// Cancel C after its delay has elapsed
@@ -709,24 +699,24 @@ describe('Scheduler', () => {
699
cancelCallback(taskC);
700
701
// Only A should flush
712
- expect(Scheduler).toFlushAndYield(['A']);
702
+ await waitForAll(['A']);
703
});
704
715
- it('gracefully handles scheduled tasks that are not a function', () => {
705
+ it('gracefully handles scheduled tasks that are not a function', async () => {
706
scheduleCallback(ImmediatePriority, null);
717
- expect(Scheduler).toFlushWithoutYielding();
707
+ await waitForAll([]);
708
709
scheduleCallback(ImmediatePriority, undefined);
720
- expect(Scheduler).toFlushWithoutYielding();
710
+ await waitForAll([]);
711
712
scheduleCallback(ImmediatePriority, {});
723
- expect(Scheduler).toFlushWithoutYielding();
713
+ await waitForAll([]);
714
715
scheduleCallback(ImmediatePriority, 42);
726
- expect(Scheduler).toFlushWithoutYielding();
716
+ await waitForAll([]);
717
});
718
729
- it('toFlushUntilNextPaint stops if a continuation is returned', () => {
719
+ it('toFlushUntilNextPaint stops if a continuation is returned', async () => {
720
scheduleCallback(NormalPriority, () => {
721
Scheduler.unstable_yieldValue('Original Task');
722
Scheduler.unstable_yieldValue('shouldYield: ' + shouldYield());
@@ -736,7 +726,7 @@ describe('Scheduler', () => {
726
};
727
});
728
739
- expect(Scheduler).toFlushUntilNextPaint([
729
+ await waitForPaint([
730
'Original Task',
731
// Immediately before returning a continuation, `shouldYield` returns
732
// false, which means there must be time remaining in the frame.
@@ -750,10 +740,10 @@ describe('Scheduler', () => {
740
expect(Scheduler.unstable_now()).toBe(0);
741
742
// Continue the task
753
- expect(Scheduler).toFlushAndYield(['Continuation Task']);
743
+ await waitForAll(['Continuation Task']);
744
});
745
756
- it("toFlushAndYield keeps flushing even if there's a continuation", () => {
746
+ it("toFlushAndYield keeps flushing even if there's a continuation", async () => {
747
scheduleCallback(NormalPriority, () => {
748
Scheduler.unstable_yieldValue('Original Task');
749
Scheduler.unstable_yieldValue('shouldYield: ' + shouldYield());
@@ -763,7 +753,7 @@ describe('Scheduler', () => {
753
};
754
});
755
766
- expect(Scheduler).toFlushAndYield([
756
+ await waitForAll([
757
'Original Task',
758
// Immediately before returning a continuation, `shouldYield` returns
759
// false, which means there must be time remaining in the frame.
packages/scheduler/src/__tests__/SchedulerProfiling-test.js
+28
-22
@@ -24,6 +24,8 @@ let cancelCallback;
24
// let wrapCallback;
25
// let getCurrentPriorityLevel;
26
// let shouldYield;
27
+let waitForAll;
28
+let waitFor;
29
30
function priorityLevelToString(priorityLevel) {
31
switch (priorityLevel) {
@@ -69,6 +71,10 @@ describe('Scheduler', () => {
71
// wrapCallback = Scheduler.unstable_wrapCallback;
72
// getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
73
// shouldYield = Scheduler.unstable_shouldYield;
74
+
75
+ const InternalTestUtils = require('internal-test-utils');
76
+ waitForAll = InternalTestUtils.waitForAll;
77
+ waitFor = InternalTestUtils.waitFor;
78
});
79
80
const TaskStartEvent = 1;
@@ -254,7 +260,7 @@ describe('Scheduler', () => {
260
return '\n' + result;
261
}
262
257
- it('creates a basic flamegraph', () => {
263
+ it('creates a basic flamegraph', async () => {
264
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
265
266
Scheduler.unstable_advanceTime(100);
@@ -280,9 +286,9 @@ describe('Scheduler', () => {
286
},
287
{label: 'Foo'},
288
);
283
- expect(Scheduler).toFlushAndYieldThrough(['Yield 1', 'Yield 3']);
289
+ await waitFor(['Yield 1', 'Yield 3']);
290
Scheduler.unstable_advanceTime(100);
285
- expect(Scheduler).toFlushAndYield(['Yield 2', 'Yield 4']);
291
+ await waitForAll(['Yield 2', 'Yield 4']);
292
293
expect(stopProfilingAndPrintFlamegraph()).toEqual(
294
`
@@ -293,7 +299,7 @@ Task 1 [Normal] │ ████████░░░░░░░
299
);
300
});
301
296
- it('marks when a task is canceled', () => {
302
+ it('marks when a task is canceled', async () => {
303
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
304
305
const task = scheduleCallback(NormalPriority, () => {
@@ -306,13 +312,13 @@ Task 1 [Normal] │ ████████░░░░░░░
312
};
313
});
314
309
- expect(Scheduler).toFlushAndYieldThrough(['Yield 1', 'Yield 2']);
315
+ await waitFor(['Yield 1', 'Yield 2']);
316
Scheduler.unstable_advanceTime(100);
317
318
cancelCallback(task);
319
320
Scheduler.unstable_advanceTime(1000);
315
- expect(Scheduler).toFlushWithoutYielding();
321
+ await waitForAll([]);
322
expect(stopProfilingAndPrintFlamegraph()).toEqual(
323
`
324
!!! Main thread │░░░░░░██████████████████████
@@ -321,7 +327,7 @@ Task 1 [Normal] │██████░░🡐 canceled
327
);
328
});
329
324
- it('marks when a task errors', () => {
330
+ it('marks when a task errors', async () => {
331
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
332
333
scheduleCallback(NormalPriority, () => {
@@ -333,7 +339,7 @@ Task 1 [Normal] │██████░░🡐 canceled
339
Scheduler.unstable_advanceTime(100);
340
341
Scheduler.unstable_advanceTime(1000);
336
- expect(Scheduler).toFlushWithoutYielding();
342
+ await waitForAll([]);
343
expect(stopProfilingAndPrintFlamegraph()).toEqual(
344
`
345
!!! Main thread │░░░░░░██████████████████████
@@ -342,7 +348,7 @@ Task 1 [Normal] │██████🡐 errored
348
);
349
});
350
345
- it('marks when multiple tasks are canceled', () => {
351
+ it('marks when multiple tasks are canceled', async () => {
352
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
353
354
const task1 = scheduleCallback(NormalPriority, () => {
@@ -364,7 +370,7 @@ Task 1 [Normal] │██████🡐 errored
370
};
371
});
372
367
- expect(Scheduler).toFlushAndYieldThrough(['Yield 1', 'Yield 2']);
373
+ await waitFor(['Yield 1', 'Yield 2']);
374
Scheduler.unstable_advanceTime(100);
375
376
cancelCallback(task1);
@@ -373,7 +379,7 @@ Task 1 [Normal] │██████🡐 errored
379
// Advance more time. This should not affect the size of the main
380
// thread row, since the Scheduler queue is empty.
381
Scheduler.unstable_advanceTime(1000);
376
- expect(Scheduler).toFlushWithoutYielding();
382
+ await waitForAll([]);
383
384
// The main thread row should end when the callback is cancelled.
385
expect(stopProfilingAndPrintFlamegraph()).toEqual(
@@ -385,14 +391,14 @@ Task 2 [Normal] │░░░░░░░░🡐 canceled
391
);
392
});
393
388
- it('handles cancelling a task that already finished', () => {
394
+ it('handles cancelling a task that already finished', async () => {
395
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
396
397
const task = scheduleCallback(NormalPriority, () => {
398
Scheduler.unstable_yieldValue('A');
399
Scheduler.unstable_advanceTime(1000);
400
});
395
- expect(Scheduler).toFlushAndYield(['A']);
401
+ await waitForAll(['A']);
402
cancelCallback(task);
403
expect(stopProfilingAndPrintFlamegraph()).toEqual(
404
`
@@ -402,7 +408,7 @@ Task 1 [Normal] │████████████████
408
);
409
});
410
405
- it('handles cancelling a task multiple times', () => {
411
+ it('handles cancelling a task multiple times', async () => {
412
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
413
414
scheduleCallback(
@@ -426,7 +432,7 @@ Task 1 [Normal] │████████████████
432
cancelCallback(task);
433
cancelCallback(task);
434
cancelCallback(task);
429
- expect(Scheduler).toFlushAndYield(['A']);
435
+ await waitForAll(['A']);
436
expect(stopProfilingAndPrintFlamegraph()).toEqual(
437
`
438
!!! Main thread │████████████░░░░░░░░░░░░░░░░░░░░
@@ -436,7 +442,7 @@ Task 2 [Normal] │ ░░░░░░░░🡐 canceled
442
);
443
});
444
439
- it('handles delayed tasks', () => {
445
+ it('handles delayed tasks', async () => {
446
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
447
scheduleCallback(
448
NormalPriority,
@@ -448,11 +454,11 @@ Task 2 [Normal] │ ░░░░░░░░🡐 canceled
454
delay: 1000,
455
},
456
);
451
- expect(Scheduler).toFlushWithoutYielding();
457
+ await waitForAll([]);
458
459
Scheduler.unstable_advanceTime(1000);
460
455
- expect(Scheduler).toFlushAndYield(['A']);
461
+ await waitForAll(['A']);
462
463
expect(stopProfilingAndPrintFlamegraph()).toEqual(
464
`
@@ -462,7 +468,7 @@ Task 1 [Normal] │ █████████
468
);
469
});
470
465
- it('handles cancelling a delayed task', () => {
471
+ it('handles cancelling a delayed task', async () => {
472
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
473
const task = scheduleCallback(
474
NormalPriority,
@@ -470,7 +476,7 @@ Task 1 [Normal] │ █████████
476
{delay: 1000},
477
);
478
cancelCallback(task);
473
- expect(Scheduler).toFlushWithoutYielding();
479
+ await waitForAll([]);
480
expect(stopProfilingAndPrintFlamegraph()).toEqual(
481
`
482
!!! Main thread │
@@ -492,7 +498,7 @@ Task 1 [Normal] │ █████████
498
taskId++;
499
const task = scheduleCallback(NormalPriority, () => {});
500
cancelCallback(task);
495
- expect(Scheduler).toFlushAndYield([]);
501
+ await waitForAll([]);
502
}
503
504
expect(console.error).toHaveBeenCalledTimes(1);
@@ -509,7 +515,7 @@ Task 1 [Normal] │ █████████
515
scheduleCallback(NormalPriority, () => {
516
Scheduler.unstable_advanceTime(1000);
517
});
512
- expect(Scheduler).toFlushAndYield([]);
518
+ await waitForAll([]);
519
520
// Note: The exact task id is not super important. That just how many tasks
521
// it happens to take before the array is resized.
packages/use-subscription/src/__tests__/useSubscription-test.js
+50
-46
@@ -16,6 +16,9 @@ let React;
16
let ReactTestRenderer;
17
let Scheduler;
18
let ReplaySubject;
19
+let assertLog;
20
+let waitForAll;
21
+let waitFor;
22
23
describe('useSubscription', () => {
24
beforeEach(() => {
@@ -31,6 +34,11 @@ describe('useSubscription', () => {
34
35
BehaviorSubject = require('rxjs').BehaviorSubject;
36
ReplaySubject = require('rxjs').ReplaySubject;
37
+
38
+ const InternalTestUtils = require('internal-test-utils');
39
+ waitForAll = InternalTestUtils.waitForAll;
40
+ assertLog = InternalTestUtils.assertLog;
41
+ waitFor = InternalTestUtils.waitFor;
42
});
43
44
function createBehaviorSubject(initialValue) {
@@ -49,7 +57,7 @@ describe('useSubscription', () => {
57
return replaySubject;
58
}
59
52
- it('supports basic subscription pattern', () => {
60
+ it('supports basic subscription pattern', async () => {
61
function Child({value = 'default'}) {
62
Scheduler.unstable_yieldValue(value);
63
return null;
@@ -79,21 +87,21 @@ describe('useSubscription', () => {
87
{unstable_isConcurrent: true},
88
);
89
});
82
- expect(Scheduler).toHaveYielded(['default']);
90
+ assertLog(['default']);
91
92
// Updates while subscribed should re-render the child component
93
act(() => observable.next(123));
86
- expect(Scheduler).toHaveYielded([123]);
94
+ assertLog([123]);
95
act(() => observable.next('abc'));
88
- expect(Scheduler).toHaveYielded(['abc']);
96
+ assertLog(['abc']);
97
98
// Unmounting the subscriber should remove listeners
99
act(() => renderer.update(<div />));
100
act(() => observable.next(456));
93
- expect(Scheduler).toFlushAndYield([]);
101
+ await waitForAll([]);
102
});
103
96
- it('should support observable types like RxJS ReplaySubject', () => {
104
+ it('should support observable types like RxJS ReplaySubject', async () => {
105
function Child({value = 'default'}) {
106
Scheduler.unstable_yieldValue(value);
107
return null;
@@ -131,19 +139,19 @@ describe('useSubscription', () => {
139
{unstable_isConcurrent: true},
140
);
141
});
134
- expect(Scheduler).toHaveYielded(['initial']);
142
+ assertLog(['initial']);
143
act(() => observable.next('updated'));
136
- expect(Scheduler).toHaveYielded(['updated']);
144
+ assertLog(['updated']);
145
138
- Scheduler.unstable_flushAll();
146
+ await waitForAll([]);
147
148
// Unsetting the subscriber prop should reset subscribed values
149
observable = createReplaySubject(undefined);
150
act(() => renderer.update(<Subscription source={observable} />));
143
- expect(Scheduler).toHaveYielded(['default']);
151
+ assertLog(['default']);
152
});
153
146
- it('should unsubscribe from old sources and subscribe to new sources when memoized props change', () => {
154
+ it('should unsubscribe from old sources and subscribe to new sources when memoized props change', async () => {
155
function Child({value = 'default'}) {
156
Scheduler.unstable_yieldValue(value);
157
return null;
@@ -182,29 +190,29 @@ describe('useSubscription', () => {
190
});
191
192
// Updates while subscribed should re-render the child component
185
- expect(Scheduler).toHaveYielded(['a-0']);
193
+ assertLog(['a-0']);
194
expect(subscriptions).toHaveLength(1);
195
expect(subscriptions[0]).toBe(observableA);
196
197
// Unsetting the subscriber prop should reset subscribed values
198
act(() => renderer.update(<Subscription source={observableB} />));
199
192
- expect(Scheduler).toHaveYielded(['b-0']);
200
+ assertLog(['b-0']);
201
expect(subscriptions).toHaveLength(2);
202
expect(subscriptions[1]).toBe(observableB);
203
204
// Updates to the old subscribable should not re-render the child component
205
act(() => observableA.next('a-1'));
198
- expect(Scheduler).toFlushAndYield([]);
206
+ await waitForAll([]);
207
208
// Updates to the bew subscribable should re-render the child component
209
act(() => observableB.next('b-1'));
202
- expect(Scheduler).toHaveYielded(['b-1']);
210
+ assertLog(['b-1']);
211
212
expect(subscriptions).toHaveLength(2);
213
});
214
207
- it('should unsubscribe from old sources and subscribe to new sources when useCallback functions change', () => {
215
+ it('should unsubscribe from old sources and subscribe to new sources when useCallback functions change', async () => {
216
function Child({value = 'default'}) {
217
Scheduler.unstable_yieldValue(value);
218
return null;
@@ -241,28 +249,28 @@ describe('useSubscription', () => {
249
});
250
251
// Updates while subscribed should re-render the child component
244
- expect(Scheduler).toHaveYielded(['a-0']);
252
+ assertLog(['a-0']);
253
expect(subscriptions).toHaveLength(1);
254
expect(subscriptions[0]).toBe(observableA);
255
256
// Unsetting the subscriber prop should reset subscribed values
257
act(() => renderer.update(<Subscription source={observableB} />));
250
- expect(Scheduler).toHaveYielded(['b-0']);
258
+ assertLog(['b-0']);
259
expect(subscriptions).toHaveLength(2);
260
expect(subscriptions[1]).toBe(observableB);
261
262
// Updates to the old subscribable should not re-render the child component
263
act(() => observableA.next('a-1'));
256
- expect(Scheduler).toFlushAndYield([]);
264
+ await waitForAll([]);
265
266
// Updates to the bew subscribable should re-render the child component
267
act(() => observableB.next('b-1'));
260
- expect(Scheduler).toHaveYielded(['b-1']);
268
+ assertLog(['b-1']);
269
270
expect(subscriptions).toHaveLength(2);
271
});
272
265
- it('should ignore values emitted by a new subscribable until the commit phase', () => {
273
+ it('should ignore values emitted by a new subscribable until the commit phase', async () => {
274
const log = [];
275
276
function Grandchild({value}) {
@@ -326,16 +334,16 @@ describe('useSubscription', () => {
334
unstable_isConcurrent: true,
335
});
336
});
329
- expect(Scheduler).toHaveYielded(['Child: a-0', 'Grandchild: a-0']);
337
+ assertLog(['Child: a-0', 'Grandchild: a-0']);
338
expect(log).toEqual(['Parent.componentDidMount']);
339
340
// Start React update, but don't finish
333
- act(() => {
341
+ await act(async () => {
342
React.startTransition(() => {
343
renderer.update(<Parent observed={observableB} />);
344
});
345
338
- expect(Scheduler).toFlushAndYieldThrough(['Child: b-0']);
346
+ await waitFor(['Child: b-0']);
347
expect(log).toEqual(['Parent.componentDidMount']);
348
349
// Emit some updates from the uncommitted subscribable
@@ -351,7 +359,7 @@ describe('useSubscription', () => {
359
// We expect the last emitted update to be rendered (because of the commit phase value check)
360
// But the intermediate ones should be ignored,
361
// And the final rendered output should be the higher-priority observable.
354
- expect(Scheduler).toHaveYielded([
362
+ assertLog([
363
'Grandchild: b-0',
364
'Child: b-3',
365
'Grandchild: b-3',
@@ -365,7 +373,7 @@ describe('useSubscription', () => {
373
]);
374
});
375
368
- it('should not drop values emitted between updates', () => {
376
+ it('should not drop values emitted between updates', async () => {
377
const log = [];
378
379
function Grandchild({value}) {
@@ -429,16 +437,16 @@ describe('useSubscription', () => {
437
unstable_isConcurrent: true,
438
});
439
});
432
- expect(Scheduler).toHaveYielded(['Child: a-0', 'Grandchild: a-0']);
440
+ assertLog(['Child: a-0', 'Grandchild: a-0']);
441
expect(log).toEqual(['Parent.componentDidMount:a-0']);
442
log.splice(0);
443
444
// Start React update, but don't finish
437
- act(() => {
445
+ await act(async () => {
446
React.startTransition(() => {
447
renderer.update(<Parent observed={observableB} />);
448
});
441
- expect(Scheduler).toFlushAndYieldThrough(['Child: b-0']);
449
+ await waitFor(['Child: b-0']);
450
expect(log).toEqual([]);
451
452
// Emit some updates from the old subscribable
@@ -455,7 +463,7 @@ describe('useSubscription', () => {
463
}
464
465
// Flush everything and ensure that the correct subscribable is used
458
- expect(Scheduler).toFlushAndYield([
466
+ await waitForAll([
467
'Child: a-2',
468
'Grandchild: a-2',
469
'Child: a-2',
@@ -467,7 +475,7 @@ describe('useSubscription', () => {
475
// Updates from the new subscribable should be ignored.
476
log.splice(0);
477
act(() => observableB.next('b-1'));
470
- expect(Scheduler).toFlushAndYield([]);
478
+ await waitForAll([]);
479
expect(log).toEqual([]);
480
});
481
@@ -514,10 +522,9 @@ describe('useSubscription', () => {
522
},
523
};
524
517
- eventHandler.subscribe(value => {
525
+ eventHandler.subscribe(async value => {
526
if (value === false) {
527
renderer.unmount();
520
- expect(Scheduler).toFlushAndYield([]);
528
}
529
});
530
@@ -528,13 +535,13 @@ describe('useSubscription', () => {
535
{unstable_isConcurrent: true},
536
);
537
});
531
- expect(Scheduler).toHaveYielded([true]);
538
+ assertLog([true]);
539
540
// This event should unmount
541
eventHandler.change(false);
542
});
543
537
- it('does not return a value from the previous subscription if the source is updated', () => {
544
+ it('does not return a value from the previous subscription if the source is updated', async () => {
545
const subscription1 = {
546
getCurrentValue: () => 'one',
547
subscribe: () => () => {},
@@ -562,13 +569,13 @@ describe('useSubscription', () => {
569
{unstable_isConcurrent: true},
570
);
571
});
565
- Scheduler.unstable_flushAll();
572
+ await waitForAll([]);
573
574
act(() => renderer.update(<Subscription subscription={subscription2} />));
568
- Scheduler.unstable_flushAll();
575
+ await waitForAll([]);
576
});
577
571
- it('should not tear if a mutation occurs during a concurrent update', () => {
578
+ it('should not tear if a mutation occurs during a concurrent update', async () => {
579
const input = document.createElement('input');
580
581
const mutate = value => {
@@ -590,7 +597,7 @@ describe('useSubscription', () => {
597
return value;
598
};
599
593
- act(() => {
600
+ await act(async () => {
601
// Initial render of "A"
602
mutate('A');
603
ReactTestRenderer.create(
@@ -600,13 +607,13 @@ describe('useSubscription', () => {
607
</React.Fragment>,
608
{unstable_isConcurrent: true},
609
);
603
- expect(Scheduler).toFlushAndYield(['render:first:A', 'render:second:A']);
610
+ await waitForAll(['render:first:A', 'render:second:A']);
611
612
// Update state "A" -> "B"
613
// This update will be eagerly evaluated,
614
// so the tearing case this test is guarding against would not happen.
615
mutate('B');
609
- expect(Scheduler).toFlushAndYield(['render:first:B', 'render:second:B']);
616
+ await waitForAll(['render:first:B', 'render:second:B']);
617
618
// No more pending updates
619
jest.runAllTimers();
@@ -618,14 +625,11 @@ describe('useSubscription', () => {
625
React.startTransition(() => {
626
mutate('C');
627
});
621
- expect(Scheduler).toFlushAndYieldThrough([
622
- 'render:first:C',
623
- 'render:second:C',
624
- ]);
628
+ await waitFor(['render:first:C', 'render:second:C']);
629
React.startTransition(() => {
630
mutate('D');
631
});
628
- expect(Scheduler).toFlushAndYield(['render:first:D', 'render:second:D']);
632
+ await waitForAll(['render:first:D', 'render:second:D']);
633
634
// No more pending updates
635
jest.runAllTimers();
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js
+8
-4
@@ -17,6 +17,7 @@ let Scheduler;
17
let useSyncExternalStore;
18
let useSyncExternalStoreWithSelector;
19
let act;
20
+let assertLog;
21
22
// This tests the userspace shim of `useSyncExternalStore` in a server-rendering
23
// (Node) environment
@@ -50,6 +51,9 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
51
Scheduler = require('scheduler');
52
act = require('jest-react').act;
53
54
+ const InternalTestUtils = require('internal-test-utils');
55
+ assertLog = InternalTestUtils.assertLog;
56
+
57
if (gate(flags => flags.source)) {
58
// The `shim/with-selector` module composes the main
59
// `use-sync-external-store` entrypoint. In the compiled artifacts, this
@@ -116,7 +120,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
120
await act(() => {
121
root.render(<App />);
122
});
119
- expect(Scheduler).toHaveYielded(['client']);
123
+ assertLog(['client']);
124
expect(root).toMatchRenderedOutput('client');
125
});
126
@@ -159,7 +163,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
163
const root = ReactNoop.createRoot();
164
act(() => root.render(<App />));
165
162
- expect(Scheduler).toHaveYielded(['A0', 'B0']);
166
+ assertLog(['A0', 'B0']);
167
expect(root).toMatchRenderedOutput('A0B0');
168
169
// Update b but not a
@@ -167,7 +171,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
171
store.set({a: 0, b: 1});
172
});
173
// Only b re-renders
170
- expect(Scheduler).toHaveYielded(['B1']);
174
+ assertLog(['B1']);
175
expect(root).toMatchRenderedOutput('A0B1');
176
177
// Update a but not b
@@ -175,7 +179,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
179
store.set({a: 1, b: 1});
180
});
181
// Only a re-renders
178
- expect(Scheduler).toHaveYielded(['A1']);
182
+ assertLog(['A1']);
183
expect(root).toMatchRenderedOutput('A1B1');
184
});
185
});
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+41
-43
@@ -20,6 +20,7 @@ let act;
20
let useState;
21
let useEffect;
22
let useLayoutEffect;
23
+let assertLog;
24
25
// This tests shared behavior between the built-in and shim implementations of
26
// of useSyncExternalStore.
@@ -55,6 +56,9 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
56
useEffect = React.useEffect;
57
useLayoutEffect = React.useLayoutEffect;
58
59
+ const InternalTestUtils = require('internal-test-utils');
60
+ assertLog = InternalTestUtils.assertLog;
61
+
62
const internalAct = require('jest-react').act;
63
64
// The internal act implementation doesn't batch updates by default, since
@@ -140,13 +144,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
144
const root = createRoot(container);
145
await act(() => root.render(<App />));
146
143
- expect(Scheduler).toHaveYielded(['Initial']);
147
+ assertLog(['Initial']);
148
expect(container.textContent).toEqual('Initial');
149
150
await act(() => {
151
store.set('Updated');
152
});
149
- expect(Scheduler).toHaveYielded(['Updated']);
153
+ assertLog(['Updated']);
154
expect(container.textContent).toEqual('Updated');
155
});
156
@@ -162,7 +166,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
166
const root = createRoot(container);
167
act(() => root.render(<App />));
168
165
- expect(Scheduler).toHaveYielded(['Initial']);
169
+ assertLog(['Initial']);
170
expect(container.textContent).toEqual('Initial');
171
172
// Update to the same value
@@ -170,7 +174,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
174
store.set('Initial');
175
});
176
// Should not re-render
173
- expect(Scheduler).toHaveYielded([]);
177
+ assertLog([]);
178
expect(container.textContent).toEqual('Initial');
179
});
180
@@ -190,13 +194,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
194
const root = createRoot(container);
195
await act(() => root.render(<App />));
196
193
- expect(Scheduler).toHaveYielded([0]);
197
+ assertLog([0]);
198
expect(container.textContent).toEqual('0');
199
200
await act(() => {
201
storeA.set(1);
202
});
199
- expect(Scheduler).toHaveYielded([1]);
203
+ assertLog([1]);
204
expect(container.textContent).toEqual('1');
205
206
// Switch stores and update in the same batch
@@ -208,7 +212,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
212
});
213
});
214
// Now reading from B instead of A
211
- expect(Scheduler).toHaveYielded([0]);
215
+ assertLog([0]);
216
expect(container.textContent).toEqual('0');
217
218
// Update A
@@ -216,14 +220,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
220
storeA.set(3);
221
});
222
// Nothing happened, because we're no longer subscribed to A
219
- expect(Scheduler).toHaveYielded([]);
223
+ assertLog([]);
224
expect(container.textContent).toEqual('0');
225
226
// Update B
227
await act(() => {
228
storeB.set(1);
229
});
226
- expect(Scheduler).toHaveYielded([1]);
230
+ assertLog([1]);
231
expect(container.textContent).toEqual('1');
232
});
233
@@ -252,7 +256,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
256
const root = createRoot(container);
257
act(() => root.render(<App />));
258
255
- expect(Scheduler).toHaveYielded(['A0', 'B0']);
259
+ assertLog(['A0', 'B0']);
260
expect(container.textContent).toEqual('A0B0');
261
262
// Update b but not a
@@ -260,7 +264,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
264
store.set({a: 0, b: 1});
265
});
266
// Only b re-renders
263
- expect(Scheduler).toHaveYielded(['B1']);
267
+ assertLog(['B1']);
268
expect(container.textContent).toEqual('A0B1');
269
270
// Update a but not b
@@ -268,7 +272,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
272
store.set({a: 1, b: 1});
273
});
274
// Only a re-renders
271
- expect(Scheduler).toHaveYielded(['A1']);
275
+ assertLog(['A1']);
276
expect(container.textContent).toEqual('A1B1');
277
});
278
@@ -292,13 +296,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
296
const container = document.createElement('div');
297
const root = createRoot(container);
298
act(() => root.render(<App />));
295
- expect(Scheduler).toHaveYielded([0, 'Passive effect: 0']);
299
+ assertLog([0, 'Passive effect: 0']);
300
301
// Schedule an update. We'll intentionally not use `act` so that we can
302
// insert a mutation before React subscribes to the store in a
303
// passive effect.
304
store.set(1);
301
- expect(Scheduler).toHaveYielded([
305
+ assertLog([
306
1,
307
// Passive effect hasn't fired yet
308
]);
@@ -306,7 +310,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
310
311
// Flip the store state back to the previous value.
312
store.set(0);
309
- expect(Scheduler).toHaveYielded([
313
+ assertLog([
314
'Passive effect: 1',
315
// Re-render. If the current state were tracked by updating a ref in a
316
// passive effect, then this would break because the previous render's
@@ -362,14 +366,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
366
const container = document.createElement('div');
367
const root = createRoot(container);
368
act(() => root.render(<App />));
365
- expect(Scheduler).toHaveYielded(['A1']);
369
+ assertLog(['A1']);
370
expect(container.textContent).toEqual('A1');
371
372
act(() => {
373
// Change getSnapshot and update the store in the same batch
374
setStep(1);
375
});
372
- expect(Scheduler).toHaveYielded([
376
+ assertLog([
377
'B1',
378
'Update B in commit phase',
379
// If Child2 had used the old getSnapshot to bail out, then it would have
@@ -420,7 +424,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
424
const container = document.createElement('div');
425
const root = createRoot(container);
426
act(() => root.render(<App />));
423
- expect(Scheduler).toHaveYielded(['A1']);
427
+ assertLog(['A1']);
428
expect(container.textContent).toEqual('A1');
429
430
// This will cause a layout effect, and in the layout effect we'll update
@@ -428,7 +432,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
432
act(() => {
433
setStep(1);
434
});
431
- expect(Scheduler).toHaveYielded([
435
+ assertLog([
436
'A1',
437
// This updates B, but since Child2 doesn't subscribe to B, it doesn't
438
// need to re-render.
@@ -467,13 +471,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
471
</>,
472
),
473
);
470
- expect(Scheduler).toHaveYielded([0, 0]);
474
+ assertLog([0, 0]);
475
expect(container.textContent).toEqual('00');
476
477
await act(() => {
478
store.set(1);
479
});
476
- expect(Scheduler).toHaveYielded([1, 1, 'Reset back to 0', 0, 0]);
480
+ assertLog([1, 1, 'Reset back to 0', 0, 0]);
481
expect(container.textContent).toEqual('00');
482
});
483
@@ -494,7 +498,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
498
const container = document.createElement('div');
499
const root = createRoot(container);
500
act(() => root.render(<App />));
497
- expect(Scheduler).toHaveYielded([0]);
501
+ assertLog([0]);
502
503
// Update the store and getSnapshot at the same time
504
act(() => {
@@ -504,7 +508,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
508
});
509
});
510
// It should read from B instead of A
507
- expect(Scheduler).toHaveYielded([2]);
511
+ assertLog([2]);
512
expect(container.textContent).toEqual('2');
513
});
514
@@ -549,7 +553,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
553
</ErrorBoundary>,
554
),
555
);
552
- expect(Scheduler).toHaveYielded([0]);
556
+ assertLog([0]);
557
expect(container.textContent).toEqual('0');
558
559
// Update that throws in a getSnapshot. We can catch it with an error boundary.
@@ -557,14 +561,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
561
store.set({value: 1, throwInGetSnapshot: true, throwInIsEqual: false});
562
});
563
if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
560
- expect(Scheduler).toHaveYielded([
564
+ assertLog([
565
'Error in getSnapshot',
566
// In a concurrent root, React renders a second time to attempt to
567
// recover from the error.
568
'Error in getSnapshot',
569
]);
570
} else {
567
- expect(Scheduler).toHaveYielded(['Error in getSnapshot']);
571
+ assertLog(['Error in getSnapshot']);
572
}
573
expect(container.textContent).toEqual('Error in getSnapshot');
574
});
@@ -644,14 +648,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
648
const root = createRoot(container);
649
act(() => root.render(<App />));
650
647
- expect(Scheduler).toHaveYielded(['App', 'Selector', 'A0']);
651
+ assertLog(['App', 'Selector', 'A0']);
652
expect(container.textContent).toEqual('A0');
653
654
// Update the store
655
await act(() => {
656
store.set({a: 1, b: 0});
657
});
654
- expect(Scheduler).toHaveYielded([
658
+ assertLog([
659
// The selector runs before React starts rendering
660
'Selector',
661
'App',
@@ -703,7 +707,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
707
const root = createRoot(container);
708
act(() => root.render(<App />));
709
706
- expect(Scheduler).toHaveYielded(['A0', 'B0']);
710
+ assertLog(['A0', 'B0']);
711
expect(container.textContent).toEqual('A0B0');
712
713
// Update b but not a
@@ -711,7 +715,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
715
store.set({a: 0, b: 1});
716
});
717
// Only b re-renders
714
- expect(Scheduler).toHaveYielded(['B1']);
718
+ assertLog(['B1']);
719
expect(container.textContent).toEqual('A0B1');
720
721
// Update a but not b
@@ -719,7 +723,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
723
store.set({a: 1, b: 1});
724
});
725
// Only a re-renders
722
- expect(Scheduler).toHaveYielded(['A1']);
726
+ assertLog(['A1']);
727
expect(container.textContent).toEqual('A1B1');
728
});
729
@@ -751,7 +755,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
755
act(() => {
756
ReactDOMClient.hydrateRoot(container, <App />);
757
});
754
- expect(Scheduler).toHaveYielded([
758
+ assertLog([
759
// First it hydrates the server rendered HTML
760
'server',
761
'Passive effect: server',
@@ -769,7 +773,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
773
'Text content did not match',
774
);
775
});
772
- expect(Scheduler).toHaveYielded(['client', 'Passive effect: client']);
776
+ assertLog(['client', 'Passive effect: client']);
777
}
778
expect(container.textContent).toEqual('client');
779
expect(ref.current).toEqual(serverRenderedDiv);
@@ -793,13 +797,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
797
const root = createRoot(container);
798
await act(() => root.render(<App />));
799
796
- expect(Scheduler).toHaveYielded(['INITIAL']);
800
+ assertLog(['INITIAL']);
801
expect(container.textContent).toEqual('INITIAL');
802
803
await act(() => {
804
store.set('Updated');
805
});
802
- expect(Scheduler).toHaveYielded(['UPDATED']);
806
+ assertLog(['UPDATED']);
807
expect(container.textContent).toEqual('UPDATED');
808
});
809
@@ -857,18 +861,12 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
861
await act(() => {
862
root.render(<App step={0} />);
863
});
860
- expect(Scheduler).toHaveYielded([
861
- 'Inline selector',
862
- 'A',
863
- 'B',
864
- 'C',
865
- 'Sibling: 0',
866
- ]);
864
+ assertLog(['Inline selector', 'A', 'B', 'C', 'Sibling: 0']);
865
866
await act(() => {
867
root.render(<App step={1} />);
868
});
871
- expect(Scheduler).toHaveYielded([
869
+ assertLog([
870
// We had to call the selector again because it's not memoized
871
'Inline selector',
872
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js
+5
-1
@@ -16,6 +16,7 @@ let React;
16
let ReactDOM;
17
let ReactDOMServer;
18
let Scheduler;
19
+let assertLog;
20
21
// This tests the userspace shim of `useSyncExternalStore` in a server-rendering
22
// (Node) environment
@@ -45,6 +46,9 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
46
ReactDOMServer = require('react-dom/server');
47
Scheduler = require('scheduler');
48
49
+ const InternalTestUtils = require('internal-test-utils');
50
+ assertLog = InternalTestUtils.assertLog;
51
+
52
useSyncExternalStore =
53
require('use-sync-external-store/shim').useSyncExternalStore;
54
});
@@ -92,7 +96,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
96
const html = ReactDOMServer.renderToString(<App />);
97
98
// We don't call getServerSnapshot in the shim
95
- expect(Scheduler).toHaveYielded(['client']);
99
+ assertLog(['client']);
100
expect(html).toEqual('client');
101
});
102
});