@samitouri / QOS-React-2 / commits / 64dde70827

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