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

Codemod tests to waitFor pattern (6/?) (#26305)

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 11:45 UTC e98695db918721a08eae0a2dceeb5719c1aa7261
10 files changed +517 -556
packages/react-reconciler/src/__tests__/ReactMemo-test.js
+44 -48
@@ -18,6 +18,8 @@ let ReactNoop;
18 let Suspense;
19 let Scheduler;
20 let act;
21 +let waitForAll;
22 +let assertLog;
23
24 describe('memo', () => {
25 beforeEach(() => {
@@ -29,6 +31,10 @@ describe('memo', () => {
31 Scheduler = require('scheduler');
32 act = require('jest-react').act;
33 ({Suspense} = React);
34 +
35 + const InternalTestUtils = require('internal-test-utils');
36 + waitForAll = InternalTestUtils.waitForAll;
37 + assertLog = InternalTestUtils.assertLog;
38 });
39
40 function Text(props) {
@@ -105,9 +111,7 @@ describe('memo', () => {
111 <Counter count={0} />
112 </Suspense>,
113 );
108 - expect(Scheduler).toFlushAndYield(['Loading...']);
109 - await Promise.resolve();
110 - expect(Scheduler).toFlushAndYield([0]);
114 + await waitForAll(['Loading...', 0]);
115 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
116
117 // Should bail out because props have not changed
@@ -116,7 +120,7 @@ describe('memo', () => {
120 <Counter count={0} />
121 </Suspense>,
122 );
119 - expect(Scheduler).toFlushAndYield([]);
123 + await waitForAll([]);
124 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
125
126 // Should update because count prop changed
@@ -125,7 +129,7 @@ describe('memo', () => {
129 <Counter count={1} />
130 </Suspense>,
131 );
128 - expect(Scheduler).toFlushAndYield([1]);
132 + await waitForAll([1]);
133 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
134 });
135
@@ -160,19 +164,17 @@ describe('memo', () => {
164
165 const parent = React.createRef(null);
166 ReactNoop.render(<Parent ref={parent} />);
163 - expect(Scheduler).toFlushAndYield(['Loading...']);
164 - await Promise.resolve();
165 - expect(Scheduler).toFlushAndYield(['Count: 0']);
167 + await waitForAll(['Loading...', 'Count: 0']);
168 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
169
170 // Should bail out because props have not changed
171 ReactNoop.render(<Parent ref={parent} />);
170 - expect(Scheduler).toFlushAndYield([]);
172 + await waitForAll([]);
173 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
174
175 // Should update because there was a context change
176 parent.current.setState({count: 1});
175 - expect(Scheduler).toFlushAndYield(['Count: 1']);
177 + await waitForAll(['Count: 1']);
178 expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
179 });
180
@@ -273,7 +275,7 @@ describe('memo', () => {
275 await act(async () => {
276 root.render(<App prop="A" />);
277 });
276 - expect(Scheduler).toHaveYielded([
278 + assertLog([
279 'SimpleMemo [A0]',
280 'ComplexMemo [A0]',
281 'MemoWithIndirection [A0]',
@@ -283,7 +285,7 @@ describe('memo', () => {
285 await act(async () => {
286 root.render(<App prop="B" />);
287 });
286 - expect(Scheduler).toHaveYielded([
288 + assertLog([
289 'SimpleMemo [B0]',
290 'ComplexMemo [B0]',
291 'MemoWithIndirection [B0]',
@@ -298,7 +300,7 @@ describe('memo', () => {
300 root.render(<App prop="B" />);
301 });
302 // Nothing re-renders
301 - expect(Scheduler).toHaveYielded([]);
303 + assertLog([]);
304
305 // Demonstrate what happens when the prop object changes, it bails out
306 // because all the props are the same, but we still render the
@@ -309,7 +311,7 @@ describe('memo', () => {
311 });
312 // The components should re-render with the new local state, but none
313 // of the props objects should have changed
312 - expect(Scheduler).toHaveYielded([
314 + assertLog([
315 'SimpleMemo [B1]',
316 'ComplexMemo [B1]',
317 'MemoWithIndirection [B1]',
@@ -322,7 +324,7 @@ describe('memo', () => {
324 });
325 // The components should re-render with the new local state, but none
326 // of the props objects should have changed
325 - expect(Scheduler).toHaveYielded([
327 + assertLog([
328 'SimpleMemo [B2]',
329 'ComplexMemo [B2]',
330 'MemoWithIndirection [B2]',
@@ -345,9 +347,7 @@ describe('memo', () => {
347 <Counter count={0} />
348 </Suspense>,
349 );
348 - expect(Scheduler).toFlushAndYield(['Loading...']);
349 - await Promise.resolve();
350 - expect(Scheduler).toFlushAndYield([0]);
350 + await waitForAll(['Loading...', 0]);
351 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
352
353 // Should bail out because props have not changed
@@ -356,7 +356,7 @@ describe('memo', () => {
356 <Counter count={0} />
357 </Suspense>,
358 );
359 - expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 0']);
359 + await waitForAll(['Old count: 0, New count: 0']);
360 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
361
362 // Should update because count prop changed
@@ -365,7 +365,7 @@ describe('memo', () => {
365 <Counter count={1} />
366 </Suspense>,
367 );
368 - expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 1', 1]);
368 + await waitForAll(['Old count: 0, New count: 1', 1]);
369 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
370 });
371
@@ -383,9 +383,7 @@ describe('memo', () => {
383 <Counter count={0} />
384 </Suspense>,
385 );
386 - expect(Scheduler).toFlushAndYield(['Loading...']);
387 - await Promise.resolve();
388 - expect(Scheduler).toFlushAndYield(['0!']);
386 + await waitForAll(['Loading...', '0!']);
387 expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
388
389 // Should bail out because props have not changed
@@ -394,7 +392,7 @@ describe('memo', () => {
392 <Counter count={0} />
393 </Suspense>,
394 );
397 - expect(Scheduler).toFlushAndYield([]);
395 + await waitForAll([]);
396 expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
397
398 // Should update because count prop changed
@@ -403,7 +401,7 @@ describe('memo', () => {
401 <Counter count={1} />
402 </Suspense>,
403 );
406 - expect(Scheduler).toFlushAndYield(['1!']);
404 + await waitForAll(['1!']);
405 expect(ReactNoop).toMatchRenderedOutput(<span prop="1!" />);
406 });
407
@@ -436,10 +434,8 @@ describe('memo', () => {
434 <Counter e={5} />
435 </Suspense>,
436 );
439 - expect(Scheduler).toFlushAndYield(['Loading...']);
440 - await Promise.resolve();
441 - expect(() => {
442 - expect(Scheduler).toFlushAndYield([15]);
437 + await expect(async () => {
438 + await waitForAll(['Loading...', 15]);
439 }).toErrorDev([
440 'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
441 ]);
@@ -451,7 +447,7 @@ describe('memo', () => {
447 <Counter e={5} />
448 </Suspense>,
449 );
454 - expect(Scheduler).toFlushAndYield([]);
450 + await waitForAll([]);
451 expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
452
453 // Should update because count prop changed
@@ -460,7 +456,7 @@ describe('memo', () => {
456 <Counter e={10} />
457 </Suspense>,
458 );
463 - expect(Scheduler).toFlushAndYield([20]);
459 + await waitForAll([20]);
460 expect(ReactNoop).toMatchRenderedOutput(<span prop={20} />);
461 });
462
@@ -480,7 +476,7 @@ describe('memo', () => {
476 );
477 });
478
483 - it('validates propTypes declared on the inner component', () => {
479 + it('validates propTypes declared on the inner component', async () => {
480 function FnInner(props) {
481 return props.inner;
482 }
@@ -488,23 +484,23 @@ describe('memo', () => {
484 const Fn = React.memo(FnInner);
485
486 // Mount
491 - expect(() => {
487 + await expect(async () => {
488 ReactNoop.render(<Fn inner="2" />);
493 - expect(Scheduler).toFlushWithoutYielding();
489 + await waitForAll([]);
490 }).toErrorDev(
491 'Invalid prop `inner` of type `string` supplied to `FnInner`, expected `number`.',
492 );
493
494 // Update
499 - expect(() => {
495 + await expect(async () => {
496 ReactNoop.render(<Fn inner={false} />);
501 - expect(Scheduler).toFlushWithoutYielding();
497 + await waitForAll([]);
498 }).toErrorDev(
499 'Invalid prop `inner` of type `boolean` supplied to `FnInner`, expected `number`.',
500 );
501 });
502
507 - it('validates propTypes declared on the outer component', () => {
503 + it('validates propTypes declared on the outer component', async () => {
504 function FnInner(props) {
505 return props.outer;
506 }
@@ -512,25 +508,25 @@ describe('memo', () => {
508 Fn.propTypes = {outer: PropTypes.number.isRequired};
509
510 // Mount
515 - expect(() => {
511 + await expect(async () => {
512 ReactNoop.render(<Fn outer="3" />);
517 - expect(Scheduler).toFlushWithoutYielding();
513 + await waitForAll([]);
514 }).toErrorDev(
515 // Outer props are checked in createElement
516 'Invalid prop `outer` of type `string` supplied to `FnInner`, expected `number`.',
517 );
518
519 // Update
524 - expect(() => {
520 + await expect(async () => {
521 ReactNoop.render(<Fn outer={false} />);
526 - expect(Scheduler).toFlushWithoutYielding();
522 + await waitForAll([]);
523 }).toErrorDev(
524 // Outer props are checked in createElement
525 'Invalid prop `outer` of type `boolean` supplied to `FnInner`, expected `number`.',
526 );
527 });
528
533 - it('validates nested propTypes declarations', () => {
529 + it('validates nested propTypes declarations', async () => {
530 function Inner(props) {
531 return props.inner + props.middle + props.outer;
532 }
@@ -549,20 +545,20 @@ describe('memo', () => {
545 <Outer />
546 </div>,
547 );
552 - expect(() => {
553 - expect(Scheduler).toFlushWithoutYielding();
548 + await expect(async () => {
549 + await waitForAll([]);
550 }).toErrorDev([
551 'Inner: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
552 ]);
553
554 // Mount
559 - expect(() => {
555 + await expect(async () => {
556 ReactNoop.render(
557 <div>
558 <Outer inner="2" middle="3" outer="4" />
559 </div>,
560 );
565 - expect(Scheduler).toFlushWithoutYielding();
561 + await waitForAll([]);
562 }).toErrorDev([
563 'Invalid prop `outer` of type `string` supplied to `Inner`, expected `number`.',
564 'Invalid prop `middle` of type `string` supplied to `Inner`, expected `number`.',
@@ -570,13 +566,13 @@ describe('memo', () => {
566 ]);
567
568 // Update
573 - expect(() => {
569 + await expect(async () => {
570 ReactNoop.render(
571 <div>
572 <Outer inner={false} middle={false} outer={false} />
573 </div>,
574 );
579 - expect(Scheduler).toFlushWithoutYielding();
575 + await waitForAll([]);
576 }).toErrorDev([
577 'Invalid prop `outer` of type `boolean` supplied to `Inner`, expected `number`.',
578 'Invalid prop `middle` of type `boolean` supplied to `Inner`, expected `number`.',
packages/react-reconciler/src/__tests__/ReactNewContext-test.js
+98 -90
@@ -14,6 +14,8 @@ let useContext;
14 let ReactNoop;
15 let Scheduler;
16 let gen;
17 +let waitForAll;
18 +let waitFor;
19
20 describe('ReactNewContext', () => {
21 beforeEach(() => {
@@ -24,6 +26,10 @@ describe('ReactNewContext', () => {
26 ReactNoop = require('react-noop-renderer');
27 Scheduler = require('scheduler');
28 gen = require('random-seed');
29 +
30 + const InternalTestUtils = require('internal-test-utils');
31 + waitForAll = InternalTestUtils.waitForAll;
32 + waitFor = InternalTestUtils.waitFor;
33 });
34
35 afterEach(() => {
@@ -110,7 +116,7 @@ describe('ReactNewContext', () => {
116
117 function sharedContextTests(label, getConsumer) {
118 describe(`reading context with ${label}`, () => {
113 - it('simple mount and update', () => {
119 + it('simple mount and update', async () => {
120 const Context = React.createContext(1);
121 const Consumer = getConsumer(Context);
122
@@ -131,16 +137,16 @@ describe('ReactNewContext', () => {
137 }
138
139 ReactNoop.render(<App value={2} />);
134 - expect(Scheduler).toFlushWithoutYielding();
140 + await waitForAll([]);
141 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
142
143 // Update
144 ReactNoop.render(<App value={3} />);
139 - expect(Scheduler).toFlushWithoutYielding();
145 + await waitForAll([]);
146 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
147 });
148
143 - it('propagates through shouldComponentUpdate false', () => {
149 + it('propagates through shouldComponentUpdate false', async () => {
150 const Context = React.createContext(1);
151 const ContextConsumer = getConsumer(Context);
152
@@ -189,7 +195,7 @@ describe('ReactNewContext', () => {
195 }
196
197 ReactNoop.render(<App value={2} />);
192 - expect(Scheduler).toFlushAndYield([
198 + await waitForAll([
199 'App',
200 'Provider',
201 'Indirection',
@@ -201,15 +207,11 @@ describe('ReactNewContext', () => {
207
208 // Update
209 ReactNoop.render(<App value={3} />);
204 - expect(Scheduler).toFlushAndYield([
205 - 'App',
206 - 'Provider',
207 - 'Consumer render prop',
208 - ]);
210 + await waitForAll(['App', 'Provider', 'Consumer render prop']);
211 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
212 });
213
212 - it('consumers bail out if context value is the same', () => {
214 + it('consumers bail out if context value is the same', async () => {
215 const Context = React.createContext(1);
216 const ContextConsumer = getConsumer(Context);
217
@@ -258,7 +260,7 @@ describe('ReactNewContext', () => {
260 }
261
262 ReactNoop.render(<App value={2} />);
261 - expect(Scheduler).toFlushAndYield([
263 + await waitForAll([
264 'App',
265 'Provider',
266 'Indirection',
@@ -270,7 +272,7 @@ describe('ReactNewContext', () => {
272
273 // Update with the same context value
274 ReactNoop.render(<App value={2} />);
273 - expect(Scheduler).toFlushAndYield([
275 + await waitForAll([
276 'App',
277 'Provider',
278 // Don't call render prop again
@@ -278,7 +280,7 @@ describe('ReactNewContext', () => {
280 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
281 });
282
281 - it('nested providers', () => {
283 + it('nested providers', async () => {
284 const Context = React.createContext(1);
285 const Consumer = getConsumer(Context);
286
@@ -325,16 +327,16 @@ describe('ReactNewContext', () => {
327 }
328
329 ReactNoop.render(<App value={2} />);
328 - expect(Scheduler).toFlushWithoutYielding();
330 + await waitForAll([]);
331 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 8" />);
332
333 // Update
334 ReactNoop.render(<App value={3} />);
333 - expect(Scheduler).toFlushWithoutYielding();
335 + await waitForAll([]);
336 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 12" />);
337 });
338
337 - it('should provide the correct (default) values to consumers outside of a provider', () => {
339 + it('should provide the correct (default) values to consumers outside of a provider', async () => {
340 const FooContext = React.createContext({value: 'foo-initial'});
341 const BarContext = React.createContext({value: 'bar-initial'});
342 const FooConsumer = getConsumer(FooContext);
@@ -369,10 +371,10 @@ describe('ReactNewContext', () => {
371 </BarConsumer>
372 </>,
373 );
372 - expect(Scheduler).toFlushWithoutYielding();
374 + await waitForAll([]);
375 });
376
375 - it('multiple consumers in different branches', () => {
377 + it('multiple consumers in different branches', async () => {
378 const Context = React.createContext(1);
379 const Consumer = getConsumer(Context);
380
@@ -420,7 +422,7 @@ describe('ReactNewContext', () => {
422 }
423
424 ReactNoop.render(<App value={2} />);
423 - expect(Scheduler).toFlushWithoutYielding();
425 + await waitForAll([]);
426 expect(ReactNoop).toMatchRenderedOutput(
427 <>
428 <span prop="Result: 4" />
@@ -430,7 +432,7 @@ describe('ReactNewContext', () => {
432
433 // Update
434 ReactNoop.render(<App value={3} />);
433 - expect(Scheduler).toFlushWithoutYielding();
435 + await waitForAll([]);
436 expect(ReactNoop).toMatchRenderedOutput(
437 <>
438 <span prop="Result: 6" />
@@ -440,7 +442,7 @@ describe('ReactNewContext', () => {
442
443 // Another update
444 ReactNoop.render(<App value={4} />);
443 - expect(Scheduler).toFlushWithoutYielding();
445 + await waitForAll([]);
446 expect(ReactNoop).toMatchRenderedOutput(
447 <>
448 <span prop="Result: 8" />
@@ -449,7 +451,7 @@ describe('ReactNewContext', () => {
451 );
452 });
453
452 - it('compares context values with Object.is semantics', () => {
454 + it('compares context values with Object.is semantics', async () => {
455 const Context = React.createContext(1);
456 const ContextConsumer = getConsumer(Context);
457
@@ -498,7 +500,7 @@ describe('ReactNewContext', () => {
500 }
501
502 ReactNoop.render(<App value={NaN} />);
501 - expect(Scheduler).toFlushAndYield([
503 + await waitForAll([
504 'App',
505 'Provider',
506 'Indirection',
@@ -510,7 +512,7 @@ describe('ReactNewContext', () => {
512
513 // Update
514 ReactNoop.render(<App value={NaN} />);
513 - expect(Scheduler).toFlushAndYield([
515 + await waitForAll([
516 'App',
517 'Provider',
518 // Consumer should not re-render again
@@ -519,7 +521,7 @@ describe('ReactNewContext', () => {
521 expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: NaN" />);
522 });
523
522 - it('context unwinds when interrupted', () => {
524 + it('context unwinds when interrupted', async () => {
525 const Context = React.createContext('Default');
526 const ContextConsumer = getConsumer(Context);
527
@@ -564,14 +566,14 @@ describe('ReactNewContext', () => {
566 }
567
568 ReactNoop.render(<App value="A" />);
567 - expect(Scheduler).toFlushWithoutYielding();
569 + await waitForAll([]);
570 expect(ReactNoop).toMatchRenderedOutput(
571 // The second provider should use the default value.
572 <span prop="Result: Does not unwind" />,
573 );
574 });
575
574 - it("does not re-render if there's an update in a child", () => {
576 + it("does not re-render if there's an update in a child", async () => {
577 const Context = React.createContext(0);
578 const Consumer = getConsumer(Context);
579
@@ -603,19 +605,19 @@ describe('ReactNewContext', () => {
605
606 // Initial mount
607 ReactNoop.render(<App value={1} />);
606 - expect(Scheduler).toFlushAndYield(['Consumer render prop', 'Child']);
608 + await waitForAll(['Consumer render prop', 'Child']);
609 expect(ReactNoop).toMatchRenderedOutput(
610 <span prop="Context: 1, Step: 0" />,
611 );
612
613 child.setState({step: 1});
612 - expect(Scheduler).toFlushAndYield(['Child']);
614 + await waitForAll(['Child']);
615 expect(ReactNoop).toMatchRenderedOutput(
616 <span prop="Context: 1, Step: 1" />,
617 );
618 });
619
618 - it('consumer bails out if value is unchanged and something above bailed out', () => {
620 + it('consumer bails out if value is unchanged and something above bailed out', async () => {
621 const Context = React.createContext(0);
622 const Consumer = getConsumer(Context);
623
@@ -660,7 +662,7 @@ describe('ReactNewContext', () => {
662
663 // Initial mount
664 ReactNoop.render(<App value={1} />);
663 - expect(Scheduler).toFlushAndYield([
665 + await waitForAll([
666 'App',
667 'PureIndirection',
668 'ChildWithInlineRenderCallback',
@@ -677,7 +679,7 @@ describe('ReactNewContext', () => {
679
680 // Update (bailout)
681 ReactNoop.render(<App value={1} />);
680 - expect(Scheduler).toFlushAndYield(['App']);
682 + await waitForAll(['App']);
683 expect(ReactNoop).toMatchRenderedOutput(
684 <>
685 <span prop={1} />
@@ -687,7 +689,7 @@ describe('ReactNewContext', () => {
689
690 // Update (no bailout)
691 ReactNoop.render(<App value={2} />);
690 - expect(Scheduler).toFlushAndYield(['App', 'Consumer', 'Consumer']);
692 + await waitForAll(['App', 'Consumer', 'Consumer']);
693 expect(ReactNoop).toMatchRenderedOutput(
694 <>
695 <span prop={2} />
@@ -697,7 +699,7 @@ describe('ReactNewContext', () => {
699 });
700
701 // @gate www
700 - it("context consumer doesn't bail out inside hidden subtree", () => {
702 + it("context consumer doesn't bail out inside hidden subtree", async () => {
703 const Context = React.createContext('dark');
704 const Consumer = getConsumer(Context);
705
@@ -712,7 +714,7 @@ describe('ReactNewContext', () => {
714 }
715
716 ReactNoop.render(<App theme="dark" />);
715 - expect(Scheduler).toFlushAndYield(['dark']);
717 + await waitForAll(['dark']);
718 expect(ReactNoop.getChildrenAsJSX()).toEqual(
719 <div hidden={true}>
720 <span prop="dark" />
@@ -720,7 +722,7 @@ describe('ReactNewContext', () => {
722 );
723
724 ReactNoop.render(<App theme="light" />);
723 - expect(Scheduler).toFlushAndYield(['light']);
725 + await waitForAll(['light']);
726 expect(ReactNoop.getChildrenAsJSX()).toEqual(
727 <div hidden={true}>
728 <span prop="light" />
@@ -729,7 +731,7 @@ describe('ReactNewContext', () => {
731 });
732
733 // This is a regression case for https://github.com/facebook/react/issues/12389.
732 - it('does not run into an infinite loop', () => {
734 + it('does not run into an infinite loop', async () => {
735 const Context = React.createContext(null);
736 const Consumer = getConsumer(Context);
737
@@ -759,15 +761,15 @@ describe('ReactNewContext', () => {
761 }
762
763 ReactNoop.render(<App reverse={false} />);
762 - expect(Scheduler).toFlushWithoutYielding();
764 + await waitForAll([]);
765 ReactNoop.render(<App reverse={true} />);
764 - expect(Scheduler).toFlushWithoutYielding();
766 + await waitForAll([]);
767 ReactNoop.render(<App reverse={false} />);
766 - expect(Scheduler).toFlushWithoutYielding();
768 + await waitForAll([]);
769 });
770
771 // This is a regression case for https://github.com/facebook/react/issues/12686
770 - it('does not skip some siblings', () => {
772 + it('does not skip some siblings', async () => {
773 const Context = React.createContext(0);
774 const ContextConsumer = getConsumer(Context);
775
@@ -816,7 +818,7 @@ describe('ReactNewContext', () => {
818 // Initial mount
819 let inst;
820 ReactNoop.render(<App ref={ref => (inst = ref)} />);
819 - expect(Scheduler).toFlushAndYield(['App']);
821 + await waitForAll(['App']);
822 expect(ReactNoop).toMatchRenderedOutput(
823 <>
824 <span prop="static 1" />
@@ -825,7 +827,7 @@ describe('ReactNewContext', () => {
827 );
828 // Update the first time
829 inst.setState({step: 1});
828 - expect(Scheduler).toFlushAndYield(['App', 'Consumer']);
830 + await waitForAll(['App', 'Consumer']);
831 expect(ReactNoop).toMatchRenderedOutput(
832 <>
833 <span prop="static 1" />
@@ -835,7 +837,7 @@ describe('ReactNewContext', () => {
837 );
838 // Update the second time
839 inst.setState({step: 2});
838 - expect(Scheduler).toFlushAndYield(['App', 'Consumer']);
840 + await waitForAll(['App', 'Consumer']);
841 expect(ReactNoop).toMatchRenderedOutput(
842 <>
843 <span prop="static 1" />
@@ -863,7 +865,7 @@ describe('ReactNewContext', () => {
865 );
866 });
867
866 - it('warns if multiple renderers concurrently render the same context', () => {
868 + it('warns if multiple renderers concurrently render the same context', async () => {
869 spyOnDev(console, 'error').mockImplementation(() => {});
870 const Context = React.createContext(0);
871
@@ -885,17 +887,20 @@ describe('ReactNewContext', () => {
887 ReactNoop.render(<App value={1} />);
888 });
889 // Render past the Provider, but don't commit yet
888 - expect(Scheduler).toFlushAndYieldThrough(['Foo']);
890 + await waitFor(['Foo']);
891
892 // Get a new copy of ReactNoop
893 jest.resetModules();
894 React = require('react');
895 ReactNoop = require('react-noop-renderer');
896 Scheduler = require('scheduler');
897 + const InternalTestUtils = require('internal-test-utils');
898 + waitForAll = InternalTestUtils.waitForAll;
899 + waitFor = InternalTestUtils.waitFor;
900
901 // Render the provider again using a different renderer
902 ReactNoop.render(<App value={1} />);
898 - expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
903 + await waitForAll(['Foo', 'Foo']);
904
905 if (__DEV__) {
906 expect(console.error.mock.calls[0][0]).toContain(
@@ -905,7 +910,7 @@ describe('ReactNewContext', () => {
910 }
911 });
912
908 - it('does not warn if multiple renderers use the same context sequentially', () => {
913 + it('does not warn if multiple renderers use the same context sequentially', async () => {
914 spyOnDev(console, 'error');
915 const Context = React.createContext(0);
916
@@ -926,24 +931,27 @@ describe('ReactNewContext', () => {
931 React.startTransition(() => {
932 ReactNoop.render(<App value={1} />);
933 });
929 - expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
934 + await waitForAll(['Foo', 'Foo']);
935
936 // Get a new copy of ReactNoop
937 jest.resetModules();
938 React = require('react');
939 ReactNoop = require('react-noop-renderer');
940 Scheduler = require('scheduler');
941 + const InternalTestUtils = require('internal-test-utils');
942 + waitForAll = InternalTestUtils.waitForAll;
943 + waitFor = InternalTestUtils.waitFor;
944
945 // Render the provider again using a different renderer
946 ReactNoop.render(<App value={1} />);
939 - expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
947 + await waitForAll(['Foo', 'Foo']);
948
949 if (__DEV__) {
950 expect(console.error).not.toHaveBeenCalled();
951 }
952 });
953
946 - it('provider bails out if children and value are unchanged (like sCU)', () => {
954 + it('provider bails out if children and value are unchanged (like sCU)', async () => {
955 const Context = React.createContext(0);
956
957 function Child() {
@@ -962,19 +970,19 @@ describe('ReactNewContext', () => {
970
971 // Initial mount
972 ReactNoop.render(<App value={1} />);
965 - expect(Scheduler).toFlushAndYield(['App', 'Child']);
973 + await waitForAll(['App', 'Child']);
974 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
975
976 // Update
977 ReactNoop.render(<App value={1} />);
970 - expect(Scheduler).toFlushAndYield([
978 + await waitForAll([
979 'App',
980 // Child does not re-render
981 ]);
982 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
983 });
984
977 - it('provider does not bail out if legacy context changed above', () => {
985 + it('provider does not bail out if legacy context changed above', async () => {
986 const Context = React.createContext(0);
987
988 function Child() {
@@ -1021,22 +1029,22 @@ describe('ReactNewContext', () => {
1029 </App>
1030 </LegacyProvider>,
1031 );
1024 - expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']);
1032 + await waitForAll(['LegacyProvider', 'App', 'Child']);
1033 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1034
1035 // Update App with same value (should bail out)
1036 appRef.current.setState({value: 1});
1029 - expect(Scheduler).toFlushAndYield(['App']);
1037 + await waitForAll(['App']);
1038 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1039
1040 // Update LegacyProvider (should not bail out)
1041 legacyProviderRef.current.setState({value: 1});
1034 - expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']);
1042 + await waitForAll(['LegacyProvider', 'App', 'Child']);
1043 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1044
1045 // Update App with same value (should bail out)
1046 appRef.current.setState({value: 1});
1039 - expect(Scheduler).toFlushAndYield(['App']);
1047 + await waitForAll(['App']);
1048 expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1049 });
1050 });
@@ -1055,7 +1063,7 @@ describe('ReactNewContext', () => {
1063 }
1064 });
1065
1058 - it('can read other contexts inside consumer render prop', () => {
1066 + it('can read other contexts inside consumer render prop', async () => {
1067 const FooContext = React.createContext(0);
1068 const BarContext = React.createContext(0);
1069
@@ -1092,17 +1100,17 @@ describe('ReactNewContext', () => {
1100 }
1101
1102 ReactNoop.render(<App foo={1} bar={1} />);
1095 - expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1']);
1103 + await waitForAll(['Foo: 1, Bar: 1']);
1104 expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 1, Bar: 1" />);
1105
1106 // Update foo
1107 ReactNoop.render(<App foo={2} bar={1} />);
1100 - expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']);
1108 + await waitForAll(['Foo: 2, Bar: 1']);
1109 expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 1" />);
1110
1111 // Update bar
1112 ReactNoop.render(<App foo={2} bar={2} />);
1105 - expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']);
1113 + await waitForAll(['Foo: 2, Bar: 2']);
1114 expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 2" />);
1115 });
1116
@@ -1111,7 +1119,7 @@ describe('ReactNewContext', () => {
1119 // If we bailed out on referential equality, it would be confusing that you
1120 // can call this.setState(), but an autobound render callback "blocked" the update.
1121 // https://github.com/facebook/react/pull/12470#issuecomment-376917711
1114 - it('consumer does not bail out if there were no bailouts above it', () => {
1122 + it('consumer does not bail out if there were no bailouts above it', async () => {
1123 const Context = React.createContext(0);
1124 const Consumer = Context.Consumer;
1125
@@ -1138,12 +1146,12 @@ describe('ReactNewContext', () => {
1146 // Initial mount
1147 let inst;
1148 ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
1141 - expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1149 + await waitForAll(['App', 'App#renderConsumer']);
1150 expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
1151
1152 // Update
1153 inst.setState({text: 'goodbye'});
1146 - expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1154 + await waitForAll(['App', 'App#renderConsumer']);
1155 expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
1156 });
1157 });
@@ -1152,7 +1160,7 @@ describe('ReactNewContext', () => {
1160 // Unstable changedBits API was removed. Port this test to context selectors
1161 // once that exists.
1162 // @gate FIXME
1155 - it('can read the same context multiple times in the same function', () => {
1163 + it('can read the same context multiple times in the same function', async () => {
1164 const Context = React.createContext({foo: 0, bar: 0, baz: 0}, (a, b) => {
1165 let result = 0;
1166 if (a.foo !== b.foo) {
@@ -1212,7 +1220,7 @@ describe('ReactNewContext', () => {
1220 }
1221
1222 ReactNoop.render(<App foo={1} bar={1} baz={1} />);
1215 - expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1', 'Baz: 1']);
1223 + await waitForAll(['Foo: 1, Bar: 1', 'Baz: 1']);
1224 expect(ReactNoop).toMatchRenderedOutput([
1225 <span prop="Foo: 1, Bar: 1" />,
1226 <span prop="Baz: 1" />,
@@ -1220,7 +1228,7 @@ describe('ReactNewContext', () => {
1228
1229 // Update only foo
1230 ReactNoop.render(<App foo={2} bar={1} baz={1} />);
1223 - expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']);
1231 + await waitForAll(['Foo: 2, Bar: 1']);
1232 expect(ReactNoop).toMatchRenderedOutput([
1233 <span prop="Foo: 2, Bar: 1" />,
1234 <span prop="Baz: 1" />,
@@ -1228,7 +1236,7 @@ describe('ReactNewContext', () => {
1236
1237 // Update only bar
1238 ReactNoop.render(<App foo={2} bar={2} baz={1} />);
1231 - expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']);
1239 + await waitForAll(['Foo: 2, Bar: 2']);
1240 expect(ReactNoop).toMatchRenderedOutput([
1241 <span prop="Foo: 2, Bar: 2" />,
1242 <span prop="Baz: 1" />,
@@ -1236,7 +1244,7 @@ describe('ReactNewContext', () => {
1244
1245 // Update only baz
1246 ReactNoop.render(<App foo={2} bar={2} baz={2} />);
1239 - expect(Scheduler).toFlushAndYield(['Baz: 2']);
1247 + await waitForAll(['Baz: 2']);
1248 expect(ReactNoop).toMatchRenderedOutput([
1249 <span prop="Foo: 2, Bar: 2" />,
1250 <span prop="Baz: 2" />,
@@ -1248,7 +1256,7 @@ describe('ReactNewContext', () => {
1256 // If we bailed out on referential equality, it would be confusing that you
1257 // can call this.setState(), but an autobound render callback "blocked" the update.
1258 // https://github.com/facebook/react/pull/12470#issuecomment-376917711
1251 - it('does not bail out if there were no bailouts above it', () => {
1259 + it('does not bail out if there were no bailouts above it', async () => {
1260 const Context = React.createContext(0);
1261
1262 class Consumer extends React.Component {
@@ -1281,12 +1289,12 @@ describe('ReactNewContext', () => {
1289 // Initial mount
1290 let inst;
1291 ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
1284 - expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1292 + await waitForAll(['App', 'App#renderConsumer']);
1293 expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
1294
1295 // Update
1296 inst.setState({text: 'goodbye'});
1289 - expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1297 + await waitForAll(['App', 'App#renderConsumer']);
1298 expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
1299 });
1300
@@ -1361,7 +1369,7 @@ describe('ReactNewContext', () => {
1369 // If we bailed out on referential equality, it would be confusing that you
1370 // can call this.setState(), but an autobound render callback "blocked" the update.
1371 // https://github.com/facebook/react/pull/12470#issuecomment-376917711
1364 - it('does not bail out if there were no bailouts above it', () => {
1372 + it('does not bail out if there were no bailouts above it', async () => {
1373 const Context = React.createContext(0);
1374
1375 function Consumer({children}) {
@@ -1392,17 +1400,17 @@ describe('ReactNewContext', () => {
1400 // Initial mount
1401 let inst;
1402 ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
1395 - expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1403 + await waitForAll(['App', 'App#renderConsumer']);
1404 expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
1405
1406 // Update
1407 inst.setState({text: 'goodbye'});
1400 - expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
1408 + await waitForAll(['App', 'App#renderConsumer']);
1409 expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
1410 });
1411 });
1412
1405 - it('unwinds after errors in complete phase', () => {
1413 + it('unwinds after errors in complete phase', async () => {
1414 const Context = React.createContext(0);
1415
1416 // This is a regression test for stack misalignment
@@ -1419,7 +1427,7 @@ describe('ReactNewContext', () => {
1427 <Context.Consumer>{value => <span prop={value} />}</Context.Consumer>
1428 </Context.Provider>,
1429 );
1422 - expect(Scheduler).toFlushWithoutYielding();
1430 + await waitForAll([]);
1431 expect(ReactNoop).toMatchRenderedOutput(<span prop={10} />);
1432 });
1433
@@ -1631,7 +1639,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1639 });
1640 });
1641
1634 - it('should warn with an error message when using context as a consumer in DEV', () => {
1642 + it('should warn with an error message when using context as a consumer in DEV', async () => {
1643 const BarContext = React.createContext({value: 'bar-initial'});
1644 const BarConsumer = BarContext;
1645
@@ -1647,9 +1655,9 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1655 );
1656 }
1657
1650 - expect(() => {
1658 + await expect(async () => {
1659 ReactNoop.render(<Component />);
1652 - expect(Scheduler).toFlushWithoutYielding();
1660 + await waitForAll([]);
1661 }).toErrorDev(
1662 'Rendering <Context> directly is not supported and will be removed in ' +
1663 'a future major release. Did you mean to render <Context.Consumer> instead?',
@@ -1657,7 +1665,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1665 });
1666
1667 // False positive regression test.
1660 - it('should not warn when using Consumer from React < 16.6 with newer renderer', () => {
1668 + it('should not warn when using Consumer from React < 16.6 with newer renderer', async () => {
1669 const BarContext = React.createContext({value: 'bar-initial'});
1670 // React 16.5 and earlier didn't have a separate object.
1671 BarContext.Consumer = BarContext;
@@ -1675,10 +1683,10 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1683 }
1684
1685 ReactNoop.render(<Component />);
1678 - expect(Scheduler).toFlushWithoutYielding();
1686 + await waitForAll([]);
1687 });
1688
1681 - it('should warn with an error message when using nested context consumers in DEV', () => {
1689 + it('should warn with an error message when using nested context consumers in DEV', async () => {
1690 const BarContext = React.createContext({value: 'bar-initial'});
1691 const BarConsumer = BarContext;
1692
@@ -1694,16 +1702,16 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1702 );
1703 }
1704
1697 - expect(() => {
1705 + await expect(async () => {
1706 ReactNoop.render(<Component />);
1699 - expect(Scheduler).toFlushWithoutYielding();
1707 + await waitForAll([]);
1708 }).toErrorDev(
1709 'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
1710 'a future major release. Did you mean to render <Context.Consumer> instead?',
1711 );
1712 });
1713
1706 - it('should warn with an error message when using Context.Consumer.Provider DEV', () => {
1714 + it('should warn with an error message when using Context.Consumer.Provider DEV', async () => {
1715 const BarContext = React.createContext({value: 'bar-initial'});
1716
1717 function Component() {
@@ -1718,9 +1726,9 @@ Context fuzz tester error! Copy and paste the following line into the test suite
1726 );
1727 }
1728
1721 - expect(() => {
1729 + await expect(async () => {
1730 ReactNoop.render(<Component />);
1723 - expect(Scheduler).toFlushWithoutYielding();
1731 + await waitForAll([]);
1732 }).toErrorDev(
1733 'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
1734 'a future major release. Did you mean to render <Context.Provider> instead?',
packages/react-reconciler/src/__tests__/ReactNoopRendererAct-test.js
+4 -3
@@ -13,6 +13,7 @@ const React = require('react');
13 const ReactNoop = require('react-noop-renderer');
14 const Scheduler = require('scheduler');
15 const act = require('jest-react').act;
16 +const {assertLog, waitForAll} = require('internal-test-utils');
17
18 // TODO: These tests are no longer specific to the noop renderer
19 // implementation. They test the internal implementation we use in the React
@@ -34,7 +35,7 @@ describe('internal act()', () => {
35 />,
36 );
37 });
37 - expect(Scheduler).toFlushWithoutYielding();
38 + await waitForAll([]);
39 expect(calledLog).toEqual([0]);
40 });
41
@@ -56,8 +57,8 @@ describe('internal act()', () => {
57 await act(async () => {
58 ReactNoop.render(<App />);
59 });
59 - expect(Scheduler).toHaveYielded(['stage 1', 'stage 2']);
60 - expect(Scheduler).toFlushWithoutYielding();
60 + assertLog(['stage 1', 'stage 2']);
61 + await waitForAll([]);
62 expect(ReactNoop).toMatchRenderedOutput('1');
63 });
64 });
packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
+100 -115
@@ -10,6 +10,9 @@ let useEffect;
10 let useMemo;
11 let useRef;
12 let startTransition;
13 +let waitForPaint;
14 +let waitFor;
15 +let assertLog;
16
17 describe('ReactOffscreen', () => {
18 beforeEach(() => {
@@ -27,6 +30,11 @@ describe('ReactOffscreen', () => {
30 useMemo = React.useMemo;
31 useRef = React.useRef;
32 startTransition = React.startTransition;
33 +
34 + const InternalTestUtils = require('internal-test-utils');
35 + waitForPaint = InternalTestUtils.waitForPaint;
36 + waitFor = InternalTestUtils.waitFor;
37 + assertLog = InternalTestUtils.assertLog;
38 });
39
40 function Text(props) {
@@ -68,10 +76,10 @@ describe('ReactOffscreen', () => {
76 const root = ReactNoop.createRoot();
77 await act(async () => {
78 root.render(<App mode="unstable-defer-without-hiding" />);
71 - expect(Scheduler).toFlushUntilNextPaint(['Normal']);
79 + await waitForPaint(['Normal']);
80 expect(root).toMatchRenderedOutput(<span prop="Normal" />);
81 });
74 - expect(Scheduler).toHaveYielded(['Deferred']);
82 + assertLog(['Deferred']);
83 expect(root).toMatchRenderedOutput(
84 <>
85 <span prop="Normal" />
@@ -83,7 +91,7 @@ describe('ReactOffscreen', () => {
91 await act(async () => {
92 root.render(<App mode="visible" />);
93 });
86 - expect(Scheduler).toHaveYielded(['Normal', 'Deferred']);
94 + assertLog(['Normal', 'Deferred']);
95 expect(root).toMatchRenderedOutput(
96 <>
97 <span prop="Normal" />
@@ -93,7 +101,7 @@ describe('ReactOffscreen', () => {
101
102 await act(async () => {
103 root.render(<App mode="unstable-defer-without-hiding" />);
96 - expect(Scheduler).toFlushUntilNextPaint(['Normal']);
104 + await waitForPaint(['Normal']);
105 expect(root).toMatchRenderedOutput(
106 <>
107 <span prop="Normal" />
@@ -101,7 +109,7 @@ describe('ReactOffscreen', () => {
109 </>,
110 );
111 });
104 - expect(Scheduler).toHaveYielded(['Deferred']);
112 + assertLog(['Deferred']);
113 expect(root).toMatchRenderedOutput(
114 <>
115 <span prop="Normal" />
@@ -133,7 +141,7 @@ describe('ReactOffscreen', () => {
141 ReactNoop.flushSync();
142
143 // Should not defer the hidden tree
136 - expect(Scheduler).toHaveYielded(['A', 'Outside']);
144 + assertLog(['A', 'Outside']);
145 });
146 expect(root).toMatchRenderedOutput(
147 <>
@@ -146,7 +154,7 @@ describe('ReactOffscreen', () => {
154 await act(async () => {
155 setState('B');
156 });
149 - expect(Scheduler).toHaveYielded(['B']);
157 + assertLog(['B']);
158 expect(root).toMatchRenderedOutput(
159 <>
160 <span prop="B" />
@@ -175,11 +183,11 @@ describe('ReactOffscreen', () => {
183 </>,
184 );
185 // Should defer the hidden tree.
178 - expect(Scheduler).toFlushUntilNextPaint(['Outside']);
186 + await waitForPaint(['Outside']);
187 });
188
189 // The hidden tree was rendered at lower priority.
182 - expect(Scheduler).toHaveYielded(['A']);
190 + assertLog(['A']);
191
192 expect(root).toMatchRenderedOutput(
193 <>
@@ -192,7 +200,7 @@ describe('ReactOffscreen', () => {
200 await act(async () => {
201 setState('B');
202 });
195 - expect(Scheduler).toHaveYielded(['B']);
203 + assertLog(['B']);
204 expect(root).toMatchRenderedOutput(
205 <>
206 <span prop="B" />
@@ -224,7 +232,7 @@ describe('ReactOffscreen', () => {
232 );
233 });
234 // No layout effect.
227 - expect(Scheduler).toHaveYielded(['Child']);
235 + assertLog(['Child']);
236 expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
237
238 // Unhide the tree. The layout effect is mounted.
@@ -235,7 +243,7 @@ describe('ReactOffscreen', () => {
243 </Offscreen>,
244 );
245 });
238 - expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
246 + assertLog(['Child', 'Mount layout']);
247 expect(root).toMatchRenderedOutput(<span prop="Child" />);
248 });
249
@@ -259,7 +267,7 @@ describe('ReactOffscreen', () => {
267 </Offscreen>,
268 );
269 });
262 - expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
270 + assertLog(['Child', 'Mount layout']);
271 expect(root).toMatchRenderedOutput(<span prop="Child" />);
272
273 // Hide the tree. The layout effect is unmounted.
@@ -270,7 +278,7 @@ describe('ReactOffscreen', () => {
278 </Offscreen>,
279 );
280 });
273 - expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
281 + assertLog(['Unmount layout', 'Child']);
282 expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
283
284 // Unhide the tree. The layout effect is re-mounted.
@@ -281,7 +289,7 @@ describe('ReactOffscreen', () => {
289 </Offscreen>,
290 );
291 });
284 - expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
292 + assertLog(['Child', 'Mount layout']);
293 expect(root).toMatchRenderedOutput(<span prop="Child" />);
294 });
295
@@ -316,7 +324,7 @@ describe('ReactOffscreen', () => {
324 );
325 });
326
319 - expect(Scheduler).toHaveYielded(['child']);
327 + assertLog(['child']);
328 expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
329
330 await act(async () => {
@@ -330,7 +338,7 @@ describe('ReactOffscreen', () => {
338 );
339 });
340
333 - expect(Scheduler).toHaveYielded(['child']);
341 + assertLog(['child']);
342 expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
343
344 await act(async () => {
@@ -344,7 +352,7 @@ describe('ReactOffscreen', () => {
352 );
353 });
354
347 - expect(Scheduler).toHaveYielded(['child']);
355 + assertLog(['child']);
356 expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
357
358 await act(async () => {
@@ -372,7 +380,7 @@ describe('ReactOffscreen', () => {
380 );
381 });
382
375 - expect(Scheduler).toHaveYielded(['child']);
383 + assertLog(['child']);
384
385 await act(async () => {
386 // Outer offscreen is hidden.
@@ -386,7 +394,7 @@ describe('ReactOffscreen', () => {
394 );
395 });
396
389 - expect(Scheduler).toHaveYielded(['child']);
397 + assertLog(['child']);
398 });
399
400 // @gate enableOffscreen
@@ -410,7 +418,7 @@ describe('ReactOffscreen', () => {
418 </Offscreen>,
419 );
420 });
413 - expect(Scheduler).toHaveYielded(['Child']);
421 + assertLog(['Child']);
422 expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
423
424 // Show the tree. The layout effect is mounted.
@@ -421,7 +429,7 @@ describe('ReactOffscreen', () => {
429 </Offscreen>,
430 );
431 });
424 - expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
432 + assertLog(['Child', 'Mount layout']);
433 expect(root).toMatchRenderedOutput(<span prop="Child" />);
434
435 // Hide the tree again. The layout effect is un-mounted.
@@ -432,7 +440,7 @@ describe('ReactOffscreen', () => {
440 </Offscreen>,
441 );
442 });
435 - expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
443 + assertLog(['Unmount layout', 'Child']);
444 expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
445 });
446
@@ -458,7 +466,7 @@ describe('ReactOffscreen', () => {
466 </Offscreen>,
467 );
468 });
461 - expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
469 + assertLog(['Child', 'Mount layout']);
470 expect(root).toMatchRenderedOutput(<span prop="Child" />);
471
472 // Hide the tree. The layout effect is unmounted.
@@ -469,7 +477,7 @@ describe('ReactOffscreen', () => {
477 </Offscreen>,
478 );
479 });
472 - expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
480 + assertLog(['Unmount layout', 'Child']);
481
482 // After the layout effect is unmounted, the child is hidden.
483 expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
@@ -497,7 +505,7 @@ describe('ReactOffscreen', () => {
505 </LegacyHidden>,
506 );
507 });
500 - expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
508 + assertLog(['Child', 'Mount layout']);
509
510 await act(async () => {
511 root.render(
@@ -506,7 +514,7 @@ describe('ReactOffscreen', () => {
514 </LegacyHidden>,
515 );
516 });
509 - expect(Scheduler).toHaveYielded(['Child']);
517 + assertLog(['Child']);
518
519 await act(async () => {
520 root.render(
@@ -515,12 +523,12 @@ describe('ReactOffscreen', () => {
523 </LegacyHidden>,
524 );
525 });
518 - expect(Scheduler).toHaveYielded(['Child']);
526 + assertLog(['Child']);
527
528 await act(async () => {
529 root.render(null);
530 });
523 - expect(Scheduler).toHaveYielded(['Unmount layout']);
531 + assertLog(['Unmount layout']);
532 });
533
534 // @gate enableOffscreen
@@ -665,7 +673,7 @@ describe('ReactOffscreen', () => {
673 await act(async () => {
674 root.render(<App show={false} />);
675 });
668 - expect(Scheduler).toHaveYielded(['Outer: 0', 'Inner: 0']);
676 + assertLog(['Outer: 0', 'Inner: 0']);
677 expect(root).toMatchRenderedOutput(
678 <>
679 <span prop="Outer: 0" />
@@ -681,7 +689,7 @@ describe('ReactOffscreen', () => {
689 setInner(1);
690 // Only the outer updates finishes because the inner update is inside a
691 // hidden tree. The outer update is deferred to a later render.
684 - expect(Scheduler).toFlushUntilNextPaint(['Outer: 1']);
692 + await waitForPaint(['Outer: 1']);
693 expect(root).toMatchRenderedOutput(
694 <>
695 <span prop="Outer: 1" />
@@ -706,7 +714,7 @@ describe('ReactOffscreen', () => {
714 root.render(<App show={true} />);
715 });
716
709 - expect(Scheduler).toHaveYielded([
717 + assertLog([
718 'Outer: 1',
719
720 // There are two pending updates on Inner, but only the first one
@@ -723,7 +731,7 @@ describe('ReactOffscreen', () => {
731 );
732 expect(areOuterAndInnerConsistent()).toBe(true);
733 });
726 - expect(Scheduler).toHaveYielded(['Outer: 2', 'Inner: 2']);
734 + assertLog(['Outer: 2', 'Inner: 2']);
735 expect(root).toMatchRenderedOutput(
736 <>
737 <span prop="Outer: 2" />
@@ -746,7 +754,7 @@ describe('ReactOffscreen', () => {
754 await act(async () => {
755 root.render(<Offscreen hidden={false} />);
756 });
749 - expect(Scheduler).toHaveYielded([]);
757 + assertLog([]);
758 expect(root).toMatchRenderedOutput(null);
759
760 await act(async () => {
@@ -759,7 +767,7 @@ describe('ReactOffscreen', () => {
767 </Offscreen>,
768 );
769 });
762 - expect(Scheduler).toFlushAndYieldThrough(['Initial']);
770 + await waitFor(['Initial']);
771
772 // Before it finishes rendering, the whole tree gets deleted
773 ReactNoop.flushSync(() => {
@@ -802,7 +810,7 @@ describe('ReactOffscreen', () => {
810 </Offscreen>,
811 );
812 });
805 - expect(Scheduler).toHaveYielded(['A']);
813 + assertLog(['A']);
814 expect(root).toMatchRenderedOutput(<span hidden={true} prop="A" />);
815
816 // Schedule an update to a hidden class component. The update will finish
@@ -813,7 +821,7 @@ describe('ReactOffscreen', () => {
821 Scheduler.unstable_yieldValue('B update finished');
822 });
823 });
816 - expect(Scheduler).toHaveYielded(['B']);
824 + assertLog(['B']);
825 expect(root).toMatchRenderedOutput(<span hidden={true} prop="B" />);
826
827 // Now reveal the hidden component. Simultaneously, schedule another
@@ -829,11 +837,7 @@ describe('ReactOffscreen', () => {
837 Scheduler.unstable_yieldValue('C update finished');
838 });
839 });
832 - expect(Scheduler).toHaveYielded([
833 - 'C',
834 - 'B update finished',
835 - 'C update finished',
836 - ]);
840 + assertLog(['C', 'B update finished', 'C update finished']);
841 expect(root).toMatchRenderedOutput(<span prop="C" />);
842 });
843
@@ -863,7 +867,7 @@ describe('ReactOffscreen', () => {
867 </Offscreen>,
868 );
869 });
866 - expect(Scheduler).toHaveYielded(['componentDidMount']);
870 + assertLog(['componentDidMount']);
871
872 // Hide the class component
873 await act(async () => {
@@ -873,7 +877,7 @@ describe('ReactOffscreen', () => {
877 </Offscreen>,
878 );
879 });
876 - expect(Scheduler).toHaveYielded(['componentWillUnmount']);
880 + assertLog(['componentWillUnmount']);
881
882 // Reappear the class component. componentDidMount should fire, not
883 // componentDidUpdate.
@@ -884,7 +888,7 @@ describe('ReactOffscreen', () => {
888 </Offscreen>,
889 );
890 });
887 - expect(Scheduler).toHaveYielded(['componentDidMount']);
891 + assertLog(['componentDidMount']);
892 });
893
894 // @gate enableOffscreen
@@ -911,7 +915,7 @@ describe('ReactOffscreen', () => {
915 </Offscreen>,
916 );
917 });
914 - expect(Scheduler).toHaveYielded(['Mount B']);
918 + assertLog(['Mount B']);
919
920 // Hide the component
921 await act(async () => {
@@ -921,7 +925,7 @@ describe('ReactOffscreen', () => {
925 </Offscreen>,
926 );
927 });
924 - expect(Scheduler).toHaveYielded(['Unmount B']);
928 + assertLog(['Unmount B']);
929
930 // Reappear the component and also add some new siblings.
931 await act(async () => {
@@ -936,7 +940,7 @@ describe('ReactOffscreen', () => {
940 // B's effect should fire in between A and C even though it's been reused
941 // from a previous render. In other words, it's the same order as if all
942 // three siblings were brand new.
939 - expect(Scheduler).toHaveYielded(['Mount A', 'Mount B', 'Mount C']);
943 + assertLog(['Mount A', 'Mount B', 'Mount C']);
944 },
945 );
946
@@ -967,7 +971,7 @@ describe('ReactOffscreen', () => {
971 </Offscreen>,
972 );
973 });
970 - expect(Scheduler).toHaveYielded(['Mount B']);
974 + assertLog(['Mount B']);
975
976 // We're going to schedule an update on a hidden component, so stash a
977 // reference to its setState before the ref gets detached
@@ -981,7 +985,7 @@ describe('ReactOffscreen', () => {
985 </Offscreen>,
986 );
987 });
984 - expect(Scheduler).toHaveYielded(['Unmount B']);
988 + assertLog(['Unmount B']);
989
990 // Reappear the component and also add some new siblings.
991 await act(async () => {
@@ -999,12 +1003,7 @@ describe('ReactOffscreen', () => {
1003 // B's effect should fire in between A and C even though it's been reused
1004 // from a previous render. In other words, it's the same order as if all
1005 // three siblings were brand new.
1002 - expect(Scheduler).toHaveYielded([
1003 - 'Mount A',
1004 - 'Mount B',
1005 - 'setState callback B',
1006 - 'Mount C',
1007 - ]);
1006 + assertLog(['Mount A', 'Mount B', 'setState callback B', 'Mount C']);
1007 },
1008 );
1009
@@ -1037,7 +1036,7 @@ describe('ReactOffscreen', () => {
1036 await act(async () => {
1037 root.render(<App showMore={false} />);
1038 });
1040 - expect(Scheduler).toHaveYielded([
1039 + assertLog([
1040 // First mount the outer visible shell
1041 'Shell',
1042 'Mount Shell',
@@ -1060,7 +1059,7 @@ describe('ReactOffscreen', () => {
1059 await act(async () => {
1060 root.render(<App showMore={true} />);
1061 });
1063 - expect(Scheduler).toHaveYielded([
1062 + assertLog([
1063 'Shell',
1064 'More',
1065
@@ -1100,7 +1099,7 @@ describe('ReactOffscreen', () => {
1099 await act(async () => {
1100 root.render(<App showMore={false} />);
1101 });
1103 - expect(Scheduler).toHaveYielded([
1102 + assertLog([
1103 // First mount the outer visible shell
1104 'Shell',
1105 'Mount Shell',
@@ -1123,7 +1122,7 @@ describe('ReactOffscreen', () => {
1122 await act(async () => {
1123 root.render(<App showMore={true} />);
1124 });
1126 - expect(Scheduler).toHaveYielded(['Shell', 'More']);
1125 + assertLog(['Shell', 'More']);
1126 });
1127
1128 // @gate enableOffscreen
@@ -1155,14 +1154,14 @@ describe('ReactOffscreen', () => {
1154 await act(async () => {
1155 root.render(<App show={true} step={1} />);
1156 });
1158 - expect(Scheduler).toHaveYielded([1, 'Commit mount [1]']);
1157 + assertLog([1, 'Commit mount [1]']);
1158 expect(root).toMatchRenderedOutput(<span prop={1} />);
1159
1160 // Hide the tree. This will unmount the effect.
1161 await act(async () => {
1162 root.render(<App show={false} step={1} />);
1163 });
1165 - expect(Scheduler).toHaveYielded(['Commit unmount [1]']);
1164 + assertLog(['Commit unmount [1]']);
1165 expect(root).toMatchRenderedOutput(<span hidden={true} prop={1} />);
1166
1167 // Update.
@@ -1170,7 +1169,7 @@ describe('ReactOffscreen', () => {
1169 root.render(<App show={false} step={2} />);
1170 });
1171 // The update is prerendered but no effects are fired
1173 - expect(Scheduler).toHaveYielded([2]);
1172 + assertLog([2]);
1173 expect(root).toMatchRenderedOutput(<span hidden={true} prop={2} />);
1174
1175 // Reveal the tree.
@@ -1179,7 +1178,7 @@ describe('ReactOffscreen', () => {
1178 });
1179 // The update doesn't render because it was already prerendered, but we do
1180 // fire the effect.
1182 - expect(Scheduler).toHaveYielded(['Commit mount [2]']);
1181 + assertLog(['Commit mount [2]']);
1182 expect(root).toMatchRenderedOutput(<span prop={2} />);
1183 });
1184
@@ -1216,24 +1215,24 @@ describe('ReactOffscreen', () => {
1215 await act(async () => {
1216 root.render(<App show={true} />);
1217 });
1219 - expect(Scheduler).toHaveYielded(['Mount Child', 'Mount Parent']);
1218 + assertLog(['Mount Child', 'Mount Parent']);
1219
1220 // First demonstrate what happens during a normal deletion
1221 await act(async () => {
1222 root.render(null);
1223 });
1225 - expect(Scheduler).toHaveYielded(['Unmount Parent', 'Unmount Child']);
1224 + assertLog(['Unmount Parent', 'Unmount Child']);
1225
1226 // Now redo the same thing but hide instead of deleting
1227 await act(async () => {
1228 root.render(<App show={true} />);
1229 });
1231 - expect(Scheduler).toHaveYielded(['Mount Child', 'Mount Parent']);
1230 + assertLog(['Mount Child', 'Mount Parent']);
1231 await act(async () => {
1232 root.render(<App show={false} />);
1233 });
1234 // The order is the same as during a deletion: parent before child
1236 - expect(Scheduler).toHaveYielded(['Unmount Parent', 'Unmount Child']);
1235 + assertLog(['Unmount Parent', 'Unmount Child']);
1236 });
1237
1238 // TODO: As of now, there's no way to hide a tree without also unmounting its
@@ -1270,12 +1269,7 @@ describe('ReactOffscreen', () => {
1269 await act(async () => {
1270 root.render(<App showMore={true} step={1} />);
1271 });
1273 - expect(Scheduler).toHaveYielded([
1274 - 'Shell 1',
1275 - 'More 1',
1276 - 'Mount Shell 1',
1277 - 'Mount More 1',
1278 - ]);
1272 + assertLog(['Shell 1', 'More 1', 'Mount Shell 1', 'Mount More 1']);
1273 expect(root).toMatchRenderedOutput(
1274 <>
1275 <span prop="Shell 1" />
@@ -1287,7 +1281,7 @@ describe('ReactOffscreen', () => {
1281 await act(async () => {
1282 root.render(<App showMore={false} step={2} />);
1283 });
1290 - expect(Scheduler).toHaveYielded([
1284 + assertLog([
1285 // First update the outer visible shell
1286 'Shell 2',
1287 'Unmount Shell 1',
@@ -1342,7 +1336,7 @@ describe('ReactOffscreen', () => {
1336 await act(async () => {
1337 root.render(<App showOuter={false} showInner={false} />);
1338 });
1345 - expect(Scheduler).toHaveYielded(['Outer']);
1339 + assertLog(['Outer']);
1340 expect(root).toMatchRenderedOutput(
1341 <div hidden={true}>
1342 <span prop="Outer" />
@@ -1353,7 +1347,7 @@ describe('ReactOffscreen', () => {
1347 await act(async () => {
1348 root.render(<App showOuter={false} showInner={true} />);
1349 });
1356 - expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
1350 + assertLog(['Outer', 'Inner']);
1351 expect(root).toMatchRenderedOutput(
1352 <div hidden={true}>
1353 <span prop="Outer" />
@@ -1369,7 +1363,7 @@ describe('ReactOffscreen', () => {
1363 });
1364 // The effects fire, but the tree is not re-rendered because it already
1365 // prerendered.
1372 - expect(Scheduler).toHaveYielded(['Mount Outer', 'Mount Inner']);
1366 + assertLog(['Mount Outer', 'Mount Inner']);
1367 expect(root).toMatchRenderedOutput(
1368 <div>
1369 <span prop="Outer" />
@@ -1418,7 +1412,7 @@ describe('ReactOffscreen', () => {
1412 await act(async () => {
1413 root.render(<App showOuter={false} showInner={false} />);
1414 });
1421 - expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
1415 + assertLog(['Outer', 'Inner']);
1416 // Both the inner and the outer tree should be hidden. Hiding the inner tree
1417 // is arguably redundant, but the advantage of hiding both is that later you
1418 // can reveal the outer tree without having to examine the inner one.
@@ -1435,7 +1429,7 @@ describe('ReactOffscreen', () => {
1429 await act(async () => {
1430 root.render(<App showOuter={true} showInner={false} />);
1431 });
1438 - expect(Scheduler).toHaveYielded(['Mount Outer']);
1432 + assertLog(['Mount Outer']);
1433 expect(root).toMatchRenderedOutput(
1434 <div>
1435 <span prop="Outer" />
@@ -1535,7 +1529,7 @@ describe('ReactOffscreen', () => {
1529 root.render(<App />);
1530 });
1531
1538 - expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
1532 + assertLog(['HighPriorityComponent 0', 'Child 0']);
1533 expect(root).toMatchRenderedOutput(
1534 <>
1535 <span prop="HighPriorityComponent 0" />
@@ -1549,10 +1543,7 @@ describe('ReactOffscreen', () => {
1543 await act(async () => {
1544 updateChildState(1);
1545 updateHighPriorityComponentState(1);
1552 - expect(Scheduler).toFlushUntilNextPaint([
1553 - 'HighPriorityComponent 1',
1554 - 'Child 1',
1555 - ]);
1546 + await waitForPaint(['HighPriorityComponent 1', 'Child 1']);
1547 expect(root).toMatchRenderedOutput(
1548 <>
1549 <span prop="HighPriorityComponent 1" />
@@ -1569,7 +1560,7 @@ describe('ReactOffscreen', () => {
1560 await act(async () => {
1561 updateChildState(2);
1562 updateHighPriorityComponentState(2);
1572 - expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 2']);
1563 + await waitForPaint(['HighPriorityComponent 2']);
1564 expect(root).toMatchRenderedOutput(
1565 <>
1566 <span prop="HighPriorityComponent 2" />
@@ -1578,7 +1569,7 @@ describe('ReactOffscreen', () => {
1569 );
1570 });
1571
1581 - expect(Scheduler).toHaveYielded(['Child 2']);
1572 + assertLog(['Child 2']);
1573 expect(root).toMatchRenderedOutput(
1574 <>
1575 <span prop="HighPriorityComponent 2" />
@@ -1594,10 +1585,7 @@ describe('ReactOffscreen', () => {
1585 await act(async () => {
1586 updateChildState(3);
1587 updateHighPriorityComponentState(3);
1597 - expect(Scheduler).toFlushUntilNextPaint([
1598 - 'HighPriorityComponent 3',
1599 - 'Child 3',
1600 - ]);
1588 + await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
1589 expect(root).toMatchRenderedOutput(
1590 <>
1591 <span prop="HighPriorityComponent 3" />
@@ -1666,7 +1654,7 @@ describe('ReactOffscreen', () => {
1654 root.render(<App />);
1655 });
1656
1669 - expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
1657 + assertLog(['HighPriorityComponent 0', 'Child 0']);
1658
1659 nextRenderTriggerDetach = true;
1660
@@ -1675,7 +1663,7 @@ describe('ReactOffscreen', () => {
1663 await act(async () => {
1664 updateChildState(1);
1665 updateHighPriorityComponentState(1);
1678 - expect(Scheduler).toFlushUntilNextPaint([
1666 + await waitForPaint([
1667 'HighPriorityComponent 1',
1668 'Child 1',
1669 'HighPriorityComponent 2',
@@ -1688,7 +1676,7 @@ describe('ReactOffscreen', () => {
1676 );
1677 });
1678
1691 - expect(Scheduler).toHaveYielded(['Child 2']);
1679 + assertLog(['Child 2']);
1680 expect(root).toMatchRenderedOutput(
1681 <>
1682 <span prop="HighPriorityComponent 2" />
@@ -1703,10 +1691,7 @@ describe('ReactOffscreen', () => {
1691 await act(async () => {
1692 updateChildState(3);
1693 updateHighPriorityComponentState(3);
1706 - expect(Scheduler).toFlushUntilNextPaint([
1707 - 'HighPriorityComponent 3',
1708 - 'Child 3',
1709 - ]);
1694 + await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
1695 expect(root).toMatchRenderedOutput(
1696 <>
1697 <span prop="HighPriorityComponent 3" />
@@ -1865,33 +1850,33 @@ describe('ReactOffscreen', () => {
1850
1851 expect(offscreenRef).not.toBeNull();
1852 expect(spanRef.current).not.toBeNull();
1868 - expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
1853 + assertLog(['Mount Layout Child', 'Mount Child']);
1854
1855 await act(async () => {
1856 offscreenRef.detach();
1857 });
1858
1859 expect(spanRef.current).toBeNull();
1875 - expect(Scheduler).toHaveYielded(['Unmount Layout Child', 'Unmount Child']);
1860 + assertLog(['Unmount Layout Child', 'Unmount Child']);
1861
1862 // Calling attach on already attached Offscreen.
1863 await act(async () => {
1864 offscreenRef.detach();
1865 });
1866
1882 - expect(Scheduler).toHaveYielded([]);
1867 + assertLog([]);
1868
1869 await act(async () => {
1870 offscreenRef.attach();
1871 });
1872
1873 expect(spanRef.current).not.toBeNull();
1889 - expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
1874 + assertLog(['Mount Layout Child', 'Mount Child']);
1875
1876 // Calling attach on already attached Offscreen
1877 offscreenRef.attach();
1878
1894 - expect(Scheduler).toHaveYielded([]);
1879 + assertLog([]);
1880 });
1881
1882 // @gate enableOffscreen
@@ -1919,7 +1904,7 @@ describe('ReactOffscreen', () => {
1904 root.render(<App />);
1905 });
1906
1922 - expect(Scheduler).toHaveYielded([
1907 + assertLog([
1908 'outer',
1909 'middle',
1910 'inner',
@@ -1940,7 +1925,7 @@ describe('ReactOffscreen', () => {
1925
1926 expect(innerOffscreen).toBeNull();
1927
1943 - expect(Scheduler).toHaveYielded([
1928 + assertLog([
1929 'unmount layout middle',
1930 'unmount layout inner',
1931 'unmount middle',
@@ -1951,7 +1936,7 @@ describe('ReactOffscreen', () => {
1936 outerOffscreen.attach();
1937 });
1938
1954 - expect(Scheduler).toHaveYielded([
1939 + assertLog([
1940 'mount layout inner',
1941 'mount layout middle',
1942 'mount inner',
@@ -1962,27 +1947,27 @@ describe('ReactOffscreen', () => {
1947 innerOffscreen.detach();
1948 });
1949
1965 - expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
1950 + assertLog(['unmount layout inner', 'unmount inner']);
1951
1952 // Calling detach on already detached Offscreen.
1953 await act(async () => {
1954 innerOffscreen.detach();
1955 });
1956
1972 - expect(Scheduler).toHaveYielded([]);
1957 + assertLog([]);
1958
1959 await act(async () => {
1960 innerOffscreen.attach();
1961 });
1962
1978 - expect(Scheduler).toHaveYielded(['mount layout inner', 'mount inner']);
1963 + assertLog(['mount layout inner', 'mount inner']);
1964
1965 await act(async () => {
1966 innerOffscreen.detach();
1967 outerOffscreen.attach();
1968 });
1969
1985 - expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
1970 + assertLog(['unmount layout inner', 'unmount inner']);
1971 });
1972
1973 // @gate enableOffscreen
@@ -2011,7 +1996,7 @@ describe('ReactOffscreen', () => {
1996 root.render(<App />);
1997 });
1998
2014 - expect(Scheduler).toHaveYielded(['attach child']);
1999 + assertLog(['attach child']);
2000
2001 await act(async () => {
2002 const instance = offscreen.current;
@@ -2020,14 +2005,14 @@ describe('ReactOffscreen', () => {
2005 instance.attach();
2006 });
2007
2023 - expect(Scheduler).toHaveYielded([]);
2008 + assertLog([]);
2009
2010 await act(async () => {
2011 const instance = offscreen.current;
2012 instance.detach();
2013 });
2014
2030 - expect(Scheduler).toHaveYielded(['detach child']);
2015 + assertLog(['detach child']);
2016
2017 await act(async () => {
2018 const instance = offscreen.current;
@@ -2036,7 +2021,7 @@ describe('ReactOffscreen', () => {
2021 instance.detach();
2022 });
2023
2039 - expect(Scheduler).toHaveYielded([]);
2024 + assertLog([]);
2025 });
2026
2027 // @gate enableOffscreen
@@ -2070,6 +2055,6 @@ describe('ReactOffscreen', () => {
2055 await act(() => {
2056 root.render(<App />);
2057 });
2073 - expect(Scheduler).toHaveYielded(['attach child']);
2058 + assertLog(['attach child']);
2059 });
2060 });
packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
+24 -22
@@ -9,6 +9,8 @@ let useState;
9 let useEffect;
10 let startTransition;
11 let textCache;
12 +let waitForPaint;
13 +let assertLog;
14
15 describe('ReactOffscreen', () => {
16 beforeEach(() => {
@@ -25,6 +27,10 @@ describe('ReactOffscreen', () => {
27 useEffect = React.useEffect;
28 startTransition = React.startTransition;
29
30 + const InternalTestUtils = require('internal-test-utils');
31 + waitForPaint = InternalTestUtils.waitForPaint;
32 + assertLog = InternalTestUtils.assertLog;
33 +
34 textCache = new Map();
35 });
36
@@ -115,7 +121,7 @@ describe('ReactOffscreen', () => {
121 await act(async () => {
122 root.render(<App />);
123 });
118 - expect(Scheduler).toHaveYielded(['Visible', 'Suspend! [Hidden]']);
124 + assertLog(['Visible', 'Suspend! [Hidden]']);
125 expect(root).toMatchRenderedOutput(<span>Visible</span>);
126
127 // When the data resolves, we should be able to finish prerendering
@@ -123,7 +129,7 @@ describe('ReactOffscreen', () => {
129 await act(async () => {
130 await resolveText('Hidden');
131 });
126 - expect(Scheduler).toHaveYielded(['Hidden']);
132 + assertLog(['Hidden']);
133 expect(root).toMatchRenderedOutput(
134 <>
135 <span>Visible</span>
@@ -155,11 +161,7 @@ describe('ReactOffscreen', () => {
161 await act(async () => {
162 root.render(<App />);
163 });
158 - expect(Scheduler).toHaveYielded([
159 - 'Visible',
160 - 'Suspend! [Hidden]',
161 - 'Loading...',
162 - ]);
164 + assertLog(['Visible', 'Suspend! [Hidden]', 'Loading...']);
165 // Nearest Suspense boundary switches to a fallback even though the
166 // suspended content is hidden.
167 expect(root).toMatchRenderedOutput(
@@ -197,7 +199,7 @@ describe('ReactOffscreen', () => {
199 </Details>,
200 );
201 });
200 - expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
202 + assertLog(['Closed', 'Suspend! [Async]']);
203 expect(root).toMatchRenderedOutput(<span>Closed</span>);
204
205 // But when we switch the boundary from hidden to visible, it should
@@ -211,7 +213,7 @@ describe('ReactOffscreen', () => {
213 );
214 });
215 });
214 - expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
216 + assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
217 // It should suspend with delay to prevent the already-visible Suspense
218 // boundary from switching to a fallback
219 expect(root).toMatchRenderedOutput(<span>Closed</span>);
@@ -220,7 +222,7 @@ describe('ReactOffscreen', () => {
222 await act(async () => {
223 await resolveText('Async');
224 });
223 - expect(Scheduler).toHaveYielded(['Open', 'Async']);
225 + assertLog(['Open', 'Async']);
226 expect(root).toMatchRenderedOutput(
227 <>
228 <span>Open</span>
@@ -254,7 +256,7 @@ describe('ReactOffscreen', () => {
256 </Details>,
257 );
258 });
257 - expect(Scheduler).toHaveYielded(['Open', '(empty)']);
259 + assertLog(['Open', '(empty)']);
260 expect(root).toMatchRenderedOutput(
261 <>
262 <span>Open</span>
@@ -272,7 +274,7 @@ describe('ReactOffscreen', () => {
274 );
275 });
276 });
275 - expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
277 + assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
278 // It should suspend with delay to prevent the already-visible Suspense
279 // boundary from switching to a fallback
280 expect(root).toMatchRenderedOutput(
@@ -294,7 +296,7 @@ describe('ReactOffscreen', () => {
296 });
297 // Now the visible part of the tree can commit without being blocked
298 // by the suspended content, which is hidden.
297 - expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
299 + assertLog(['Closed', 'Suspend! [Async]']);
300 expect(root).toMatchRenderedOutput(
301 <>
302 <span>Closed</span>
@@ -306,7 +308,7 @@ describe('ReactOffscreen', () => {
308 await act(async () => {
309 await resolveText('Async');
310 });
309 - expect(Scheduler).toHaveYielded(['Async']);
311 + assertLog(['Async']);
312 expect(root).toMatchRenderedOutput(
313 <>
314 <span>Closed</span>
@@ -339,7 +341,7 @@ describe('ReactOffscreen', () => {
341 await act(async () => {
342 root.render(<App show={false} />);
343 });
342 - expect(Scheduler).toHaveYielded(['A']);
344 + assertLog(['A']);
345
346 await act(async () => {
347 startTransition(() => {
@@ -377,7 +379,7 @@ describe('ReactOffscreen', () => {
379 await act(async () => {
380 root.render(<App show={false} />);
381 });
380 - expect(Scheduler).toHaveYielded(['A0']);
382 + assertLog(['A0']);
383 expect(root).toMatchRenderedOutput(<span hidden={true}>A0</span>);
384
385 await act(async () => {
@@ -388,7 +390,7 @@ describe('ReactOffscreen', () => {
390 setText('B');
391 });
392 });
391 - expect(Scheduler).toHaveYielded([
393 + assertLog([
394 // The high priority render suspends again
395 'Suspend! [B0]',
396 // There's still pending work in another lane, so we should attempt
@@ -401,7 +403,7 @@ describe('ReactOffscreen', () => {
403 await act(async () => {
404 resolveText('B1');
405 });
404 - expect(Scheduler).toHaveYielded(['B1']);
406 + assertLog(['B1']);
407 expect(root).toMatchRenderedOutput(<span hidden={true}>B1</span>);
408 });
409
@@ -465,7 +467,7 @@ describe('ReactOffscreen', () => {
467 await act(async () => {
468 root.render(<App show={true} />);
469 });
468 - expect(Scheduler).toHaveYielded([
470 + assertLog([
471 'Outer: 0',
472 'Inner: 0',
473 'Async: 0',
@@ -487,7 +489,7 @@ describe('ReactOffscreen', () => {
489 // In the same render, also hide the offscreen tree.
490 root.render(<App show={false} />);
491
490 - expect(Scheduler).toFlushUntilNextPaint([
492 + await waitForPaint([
493 // The outer update will commit, but the inner update is deferred until
494 // a later render.
495 'Outer: 1',
@@ -535,7 +537,7 @@ describe('ReactOffscreen', () => {
537 ReactNoop.flushSync(() => {
538 root.render(<App show={true} />);
539 });
538 - expect(Scheduler).toHaveYielded([
540 + assertLog([
541 'Outer: 1',
542
543 // There are two pending updates on Inner, but only the first one
@@ -549,7 +551,7 @@ describe('ReactOffscreen', () => {
551 'Inner and outer are consistent',
552 ]);
553 });
552 - expect(Scheduler).toHaveYielded([
554 + assertLog([
555 'Outer: 2',
556 'Inner: 2',
557 'Suspend! [Async: 2]',
packages/react-reconciler/src/__tests__/ReactPersistent-test.js
+16 -15
@@ -12,7 +12,7 @@
12
13 let React;
14 let ReactNoopPersistent;
15 -let Scheduler;
15 +let waitForAll;
16
17 describe('ReactPersistent', () => {
18 beforeEach(() => {
@@ -20,7 +20,8 @@ describe('ReactPersistent', () => {
20
21 React = require('react');
22 ReactNoopPersistent = require('react-noop-renderer/persistent');
23 - Scheduler = require('scheduler');
23 + const InternalTestUtils = require('internal-test-utils');
24 + waitForAll = InternalTestUtils.waitForAll;
25 });
26
27 // Inlined from shared folder so we can run this test on a bundle.
@@ -57,7 +58,7 @@ describe('ReactPersistent', () => {
58 return ReactNoopPersistent.dangerouslyGetChildren();
59 }
60
60 - it('can update child nodes of a host instance', () => {
61 + it('can update child nodes of a host instance', async () => {
62 function Bar(props) {
63 return <span>{props.text}</span>;
64 }
@@ -72,19 +73,19 @@ describe('ReactPersistent', () => {
73 }
74
75 render(<Foo text="Hello" />);
75 - expect(Scheduler).toFlushWithoutYielding();
76 + await waitForAll([]);
77 const originalChildren = dangerouslyGetChildren();
78 expect(originalChildren).toEqual([div(span())]);
79
80 render(<Foo text="World" />);
80 - expect(Scheduler).toFlushWithoutYielding();
81 + await waitForAll([]);
82 const newChildren = dangerouslyGetChildren();
83 expect(newChildren).toEqual([div(span(), span())]);
84
85 expect(originalChildren).toEqual([div(span())]);
86 });
87
87 - it('can reuse child nodes between updates', () => {
88 + it('can reuse child nodes between updates', async () => {
89 function Baz(props) {
90 return <span prop={props.text} />;
91 }
@@ -106,12 +107,12 @@ describe('ReactPersistent', () => {
107 }
108
109 render(<Foo text="Hello" />);
109 - expect(Scheduler).toFlushWithoutYielding();
110 + await waitForAll([]);
111 const originalChildren = dangerouslyGetChildren();
112 expect(originalChildren).toEqual([div(span('Hello'))]);
113
114 render(<Foo text="World" />);
114 - expect(Scheduler).toFlushWithoutYielding();
115 + await waitForAll([]);
116 const newChildren = dangerouslyGetChildren();
117 expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
118
@@ -121,7 +122,7 @@ describe('ReactPersistent', () => {
122 expect(newChildren[0].children[0]).toBe(originalChildren[0].children[0]);
123 });
124
124 - it('can update child text nodes', () => {
125 + it('can update child text nodes', async () => {
126 function Foo(props) {
127 return (
128 <div>
@@ -132,19 +133,19 @@ describe('ReactPersistent', () => {
133 }
134
135 render(<Foo text="Hello" />);
135 - expect(Scheduler).toFlushWithoutYielding();
136 + await waitForAll([]);
137 const originalChildren = dangerouslyGetChildren();
138 expect(originalChildren).toEqual([div('Hello', span())]);
139
140 render(<Foo text="World" />);
140 - expect(Scheduler).toFlushWithoutYielding();
141 + await waitForAll([]);
142 const newChildren = dangerouslyGetChildren();
143 expect(newChildren).toEqual([div('World', span())]);
144
145 expect(originalChildren).toEqual([div('Hello', span())]);
146 });
147
147 - it('supports portals', () => {
148 + it('supports portals', async () => {
149 function Parent(props) {
150 return <div>{props.children}</div>;
151 }
@@ -173,7 +174,7 @@ describe('ReactPersistent', () => {
174 const portalContainer = {rootID: 'persistent-portal-test', children: []};
175 const emptyPortalChildSet = portalContainer.children;
176 render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
176 - expect(Scheduler).toFlushWithoutYielding();
177 + await waitForAll([]);
178
179 expect(emptyPortalChildSet).toEqual([]);
180
@@ -187,7 +188,7 @@ describe('ReactPersistent', () => {
188 {createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
189 </Parent>,
190 );
190 - expect(Scheduler).toFlushWithoutYielding();
191 + await waitForAll([]);
192
193 const newChildren = dangerouslyGetChildren();
194 expect(newChildren).toEqual([div()]);
@@ -204,7 +205,7 @@ describe('ReactPersistent', () => {
205
206 // Deleting the Portal, should clear its children
207 render(<Parent />);
207 - expect(Scheduler).toFlushWithoutYielding();
208 + await waitForAll([]);
209
210 const clearedPortalChildren = portalContainer.children;
211 expect(clearedPortalChildren).toEqual([]);
packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
+29 -13
@@ -18,6 +18,10 @@ let NormalPriority;
18 let IdlePriority;
19 let runWithPriority;
20 let startTransition;
21 +let waitForAll;
22 +let waitForPaint;
23 +let assertLog;
24 +let waitFor;
25
26 describe('ReactSchedulerIntegration', () => {
27 beforeEach(() => {
@@ -31,6 +35,12 @@ describe('ReactSchedulerIntegration', () => {
35 IdlePriority = Scheduler.unstable_IdlePriority;
36 runWithPriority = Scheduler.unstable_runWithPriority;
37 startTransition = React.startTransition;
38 +
39 + const InternalTestUtils = require('internal-test-utils');
40 + waitForAll = InternalTestUtils.waitForAll;
41 + waitForPaint = InternalTestUtils.waitForPaint;
42 + assertLog = InternalTestUtils.assertLog;
43 + waitFor = InternalTestUtils.waitFor;
44 });
45
46 // Note: This is based on a similar component we use in www. We can delete
@@ -76,11 +86,11 @@ describe('ReactSchedulerIntegration', () => {
86 await act(async () => {
87 ReactNoop.render(<CleanupEffect />);
88 });
79 - expect(Scheduler).toHaveYielded([]);
89 + assertLog([]);
90 await act(async () => {
91 ReactNoop.render(<Effects />);
92 });
83 - expect(Scheduler).toHaveYielded([
93 + assertLog([
94 'Cleanup Layout Effect',
95 'Layout Effect',
96 'Passive Effect',
@@ -90,12 +100,12 @@ describe('ReactSchedulerIntegration', () => {
100 ]);
101 });
102
93 - it('requests a paint after committing', () => {
103 + it('requests a paint after committing', async () => {
104 const scheduleCallback = Scheduler.unstable_scheduleCallback;
105
106 const root = ReactNoop.createRoot();
107 root.render('Initial');
98 - Scheduler.unstable_flushAll();
108 + await waitForAll([]);
109
110 scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
111 scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
@@ -115,7 +125,7 @@ describe('ReactSchedulerIntegration', () => {
125 // Flush everything up to the next paint. Should yield after the
126 // React commit.
127 Scheduler.unstable_flushUntilNextPaint();
118 - expect(Scheduler).toHaveYielded(['A', 'B', 'C']);
128 + assertLog(['A', 'B', 'C']);
129 });
130
131 // @gate www
@@ -141,7 +151,7 @@ describe('ReactSchedulerIntegration', () => {
151 root.render(<App label="A" />);
152
153 // Commit the visible content
144 - expect(Scheduler).toFlushUntilNextPaint(['Visible: A']);
154 + await waitForPaint(['Visible: A']);
155 expect(root).toMatchRenderedOutput(
156 <>
157 Visible: A
@@ -156,7 +166,7 @@ describe('ReactSchedulerIntegration', () => {
166 });
167
168 // The next commit should only include the visible content
159 - expect(Scheduler).toFlushUntilNextPaint(['Visible: B']);
169 + await waitForPaint(['Visible: B']);
170 expect(root).toMatchRenderedOutput(
171 <>
172 Visible: B
@@ -166,7 +176,7 @@ describe('ReactSchedulerIntegration', () => {
176 });
177
178 // The hidden content commits later
169 - expect(Scheduler).toHaveYielded(['Hidden: B']);
179 + assertLog(['Hidden: B']);
180 expect(root).toMatchRenderedOutput(
181 <>
182 Visible: B<div hidden={true}>Hidden: B</div>
@@ -201,6 +211,12 @@ describe(
211 ReactNoop = require('react-noop-renderer');
212 Scheduler = require('scheduler');
213 startTransition = React.startTransition;
214 +
215 + const InternalTestUtils = require('internal-test-utils');
216 + waitForAll = InternalTestUtils.waitForAll;
217 + waitForPaint = InternalTestUtils.waitForPaint;
218 + assertLog = InternalTestUtils.assertLog;
219 + waitFor = InternalTestUtils.waitFor;
220 });
221
222 afterEach(() => {
@@ -244,8 +260,8 @@ describe(
260
261 await act(async () => {
262 ReactNoop.render(<App />);
247 - expect(Scheduler).toFlushUntilNextPaint([]);
248 - expect(Scheduler).toFlushUntilNextPaint([]);
263 + await waitForPaint([]);
264 + await waitForPaint([]);
265 });
266 });
267
@@ -276,13 +292,13 @@ describe(
292 startTransition(() => {
293 ReactNoop.render(<App />);
294 });
279 - expect(Scheduler).toFlushAndYieldThrough(['A']);
295 + await waitFor(['A']);
296
297 // Start logging whenever shouldYield is called
298 logDuringShouldYield = true;
299 // Let's call it once to confirm the mock actually works
300 Scheduler.unstable_shouldYield();
285 - expect(Scheduler).toHaveYielded(['shouldYield']);
301 + assertLog(['shouldYield']);
302
303 // Expire the task
304 Scheduler.unstable_advanceTime(10000);
@@ -299,7 +315,7 @@ describe(
315 // Because the render expired, React should finish the tree without
316 // consulting `shouldYield` again
317 Scheduler.unstable_flushNumberOfYields(1);
302 - expect(Scheduler).toHaveYielded(['B', 'C']);
318 + assertLog(['B', 'C']);
319 });
320 });
321 },
packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js
+6 -2
@@ -8,6 +8,7 @@ let getCacheForType;
8
9 let caches;
10 let seededCache;
11 +let assertLog;
12
13 describe('ReactSuspenseWithNoopRenderer', () => {
14 beforeEach(() => {
@@ -22,6 +23,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
23
24 getCacheForType = React.unstable_getCacheForType;
25
26 + const InternalTestUtils = require('internal-test-utils');
27 + assertLog = InternalTestUtils.assertLog;
28 +
29 caches = [];
30 seededCache = null;
31 });
@@ -160,7 +164,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
164 await act(async () => {
165 root.render(<App />);
166 });
163 - expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
167 + assertLog(['Suspend! [Async]']);
168 expect(root).toMatchRenderedOutput('Loading...');
169
170 // When the promise resolves, a passive static effect flag is added. In the
@@ -169,7 +173,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
173 await act(async () => {
174 resolveText('Async');
175 });
172 - expect(Scheduler).toHaveYielded(['Async', 'Effect']);
176 + assertLog(['Async', 'Effect']);
177 expect(root).toMatchRenderedOutput('Async');
178 });
179 });
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+182 -238
@@ -9,6 +9,11 @@ let act;
9 let TextResource;
10 let textResourceShouldFail;
11
12 +let assertLog;
13 +let waitForPaint;
14 +let waitForAll;
15 +let waitFor;
16 +
17 describe('ReactSuspense', () => {
18 beforeEach(() => {
19 jest.resetModules();
@@ -23,6 +28,12 @@ describe('ReactSuspense', () => {
28
29 Suspense = React.Suspense;
30
31 + const InternalTestUtils = require('internal-test-utils');
32 + waitForAll = InternalTestUtils.waitForAll;
33 + waitForPaint = InternalTestUtils.waitForPaint;
34 + assertLog = InternalTestUtils.assertLog;
35 + waitFor = InternalTestUtils.waitFor;
36 +
37 TextResource = ReactCache.unstable_createResource(
38 ([text, ms = 0]) => {
39 let listeners = null;
@@ -94,7 +105,7 @@ describe('ReactSuspense', () => {
105 }
106 }
107
97 - it('suspends rendering and continues later', () => {
108 + it('suspends rendering and continues later', async () => {
109 function Bar(props) {
110 Scheduler.unstable_yieldValue('Bar');
111 return props.children;
@@ -119,7 +130,7 @@ describe('ReactSuspense', () => {
130 unstable_isConcurrent: true,
131 });
132
122 - expect(Scheduler).toFlushAndYield(['Foo']);
133 + await waitForAll(['Foo']);
134 expect(root).toMatchRenderedOutput(null);
135
136 // Navigate the shell to now render the child content.
@@ -128,7 +139,7 @@ describe('ReactSuspense', () => {
139 root.update(<Foo renderBar={true} />);
140 });
141
131 - expect(Scheduler).toFlushAndYield([
142 + await waitForAll([
143 'Foo',
144 'Bar',
145 // A suspends
@@ -142,18 +153,18 @@ describe('ReactSuspense', () => {
153 // Flush some of the time
154 jest.advanceTimersByTime(50);
155 // Still nothing...
145 - expect(Scheduler).toFlushWithoutYielding();
156 + await waitForAll([]);
157 expect(root).toMatchRenderedOutput(null);
158
159 // Flush the promise completely
160 jest.advanceTimersByTime(50);
161 // Renders successfully
151 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
152 - expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'A', 'B']);
162 + assertLog(['Promise resolved [A]']);
163 + await waitForAll(['Foo', 'Bar', 'A', 'B']);
164 expect(root).toMatchRenderedOutput('AB');
165 });
166
156 - it('suspends siblings and later recovers each independently', () => {
167 + it('suspends siblings and later recovers each independently', async () => {
168 // Render two sibling Suspense components
169 const root = ReactTestRenderer.create(
170 <>
@@ -169,7 +180,7 @@ describe('ReactSuspense', () => {
180 },
181 );
182
172 - expect(Scheduler).toFlushAndYield([
183 + await waitForAll([
184 'Suspend! [A]',
185 'Loading A...',
186 'Suspend! [B]',
@@ -182,19 +193,19 @@ describe('ReactSuspense', () => {
193 // show the placeholder
194 jest.advanceTimersByTime(5000);
195 // TODO: Should we throw if you forget to call toHaveYielded?
185 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
186 - expect(Scheduler).toFlushAndYield(['A']);
196 + assertLog(['Promise resolved [A]']);
197 + await waitForAll(['A']);
198 expect(root).toMatchRenderedOutput('ALoading B...');
199
200 // Advance time by enough that the second Suspense's promise resolves
201 // and switches back to the normal view
202 jest.advanceTimersByTime(1000);
192 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
193 - expect(Scheduler).toFlushAndYield(['B']);
203 + assertLog(['Promise resolved [B]']);
204 + await waitForAll(['B']);
205 expect(root).toMatchRenderedOutput('AB');
206 });
207
197 - it('interrupts current render if promise resolves before current render phase', () => {
208 + it('interrupts current render if promise resolves before current render phase', async () => {
209 let didResolve = false;
210 const listeners = [];
211
@@ -231,7 +242,7 @@ describe('ReactSuspense', () => {
242 unstable_isConcurrent: true,
243 },
244 );
234 - expect(Scheduler).toFlushAndYield(['Initial']);
245 + await waitForAll(['Initial']);
246 expect(root).toMatchRenderedOutput('Initial');
247
248 // The update will suspend.
@@ -247,19 +258,15 @@ describe('ReactSuspense', () => {
258 );
259 });
260 // Yield past the Suspense boundary but don't complete the last sibling.
250 - expect(Scheduler).toFlushAndYieldThrough([
251 - 'Suspend!',
252 - 'Loading...',
253 - 'After Suspense',
254 - ]);
261 + await waitFor(['Suspend!', 'Loading...', 'After Suspense']);
262
263 // The promise resolves before the current render phase has completed
264 resolveThenable();
258 - expect(Scheduler).toHaveYielded([]);
265 + assertLog([]);
266 expect(root).toMatchRenderedOutput('Initial');
267
268 // Start over from the root, instead of continuing.
262 - expect(Scheduler).toFlushAndYield([
269 + await waitForAll([
270 // Async renders again *before* Sibling
271 'Async',
272 'After Suspense',
@@ -268,7 +275,7 @@ describe('ReactSuspense', () => {
275 expect(root).toMatchRenderedOutput('AsyncAfter SuspenseSibling');
276 });
277
271 - it('throttles fallback committing globally', () => {
278 + it('throttles fallback committing globally', async () => {
279 function Foo() {
280 Scheduler.unstable_yieldValue('Foo');
281 return (
@@ -290,7 +297,7 @@ describe('ReactSuspense', () => {
297 unstable_isConcurrent: true,
298 });
299
293 - expect(Scheduler).toFlushAndYield([
300 + await waitForAll([
301 'Foo',
302 'Suspend! [A]',
303 'Suspend! [B]',
@@ -302,8 +309,8 @@ describe('ReactSuspense', () => {
309 // Resolve A.
310 jest.advanceTimersByTime(200);
311 Scheduler.unstable_advanceTime(200);
305 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
306 - expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
312 + assertLog(['Promise resolved [A]']);
313 + await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
314
315 // By this point, we have enough info to show "A" and "Loading more..."
316 // However, we've just shown the outer fallback. So we'll delay
@@ -313,17 +320,17 @@ describe('ReactSuspense', () => {
320 // Resolve B.
321 jest.advanceTimersByTime(100);
322 Scheduler.unstable_advanceTime(100);
316 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
323 + assertLog(['Promise resolved [B]']);
324
325 // By this point, B has resolved.
326 // We're still showing the outer fallback.
327 expect(root).toMatchRenderedOutput('Loading...');
321 - expect(Scheduler).toFlushAndYield(['A', 'B']);
328 + await waitForAll(['A', 'B']);
329 // Then contents of both should pop in together.
330 expect(root).toMatchRenderedOutput('AB');
331 });
332
326 - it('does not throttle fallback committing for too long', () => {
333 + it('does not throttle fallback committing for too long', async () => {
334 function Foo() {
335 Scheduler.unstable_yieldValue('Foo');
336 return (
@@ -345,7 +352,7 @@ describe('ReactSuspense', () => {
352 unstable_isConcurrent: true,
353 });
354
348 - expect(Scheduler).toFlushAndYield([
355 + await waitForAll([
356 'Foo',
357 'Suspend! [A]',
358 'Suspend! [B]',
@@ -357,8 +364,8 @@ describe('ReactSuspense', () => {
364 // Resolve A.
365 jest.advanceTimersByTime(200);
366 Scheduler.unstable_advanceTime(200);
360 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
361 - expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
367 + assertLog(['Promise resolved [A]']);
368 + await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
369
370 // By this point, we have enough info to show "A" and "Loading more..."
371 // However, we've just shown the outer fallback. So we'll delay
@@ -374,8 +381,8 @@ describe('ReactSuspense', () => {
381 // Resolve B.
382 jest.advanceTimersByTime(500);
383 Scheduler.unstable_advanceTime(500);
377 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
378 - expect(Scheduler).toFlushAndYield(['B']);
384 + assertLog(['Promise resolved [B]']);
385 + await waitForAll(['B']);
386 expect(root).toMatchRenderedOutput('AB');
387 });
388
@@ -404,16 +411,16 @@ describe('ReactSuspense', () => {
411 </Suspense>,
412 );
413
407 - expect(Scheduler).toHaveYielded(['Loading...']);
414 + assertLog(['Loading...']);
415 expect(root).toMatchRenderedOutput('Loading...');
416
417 await LazyClass;
418
412 - expect(Scheduler).toFlushUntilNextPaint(['Hi', 'Did mount: Hi']);
419 + await waitForPaint(['Hi', 'Did mount: Hi']);
420 expect(root).toMatchRenderedOutput('Hi');
421 });
422
416 - it('updates memoized child of suspense component when context updates (simple memo)', () => {
423 + it('updates memoized child of suspense component when context updates (simple memo)', async () => {
424 const {useContext, createContext, useState, memo} = React;
425
426 const ValueContext = createContext(null);
@@ -451,23 +458,23 @@ describe('ReactSuspense', () => {
458 const root = ReactTestRenderer.create(<App />, {
459 unstable_isConcurrent: true,
460 });
454 - expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
461 + await waitForAll(['Suspend! [default]', 'Loading...']);
462 jest.advanceTimersByTime(1000);
463
457 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
458 - expect(Scheduler).toFlushAndYield(['default']);
464 + assertLog(['Promise resolved [default]']);
465 + await waitForAll(['default']);
466 expect(root).toMatchRenderedOutput('default');
467
468 act(() => setValue('new value'));
462 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
469 + assertLog(['Suspend! [new value]', 'Loading...']);
470 jest.advanceTimersByTime(1000);
471
465 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
466 - expect(Scheduler).toFlushAndYield(['new value']);
472 + assertLog(['Promise resolved [new value]']);
473 + await waitForAll(['new value']);
474 expect(root).toMatchRenderedOutput('new value');
475 });
476
470 - it('updates memoized child of suspense component when context updates (manual memo)', () => {
477 + it('updates memoized child of suspense component when context updates (manual memo)', async () => {
478 const {useContext, createContext, useState, memo} = React;
479
480 const ValueContext = createContext(null);
@@ -510,23 +517,23 @@ describe('ReactSuspense', () => {
517 const root = ReactTestRenderer.create(<App />, {
518 unstable_isConcurrent: true,
519 });
513 - expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
520 + await waitForAll(['Suspend! [default]', 'Loading...']);
521 jest.advanceTimersByTime(1000);
522
516 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
517 - expect(Scheduler).toFlushAndYield(['default']);
523 + assertLog(['Promise resolved [default]']);
524 + await waitForAll(['default']);
525 expect(root).toMatchRenderedOutput('default');
526
527 act(() => setValue('new value'));
521 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
528 + assertLog(['Suspend! [new value]', 'Loading...']);
529 jest.advanceTimersByTime(1000);
530
524 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
525 - expect(Scheduler).toFlushAndYield(['new value']);
531 + assertLog(['Promise resolved [new value]']);
532 + await waitForAll(['new value']);
533 expect(root).toMatchRenderedOutput('new value');
534 });
535
529 - it('updates memoized child of suspense component when context updates (function)', () => {
536 + it('updates memoized child of suspense component when context updates (function)', async () => {
537 const {useContext, createContext, useState} = React;
538
539 const ValueContext = createContext(null);
@@ -567,23 +574,23 @@ describe('ReactSuspense', () => {
574 unstable_isConcurrent: true,
575 },
576 );
570 - expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
577 + await waitForAll(['Suspend! [default]', 'Loading...']);
578 jest.advanceTimersByTime(1000);
579
573 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
574 - expect(Scheduler).toFlushAndYield(['default']);
580 + assertLog(['Promise resolved [default]']);
581 + await waitForAll(['default']);
582 expect(root).toMatchRenderedOutput('default');
583
584 act(() => setValue('new value'));
578 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
585 + assertLog(['Suspend! [new value]', 'Loading...']);
586 jest.advanceTimersByTime(1000);
587
581 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
582 - expect(Scheduler).toFlushAndYield(['new value']);
588 + assertLog(['Promise resolved [new value]']);
589 + await waitForAll(['new value']);
590 expect(root).toMatchRenderedOutput('new value');
591 });
592
586 - it('updates memoized child of suspense component when context updates (forwardRef)', () => {
593 + it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
594 const {forwardRef, useContext, createContext, useState} = React;
595
596 const ValueContext = createContext(null);
@@ -624,23 +631,23 @@ describe('ReactSuspense', () => {
631 unstable_isConcurrent: true,
632 },
633 );
627 - expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
634 + await waitForAll(['Suspend! [default]', 'Loading...']);
635 jest.advanceTimersByTime(1000);
636
630 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
631 - expect(Scheduler).toFlushAndYield(['default']);
637 + assertLog(['Promise resolved [default]']);
638 + await waitForAll(['default']);
639 expect(root).toMatchRenderedOutput('default');
640
641 act(() => setValue('new value'));
635 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
642 + assertLog(['Suspend! [new value]', 'Loading...']);
643 jest.advanceTimersByTime(1000);
644
638 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
639 - expect(Scheduler).toFlushAndYield(['new value']);
645 + assertLog(['Promise resolved [new value]']);
646 + await waitForAll(['new value']);
647 expect(root).toMatchRenderedOutput('new value');
648 });
649
643 - it('re-fires layout effects when re-showing Suspense', () => {
650 + it('re-fires layout effects when re-showing Suspense', async () => {
651 function TextWithLayout(props) {
652 Scheduler.unstable_yieldValue(props.text);
653 React.useLayoutEffect(() => {
@@ -668,28 +675,21 @@ describe('ReactSuspense', () => {
675 unstable_isConcurrent: true,
676 });
677
671 - expect(Scheduler).toFlushAndYield(['Child 1', 'create layout']);
678 + await waitForAll(['Child 1', 'create layout']);
679 expect(root).toMatchRenderedOutput('Child 1');
680
681 act(() => {
682 _setShow(true);
683 });
677 - expect(Scheduler).toHaveYielded([
678 - 'Child 1',
679 - 'Suspend! [Child 2]',
680 - 'Loading...',
681 - ]);
684 + assertLog(['Child 1', 'Suspend! [Child 2]', 'Loading...']);
685 jest.advanceTimersByTime(1000);
683 - expect(Scheduler).toHaveYielded([
684 - 'destroy layout',
685 - 'Promise resolved [Child 2]',
686 - ]);
687 - expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2', 'create layout']);
686 + assertLog(['destroy layout', 'Promise resolved [Child 2]']);
687 + await waitForAll(['Child 1', 'Child 2', 'create layout']);
688 expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
689 });
690
691 describe('outside concurrent mode', () => {
692 - it('a mounted class component can suspend without losing state', () => {
692 + it('a mounted class component can suspend without losing state', async () => {
693 class TextWithLifecycle extends React.Component {
694 componentDidMount() {
695 Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
@@ -754,7 +754,7 @@ describe('ReactSuspense', () => {
754
755 const root = ReactTestRenderer.create(<App />);
756
757 - expect(Scheduler).toHaveYielded([
757 + assertLog([
758 'A',
759 'Suspend! [B:1]',
760 'C',
@@ -770,8 +770,8 @@ describe('ReactSuspense', () => {
770
771 jest.advanceTimersByTime(100);
772
773 - expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
774 - expect(Scheduler).toFlushUntilNextPaint([
773 + assertLog(['Promise resolved [B:1]']);
774 + await waitForPaint([
775 'B:1',
776 'Unmount [Loading...]',
777 // Should be a mount, not an update
@@ -780,25 +780,17 @@ describe('ReactSuspense', () => {
780 expect(root).toMatchRenderedOutput('AB:1C');
781
782 instance.setState({step: 2});
783 - expect(Scheduler).toHaveYielded([
784 - 'Suspend! [B:2]',
785 - 'Loading...',
786 - 'Mount [Loading...]',
787 - ]);
783 + assertLog(['Suspend! [B:2]', 'Loading...', 'Mount [Loading...]']);
784 expect(root).toMatchRenderedOutput('Loading...');
785
786 jest.advanceTimersByTime(100);
787
792 - expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
793 - expect(Scheduler).toFlushUntilNextPaint([
794 - 'B:2',
795 - 'Unmount [Loading...]',
796 - 'Update [B:2]',
797 - ]);
788 + assertLog(['Promise resolved [B:2]']);
789 + await waitForPaint(['B:2', 'Unmount [Loading...]', 'Update [B:2]']);
790 expect(root).toMatchRenderedOutput('AB:2C');
791 });
792
801 - it('bails out on timed-out primary children even if they receive an update', () => {
793 + it('bails out on timed-out primary children even if they receive an update', async () => {
794 let instance;
795 class Stateful extends React.Component {
796 state = {step: 1};
@@ -819,38 +811,30 @@ describe('ReactSuspense', () => {
811
812 const root = ReactTestRenderer.create(<App text="A" />);
813
822 - expect(Scheduler).toHaveYielded([
823 - 'Stateful: 1',
824 - 'Suspend! [A]',
825 - 'Loading...',
826 - ]);
814 + assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
815
816 jest.advanceTimersByTime(1000);
817
830 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
831 - expect(Scheduler).toFlushUntilNextPaint(['A']);
818 + assertLog(['Promise resolved [A]']);
819 + await waitForPaint(['A']);
820 expect(root).toMatchRenderedOutput('Stateful: 1A');
821
822 root.update(<App text="B" />);
835 - expect(Scheduler).toHaveYielded([
836 - 'Stateful: 1',
837 - 'Suspend! [B]',
838 - 'Loading...',
839 - ]);
823 + assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
824 expect(root).toMatchRenderedOutput('Loading...');
825
826 instance.setState({step: 2});
843 - expect(Scheduler).toHaveYielded(['Stateful: 2', 'Suspend! [B]']);
827 + assertLog(['Stateful: 2', 'Suspend! [B]']);
828 expect(root).toMatchRenderedOutput('Loading...');
829
830 jest.advanceTimersByTime(1000);
831
848 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
849 - expect(Scheduler).toFlushUntilNextPaint(['B']);
832 + assertLog(['Promise resolved [B]']);
833 + await waitForPaint(['B']);
834 expect(root).toMatchRenderedOutput('Stateful: 2B');
835 });
836
853 - it('when updating a timed-out tree, always retries the suspended component', () => {
837 + it('when updating a timed-out tree, always retries the suspended component', async () => {
838 let instance;
839 class Stateful extends React.Component {
840 state = {step: 1};
@@ -879,28 +863,20 @@ describe('ReactSuspense', () => {
863
864 const root = ReactTestRenderer.create(<App text="A" />);
865
882 - expect(Scheduler).toHaveYielded([
883 - 'Stateful: 1',
884 - 'Suspend! [A]',
885 - 'Loading...',
886 - ]);
866 + assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
867
868 jest.advanceTimersByTime(1000);
869
890 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
891 - expect(Scheduler).toFlushUntilNextPaint(['A']);
870 + assertLog(['Promise resolved [A]']);
871 + await waitForPaint(['A']);
872 expect(root).toMatchRenderedOutput('Stateful: 1A');
873
874 root.update(<App text="B" />);
895 - expect(Scheduler).toHaveYielded([
896 - 'Stateful: 1',
897 - 'Suspend! [B]',
898 - 'Loading...',
899 - ]);
875 + assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
876 expect(root).toMatchRenderedOutput('Loading...');
877
878 instance.setState({step: 2});
903 - expect(Scheduler).toHaveYielded([
879 + assertLog([
880 'Stateful: 2',
881
882 // The suspended component should suspend again. If it doesn't, the
@@ -912,8 +888,8 @@ describe('ReactSuspense', () => {
888
889 jest.advanceTimersByTime(1000);
890
915 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
916 - expect(Scheduler).toFlushUntilNextPaint(['B']);
891 + assertLog(['Promise resolved [B]']);
892 + await waitForPaint(['B']);
893 expect(root).toMatchRenderedOutput('Stateful: 2B');
894 });
895
@@ -949,14 +925,14 @@ describe('ReactSuspense', () => {
925 }
926
927 const root = ReactTestRenderer.create(<App text="A" />);
952 - expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
928 + assertLog(['Suspend! [A]', 'Loading...']);
929 root.update(<Text text="B" />);
930 // Should not fire componentWillUnmount
955 - expect(Scheduler).toHaveYielded(['B']);
931 + assertLog(['B']);
932 expect(root).toMatchRenderedOutput('B');
933 });
934
959 - it('suspends in a component that also contains useEffect', () => {
935 + it('suspends in a component that also contains useEffect', async () => {
936 const {useLayoutEffect} = React;
937
938 function AsyncTextWithEffect(props) {
@@ -989,14 +965,14 @@ describe('ReactSuspense', () => {
965 }
966
967 ReactTestRenderer.create(<App text="A" />);
992 - expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
968 + assertLog(['Suspend! [A]', 'Loading...']);
969 jest.advanceTimersByTime(500);
970
995 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
996 - expect(Scheduler).toFlushUntilNextPaint(['A', 'Did commit: A']);
971 + assertLog(['Promise resolved [A]']);
972 + await waitForPaint(['A', 'Did commit: A']);
973 });
974
999 - it('retries when an update is scheduled on a timed out tree', () => {
975 + it('retries when an update is scheduled on a timed out tree', async () => {
976 let instance;
977 class Stateful extends React.Component {
978 state = {step: 1};
@@ -1019,33 +995,30 @@ describe('ReactSuspense', () => {
995 });
996
997 // Initial render
1022 - expect(Scheduler).toFlushAndYield(['Suspend! [Step: 1]', 'Loading...']);
998 + await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
999 jest.advanceTimersByTime(1000);
1024 - expect(Scheduler).toHaveYielded(['Promise resolved [Step: 1]']);
1025 - expect(Scheduler).toFlushAndYield(['Step: 1']);
1000 + assertLog(['Promise resolved [Step: 1]']);
1001 + await waitForAll(['Step: 1']);
1002 expect(root).toMatchRenderedOutput('Step: 1');
1003
1004 // Update that suspends
1005 instance.setState({step: 2});
1030 - expect(Scheduler).toFlushAndYield(['Suspend! [Step: 2]', 'Loading...']);
1006 + await waitForAll(['Suspend! [Step: 2]', 'Loading...']);
1007 jest.advanceTimersByTime(500);
1008 expect(root).toMatchRenderedOutput('Loading...');
1009
1010 // Update while still suspended
1011 instance.setState({step: 3});
1036 - expect(Scheduler).toFlushAndYield(['Suspend! [Step: 3]']);
1012 + await waitForAll(['Suspend! [Step: 3]']);
1013 expect(root).toMatchRenderedOutput('Loading...');
1014
1015 jest.advanceTimersByTime(1000);
1040 - expect(Scheduler).toHaveYielded([
1041 - 'Promise resolved [Step: 2]',
1042 - 'Promise resolved [Step: 3]',
1043 - ]);
1044 - expect(Scheduler).toFlushAndYield(['Step: 3']);
1016 + assertLog(['Promise resolved [Step: 2]', 'Promise resolved [Step: 3]']);
1017 + await waitForAll(['Step: 3']);
1018 expect(root).toMatchRenderedOutput('Step: 3');
1019 });
1020
1048 - it('does not remount the fallback while suspended children resolve in legacy mode', () => {
1021 + it('does not remount the fallback while suspended children resolve in legacy mode', async () => {
1022 let mounts = 0;
1023 class ShouldMountOnce extends React.Component {
1024 componentDidMount() {
@@ -1069,18 +1042,18 @@ describe('ReactSuspense', () => {
1042 const root = ReactTestRenderer.create(<App />);
1043
1044 // Initial render
1072 - expect(Scheduler).toHaveYielded([
1045 + assertLog([
1046 'Suspend! [Child 1]',
1047 'Suspend! [Child 2]',
1048 'Suspend! [Child 3]',
1049 'Loading...',
1050 ]);
1078 - expect(Scheduler).toFlushAndYield([]);
1051 + await waitForAll([]);
1052
1053 jest.advanceTimersByTime(1000);
1054
1082 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
1083 - expect(Scheduler).toFlushUntilNextPaint([
1055 + assertLog(['Promise resolved [Child 1]']);
1056 + await waitForPaint([
1057 'Child 1',
1058 'Suspend! [Child 2]',
1059 'Suspend! [Child 3]',
@@ -1088,23 +1061,20 @@ describe('ReactSuspense', () => {
1061
1062 jest.advanceTimersByTime(1000);
1063
1091 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
1092 - expect(Scheduler).toFlushUntilNextPaint([
1093 - 'Child 2',
1094 - 'Suspend! [Child 3]',
1095 - ]);
1064 + assertLog(['Promise resolved [Child 2]']);
1065 + await waitForPaint(['Child 2', 'Suspend! [Child 3]']);
1066
1067 jest.advanceTimersByTime(1000);
1068
1099 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
1100 - expect(Scheduler).toFlushUntilNextPaint(['Child 3']);
1069 + assertLog(['Promise resolved [Child 3]']);
1070 + await waitForPaint(['Child 3']);
1071 expect(root).toMatchRenderedOutput(
1072 ['Child 1', 'Child 2', 'Child 3'].join(''),
1073 );
1074 expect(mounts).toBe(1);
1075 });
1076
1107 - it('does not get stuck with fallback in concurrent mode for a large delay', () => {
1077 + it('does not get stuck with fallback in concurrent mode for a large delay', async () => {
1078 function App(props) {
1079 return (
1080 <Suspense fallback={<Text text="Loading..." />}>
@@ -1118,21 +1088,21 @@ describe('ReactSuspense', () => {
1088 unstable_isConcurrent: true,
1089 });
1090
1121 - expect(Scheduler).toFlushAndYield([
1091 + await waitForAll([
1092 'Suspend! [Child 1]',
1093 'Suspend! [Child 2]',
1094 'Loading...',
1095 ]);
1096 jest.advanceTimersByTime(1000);
1127 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
1128 - expect(Scheduler).toFlushAndYield(['Child 1', 'Suspend! [Child 2]']);
1097 + assertLog(['Promise resolved [Child 1]']);
1098 + await waitForAll(['Child 1', 'Suspend! [Child 2]']);
1099 jest.advanceTimersByTime(6000);
1130 - expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
1131 - expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2']);
1100 + assertLog(['Promise resolved [Child 2]']);
1101 + await waitForAll(['Child 1', 'Child 2']);
1102 expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
1103 });
1104
1135 - it('reuses effects, including deletions, from the suspended tree', () => {
1105 + it('reuses effects, including deletions, from the suspended tree', async () => {
1106 const {useState} = React;
1107
1108 let setTab;
@@ -1149,46 +1119,34 @@ describe('ReactSuspense', () => {
1119 }
1120
1121 const root = ReactTestRenderer.create(<App />);
1152 - expect(Scheduler).toHaveYielded([
1153 - 'Suspend! [Tab: 0]',
1154 - ' + sibling',
1155 - 'Loading...',
1156 - ]);
1122 + assertLog(['Suspend! [Tab: 0]', ' + sibling', 'Loading...']);
1123 expect(root).toMatchRenderedOutput('Loading...');
1124 jest.advanceTimersByTime(1000);
1125
1160 - expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
1161 - expect(Scheduler).toFlushUntilNextPaint(['Tab: 0']);
1126 + assertLog(['Promise resolved [Tab: 0]']);
1127 + await waitForPaint(['Tab: 0']);
1128 expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
1129
1130 act(() => setTab(1));
1165 - expect(Scheduler).toHaveYielded([
1166 - 'Suspend! [Tab: 1]',
1167 - ' + sibling',
1168 - 'Loading...',
1169 - ]);
1131 + assertLog(['Suspend! [Tab: 1]', ' + sibling', 'Loading...']);
1132 expect(root).toMatchRenderedOutput('Loading...');
1133 jest.advanceTimersByTime(1000);
1134
1173 - expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
1174 - expect(Scheduler).toFlushUntilNextPaint(['Tab: 1']);
1135 + assertLog(['Promise resolved [Tab: 1]']);
1136 + await waitForPaint(['Tab: 1']);
1137 expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
1138
1139 act(() => setTab(2));
1178 - expect(Scheduler).toHaveYielded([
1179 - 'Suspend! [Tab: 2]',
1180 - ' + sibling',
1181 - 'Loading...',
1182 - ]);
1140 + assertLog(['Suspend! [Tab: 2]', ' + sibling', 'Loading...']);
1141 expect(root).toMatchRenderedOutput('Loading...');
1142 jest.advanceTimersByTime(1000);
1143
1186 - expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
1187 - expect(Scheduler).toFlushUntilNextPaint(['Tab: 2']);
1144 + assertLog(['Promise resolved [Tab: 2]']);
1145 + await waitForPaint(['Tab: 2']);
1146 expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
1147 });
1148
1191 - it('does not warn if an mounted component is pinged', () => {
1149 + it('does not warn if an mounted component is pinged', async () => {
1150 const {useState} = React;
1151
1152 const root = ReactTestRenderer.create(null);
@@ -1218,23 +1176,23 @@ describe('ReactSuspense', () => {
1176 </Suspense>,
1177 );
1178
1221 - expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
1179 + assertLog(['Suspend! [A:0]', 'Loading...']);
1180 jest.advanceTimersByTime(1000);
1181
1224 - expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
1225 - expect(Scheduler).toFlushUntilNextPaint(['A:0']);
1182 + assertLog(['Promise resolved [A:0]']);
1183 + await waitForPaint(['A:0']);
1184 expect(root).toMatchRenderedOutput('A:0');
1185
1186 act(() => setStep(1));
1229 - expect(Scheduler).toHaveYielded(['Suspend! [A:1]', 'Loading...']);
1187 + assertLog(['Suspend! [A:1]', 'Loading...']);
1188 expect(root).toMatchRenderedOutput('Loading...');
1189
1190 root.update(null);
1233 - expect(Scheduler).toFlushWithoutYielding();
1191 + await waitForAll([]);
1192 jest.advanceTimersByTime(1000);
1193 });
1194
1237 - it('memoizes promise listeners per thread ID to prevent redundant renders', () => {
1195 + it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
1196 function App() {
1197 return (
1198 <Suspense fallback={<Text text="Loading..." />}>
@@ -1249,18 +1207,13 @@ describe('ReactSuspense', () => {
1207
1208 root.update(<App />);
1209
1252 - expect(Scheduler).toHaveYielded([
1253 - 'Suspend! [A]',
1254 - 'Suspend! [B]',
1255 - 'Suspend! [C]',
1256 - 'Loading...',
1257 - ]);
1210 + assertLog(['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]', 'Loading...']);
1211
1212 // Resolve A
1213 jest.advanceTimersByTime(1000);
1214
1262 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1263 - expect(Scheduler).toFlushUntilNextPaint([
1215 + assertLog(['Promise resolved [A]']);
1216 + await waitForPaint([
1217 'A',
1218 // The promises for B and C have now been thrown twice
1219 'Suspend! [B]',
@@ -1270,8 +1223,8 @@ describe('ReactSuspense', () => {
1223 // Resolve B
1224 jest.advanceTimersByTime(1000);
1225
1273 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1274 - expect(Scheduler).toFlushUntilNextPaint([
1226 + assertLog(['Promise resolved [B]']);
1227 + await waitForPaint([
1228 // Even though the promise for B was thrown twice, we should only
1229 // re-render once.
1230 'B',
@@ -1282,8 +1235,8 @@ describe('ReactSuspense', () => {
1235 // Resolve C
1236 jest.advanceTimersByTime(1000);
1237
1285 - expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
1286 - expect(Scheduler).toFlushUntilNextPaint([
1238 + assertLog(['Promise resolved [C]']);
1239 + await waitForPaint([
1240 // Even though the promise for C was thrown three times, we should only
1241 // re-render once.
1242 'C',
@@ -1328,7 +1281,7 @@ describe('ReactSuspense', () => {
1281 jest.advanceTimersByTime(1000);
1282 });
1283
1331 - it('updates memoized child of suspense component when context updates (simple memo)', () => {
1284 + it('updates memoized child of suspense component when context updates (simple memo)', async () => {
1285 const {useContext, createContext, useState, memo} = React;
1286
1287 const ValueContext = createContext(null);
@@ -1364,23 +1317,23 @@ describe('ReactSuspense', () => {
1317 }
1318
1319 const root = ReactTestRenderer.create(<App />);
1367 - expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1320 + assertLog(['Suspend! [default]', 'Loading...']);
1321 jest.advanceTimersByTime(1000);
1322
1370 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1371 - expect(Scheduler).toFlushUntilNextPaint(['default']);
1323 + assertLog(['Promise resolved [default]']);
1324 + await waitForPaint(['default']);
1325 expect(root).toMatchRenderedOutput('default');
1326
1327 act(() => setValue('new value'));
1375 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1328 + assertLog(['Suspend! [new value]', 'Loading...']);
1329 jest.advanceTimersByTime(1000);
1330
1378 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1379 - expect(Scheduler).toFlushUntilNextPaint(['new value']);
1331 + assertLog(['Promise resolved [new value]']);
1332 + await waitForPaint(['new value']);
1333 expect(root).toMatchRenderedOutput('new value');
1334 });
1335
1383 - it('updates memoized child of suspense component when context updates (manual memo)', () => {
1336 + it('updates memoized child of suspense component when context updates (manual memo)', async () => {
1337 const {useContext, createContext, useState, memo} = React;
1338
1339 const ValueContext = createContext(null);
@@ -1421,23 +1374,23 @@ describe('ReactSuspense', () => {
1374 }
1375
1376 const root = ReactTestRenderer.create(<App />);
1424 - expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1377 + assertLog(['Suspend! [default]', 'Loading...']);
1378 jest.advanceTimersByTime(1000);
1379
1427 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1428 - expect(Scheduler).toFlushUntilNextPaint(['default']);
1380 + assertLog(['Promise resolved [default]']);
1381 + await waitForPaint(['default']);
1382 expect(root).toMatchRenderedOutput('default');
1383
1384 act(() => setValue('new value'));
1432 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1385 + assertLog(['Suspend! [new value]', 'Loading...']);
1386 jest.advanceTimersByTime(1000);
1387
1435 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1436 - expect(Scheduler).toFlushUntilNextPaint(['new value']);
1388 + assertLog(['Promise resolved [new value]']);
1389 + await waitForPaint(['new value']);
1390 expect(root).toMatchRenderedOutput('new value');
1391 });
1392
1440 - it('updates memoized child of suspense component when context updates (function)', () => {
1393 + it('updates memoized child of suspense component when context updates (function)', async () => {
1394 const {useContext, createContext, useState} = React;
1395
1396 const ValueContext = createContext(null);
@@ -1477,23 +1430,23 @@ describe('ReactSuspense', () => {
1430 </Suspense>
1431 </App>,
1432 );
1480 - expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1433 + assertLog(['Suspend! [default]', 'Loading...']);
1434 jest.advanceTimersByTime(1000);
1435
1483 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1484 - expect(Scheduler).toFlushUntilNextPaint(['default']);
1436 + assertLog(['Promise resolved [default]']);
1437 + await waitForPaint(['default']);
1438 expect(root).toMatchRenderedOutput('default');
1439
1440 act(() => setValue('new value'));
1488 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1441 + assertLog(['Suspend! [new value]', 'Loading...']);
1442 jest.advanceTimersByTime(1000);
1443
1491 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1492 - expect(Scheduler).toFlushUntilNextPaint(['new value']);
1444 + assertLog(['Promise resolved [new value]']);
1445 + await waitForPaint(['new value']);
1446 expect(root).toMatchRenderedOutput('new value');
1447 });
1448
1496 - it('updates memoized child of suspense component when context updates (forwardRef)', () => {
1449 + it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
1450 const {forwardRef, useContext, createContext, useState} = React;
1451
1452 const ValueContext = createContext(null);
@@ -1529,19 +1482,19 @@ describe('ReactSuspense', () => {
1482 }
1483
1484 const root = ReactTestRenderer.create(<App />);
1532 - expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
1485 + assertLog(['Suspend! [default]', 'Loading...']);
1486 jest.advanceTimersByTime(1000);
1487
1535 - expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
1536 - expect(Scheduler).toFlushUntilNextPaint(['default']);
1488 + assertLog(['Promise resolved [default]']);
1489 + await waitForPaint(['default']);
1490 expect(root).toMatchRenderedOutput('default');
1491
1492 act(() => setValue('new value'));
1540 - expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
1493 + assertLog(['Suspend! [new value]', 'Loading...']);
1494 jest.advanceTimersByTime(1000);
1495
1543 - expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
1544 - expect(Scheduler).toFlushUntilNextPaint(['new value']);
1496 + assertLog(['Promise resolved [new value]']);
1497 + await waitForPaint(['new value']);
1498 expect(root).toMatchRenderedOutput('new value');
1499 });
1500
@@ -1587,24 +1540,15 @@ describe('ReactSuspense', () => {
1540 }
1541
1542 const root = ReactTestRenderer.create(<App />);
1590 - expect(Scheduler).toHaveYielded([
1591 - 'Received context value [default]',
1592 - 'default',
1593 - ]);
1543 + assertLog(['Received context value [default]', 'default']);
1544 expect(root).toMatchRenderedOutput('default');
1545
1546 act(() => setValue('new value'));
1597 - expect(Scheduler).toHaveYielded([
1598 - 'Received context value [new value]',
1599 - 'Loading...',
1600 - ]);
1547 + assertLog(['Received context value [new value]', 'Loading...']);
1548 expect(root).toMatchRenderedOutput('Loading...');
1549
1550 act(() => setValue('default'));
1604 - expect(Scheduler).toHaveYielded([
1605 - 'Received context value [default]',
1606 - 'default',
1607 - ]);
1551 + assertLog(['Received context value [default]', 'default']);
1552 expect(root).toMatchRenderedOutput('default');
1553 });
1554 });
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+14 -10
@@ -12,6 +12,7 @@
12 let React;
13 let ReactNoop;
14 let Scheduler;
15 +let waitForAll;
16
17 describe('ReactSuspense', () => {
18 beforeEach(() => {
@@ -20,6 +21,9 @@ describe('ReactSuspense', () => {
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
24 +
25 + const InternalTestUtils = require('internal-test-utils');
26 + waitForAll = InternalTestUtils.waitForAll;
27 });
28
29 function createThenable() {
@@ -83,13 +87,13 @@ describe('ReactSuspense', () => {
87 );
88
89 ReactNoop.render(element);
86 - expect(Scheduler).toFlushWithoutYielding();
90 + await waitForAll([]);
91 expect(ReactNoop).toMatchRenderedOutput('Waiting');
92 expect(ops).toEqual([new Set([promise])]);
93 ops = [];
94
95 await resolve();
92 - expect(Scheduler).toFlushWithoutYielding();
96 + await waitForAll([]);
97 expect(ReactNoop).toMatchRenderedOutput('Done');
98 expect(ops).toEqual([]);
99 });
@@ -122,27 +126,27 @@ describe('ReactSuspense', () => {
126 );
127
128 ReactNoop.render(element);
125 - expect(Scheduler).toFlushWithoutYielding();
129 + await waitForAll([]);
130 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
131 expect(ops).toEqual([new Set([promise1, promise2])]);
132 ops = [];
133
134 await resolve1();
135 ReactNoop.render(element);
132 - expect(Scheduler).toFlushWithoutYielding();
136 + await waitForAll([]);
137 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
138 expect(ops).toEqual([new Set([promise2])]);
139 ops = [];
140
141 await resolve2();
142 ReactNoop.render(element);
139 - expect(Scheduler).toFlushWithoutYielding();
143 + await waitForAll([]);
144 expect(ReactNoop).toMatchRenderedOutput('DoneDone');
145 expect(ops).toEqual([]);
146 });
147
148 // @gate www
145 - it('nested suspense promises are reported only for their tier', () => {
149 + it('nested suspense promises are reported only for their tier', async () => {
150 const {promise, PromiseComp} = createThenable();
151
152 const ops1 = [];
@@ -167,7 +171,7 @@ describe('ReactSuspense', () => {
171 );
172
173 ReactNoop.render(element);
170 - expect(Scheduler).toFlushWithoutYielding();
174 + await waitForAll([]);
175 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2');
176 expect(ops1).toEqual([]);
177 expect(ops2).toEqual([new Set([promise])]);
@@ -209,7 +213,7 @@ describe('ReactSuspense', () => {
213 );
214
215 ReactNoop.render(element);
212 - expect(Scheduler).toFlushWithoutYielding();
216 + await waitForAll([]);
217 expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
218 expect(ops1).toEqual([new Set([promise1])]);
219 expect(ops2).toEqual([]);
@@ -218,7 +222,7 @@ describe('ReactSuspense', () => {
222
223 await resolve1();
224 ReactNoop.render(element);
221 - expect(Scheduler).toFlushWithoutYielding();
225 + await waitForAll([]);
226
227 // Force fallback to commit.
228 // TODO: Should be able to use `act` here.
@@ -232,7 +236,7 @@ describe('ReactSuspense', () => {
236
237 await resolve2();
238 ReactNoop.render(element);
235 - expect(Scheduler).toFlushWithoutYielding();
239 + await waitForAll([]);
240 expect(ReactNoop).toMatchRenderedOutput('DoneDone');
241 expect(ops1).toEqual([]);
242 expect(ops2).toEqual([]);