How to use advanceTimers method in Playwright Internal

Best JavaScript code snippet using playwright-internal

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

ReactSuspenseWithNoopRenderer-test.internal.js

Source: ReactSuspenseWithNoopRenderer-test.internal.js Github

copy
1let React;
2let ReactFeatureFlags;
3let Fragment;
4let ReactNoop;
5let Scheduler;
6let ReactCache;
7let Suspense;
8
9let TextResource;
10let textResourceShouldFail;
11
12function loadModules({
13  deferPassiveEffectCleanupDuringUnmount,
14  runAllPassiveEffectDestroysBeforeCreates,
15}) {
16  ReactFeatureFlags = require('@/shared/ReactFeatureFlags');
17  ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
18  ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
19  ReactFeatureFlags.flushSuspenseFallbacksInTests = false;
20  ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount = deferPassiveEffectCleanupDuringUnmount;
21  ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates = runAllPassiveEffectDestroysBeforeCreates;
22  React = require('react');
23  Fragment = React.Fragment;
24  ReactNoop = require('react-noop-renderer');
25  Scheduler = require('scheduler');
26  ReactCache = require('react-cache');
27  Suspense = React.Suspense;
28
29  TextResource = ReactCache.unstable_createResource(
30    ([text, ms = 0]) => {
31      return new Promise((resolve, reject) =>
32        setTimeout(() => {
33          if (textResourceShouldFail) {
34            Scheduler.unstable_yieldValue(`Promise rejected [${text}]`);
35            reject(new Error('Failed to load: ' + text));
36          } else {
37            Scheduler.unstable_yieldValue(`Promise resolved [${text}]`);
38            resolve(text);
39          }
40        }, ms),
41      );
42    },
43    ([text, ms]) => text,
44  );
45  textResourceShouldFail = false;
46}
47
48[true, false].forEach(deferPassiveEffectCleanupDuringUnmount => {
49  [true, false].forEach(runAllPassiveEffectDestroysBeforeCreates => {
50    describe(`ReactSuspenseWithNoopRenderer deferPassiveEffectCleanupDuringUnmount:${deferPassiveEffectCleanupDuringUnmount} runAllPassiveEffectDestroysBeforeCreates:${runAllPassiveEffectDestroysBeforeCreates}`, () => {
51      if (!true) {
52        it("empty test so Jest doesn't complain", () => {});
53        return;
54      }
55
56      beforeEach(() => {
57        jest.resetModules();
58
59        loadModules({
60          deferPassiveEffectCleanupDuringUnmount,
61          runAllPassiveEffectDestroysBeforeCreates,
62        });
63      });
64
65      // function div(...children) {
66      //   children = children.map(
67      //     c => (typeof c === 'string' ? {text: c, hidden: false} : c),
68      //   );
69      //   return {type: 'div', children, prop: undefined, hidden: false};
70      // }
71
72      function span(prop) {
73        return {type: 'span', children: [], prop, hidden: false};
74      }
75
76      function hiddenSpan(prop) {
77        return {type: 'span', children: [], prop, hidden: true};
78      }
79
80      function advanceTimers(ms) {
81        // Note: This advances Jest's virtual time but not React's. Use
82        // ReactNoop.expire for that.
83        if (typeof ms !== 'number') {
84          throw new Error('Must specify ms');
85        }
86        jest.advanceTimersByTime(ms);
87        // Wait until the end of the current tick
88        // We cannot use a timer since we're faking them
89        return Promise.resolve().then(() => {});
90      }
91
92      function Text(props) {
93        Scheduler.unstable_yieldValue(props.text);
94        return <span prop={props.text} ref={props.hostRef} />;
95      }
96
97      function AsyncText(props) {
98        const text = props.text;
99        try {
100          TextResource.read([props.text, props.ms]);
101          Scheduler.unstable_yieldValue(text);
102          return <span prop={text} />;
103        } catch (promise) {
104          if (typeof promise.then === 'function') {
105            Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
106          } else {
107            Scheduler.unstable_yieldValue(`Error! [${text}]`);
108          }
109          throw promise;
110        }
111      }
112
113      it('does not restart rendering for initial render', async () => {
114        function Bar(props) {
115          Scheduler.unstable_yieldValue('Bar');
116          return props.children;
117        }
118
119        function Foo() {
120          Scheduler.unstable_yieldValue('Foo');
121          return (
122            <>
123              <Suspense fallback={<Text text="Loading..." />}>
124                <Bar>
125                  <AsyncText text="A" ms={100} />
126                  <Text text="B" />
127                </Bar>
128              </Suspense>
129              <Text text="C" />
130              <Text text="D" />
131            </>
132          );
133        }
134
135        ReactNoop.render(<Foo />);
136        expect(Scheduler).toFlushAndYieldThrough([
137          'Foo',
138          'Bar',
139          // A suspends
140          'Suspend! [A]',
141          // But we keep rendering the siblings
142          'B',
143          'Loading...',
144          'C',
145          // We leave D incomplete.
146        ]);
147        expect(ReactNoop.getChildren()).toEqual([]);
148
149        // Flush the promise completely
150        Scheduler.unstable_advanceTime(100);
151        await advanceTimers(100);
152        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
153
154        // Even though the promise has resolved, we should now flush
155        // and commit the in progress render instead of restarting.
156        expect(Scheduler).toFlushAndYield(['D']);
157        expect(ReactNoop.getChildren()).toEqual([
158          span('Loading...'),
159          span('C'),
160          span('D'),
161        ]);
162
163        // Await one micro task to attach the retry listeners.
164        await null;
165
166        // Next, we'll flush the complete content.
167        expect(Scheduler).toFlushAndYield(['Bar', 'A', 'B']);
168
169        expect(ReactNoop.getChildren()).toEqual([
170          span('A'),
171          span('B'),
172          span('C'),
173          span('D'),
174        ]);
175      });
176
177      it('suspends rendering and continues later', async () => {
178        function Bar(props) {
179          Scheduler.unstable_yieldValue('Bar');
180          return props.children;
181        }
182
183        function Foo({renderBar}) {
184          Scheduler.unstable_yieldValue('Foo');
185          return (
186            <Suspense fallback={<Text text="Loading..." />}>
187              {renderBar ? (
188                <Bar>
189                  <AsyncText text="A" ms={100} />
190                  <Text text="B" />
191                </Bar>
192              ) : null}
193            </Suspense>
194          );
195        }
196
197        // Render empty shell.
198        ReactNoop.render(<Foo />);
199        expect(Scheduler).toFlushAndYield(['Foo']);
200
201        // The update will suspend.
202        ReactNoop.render(<Foo renderBar={true} />);
203        expect(Scheduler).toFlushAndYield([
204          'Foo',
205          'Bar',
206          // A suspends
207          'Suspend! [A]',
208          // But we keep rendering the siblings
209          'B',
210          'Loading...',
211        ]);
212        expect(ReactNoop.getChildren()).toEqual([]);
213
214        // Flush some of the time
215        await advanceTimers(50);
216        // Still nothing...
217        expect(Scheduler).toFlushWithoutYielding();
218        expect(ReactNoop.getChildren()).toEqual([]);
219
220        // Flush the promise completely
221        await advanceTimers(50);
222        // Renders successfully
223        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
224        expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'A', 'B']);
225        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
226      });
227
228      it('suspends siblings and later recovers each independently', async () => {
229        // Render two sibling Suspense components
230        ReactNoop.render(
231          <Fragment>
232            <Suspense fallback={<Text text="Loading A..." />}>
233              <AsyncText text="A" ms={5000} />
234            </Suspense>
235            <Suspense fallback={<Text text="Loading B..." />}>
236              <AsyncText text="B" ms={6000} />
237            </Suspense>
238          </Fragment>,
239        );
240        expect(Scheduler).toFlushAndYield([
241          'Suspend! [A]',
242          'Loading A...',
243          'Suspend! [B]',
244          'Loading B...',
245        ]);
246        expect(ReactNoop.getChildren()).toEqual([
247          span('Loading A...'),
248          span('Loading B...'),
249        ]);
250
251        // Advance time by enough that the first Suspense's promise resolves and
252        // switches back to the normal view. The second Suspense should still
253        // show the placeholder
254        ReactNoop.expire(5000);
255        await advanceTimers(5000);
256
257        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
258        expect(Scheduler).toFlushAndYield(['A']);
259        expect(ReactNoop.getChildren()).toEqual([
260          span('A'),
261          span('Loading B...'),
262        ]);
263
264        // Advance time by enough that the second Suspense's promise resolves
265        // and switches back to the normal view
266        ReactNoop.expire(1000);
267        await advanceTimers(1000);
268
269        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
270        expect(Scheduler).toFlushAndYield(['B']);
271        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
272      });
273
274      it('continues rendering siblings after suspending', async () => {
275        // A shell is needed. The update cause it to suspend.
276        ReactNoop.render(<Suspense fallback={<Text text="Loading..." />} />);
277        expect(Scheduler).toFlushAndYield([]);
278        // B suspends. Continue rendering the remaining siblings.
279        ReactNoop.render(
280          <Suspense fallback={<Text text="Loading..." />}>
281            <Text text="A" />
282            <AsyncText text="B" />
283            <Text text="C" />
284            <Text text="D" />
285          </Suspense>,
286        );
287        // B suspends. Continue rendering the remaining siblings.
288        expect(Scheduler).toFlushAndYield([
289          'A',
290          'Suspend! [B]',
291          'C',
292          'D',
293          'Loading...',
294        ]);
295        // Did not commit yet.
296        expect(ReactNoop.getChildren()).toEqual([]);
297
298        // Wait for data to resolve
299        await advanceTimers(100);
300        // Renders successfully
301        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
302        expect(Scheduler).toFlushAndYield(['A', 'B', 'C', 'D']);
303        expect(ReactNoop.getChildren()).toEqual([
304          span('A'),
305          span('B'),
306          span('C'),
307          span('D'),
308        ]);
309      });
310
311      it('retries on error', async () => {
312        class ErrorBoundary extends React.Component {
313          state = {error: null};
314          componentDidCatch(error) {
315            this.setState({error});
316          }
317          reset() {
318            this.setState({error: null});
319          }
320          render() {
321            if (this.state.error !== null) {
322              return (
323                <Text text={'Caught error: ' + this.state.error.message} />
324              );
325            }
326            return this.props.children;
327          }
328        }
329
330        const errorBoundary = React.createRef();
331        function App({renderContent}) {
332          return (
333            <Suspense fallback={<Text text="Loading..." />}>
334              {renderContent ? (
335                <ErrorBoundary ref={errorBoundary}>
336                  <AsyncText text="Result" ms={1000} />
337                </ErrorBoundary>
338              ) : null}
339            </Suspense>
340          );
341        }
342
343        ReactNoop.render(<App />);
344        expect(Scheduler).toFlushAndYield([]);
345        expect(ReactNoop.getChildren()).toEqual([]);
346
347        ReactNoop.render(<App renderContent={true} />);
348        expect(Scheduler).toFlushAndYield(['Suspend! [Result]', 'Loading...']);
349        expect(ReactNoop.getChildren()).toEqual([]);
350
351        textResourceShouldFail = true;
352        ReactNoop.expire(1000);
353        await advanceTimers(1000);
354        textResourceShouldFail = false;
355
356        expect(Scheduler).toHaveYielded(['Promise rejected [Result]']);
357
358        expect(Scheduler).toFlushAndYield([
359          'Error! [Result]',
360
361          // React retries one more time
362          'Error! [Result]',
363
364          // Errored again on retry. Now handle it.
365          'Caught error: Failed to load: Result',
366        ]);
367        expect(ReactNoop.getChildren()).toEqual([
368          span('Caught error: Failed to load: Result'),
369        ]);
370      });
371
372      it('retries on error after falling back to a placeholder', async () => {
373        class ErrorBoundary extends React.Component {
374          state = {error: null};
375          componentDidCatch(error) {
376            this.setState({error});
377          }
378          reset() {
379            this.setState({error: null});
380          }
381          render() {
382            if (this.state.error !== null) {
383              return (
384                <Text text={'Caught error: ' + this.state.error.message} />
385              );
386            }
387            return this.props.children;
388          }
389        }
390
391        const errorBoundary = React.createRef();
392        function App() {
393          return (
394            <Suspense fallback={<Text text="Loading..." />}>
395              <ErrorBoundary ref={errorBoundary}>
396                <AsyncText text="Result" ms={3000} />
397              </ErrorBoundary>
398            </Suspense>
399          );
400        }
401
402        ReactNoop.render(<App />);
403        expect(Scheduler).toFlushAndYield(['Suspend! [Result]', 'Loading...']);
404        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
405
406        textResourceShouldFail = true;
407        ReactNoop.expire(3000);
408        await advanceTimers(3000);
409        textResourceShouldFail = false;
410
411        expect(Scheduler).toHaveYielded(['Promise rejected [Result]']);
412        expect(Scheduler).toFlushAndYield([
413          'Error! [Result]',
414
415          // React retries one more time
416          'Error! [Result]',
417
418          // Errored again on retry. Now handle it.
419
420          'Caught error: Failed to load: Result',
421        ]);
422        expect(ReactNoop.getChildren()).toEqual([
423          span('Caught error: Failed to load: Result'),
424        ]);
425      });
426
427      it('can update at a higher priority while in a suspended state', async () => {
428        function App(props) {
429          return (
430            <Suspense fallback={<Text text="Loading..." />}>
431              <Text text={props.highPri} />
432              <AsyncText text={props.lowPri} />
433            </Suspense>
434          );
435        }
436
437        // Initial mount
438        ReactNoop.render(<App highPri="A" lowPri="1" />);
439        expect(Scheduler).toFlushAndYield(['A', 'Suspend! [1]', 'Loading...']);
440        await advanceTimers(0);
441        expect(Scheduler).toHaveYielded(['Promise resolved [1]']);
442        expect(Scheduler).toFlushAndYield(['A', '1']);
443        expect(ReactNoop.getChildren()).toEqual([span('A'), span('1')]);
444
445        // Update the low-pri text
446        ReactNoop.render(<App highPri="A" lowPri="2" />);
447        expect(Scheduler).toFlushAndYield([
448          'A',
449          // Suspends
450          'Suspend! [2]',
451          'Loading...',
452        ]);
453
454        // While we're still waiting for the low-pri update to complete, update the
455        // high-pri text at high priority.
456        ReactNoop.flushSync(() => {
457          ReactNoop.render(<App highPri="B" lowPri="1" />);
458        });
459        expect(Scheduler).toHaveYielded(['B', '1']);
460        expect(ReactNoop.getChildren()).toEqual([span('B'), span('1')]);
461
462        // Unblock the low-pri text and finish
463        await advanceTimers(0);
464        expect(Scheduler).toHaveYielded(['Promise resolved [2]']);
465        expect(ReactNoop.getChildren()).toEqual([span('B'), span('1')]);
466      });
467
468      it('keeps working on lower priority work after being pinged', async () => {
469        // Advance the virtual time so that we're close to the edge of a bucket.
470        ReactNoop.expire(149);
471
472        function App(props) {
473          return (
474            <Suspense fallback={<Text text="Loading..." />}>
475              {props.showA && <AsyncText text="A" />}
476              {props.showB && <Text text="B" />}
477            </Suspense>
478          );
479        }
480
481        ReactNoop.render(<App showA={false} showB={false} />);
482        expect(Scheduler).toFlushAndYield([]);
483        expect(ReactNoop.getChildren()).toEqual([]);
484
485        ReactNoop.render(<App showA={true} showB={false} />);
486        expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
487        expect(ReactNoop.getChildren()).toEqual([]);
488
489        // Advance React's virtual time by enough to fall into a new async bucket,
490        // but not enough to expire the suspense timeout.
491        ReactNoop.expire(120);
492        ReactNoop.render(<App showA={true} showB={true} />);
493        expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'B', 'Loading...']);
494        expect(ReactNoop.getChildren()).toEqual([]);
495
496        await advanceTimers(0);
497        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
498        expect(Scheduler).toFlushAndYield(['A', 'B']);
499        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
500      });
501
502      it('tries rendering a lower priority pending update even if a higher priority one suspends', async () => {
503        function App(props) {
504          if (props.hide) {
505            return <Text text="(empty)" />;
506          }
507          return (
508            <Suspense fallback="Loading...">
509              <AsyncText ms={2000} text="Async" />
510            </Suspense>
511          );
512        }
513
514        // Schedule a high pri update and a low pri update, without rendering in
515        // between.
516        ReactNoop.discreteUpdates(() => {
517          // High pri
518          ReactNoop.render(<App />);
519        });
520        // Low pri
521        ReactNoop.render(<App hide={true} />);
522
523        expect(Scheduler).toFlushAndYield([
524          // The first update suspends
525          'Suspend! [Async]',
526          // but we have another pending update that we can work on
527          '(empty)',
528        ]);
529        expect(ReactNoop.getChildren()).toEqual([span('(empty)')]);
530      });
531
532      it('tries each subsequent level after suspending', async () => {
533        const root = ReactNoop.createRoot();
534
535        function App({step, shouldSuspend}) {
536          return (
537            <Suspense fallback="Loading...">
538              <Text text="Sibling" />
539              {shouldSuspend ? (
540                <AsyncText ms={10000} text={'Step ' + step} />
541              ) : (
542                <Text text={'Step ' + step} />
543              )}
544            </Suspense>
545          );
546        }
547
548        function interrupt() {
549          // React has a heuristic to batch all updates that occur within the same
550          // event. This is a trick to circumvent that heuristic.
551          ReactNoop.flushSync(() => {
552            ReactNoop.renderToRootWithID(null, 'other-root');
553          });
554        }
555
556        // Mount the Suspense boundary without suspending, so that the subsequent
557        // updates suspend with a delay.
558        await ReactNoop.act(async () => {
559          root.render(<App step={0} shouldSuspend={false} />);
560        });
561        await advanceTimers(1000);
562        expect(Scheduler).toHaveYielded(['Sibling', 'Step 0']);
563
564        // Schedule an update at several distinct expiration times
565        await ReactNoop.act(async () => {
566          root.render(<App step={1} shouldSuspend={true} />);
567          Scheduler.unstable_advanceTime(1000);
568          expect(Scheduler).toFlushAndYieldThrough(['Sibling']);
569          interrupt();
570
571          root.render(<App step={2} shouldSuspend={true} />);
572          Scheduler.unstable_advanceTime(1000);
573          expect(Scheduler).toFlushAndYieldThrough(['Sibling']);
574          interrupt();
575
576          root.render(<App step={3} shouldSuspend={true} />);
577          Scheduler.unstable_advanceTime(1000);
578          expect(Scheduler).toFlushAndYieldThrough(['Sibling']);
579          interrupt();
580
581          root.render(<App step={4} shouldSuspend={false} />);
582        });
583
584        // Should suspend at each distinct level
585        expect(Scheduler).toHaveYielded([
586          'Sibling',
587          'Suspend! [Step 1]',
588          'Sibling',
589          'Suspend! [Step 2]',
590          'Sibling',
591          'Suspend! [Step 3]',
592          'Sibling',
593          'Step 4',
594        ]);
595      });
596
597      it('forces an expiration after an update times out', async () => {
598        ReactNoop.render(
599          <Fragment>
600            <Suspense fallback={<Text text="Loading..." />} />
601          </Fragment>,
602        );
603        expect(Scheduler).toFlushAndYield([]);
604
605        ReactNoop.render(
606          <Fragment>
607            <Suspense fallback={<Text text="Loading..." />}>
608              <AsyncText text="Async" ms={20000} />
609            </Suspense>
610            <Text text="Sync" />
611          </Fragment>,
612        );
613
614        expect(Scheduler).toFlushAndYield([
615          // The async child suspends
616          'Suspend! [Async]',
617          // Render the placeholder
618          'Loading...',
619          // Continue on the sibling
620          'Sync',
621        ]);
622        // The update hasn't expired yet, so we commit nothing.
623        expect(ReactNoop.getChildren()).toEqual([]);
624
625        // Advance both React's virtual time and Jest's timers by enough to expire
626        // the update, but not by enough to flush the suspending promise.
627        ReactNoop.expire(10000);
628        await advanceTimers(10000);
629        // No additional rendering work is required, since we already prepared
630        // the placeholder.
631        expect(Scheduler).toHaveYielded([]);
632        // Should have committed the placeholder.
633        expect(ReactNoop.getChildren()).toEqual([
634          span('Loading...'),
635          span('Sync'),
636        ]);
637
638        // Once the promise resolves, we render the suspended view
639        await advanceTimers(10000);
640        expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
641        expect(Scheduler).toFlushAndYield(['Async']);
642        expect(ReactNoop.getChildren()).toEqual([span('Async'), span('Sync')]);
643      });
644
645      it('switches to an inner fallback after suspending for a while', async () => {
646        // Advance the virtual time so that we're closer to the edge of a bucket.
647        ReactNoop.expire(200);
648
649        ReactNoop.render(
650          <Fragment>
651            <Text text="Sync" />
652            <Suspense fallback={<Text text="Loading outer..." />}>
653              <AsyncText text="Outer content" ms={300} />
654              <Suspense fallback={<Text text="Loading inner..." />}>
655                <AsyncText text="Inner content" ms={1000} />
656              </Suspense>
657            </Suspense>
658          </Fragment>,
659        );
660
661        expect(Scheduler).toFlushAndYield([
662          'Sync',
663          // The async content suspends
664          'Suspend! [Outer content]',
665          'Suspend! [Inner content]',
666          'Loading inner...',
667          'Loading outer...',
668        ]);
669        // The outer loading state finishes immediately.
670        expect(ReactNoop.getChildren()).toEqual([
671          span('Sync'),
672          span('Loading outer...'),
673        ]);
674
675        // Resolve the outer promise.
676        ReactNoop.expire(300);
677        await advanceTimers(300);
678        expect(Scheduler).toHaveYielded(['Promise resolved [Outer content]']);
679        expect(Scheduler).toFlushAndYield([
680          'Outer content',
681          'Suspend! [Inner content]',
682          'Loading inner...',
683        ]);
684        // Don't commit the inner placeholder yet.
685        expect(ReactNoop.getChildren()).toEqual([
686          span('Sync'),
687          span('Loading outer...'),
688        ]);
689
690        // Expire the inner timeout.
691        ReactNoop.expire(500);
692        await advanceTimers(500);
693        // Now that 750ms have elapsed since the outer placeholder timed out,
694        // we can timeout the inner placeholder.
695        expect(ReactNoop.getChildren()).toEqual([
696          span('Sync'),
697          span('Outer content'),
698          span('Loading inner...'),
699        ]);
700
701        // Finally, flush the inner promise. We should see the complete screen.
702        ReactNoop.expire(1000);
703        await advanceTimers(1000);
704        expect(Scheduler).toHaveYielded(['Promise resolved [Inner content]']);
705        expect(Scheduler).toFlushAndYield(['Inner content']);
706        expect(ReactNoop.getChildren()).toEqual([
707          span('Sync'),
708          span('Outer content'),
709          span('Inner content'),
710        ]);
711      });
712
713      it('renders an expiration boundary synchronously', async () => {
714        spyOnDev(console, 'error');
715        // Synchronously render a tree that suspends
716        ReactNoop.flushSync(() =>
717          ReactNoop.render(
718            <Fragment>
719              <Suspense fallback={<Text text="Loading..." />}>
720                <AsyncText text="Async" />
721              </Suspense>
722              <Text text="Sync" />
723            </Fragment>,
724          ),
725        );
726        expect(Scheduler).toHaveYielded([
727          // The async child suspends
728          'Suspend! [Async]',
729          // We immediately render the fallback UI
730          'Loading...',
731          // Continue on the sibling
732          'Sync',
733        ]);
734        // The tree commits synchronously
735        expect(ReactNoop.getChildren()).toEqual([
736          span('Loading...'),
737          span('Sync'),
738        ]);
739
740        // Once the promise resolves, we render the suspended view
741        await advanceTimers(0);
742        expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
743        expect(Scheduler).toFlushAndYield(['Async']);
744        expect(ReactNoop.getChildren()).toEqual([span('Async'), span('Sync')]);
745      });
746
747      it('suspending inside an expired expiration boundary will bubble to the next one', async () => {
748        ReactNoop.flushSync(() =>
749          ReactNoop.render(
750            <Fragment>
751              <Suspense fallback={<Text text="Loading (outer)..." />}>
752                <Suspense fallback={<AsyncText text="Loading (inner)..." />}>
753                  <AsyncText text="Async" />
754                </Suspense>
755                <Text text="Sync" />
756              </Suspense>
757            </Fragment>,
758          ),
759        );
760        expect(Scheduler).toHaveYielded([
761          'Suspend! [Async]',
762          'Suspend! [Loading (inner)...]',
763          'Sync',
764          'Loading (outer)...',
765        ]);
766        // The tree commits synchronously
767        expect(ReactNoop.getChildren()).toEqual([span('Loading (outer)...')]);
768      });
769
770      it('expires early by default', async () => {
771        ReactNoop.render(
772          <Fragment>
773            <Suspense fallback={<Text text="Loading..." />} />
774          </Fragment>,
775        );
776        expect(Scheduler).toFlushAndYield([]);
777
778        ReactNoop.render(
779          <Fragment>
780            <Suspense fallback={<Text text="Loading..." />}>
781              <AsyncText text="Async" ms={3000} />
782            </Suspense>
783            <Text text="Sync" />
784          </Fragment>,
785        );
786
787        expect(Scheduler).toFlushAndYield([
788          // The async child suspends
789          'Suspend! [Async]',
790          'Loading...',
791          // Continue on the sibling
792          'Sync',
793        ]);
794        // The update hasn't expired yet, so we commit nothing.
795        expect(ReactNoop.getChildren()).toEqual([]);
796
797        // Advance both React's virtual time and Jest's timers by enough to trigger
798        // the timeout, but not by enough to flush the promise or reach the true
799        // expiration time.
800        ReactNoop.expire(2000);
801        await advanceTimers(2000);
802        expect(Scheduler).toFlushWithoutYielding();
803        expect(ReactNoop.getChildren()).toEqual([
804          span('Loading...'),
805          span('Sync'),
806        ]);
807
808        // Once the promise resolves, we render the suspended view
809        await advanceTimers(1000);
810        expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
811        expect(Scheduler).toFlushAndYield(['Async']);
812        expect(ReactNoop.getChildren()).toEqual([span('Async'), span('Sync')]);
813      });
814
815      it('resolves successfully even if fallback render is pending', async () => {
816        ReactNoop.render(
817          <>
818            <Suspense fallback={<Text text="Loading..." />} />
819          </>,
820        );
821        expect(Scheduler).toFlushAndYield([]);
822        expect(ReactNoop.getChildren()).toEqual([]);
823        ReactNoop.render(
824          <>
825            <Suspense fallback={<Text text="Loading..." />}>
826              <AsyncText text="Async" ms={3000} />
827            </Suspense>
828          </>,
829        );
830        expect(ReactNoop.flushNextYield()).toEqual(['Suspend! [Async]']);
831        await advanceTimers(1500);
832        expect(Scheduler).toHaveYielded([]);
833        expect(ReactNoop.getChildren()).toEqual([]);
834        // Before we have a chance to flush, the promise resolves.
835        await advanceTimers(2000);
836        expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
837        expect(Scheduler).toFlushAndYield([
838          // We've now pinged the boundary but we don't know if we should restart yet,
839          // because we haven't completed the suspense boundary.
840          'Loading...',
841          // Once we've completed the boundary we restarted.
842          'Async',
843        ]);
844        expect(ReactNoop.getChildren()).toEqual([span('Async')]);
845      });
846
847      it('throws a helpful error when an update is suspends without a placeholder', () => {
848        ReactNoop.render(<AsyncText ms={1000} text="Async" />);
849        expect(Scheduler).toFlushAndThrow(
850          'AsyncText suspended while rendering, but no fallback UI was specified.',
851        );
852      });
853
854      it('a Suspense component correctly handles more than one suspended child', async () => {
855        ReactNoop.render(
856          <Suspense fallback={<Text text="Loading..." />}>
857            <AsyncText text="A" ms={100} />
858            <AsyncText text="B" ms={100} />
859          </Suspense>,
860        );
861        Scheduler.unstable_advanceTime(10000);
862        expect(Scheduler).toFlushExpired([
863          'Suspend! [A]',
864          'Suspend! [B]',
865          'Loading...',
866        ]);
867        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
868
869        await advanceTimers(100);
870
871        expect(Scheduler).toHaveYielded([
872          'Promise resolved [A]',
873          'Promise resolved [B]',
874        ]);
875        expect(Scheduler).toFlushAndYield(['A', 'B']);
876        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
877      });
878
879      it('can resume rendering earlier than a timeout', async () => {
880        ReactNoop.render(<Suspense fallback={<Text text="Loading..." />} />);
881        expect(Scheduler).toFlushAndYield([]);
882
883        ReactNoop.render(
884          <Suspense fallback={<Text text="Loading..." />}>
885            <AsyncText text="Async" ms={100} />
886          </Suspense>,
887        );
888        expect(Scheduler).toFlushAndYield(['Suspend! [Async]', 'Loading...']);
889        expect(ReactNoop.getChildren()).toEqual([]);
890
891        // Advance time by an amount slightly smaller than what's necessary to
892        // resolve the promise
893        await advanceTimers(99);
894
895        // Nothing has rendered yet
896        expect(Scheduler).toFlushWithoutYielding();
897        expect(ReactNoop.getChildren()).toEqual([]);
898
899        // Resolve the promise
900        await advanceTimers(1);
901        // We can now resume rendering
902        expect(Scheduler).toHaveYielded(['Promise resolved [Async]']);
903        expect(Scheduler).toFlushAndYield(['Async']);
904        expect(ReactNoop.getChildren()).toEqual([span('Async')]);
905      });
906
907      it('starts working on an update even if its priority falls between two suspended levels', async () => {
908        function App(props) {
909          return (
910            <Suspense fallback={<Text text="Loading..." />}>
911              {props.text === 'C' || props.text === 'S' ? (
912                <Text text={props.text} />
913              ) : (
914                <AsyncText text={props.text} ms={10000} />
915              )}
916            </Suspense>
917          );
918        }
919
920        // First mount without suspending. This ensures we already have content
921        // showing so that subsequent updates will suspend.
922        ReactNoop.render(<App text="S" />);
923        expect(Scheduler).toFlushAndYield(['S']);
924
925        // Schedule an update, and suspend for up to 5 seconds.
926        React.unstable_withSuspenseConfig(
927          () => ReactNoop.render(<App text="A" />),
928          {
929            timeoutMs: 5000,
930          },
931        );
932        // The update should suspend.
933        expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
934        expect(ReactNoop.getChildren()).toEqual([span('S')]);
935
936        // Advance time until right before it expires.
937        await advanceTimers(4999);
938        ReactNoop.expire(4999);
939        expect(Scheduler).toFlushWithoutYielding();
940        expect(ReactNoop.getChildren()).toEqual([span('S')]);
941
942        // Schedule another low priority update.
943        React.unstable_withSuspenseConfig(
944          () => ReactNoop.render(<App text="B" />),
945          {
946            timeoutMs: 10000,
947          },
948        );
949        // This update should also suspend.
950        expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
951        expect(ReactNoop.getChildren()).toEqual([span('S')]);
952
953        // Schedule a regular update. Its expiration time will fall between
954        // the expiration times of the previous two updates.
955        ReactNoop.render(<App text="C" />);
956        expect(Scheduler).toFlushAndYield(['C']);
957        expect(ReactNoop.getChildren()).toEqual([span('C')]);
958
959        await advanceTimers(10000);
960        // Flush the remaining work.
961        expect(Scheduler).toHaveYielded([
962          'Promise resolved [A]',
963          'Promise resolved [B]',
964        ]);
965        // Nothing else to render.
966        expect(Scheduler).toFlushWithoutYielding();
967        expect(ReactNoop.getChildren()).toEqual([span('C')]);
968      });
969
970      it('flushes all expired updates in a single batch', async () => {
971        class Foo extends React.Component {
972          componentDidUpdate() {
973            Scheduler.unstable_yieldValue('Commit: ' + this.props.text);
974          }
975          componentDidMount() {
976            Scheduler.unstable_yieldValue('Commit: ' + this.props.text);
977          }
978          render() {
979            return (
980              <Suspense fallback={<Text text="Loading..." />}>
981                <AsyncText ms={20000} text={this.props.text} />
982              </Suspense>
983            );
984          }
985        }
986
987        ReactNoop.render(<Foo text="" />);
988        ReactNoop.expire(1000);
989        jest.advanceTimersByTime(1000);
990        ReactNoop.render(<Foo text="go" />);
991        ReactNoop.expire(1000);
992        jest.advanceTimersByTime(1000);
993        ReactNoop.render(<Foo text="good" />);
994        ReactNoop.expire(1000);
995        jest.advanceTimersByTime(1000);
996        ReactNoop.render(<Foo text="goodbye" />);
997
998        Scheduler.unstable_advanceTime(10000);
999        jest.advanceTimersByTime(10000);
1000
1001        expect(Scheduler).toFlushExpired([
1002          'Suspend! [goodbye]',
1003          'Loading...',
1004          'Commit: goodbye',
1005        ]);
1006        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1007
1008        Scheduler.unstable_advanceTime(20000);
1009        await advanceTimers(20000);
1010        expect(Scheduler).toHaveYielded(['Promise resolved [goodbye]']);
1011        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1012
1013        expect(Scheduler).toFlushAndYield(['goodbye']);
1014        expect(ReactNoop.getChildren()).toEqual([span('goodbye')]);
1015      });
1016
1017      it('a suspended update that expires', async () => {
1018        // Regression test. This test used to fall into an infinite loop.
1019        function ExpensiveText({text}) {
1020          // This causes the update to expire.
1021          Scheduler.unstable_advanceTime(10000);
1022          // Then something suspends.
1023          return <AsyncText text={text} ms={200000} />;
1024        }
1025
1026        function App() {
1027          return (
1028            <Suspense fallback="Loading...">
1029              <ExpensiveText text="A" />
1030              <ExpensiveText text="B" />
1031              <ExpensiveText text="C" />
1032            </Suspense>
1033          );
1034        }
1035
1036        ReactNoop.render(<App />);
1037        expect(Scheduler).toFlushAndYield([
1038          'Suspend! [A]',
1039          'Suspend! [B]',
1040          'Suspend! [C]',
1041        ]);
1042        expect(ReactNoop).toMatchRenderedOutput('Loading...');
1043
1044        await advanceTimers(200000);
1045        expect(Scheduler).toHaveYielded([
1046          'Promise resolved [A]',
1047          'Promise resolved [B]',
1048          'Promise resolved [C]',
1049        ]);
1050
1051        expect(Scheduler).toFlushAndYield(['A', 'B', 'C']);
1052        expect(ReactNoop).toMatchRenderedOutput(
1053          <>
1054            <span prop="A" />
1055            <span prop="B" />
1056            <span prop="C" />
1057          </>,
1058        );
1059      });
1060
1061      describe('legacy mode mode', () => {
1062        it('times out immediately', async () => {
1063          function App() {
1064            return (
1065              <Suspense fallback={<Text text="Loading..." />}>
1066                <AsyncText ms={100} text="Result" />
1067              </Suspense>
1068            );
1069          }
1070
1071          // Times out immediately, ignoring the specified threshold.
1072          ReactNoop.renderLegacySyncRoot(<App />);
1073          expect(Scheduler).toHaveYielded(['Suspend! [Result]', 'Loading...']);
1074          expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1075
1076          ReactNoop.expire(100);
1077          await advanceTimers(100);
1078
1079          expect(Scheduler).toHaveYielded(['Promise resolved [Result]']);
1080          expect(Scheduler).toFlushExpired(['Result']);
1081          expect(ReactNoop.getChildren()).toEqual([span('Result')]);
1082        });
1083
1084        it('times out immediately when Suspense is in legacy mode', async () => {
1085          class UpdatingText extends React.Component {
1086            state = {step: 1};
1087            render() {
1088              return <AsyncText ms={100} text={`Step: ${this.state.step}`} />;
1089            }
1090          }
1091
1092          function Spinner() {
1093            return (
1094              <Fragment>
1095                <Text text="Loading (1)" />
1096                <Text text="Loading (2)" />
1097                <Text text="Loading (3)" />
1098              </Fragment>
1099            );
1100          }
1101
1102          const text = React.createRef(null);
1103          function App() {
1104            return (
1105              <Suspense fallback={<Spinner />}>
1106                <UpdatingText ref={text} />
1107                <Text text="Sibling" />
1108              </Suspense>
1109            );
1110          }
1111
1112          // Initial mount.
1113          ReactNoop.renderLegacySyncRoot(<App />);
1114          await advanceTimers(100);
1115          expect(Scheduler).toHaveYielded([
1116            'Suspend! [Step: 1]',
1117            'Sibling',
1118            'Loading (1)',
1119            'Loading (2)',
1120            'Loading (3)',
1121            'Promise resolved [Step: 1]',
1122          ]);
1123          expect(Scheduler).toFlushExpired(['Step: 1']);
1124          expect(ReactNoop).toMatchRenderedOutput(
1125            <>
1126              <span prop="Step: 1" />
1127              <span prop="Sibling" />
1128            </>,
1129          );
1130
1131          // Update.
1132          text.current.setState({step: 2}, () =>
1133            Scheduler.unstable_yieldValue('Update did commit'),
1134          );
1135
1136          expect(ReactNoop.flushNextYield()).toEqual([
1137            'Suspend! [Step: 2]',
1138            'Loading (1)',
1139            'Loading (2)',
1140            'Loading (3)',
1141            'Update did commit',
1142          ]);
1143          expect(ReactNoop).toMatchRenderedOutput(
1144            <>
1145              <span hidden={true} prop="Step: 1" />
1146              <span hidden={true} prop="Sibling" />
1147              <span prop="Loading (1)" />
1148              <span prop="Loading (2)" />
1149              <span prop="Loading (3)" />
1150            </>,
1151          );
1152
1153          await advanceTimers(100);
1154          expect(Scheduler).toHaveYielded(['Promise resolved [Step: 2]']);
1155          expect(Scheduler).toFlushExpired(['Step: 2']);
1156          expect(ReactNoop).toMatchRenderedOutput(
1157            <>
1158              <span prop="Step: 2" />
1159              <span prop="Sibling" />
1160            </>,
1161          );
1162        });
1163
1164        it('does not re-render siblings in loose mode', async () => {
1165          class TextWithLifecycle extends React.Component {
1166            componentDidMount() {
1167              Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
1168            }
1169            componentDidUpdate() {
1170              Scheduler.unstable_yieldValue(`Update [${this.props.text}]`);
1171            }
1172            render() {
1173              return <Text {...this.props} />;
1174            }
1175          }
1176
1177          class AsyncTextWithLifecycle extends React.Component {
1178            componentDidMount() {
1179              Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
1180            }
1181            componentDidUpdate() {
1182              Scheduler.unstable_yieldValue(`Update [${this.props.text}]`);
1183            }
1184            render() {
1185              return <AsyncText {...this.props} />;
1186            }
1187          }
1188
1189          function App() {
1190            return (
1191              <Suspense fallback={<TextWithLifecycle text="Loading..." />}>
1192                <TextWithLifecycle text="A" />
1193                <AsyncTextWithLifecycle ms={100} text="B" />
1194                <TextWithLifecycle text="C" />
1195              </Suspense>
1196            );
1197          }
1198
1199          ReactNoop.renderLegacySyncRoot(<App />, () =>
1200            Scheduler.unstable_yieldValue('Commit root'),
1201          );
1202          expect(Scheduler).toHaveYielded([
1203            'A',
1204            'Suspend! [B]',
1205            'C',
1206
1207            'Loading...',
1208            'Mount [A]',
1209            'Mount [B]',
1210            'Mount [C]',
1211            // This should be a mount, not an update.
1212            'Mount [Loading...]',
1213            'Commit root',
1214          ]);
1215          expect(ReactNoop).toMatchRenderedOutput(
1216            <>
1217              <span hidden={true} prop="A" />
1218              <span hidden={true} prop="C" />
1219
1220              <span prop="Loading..." />
1221            </>,
1222          );
1223
1224          ReactNoop.expire(1000);
1225          await advanceTimers(1000);
1226
1227          expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1228          expect(Scheduler).toFlushExpired(['B']);
1229          expect(ReactNoop).toMatchRenderedOutput(
1230            <>
1231              <span prop="A" />
1232              <span prop="B" />
1233              <span prop="C" />
1234            </>,
1235          );
1236        });
1237
1238        it('suspends inside constructor', async () => {
1239          class AsyncTextInConstructor extends React.Component {
1240            constructor(props) {
1241              super(props);
1242              const text = props.text;
1243              Scheduler.unstable_yieldValue('constructor');
1244              try {
1245                TextResource.read([props.text, props.ms]);
1246                this.state = {text};
1247              } catch (promise) {
1248                if (typeof promise.then === 'function') {
1249                  Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
1250                } else {
1251                  Scheduler.unstable_yieldValue(`Error! [${text}]`);
1252                }
1253                throw promise;
1254              }
1255            }
1256            componentDidMount() {
1257              Scheduler.unstable_yieldValue('componentDidMount');
1258            }
1259            render() {
1260              Scheduler.unstable_yieldValue(this.state.text);
1261              return <span prop={this.state.text} />;
1262            }
1263          }
1264
1265          ReactNoop.renderLegacySyncRoot(
1266            <Suspense fallback={<Text text="Loading..." />}>
1267              <AsyncTextInConstructor ms={100} text="Hi" />
1268            </Suspense>,
1269          );
1270
1271          expect(Scheduler).toHaveYielded([
1272            'constructor',
1273            'Suspend! [Hi]',
1274            'Loading...',
1275          ]);
1276          expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1277
1278          await advanceTimers(1000);
1279
1280          expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1281          expect(Scheduler).toFlushExpired([
1282            'constructor',
1283            'Hi',
1284            'componentDidMount',
1285          ]);
1286          expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1287        });
1288
1289        it('does not infinite loop if fallback contains lifecycle method', async () => {
1290          class Fallback extends React.Component {
1291            state = {
1292              name: 'foo',
1293            };
1294            componentDidMount() {
1295              this.setState({
1296                name: 'bar',
1297              });
1298            }
1299            render() {
1300              return <Text text="Loading..." />;
1301            }
1302          }
1303
1304          class Demo extends React.Component {
1305            render() {
1306              return (
1307                <Suspense fallback={<Fallback />}>
1308                  <AsyncText text="Hi" ms={100} />
1309                </Suspense>
1310              );
1311            }
1312          }
1313
1314          ReactNoop.renderLegacySyncRoot(<Demo />);
1315
1316          expect(Scheduler).toHaveYielded([
1317            'Suspend! [Hi]',
1318            'Loading...',
1319            // Re-render due to lifecycle update
1320            'Loading...',
1321          ]);
1322          expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1323          await advanceTimers(100);
1324          expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1325          expect(Scheduler).toFlushExpired(['Hi']);
1326          expect(ReactNoop.getChildren()).toEqual([span('Hi')]);
1327        });
1328
1329        if (global.__PERSISTENT__) {
1330          it('hides/unhides suspended children before layout effects fire (persistent)', async () => {
1331            const {useRef, useLayoutEffect} = React;
1332
1333            function Parent() {
1334              const child = useRef(null);
1335
1336              useLayoutEffect(() => {
1337                Scheduler.unstable_yieldValue(
1338                  ReactNoop.getPendingChildrenAsJSX(),
1339                );
1340              });
1341
1342              return (
1343                <span ref={child} hidden={false}>
1344                  <AsyncText ms={1000} text="Hi" />
1345                </span>
1346              );
1347            }
1348
1349            function App(props) {
1350              return (
1351                <Suspense fallback={<Text text="Loading..." />}>
1352                  <Parent />
1353                </Suspense>
1354              );
1355            }
1356
1357            ReactNoop.renderLegacySyncRoot(<App middleText="B" />);
1358
1359            expect(Scheduler).toHaveYielded([
1360              'Suspend! [Hi]',
1361              'Loading...',
1362              // The child should have already been hidden
1363              <>
1364                <span hidden={true} />
1365                <span prop="Loading..." />
1366              </>,
1367            ]);
1368
1369            await advanceTimers(1000);
1370
1371            expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1372            expect(Scheduler).toFlushExpired(['Hi']);
1373          });
1374        } else {
1375          it('hides/unhides suspended children before layout effects fire (mutation)', async () => {
1376            const {useRef, useLayoutEffect} = React;
1377
1378            function Parent() {
1379              const child = useRef(null);
1380
1381              useLayoutEffect(() => {
1382                Scheduler.unstable_yieldValue(
1383                  'Child is hidden: ' + child.current.hidden,
1384                );
1385              });
1386
1387              return (
1388                <span ref={child} hidden={false}>
1389                  <AsyncText ms={1000} text="Hi" />
1390                </span>
1391              );
1392            }
1393
1394            function App(props) {
1395              return (
1396                <Suspense fallback={<Text text="Loading..." />}>
1397                  <Parent />
1398                </Suspense>
1399              );
1400            }
1401
1402            ReactNoop.renderLegacySyncRoot(<App middleText="B" />);
1403
1404            expect(Scheduler).toHaveYielded([
1405              'Suspend! [Hi]',
1406              'Loading...',
1407              // The child should have already been hidden
1408              'Child is hidden: true',
1409            ]);
1410
1411            await advanceTimers(1000);
1412
1413            expect(Scheduler).toHaveYielded(['Promise resolved [Hi]']);
1414            expect(Scheduler).toFlushExpired(['Hi']);
1415          });
1416        }
1417
1418        it('handles errors in the return path of a component that suspends', async () => {
1419          // Covers an edge case where an error is thrown inside the complete phase
1420          // of a component that is in the return path of a component that suspends.
1421          // The second error should also be handled (i.e. able to be captured by
1422          // an error boundary.
1423          class ErrorBoundary extends React.Component {
1424            state = {error: null};
1425            static getDerivedStateFromError(error, errorInfo) {
1426              return {error};
1427            }
1428            render() {
1429              if (this.state.error) {
1430                return `Caught an error: ${this.state.error.message}`;
1431              }
1432              return this.props.children;
1433            }
1434          }
1435
1436          ReactNoop.renderLegacySyncRoot(
1437            <ErrorBoundary>
1438              <Suspense fallback="Loading...">
1439                <errorInCompletePhase>
1440                  <AsyncText ms={1000} text="Async" />
1441                </errorInCompletePhase>
1442              </Suspense>
1443            </ErrorBoundary>,
1444          );
1445
1446          expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
1447          expect(ReactNoop).toMatchRenderedOutput(
1448            'Caught an error: Error in host config.',
1449          );
1450        });
1451
1452        it('does not drop mounted effects', async () => {
1453          let never = {then() {}};
1454
1455          let setShouldSuspend;
1456          function App() {
1457            const [shouldSuspend, _setShouldSuspend] = React.useState(0);
1458            setShouldSuspend = _setShouldSuspend;
1459            return (
1460              <Suspense fallback="Loading...">
1461                <Child shouldSuspend={shouldSuspend} />
1462              </Suspense>
1463            );
1464          }
1465
1466          function Child({shouldSuspend}) {
1467            if (shouldSuspend) {
1468              throw never;
1469            }
1470
1471            React.useEffect(() => {
1472              Scheduler.unstable_yieldValue('Mount');
1473              return () => {
1474                Scheduler.unstable_yieldValue('Unmount');
1475              };
1476            }, []);
1477
1478            return 'Child';
1479          }
1480
1481          const root = ReactNoop.createLegacyRoot(null);
1482          await ReactNoop.act(async () => {
1483            root.render(<App />);
1484          });
1485          expect(Scheduler).toHaveYielded(['Mount']);
1486          expect(root).toMatchRenderedOutput('Child');
1487
1488          // Suspend the child. This puts it into an inconsistent state.
1489          await ReactNoop.act(async () => {
1490            setShouldSuspend(true);
1491          });
1492          expect(root).toMatchRenderedOutput('Loading...');
1493
1494          // Unmount everying
1495          await ReactNoop.act(async () => {
1496            root.render(null);
1497          });
1498          expect(Scheduler).toHaveYielded(['Unmount']);
1499        });
1500      });
1501
1502      it('does not call lifecycles of a suspended component', async () => {
1503        class TextWithLifecycle extends React.Component {
1504          componentDidMount() {
1505            Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
1506          }
1507          componentDidUpdate() {
1508            Scheduler.unstable_yieldValue(`Update [${this.props.text}]`);
1509          }
1510          componentWillUnmount() {
1511            Scheduler.unstable_yieldValue(`Unmount [${this.props.text}]`);
1512          }
1513          render() {
1514            return <Text {...this.props} />;
1515          }
1516        }
1517
1518        class AsyncTextWithLifecycle extends React.Component {
1519          componentDidMount() {
1520            Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
1521          }
1522          componentDidUpdate() {
1523            Scheduler.unstable_yieldValue(`Update [${this.props.text}]`);
1524          }
1525          componentWillUnmount() {
1526            Scheduler.unstable_yieldValue(`Unmount [${this.props.text}]`);
1527          }
1528          render() {
1529            const text = this.props.text;
1530            const ms = this.props.ms;
1531            try {
1532              TextResource.read([text, ms]);
1533              Scheduler.unstable_yieldValue(text);
1534              return <span prop={text} />;
1535            } catch (promise) {
1536              if (typeof promise.then === 'function') {
1537                Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
1538              } else {
1539                Scheduler.unstable_yieldValue(`Error! [${text}]`);
1540              }
1541              throw promise;
1542            }
1543          }
1544        }
1545
1546        function App() {
1547          return (
1548            <Suspense fallback={<TextWithLifecycle text="Loading..." />}>
1549              <TextWithLifecycle text="A" />
1550              <AsyncTextWithLifecycle ms={100} text="B" />
1551              <TextWithLifecycle text="C" />
1552            </Suspense>
1553          );
1554        }
1555
1556        ReactNoop.renderLegacySyncRoot(<App />, () =>
1557          Scheduler.unstable_yieldValue('Commit root'),
1558        );
1559        expect(Scheduler).toHaveYielded([
1560          'A',
1561          'Suspend! [B]',
1562          'C',
1563          'Loading...',
1564
1565          'Mount [A]',
1566          // B's lifecycle should not fire because it suspended
1567          // 'Mount [B]',
1568          'Mount [C]',
1569          'Mount [Loading...]',
1570          'Commit root',
1571        ]);
1572        expect(ReactNoop).toMatchRenderedOutput(
1573          <>
1574            <span hidden={true} prop="A" />
1575            <span hidden={true} prop="C" />
1576            <span prop="Loading..." />
1577          </>,
1578        );
1579      });
1580
1581      it('does not call lifecycles of a suspended component (hooks)', async () => {
1582        function TextWithLifecycle(props) {
1583          React.useLayoutEffect(() => {
1584            Scheduler.unstable_yieldValue(`Layout Effect [${props.text}]`);
1585            return () => {
1586              Scheduler.unstable_yieldValue(
1587                `Destroy Layout Effect [${props.text}]`,
1588              );
1589            };
1590          }, [props.text]);
1591          React.useEffect(() => {
1592            Scheduler.unstable_yieldValue(`Effect [${props.text}]`);
1593            return () => {
1594              Scheduler.unstable_yieldValue(`Destroy Effect [${props.text}]`);
1595            };
1596          }, [props.text]);
1597          return <Text {...props} />;
1598        }
1599
1600        function AsyncTextWithLifecycle(props) {
1601          React.useLayoutEffect(() => {
1602            Scheduler.unstable_yieldValue(`Layout Effect [${props.text}]`);
1603            return () => {
1604              Scheduler.unstable_yieldValue(
1605                `Destroy Layout Effect [${props.text}]`,
1606              );
1607            };
1608          }, [props.text]);
1609          React.useEffect(() => {
1610            Scheduler.unstable_yieldValue(`Effect [${props.text}]`);
1611            return () => {
1612              Scheduler.unstable_yieldValue(`Destroy Effect [${props.text}]`);
1613            };
1614          }, [props.text]);
1615          const text = props.text;
1616          const ms = props.ms;
1617          try {
1618            TextResource.read([text, ms]);
1619            Scheduler.unstable_yieldValue(text);
1620            return <span prop={text} />;
1621          } catch (promise) {
1622            if (typeof promise.then === 'function') {
1623              Scheduler.unstable_yieldValue(`Suspend! [${text}]`);
1624            } else {
1625              Scheduler.unstable_yieldValue(`Error! [${text}]`);
1626            }
1627            throw promise;
1628          }
1629        }
1630
1631        function App({text}) {
1632          return (
1633            <Suspense fallback={<TextWithLifecycle text="Loading..." />}>
1634              <TextWithLifecycle text="A" />
1635              <AsyncTextWithLifecycle ms={100} text={text} />
1636              <TextWithLifecycle text="C" />
1637            </Suspense>
1638          );
1639        }
1640
1641        ReactNoop.renderLegacySyncRoot(<App text="B" />, () =>
1642          Scheduler.unstable_yieldValue('Commit root'),
1643        );
1644        expect(Scheduler).toHaveYielded([
1645          'A',
1646          'Suspend! [B]',
1647          'C',
1648          'Loading...',
1649
1650          'Layout Effect [A]',
1651          // B's effect should not fire because it suspended
1652          // 'Layout Effect [B]',
1653          'Layout Effect [C]',
1654          'Layout Effect [Loading...]',
1655          'Commit root',
1656        ]);
1657
1658        // Flush passive effects.
1659        expect(Scheduler).toFlushAndYield([
1660          'Effect [A]',
1661          // B's effect should not fire because it suspended
1662          // 'Effect [B]',
1663          'Effect [C]',
1664          'Effect [Loading...]',
1665        ]);
1666
1667        expect(ReactNoop).toMatchRenderedOutput(
1668          <>
1669            <span hidden={true} prop="A" />
1670            <span hidden={true} prop="C" />
1671            <span prop="Loading..." />
1672          </>,
1673        );
1674
1675        Scheduler.unstable_advanceTime(500);
1676        await advanceTimers(500);
1677
1678        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1679
1680        if (
1681          ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount &&
1682          ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates
1683        ) {
1684          expect(Scheduler).toFlushAndYield([
1685            'B',
1686            'Destroy Layout Effect [Loading...]',
1687            'Layout Effect [B]',
1688            'Destroy Effect [Loading...]',
1689            'Effect [B]',
1690          ]);
1691        } else {
1692          expect(Scheduler).toFlushAndYield([
1693            'B',
1694            'Destroy Layout Effect [Loading...]',
1695            'Destroy Effect [Loading...]',
1696            'Layout Effect [B]',
1697            'Effect [B]',
1698          ]);
1699        }
1700
1701        // Update
1702        ReactNoop.renderLegacySyncRoot(<App text="B2" />, () =>
1703          Scheduler.unstable_yieldValue('Commit root'),
1704        );
1705
1706        expect(Scheduler).toHaveYielded([
1707          'A',
1708          'Suspend! [B2]',
1709          'C',
1710          'Loading...',
1711
1712          // B2's effect should not fire because it suspended
1713          // 'Layout Effect [B2]',
1714          'Layout Effect [Loading...]',
1715          'Commit root',
1716        ]);
1717
1718        // Flush passive effects.
1719        expect(Scheduler).toFlushAndYield([
1720          // B2's effect should not fire because it suspended
1721          // 'Effect [B2]',
1722          'Effect [Loading...]',
1723        ]);
1724
1725        Scheduler.unstable_advanceTime(500);
1726        await advanceTimers(500);
1727
1728        expect(Scheduler).toHaveYielded(['Promise resolved [B2]']);
1729
1730        if (
1731          ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount &&
1732          ReactFeatureFlags.runAllPassiveEffectDestroysBeforeCreates
1733        ) {
1734          expect(Scheduler).toFlushAndYield([
1735            'B2',
1736            'Destroy Layout Effect [Loading...]',
1737            'Destroy Layout Effect [B]',
1738            'Layout Effect [B2]',
1739            'Destroy Effect [Loading...]',
1740            'Destroy Effect [B]',
1741            'Effect [B2]',
1742          ]);
1743        } else {
1744          expect(Scheduler).toFlushAndYield([
1745            'B2',
1746            'Destroy Layout Effect [Loading...]',
1747            'Destroy Effect [Loading...]',
1748            'Destroy Layout Effect [B]',
1749            'Layout Effect [B2]',
1750            'Destroy Effect [B]',
1751            'Effect [B2]',
1752          ]);
1753        }
1754      });
1755
1756      it('suspends for longer if something took a long (CPU bound) time to render', async () => {
1757        function Foo({renderContent}) {
1758          Scheduler.unstable_yieldValue('Foo');
1759          return (
1760            <Suspense fallback={<Text text="Loading..." />}>
1761              {renderContent ? <AsyncText text="A" ms={5000} /> : null}
1762            </Suspense>
1763          );
1764        }
1765
1766        ReactNoop.render(<Foo />);
1767        expect(Scheduler).toFlushAndYield(['Foo']);
1768
1769        ReactNoop.render(<Foo renderContent={true} />);
1770        Scheduler.unstable_advanceTime(100);
1771        await advanceTimers(100);
1772        // Start rendering
1773        expect(Scheduler).toFlushAndYieldThrough(['Foo']);
1774        // For some reason it took a long time to render Foo.
1775        Scheduler.unstable_advanceTime(1250);
1776        await advanceTimers(1250);
1777        expect(Scheduler).toFlushAndYield([
1778          // A suspends
1779          'Suspend! [A]',
1780          'Loading...',
1781        ]);
1782        // We're now suspended and we haven't shown anything yet.
1783        expect(ReactNoop.getChildren()).toEqual([]);
1784
1785        // Flush some of the time
1786        Scheduler.unstable_advanceTime(450);
1787        await advanceTimers(450);
1788        // Because we've already been waiting for so long we can
1789        // wait a bit longer. Still nothing...
1790        expect(Scheduler).toFlushWithoutYielding();
1791        expect(ReactNoop.getChildren()).toEqual([]);
1792
1793        // Eventually we'll show the fallback.
1794        Scheduler.unstable_advanceTime(500);
1795        await advanceTimers(500);
1796        // No need to rerender.
1797        expect(Scheduler).toFlushWithoutYielding();
1798        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1799
1800        // Flush the promise completely
1801        Scheduler.unstable_advanceTime(4500);
1802        await advanceTimers(4500);
1803        // Renders successfully
1804        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1805        expect(Scheduler).toFlushAndYield(['A']);
1806        expect(ReactNoop.getChildren()).toEqual([span('A')]);
1807      });
1808
1809      it('does not suspends if a fallback has been shown for a long time', async () => {
1810        function Foo() {
1811          Scheduler.unstable_yieldValue('Foo');
1812          return (
1813            <Suspense fallback={<Text text="Loading..." />}>
1814              <AsyncText text="A" ms={5000} />
1815              <Suspense fallback={<Text text="Loading more..." />}>
1816                <AsyncText text="B" ms={10000} />
1817              </Suspense>
1818            </Suspense>
1819          );
1820        }
1821
1822        ReactNoop.render(<Foo />);
1823        // Start rendering
1824        expect(Scheduler).toFlushAndYield([
1825          'Foo',
1826          // A suspends
1827          'Suspend! [A]',
1828          // B suspends
1829          'Suspend! [B]',
1830          'Loading more...',
1831          'Loading...',
1832        ]);
1833        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1834
1835        // Wait a long time.
1836        Scheduler.unstable_advanceTime(5000);
1837        await advanceTimers(5000);
1838        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1839
1840        // Retry with the new content.
1841        expect(Scheduler).toFlushAndYield([
1842          'A',
1843          // B still suspends
1844          'Suspend! [B]',
1845          'Loading more...',
1846        ]);
1847        // Because we've already been waiting for so long we've exceeded
1848        // our threshold and we show the next level immediately.
1849        expect(ReactNoop.getChildren()).toEqual([
1850          span('A'),
1851          span('Loading more...'),
1852        ]);
1853
1854        // Flush the last promise completely
1855        Scheduler.unstable_advanceTime(5000);
1856        await advanceTimers(5000);
1857        // Renders successfully
1858        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1859        expect(Scheduler).toFlushAndYield(['B']);
1860        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
1861      });
1862
1863      it('does suspend if a fallback has been shown for a short time', async () => {
1864        function Foo() {
1865          Scheduler.unstable_yieldValue('Foo');
1866          return (
1867            <Suspense fallback={<Text text="Loading..." />}>
1868              <AsyncText text="A" ms={200} />
1869              <Suspense fallback={<Text text="Loading more..." />}>
1870                <AsyncText text="B" ms={450} />
1871              </Suspense>
1872            </Suspense>
1873          );
1874        }
1875
1876        ReactNoop.render(<Foo />);
1877        // Start rendering
1878        expect(Scheduler).toFlushAndYield([
1879          'Foo',
1880          // A suspends
1881          'Suspend! [A]',
1882          // B suspends
1883          'Suspend! [B]',
1884          'Loading more...',
1885          'Loading...',
1886        ]);
1887        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1888
1889        // Wait a short time.
1890        Scheduler.unstable_advanceTime(250);
1891        await advanceTimers(250);
1892        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
1893
1894        // Retry with the new content.
1895        expect(Scheduler).toFlushAndYield([
1896          'A',
1897          // B still suspends
1898          'Suspend! [B]',
1899          'Loading more...',
1900        ]);
1901        // Because we've already been waiting for so long we can
1902        // wait a bit longer. Still nothing...
1903        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1904
1905        Scheduler.unstable_advanceTime(200);
1906        await advanceTimers(200);
1907
1908        // Before we commit another Promise resolves.
1909        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
1910        // We're still showing the first loading state.
1911        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1912        // Restart and render the complete content.
1913        expect(Scheduler).toFlushAndYield(['A', 'B']);
1914        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
1915      });
1916
1917      it('does not suspend for very long after a higher priority update', async () => {
1918        function Foo({renderContent}) {
1919          Scheduler.unstable_yieldValue('Foo');
1920          return (
1921            <Suspense fallback={<Text text="Loading..." />}>
1922              {renderContent ? <AsyncText text="A" ms={5000} /> : null}
1923            </Suspense>
1924          );
1925        }
1926
1927        ReactNoop.render(<Foo />);
1928        expect(Scheduler).toFlushAndYield(['Foo']);
1929
1930        ReactNoop.discreteUpdates(() =>
1931          ReactNoop.render(<Foo renderContent={true} />),
1932        );
1933        expect(Scheduler).toFlushAndYieldThrough(['Foo']);
1934
1935        // Advance some time.
1936        Scheduler.unstable_advanceTime(100);
1937        await advanceTimers(100);
1938
1939        expect(Scheduler).toFlushAndYield([
1940          // A suspends
1941          'Suspend! [A]',
1942          'Loading...',
1943        ]);
1944
1945        // We're now suspended and we haven't shown anything yet.
1946        expect(ReactNoop.getChildren()).toEqual([]);
1947
1948        // Flush some of the time
1949        Scheduler.unstable_advanceTime(500);
1950        jest.advanceTimersByTime(500);
1951
1952        // We should have already shown the fallback.
1953        // When we wrote this test, we inferred the start time of high priority
1954        // updates as way earlier in the past. This test ensures that we don't
1955        // use this assumption to add a very long JND.
1956        expect(Scheduler).toFlushWithoutYielding();
1957        expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
1958      });
1959
1960      // TODO: flip to "warns" when this is implemented again.
1961      it('does not warn when a low priority update suspends inside a high priority update for functional components', async () => {
1962        let _setShow;
1963        function App() {
1964          let [show, setShow] = React.useState(false);
1965          _setShow = setShow;
1966          return (
1967            <Suspense fallback="Loading...">
1968              {show && <AsyncText text="A" />}
1969            </Suspense>
1970          );
1971        }
1972
1973        await ReactNoop.act(async () => {
1974          ReactNoop.render(<App />);
1975        });
1976
1977        // TODO: assert toErrorDev() when the warning is implemented again.
1978        ReactNoop.act(() => {
1979          Scheduler.unstable_runWithPriority(
1980            Scheduler.unstable_UserBlockingPriority,
1981            () => _setShow(true),
1982          );
1983        });
1984      });
1985
1986      // TODO: flip to "warns" when this is implemented again.
1987      it('does not warn when a low priority update suspends inside a high priority update for class components', async () => {
1988        let show;
1989        class App extends React.Component {
1990          state = {show: false};
1991
1992          render() {
1993            show = () => this.setState({show: true});
1994            return (
1995              <Suspense fallback="Loading...">
1996                {this.state.show && <AsyncText text="A" />}
1997              </Suspense>
1998            );
1999          }
2000        }
2001
2002        await ReactNoop.act(async () => {
2003          ReactNoop.render(<App />);
2004        });
2005
2006        // TODO: assert toErrorDev() when the warning is implemented again.
2007        ReactNoop.act(() => {
2008          Scheduler.unstable_runWithPriority(
2009            Scheduler.unstable_UserBlockingPriority,
2010            () => show(),
2011          );
2012        });
2013      });
2014
2015      it('does not warn about wrong Suspense priority if no new fallbacks are shown', async () => {
2016        let showB;
2017        class App extends React.Component {
2018          state = {showB: false};
2019
2020          render() {
2021            showB = () => this.setState({showB: true});
2022            return (
2023              <Suspense fallback="Loading...">
2024                {<AsyncText text="A" />}
2025                {this.state.showB && <AsyncText text="B" />}
2026              </Suspense>
2027            );
2028          }
2029        }
2030
2031        await ReactNoop.act(async () => {
2032          ReactNoop.render(<App />);
2033        });
2034
2035        expect(Scheduler).toHaveYielded(['Suspend! [A]']);
2036        expect(ReactNoop).toMatchRenderedOutput('Loading...');
2037
2038        ReactNoop.act(() => {
2039          Scheduler.unstable_runWithPriority(
2040            Scheduler.unstable_UserBlockingPriority,
2041            () => showB(),
2042          );
2043        });
2044
2045        expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Suspend! [B]']);
2046      });
2047
2048      // TODO: flip to "warns" when this is implemented again.
2049      it(
2050        'does not warn when component that triggered user-blocking update is between Suspense boundary ' +
2051          'and component that suspended',
2052        async () => {
2053          let _setShow;
2054          function A() {
2055            const [show, setShow] = React.useState(false);
2056            _setShow = setShow;
2057            return show && <AsyncText text="A" />;
2058          }
2059          function App() {
2060            return (
2061              <Suspense fallback="Loading...">
2062                <A />
2063              </Suspense>
2064            );
2065          }
2066          await ReactNoop.act(async () => {
2067            ReactNoop.render(<App />);
2068          });
2069
2070          // TODO: assert toErrorDev() when the warning is implemented again.
2071          ReactNoop.act(() => {
2072            Scheduler.unstable_runWithPriority(
2073              Scheduler.unstable_UserBlockingPriority,
2074              () => _setShow(true),
2075            );
2076          });
2077        },
2078      );
2079
2080      it('normal priority updates suspending do not warn for class components', async () => {
2081        let show;
2082        class App extends React.Component {
2083          state = {show: false};
2084
2085          render() {
2086            show = () => this.setState({show: true});
2087            return (
2088              <Suspense fallback="Loading...">
2089                {this.state.show && <AsyncText text="A" />}
2090              </Suspense>
2091            );
2092          }
2093        }
2094
2095        await ReactNoop.act(async () => {
2096          ReactNoop.render(<App />);
2097        });
2098
2099        // also make sure lowpriority is okay
2100        await ReactNoop.act(async () => show(true));
2101
2102        expect(Scheduler).toHaveYielded(['Suspend! [A]']);
2103        Scheduler.unstable_advanceTime(100);
2104        await advanceTimers(100);
2105
2106        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2107      });
2108
2109      it('normal priority updates suspending do not warn for functional components', async () => {
2110        let _setShow;
2111        function App() {
2112          let [show, setShow] = React.useState(false);
2113          _setShow = setShow;
2114          return (
2115            <Suspense fallback="Loading...">
2116              {show && <AsyncText text="A" />}
2117            </Suspense>
2118          );
2119        }
2120
2121        await ReactNoop.act(async () => {
2122          ReactNoop.render(<App />);
2123        });
2124
2125        // also make sure lowpriority is okay
2126        await ReactNoop.act(async () => _setShow(true));
2127
2128        expect(Scheduler).toHaveYielded(['Suspend! [A]']);
2129        Scheduler.unstable_advanceTime(100);
2130        await advanceTimers(100);
2131
2132        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2133      });
2134
2135      it('shows the parent fallback if the inner fallback should be avoided', async () => {
2136        function Foo({showC}) {
2137          Scheduler.unstable_yieldValue('Foo');
2138          return (
2139            <Suspense fallback={<Text text="Initial load..." />}>
2140              <Suspense
2141                unstable_avoidThisFallback={true}
2142                fallback={<Text text="Updating..." />}>
2143                <AsyncText text="A" ms={5000} />
2144                {showC ? <AsyncText text="C" ms={5000} /> : null}
2145              </Suspense>
2146              <Text text="B" />
2147            </Suspense>
2148          );
2149        }
2150
2151        ReactNoop.render(<Foo />);
2152        expect(Scheduler).toFlushAndYield([
2153          'Foo',
2154          'Suspend! [A]',
2155          'B',
2156          'Initial load...',
2157        ]);
2158        expect(ReactNoop.getChildren()).toEqual([span('Initial load...')]);
2159
2160        // Eventually we resolve and show the data.
2161        Scheduler.unstable_advanceTime(5000);
2162        await advanceTimers(5000);
2163        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2164        expect(Scheduler).toFlushAndYield(['A', 'B']);
2165        expect(ReactNoop.getChildren()).toEqual([span('A'), span('B')]);
2166
2167        // Update to show C
2168        ReactNoop.render(<Foo showC={true} />);
2169        expect(Scheduler).toFlushAndYield([
2170          'Foo',
2171          'A',
2172          'Suspend! [C]',
2173          'Updating...',
2174          'B',
2175        ]);
2176        // Flush to skip suspended time.
2177        Scheduler.unstable_advanceTime(600);
2178        await advanceTimers(600);
2179        // Since the optional suspense boundary is already showing its content,
2180        // we have to use the inner fallback instead.
2181        expect(ReactNoop.getChildren()).toEqual([
2182          hiddenSpan('A'),
2183          span('Updating...'),
2184          span('B'),
2185        ]);
2186
2187        // Later we load the data.
2188        Scheduler.unstable_advanceTime(5000);
2189        await advanceTimers(5000);
2190        expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
2191        expect(Scheduler).toFlushAndYield(['A', 'C']);
2192        expect(ReactNoop.getChildren()).toEqual([
2193          span('A'),
2194          span('C'),
2195          span('B'),
2196        ]);
2197      });
2198
2199      it('favors showing the inner fallback for nested top level avoided fallback', async () => {
2200        function Foo({showB}) {
2201          Scheduler.unstable_yieldValue('Foo');
2202          return (
2203            <Suspense
2204              unstable_avoidThisFallback={true}
2205              fallback={<Text text="Loading A..." />}>
2206              <Text text="A" />
2207              <Suspense
2208                unstable_avoidThisFallback={true}
2209                fallback={<Text text="Loading B..." />}>
2210                <AsyncText text="B" ms={5000} />
2211              </Suspense>
2212            </Suspense>
2213          );
2214        }
2215
2216        ReactNoop.render(<Foo />);
2217        expect(Scheduler).toFlushAndYield([
2218          'Foo',
2219          'A',
2220          'Suspend! [B]',
2221          'Loading B...',
2222        ]);
2223        // Flush to skip suspended time.
2224        Scheduler.unstable_advanceTime(600);
2225        await advanceTimers(600);
2226
2227        expect(ReactNoop.getChildren()).toEqual([
2228          span('A'),
2229          span('Loading B...'),
2230        ]);
2231      });
2232
2233      it('keeps showing an avoided parent fallback if it is already showing', async () => {
2234        function Foo({showB}) {
2235          Scheduler.unstable_yieldValue('Foo');
2236          return (
2237            <Suspense fallback={<Text text="Initial load..." />}>
2238              <Suspense
2239                unstable_avoidThisFallback={true}
2240                fallback={<Text text="Loading A..." />}>
2241                <Text text="A" />
2242                {showB ? (
2243                  <Suspense
2244                    unstable_avoidThisFallback={true}
2245                    fallback={<Text text="Loading B..." />}>
2246                    <AsyncText text="B" ms={5000} />
2247                  </Suspense>
2248                ) : null}
2249              </Suspense>
2250            </Suspense>
2251          );
2252        }
2253
2254        ReactNoop.render(<Foo />);
2255        expect(Scheduler).toFlushAndYield(['Foo', 'A']);
2256        expect(ReactNoop.getChildren()).toEqual([span('A')]);
2257
2258        ReactNoop.render(<Foo showB={true} />);
2259
2260        expect(Scheduler).toFlushAndYield([
2261          'Foo',
2262          'A',
2263          'Suspend! [B]',
2264          'Loading B...',
2265        ]);
2266        // Still suspended.
2267        expect(ReactNoop.getChildren()).toEqual([span('A')]);
2268
2269        // Flush to skip suspended time.
2270        Scheduler.unstable_advanceTime(600);
2271        await advanceTimers(600);
2272
2273        expect(ReactNoop.getChildren()).toEqual([
2274          span('A'),
2275          span('Loading B...'),
2276        ]);
2277      });
2278
2279      it('commits a suspended idle pri render within a reasonable time', async () => {
2280        function Foo({renderContent}) {
2281          return (
2282            <Fragment>
2283              <Suspense fallback={<Text text="Loading A..." />}>
2284                {renderContent ? <AsyncText text="A" ms={10000} /> : null}
2285              </Suspense>
2286            </Fragment>
2287          );
2288        }
2289
2290        ReactNoop.render(<Foo />);
2291        expect(Scheduler).toFlushAndYield([]);
2292
2293        ReactNoop.render(<Foo renderContent={1} />);
2294
2295        // Took a long time to render. This is to ensure we get a long suspense time.
2296        // Could also use something like withSuspenseConfig to simulate this.
2297        Scheduler.unstable_advanceTime(1500);
2298        await advanceTimers(1500);
2299
2300        expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading A...']);
2301        // We're still suspended.
2302        expect(ReactNoop.getChildren()).toEqual([]);
2303
2304        // Schedule an update at idle pri.
2305        Scheduler.unstable_runWithPriority(
2306          Scheduler.unstable_IdlePriority,
2307          () => ReactNoop.render(<Foo renderContent={2} />),
2308        );
2309        // We won't even work on Idle priority.
2310        expect(Scheduler).toFlushAndYield([]);
2311
2312        // We're still suspended.
2313        expect(ReactNoop.getChildren()).toEqual([]);
2314
2315        // Advance time a little bit.
2316        Scheduler.unstable_advanceTime(150);
2317        await advanceTimers(150);
2318
2319        // We should not have committed yet because we had a long suspense time.
2320        expect(ReactNoop.getChildren()).toEqual([]);
2321
2322        // Flush to skip suspended time.
2323        Scheduler.unstable_advanceTime(600);
2324        await advanceTimers(600);
2325
2326        expect(ReactNoop.getChildren()).toEqual([span('Loading A...')]);
2327      });
2328
2329      describe('delays transitions when there a suspense config is supplied', () => {
2330        const SUSPENSE_CONFIG = {
2331          timeoutMs: 2000,
2332        };
2333
2334        it('top level render', async () => {
2335          function App({page}) {
2336            return (
2337              <Suspense fallback={<Text text="Loading..." />}>
2338                <AsyncText text={page} ms={5000} />
2339              </Suspense>
2340            );
2341          }
2342
2343          // Initial render.
2344          React.unstable_withSuspenseConfig(
2345            () => ReactNoop.render(<App page="A" />),
2346            SUSPENSE_CONFIG,
2347          );
2348
2349          expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2350          // Only a short time is needed to unsuspend the initial loading state.
2351          Scheduler.unstable_advanceTime(400);
2352          await advanceTimers(400);
2353          expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
2354
2355          // Later we load the data.
2356          Scheduler.unstable_advanceTime(5000);
2357          await advanceTimers(5000);
2358          expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2359          expect(Scheduler).toFlushAndYield(['A']);
2360          expect(ReactNoop.getChildren()).toEqual([span('A')]);
2361
2362          // Start transition.
2363          React.unstable_withSuspenseConfig(
2364            () => ReactNoop.render(<App page="B" />),
2365            SUSPENSE_CONFIG,
2366          );
2367
2368          expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2369          Scheduler.unstable_advanceTime(1000);
2370          await advanceTimers(1000);
2371          // Even after a second, we have still not yet flushed the loading state.
2372          expect(ReactNoop.getChildren()).toEqual([span('A')]);
2373          Scheduler.unstable_advanceTime(1100);
2374          await advanceTimers(1100);
2375          // After the timeout, we do show the loading state.
2376          expect(ReactNoop.getChildren()).toEqual([
2377            hiddenSpan('A'),
2378            span('Loading...'),
2379          ]);
2380          // Later we load the data.
2381          Scheduler.unstable_advanceTime(3000);
2382          await advanceTimers(3000);
2383          expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2384          expect(Scheduler).toFlushAndYield(['B']);
2385          expect(ReactNoop.getChildren()).toEqual([span('B')]);
2386        });
2387
2388        it('hooks', async () => {
2389          let transitionToPage;
2390          function App() {
2391            let [page, setPage] = React.useState('none');
2392            transitionToPage = setPage;
2393            if (page === 'none') {
2394              return null;
2395            }
2396            return (
2397              <Suspense fallback={<Text text="Loading..." />}>
2398                <AsyncText text={page} ms={5000} />
2399              </Suspense>
2400            );
2401          }
2402
2403          ReactNoop.render(<App />);
2404          expect(Scheduler).toFlushAndYield([]);
2405
2406          // Initial render.
2407          await ReactNoop.act(async () => {
2408            React.unstable_withSuspenseConfig(
2409              () => transitionToPage('A'),
2410              SUSPENSE_CONFIG,
2411            );
2412
2413            expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2414            // Only a short time is needed to unsuspend the initial loading state.
2415            Scheduler.unstable_advanceTime(400);
2416            await advanceTimers(400);
2417            expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
2418          });
2419
2420          // Later we load the data.
2421          Scheduler.unstable_advanceTime(5000);
2422          await advanceTimers(5000);
2423          expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2424          expect(Scheduler).toFlushAndYield(['A']);
2425          expect(ReactNoop.getChildren()).toEqual([span('A')]);
2426
2427          // Start transition.
2428          await ReactNoop.act(async () => {
2429            React.unstable_withSuspenseConfig(
2430              () => transitionToPage('B'),
2431              SUSPENSE_CONFIG,
2432            );
2433
2434            expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2435            Scheduler.unstable_advanceTime(1000);
2436            await advanceTimers(1000);
2437            // Even after a second, we have still not yet flushed the loading state.
2438            expect(ReactNoop.getChildren()).toEqual([span('A')]);
2439            Scheduler.unstable_advanceTime(1100);
2440            await advanceTimers(1100);
2441            // After the timeout, we do show the loading state.
2442            expect(ReactNoop.getChildren()).toEqual([
2443              hiddenSpan('A'),
2444              span('Loading...'),
2445            ]);
2446          });
2447          // Later we load the data.
2448          Scheduler.unstable_advanceTime(3000);
2449          await advanceTimers(3000);
2450          expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2451          expect(Scheduler).toFlushAndYield(['B']);
2452          expect(ReactNoop.getChildren()).toEqual([span('B')]);
2453        });
2454
2455        it('classes', async () => {
2456          let transitionToPage;
2457          class App extends React.Component {
2458            state = {page: 'none'};
2459            render() {
2460              transitionToPage = page => this.setState({page});
2461              let page = this.state.page;
2462              if (page === 'none') {
2463                return null;
2464              }
2465              return (
2466                <Suspense fallback={<Text text="Loading..." />}>
2467                  <AsyncText text={page} ms={5000} />
2468                </Suspense>
2469              );
2470            }
2471          }
2472
2473          ReactNoop.render(<App />);
2474          expect(Scheduler).toFlushAndYield([]);
2475
2476          // Initial render.
2477          await ReactNoop.act(async () => {
2478            React.unstable_withSuspenseConfig(
2479              () => transitionToPage('A'),
2480              SUSPENSE_CONFIG,
2481            );
2482
2483            expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2484            // Only a short time is needed to unsuspend the initial loading state.
2485            Scheduler.unstable_advanceTime(400);
2486            await advanceTimers(400);
2487            expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
2488          });
2489
2490          // Later we load the data.
2491          Scheduler.unstable_advanceTime(5000);
2492          await advanceTimers(5000);
2493          expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2494          expect(Scheduler).toFlushAndYield(['A']);
2495          expect(ReactNoop.getChildren()).toEqual([span('A')]);
2496
2497          // Start transition.
2498          await ReactNoop.act(async () => {
2499            React.unstable_withSuspenseConfig(
2500              () => transitionToPage('B'),
2501              SUSPENSE_CONFIG,
2502            );
2503
2504            expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2505            Scheduler.unstable_advanceTime(1000);
2506            await advanceTimers(1000);
2507            // Even after a second, we have still not yet flushed the loading state.
2508            expect(ReactNoop.getChildren()).toEqual([span('A')]);
2509            Scheduler.unstable_advanceTime(1100);
2510            await advanceTimers(1100);
2511            // After the timeout, we do show the loading state.
2512            expect(ReactNoop.getChildren()).toEqual([
2513              hiddenSpan('A'),
2514              span('Loading...'),
2515            ]);
2516          });
2517          // Later we load the data.
2518          Scheduler.unstable_advanceTime(3000);
2519          await advanceTimers(3000);
2520          expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2521          expect(Scheduler).toFlushAndYield(['B']);
2522          expect(ReactNoop.getChildren()).toEqual([span('B')]);
2523        });
2524      });
2525
2526      it('disables suspense config when nothing is passed to withSuspenseConfig', async () => {
2527        function App({page}) {
2528          return (
2529            <Suspense fallback={<Text text="Loading..." />}>
2530              <AsyncText text={page} ms={2000} />
2531            </Suspense>
2532          );
2533        }
2534
2535        // Initial render.
2536        ReactNoop.render(<App page="A" />);
2537        expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
2538        Scheduler.unstable_advanceTime(2000);
2539        await advanceTimers(2000);
2540        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2541        expect(Scheduler).toFlushAndYield(['A']);
2542        expect(ReactNoop.getChildren()).toEqual([span('A')]);
2543
2544        // Start transition.
2545        React.unstable_withSuspenseConfig(
2546          () => {
2547            // When we schedule an inner transition without a suspense config
2548            // so it should only suspend for a short time.
2549            React.unstable_withSuspenseConfig(() =>
2550              ReactNoop.render(<App page="B" />),
2551            );
2552          },
2553          {timeoutMs: 2000},
2554        );
2555
2556        expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
2557        // Suspended
2558        expect(ReactNoop.getChildren()).toEqual([span('A')]);
2559        Scheduler.unstable_advanceTime(500);
2560        await advanceTimers(500);
2561        // Committed loading state.
2562        expect(ReactNoop.getChildren()).toEqual([
2563          hiddenSpan('A'),
2564          span('Loading...'),
2565        ]);
2566
2567        Scheduler.unstable_advanceTime(2000);
2568        await advanceTimers(2000);
2569        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2570        expect(Scheduler).toFlushAndYield(['B']);
2571        expect(ReactNoop.getChildren()).toEqual([span('B')]);
2572
2573        React.unstable_withSuspenseConfig(
2574          () => {
2575            // First we schedule an inner unrelated update.
2576            React.unstable_withSuspenseConfig(() =>
2577              ReactNoop.render(<App page="B" unrelated={true} />),
2578            );
2579            // Then we schedule another transition to a slow page,
2580            // but at this scope we should suspend for longer.
2581            Scheduler.unstable_next(() => ReactNoop.render(<App page="C" />));
2582          },
2583          {timeoutMs: 2000},
2584        );
2585        expect(Scheduler).toFlushAndYield([
2586          'Suspend! [C]',
2587          'Loading...',
2588          'Suspend! [C]',
2589          'Loading...',
2590        ]);
2591        expect(ReactNoop.getChildren()).toEqual([span('B')]);
2592        Scheduler.unstable_advanceTime(1200);
2593        await advanceTimers(1200);
2594        // Even after a second, we have still not yet flushed the loading state.
2595        expect(ReactNoop.getChildren()).toEqual([span('B')]);
2596        Scheduler.unstable_advanceTime(1200);
2597        await advanceTimers(1200);
2598        // After the two second timeout we show the loading state.
2599        expect(ReactNoop.getChildren()).toEqual([
2600          hiddenSpan('B'),
2601          span('Loading...'),
2602        ]);
2603      });
2604
2605      it('withSuspenseConfig timeout applies when we use an updated avoided boundary', async () => {
2606        function App({page}) {
2607          return (
2608            <Suspense fallback={<Text text="Loading..." />}>
2609              <Text text="Hi!" />
2610              <Suspense
2611                fallback={<Text text={'Loading ' + page + '...'} />}
2612                unstable_avoidThisFallback={true}>
2613                <AsyncText text={page} ms={3000} />
2614              </Suspense>
2615            </Suspense>
2616          );
2617        }
2618
2619        // Initial render.
2620        ReactNoop.render(<App page="A" />);
2621        expect(Scheduler).toFlushAndYield([
2622          'Hi!',
2623          'Suspend! [A]',
2624          'Loading...',
2625        ]);
2626        Scheduler.unstable_advanceTime(3000);
2627        await advanceTimers(3000);
2628        expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2629        expect(Scheduler).toFlushAndYield(['Hi!', 'A']);
2630        expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2631
2632        // Start transition.
2633        React.unstable_withSuspenseConfig(
2634          () => ReactNoop.render(<App page="B" />),
2635          {timeoutMs: 2000},
2636        );
2637
2638        expect(Scheduler).toFlushAndYield([
2639          'Hi!',
2640          'Suspend! [B]',
2641          'Loading B...',
2642        ]);
2643
2644        // Suspended
2645        expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2646        Scheduler.unstable_advanceTime(1800);
2647        await advanceTimers(1800);
2648        expect(Scheduler).toFlushAndYield([]);
2649        // We should still be suspended here because this loading state should be avoided.
2650        expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2651        Scheduler.unstable_advanceTime(1500);
2652        await advanceTimers(1500);
2653        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2654        expect(ReactNoop.getChildren()).toEqual([
2655          span('Hi!'),
2656          hiddenSpan('A'),
2657          span('Loading B...'),
2658        ]);
2659      });
2660
2661      it('withSuspenseConfig timeout applies when we use a newly created avoided boundary', async () => {
2662        function App({page}) {
2663          return (
2664            <Suspense fallback={<Text text="Loading..." />}>
2665              <Text text="Hi!" />
2666              {page === 'A' ? (
2667                <Text text="A" />
2668              ) : (
2669                <Suspense
2670                  fallback={<Text text={'Loading ' + page + '...'} />}
2671                  unstable_avoidThisFallback={true}>
2672                  <AsyncText text={page} ms={3000} />
2673                </Suspense>
2674              )}
2675            </Suspense>
2676          );
2677        }
2678
2679        // Initial render.
2680        ReactNoop.render(<App page="A" />);
2681        expect(Scheduler).toFlushAndYield(['Hi!', 'A']);
2682        expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2683
2684        // Start transition.
2685        React.unstable_withSuspenseConfig(
2686          () => ReactNoop.render(<App page="B" />),
2687          {timeoutMs: 2000},
2688        );
2689
2690        expect(Scheduler).toFlushAndYield([
2691          'Hi!',
2692          'Suspend! [B]',
2693          'Loading B...',
2694        ]);
2695
2696        // Suspended
2697        expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2698        Scheduler.unstable_advanceTime(1800);
2699        await advanceTimers(1800);
2700        expect(Scheduler).toFlushAndYield([]);
2701        // We should still be suspended here because this loading state should be avoided.
2702        expect(ReactNoop.getChildren()).toEqual([span('Hi!'), span('A')]);
2703        Scheduler.unstable_advanceTime(1500);
2704        await advanceTimers(1500);
2705        expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2706        expect(ReactNoop.getChildren()).toEqual([
2707          span('Hi!'),
2708          span('Loading B...'),
2709        ]);
2710      });
2711
2712      it('supports delaying a busy spinner from disappearing', async () => {
2713        function useLoadingIndicator(config) {
2714          let [isLoading, setLoading] = React.useState(false);
2715          let start = React.useCallback(
2716            cb => {
2717              setLoading(true);
2718              Scheduler.unstable_next(() =>
2719                React.unstable_withSuspenseConfig(() => {
2720                  setLoading(false);
2721                  cb();
2722                }, config),
2723              );
2724            },
2725            [setLoading, config],
2726          );
2727          return [isLoading, start];
2728        }
2729
2730        const SUSPENSE_CONFIG = {
2731          timeoutMs: 10000,
2732          busyDelayMs: 500,
2733          busyMinDurationMs: 400,
2734        };
2735
2736        let transitionToPage;
2737
2738        function App() {
2739          let [page, setPage] = React.useState('A');
2740          let [isLoading, startLoading] = useLoadingIndicator(SUSPENSE_CONFIG);
2741          transitionToPage = nextPage => startLoading(() => setPage(nextPage));
2742          return (
2743            <Fragment>
2744              <Text text={page} />
2745              {isLoading ? <Text text="L" /> : null}
2746            </Fragment>
2747          );
2748        }
2749
2750        // Initial render.
2751        ReactNoop.render(<App />);
2752        expect(Scheduler).toFlushAndYield(['A']);
2753        expect(ReactNoop.getChildren()).toEqual([span('A')]);
2754
2755        await ReactNoop.act(async () => {
2756          transitionToPage('B');
2757          // Rendering B is quick and we didn't have enough
2758          // time to show the loading indicator.
2759          Scheduler.unstable_advanceTime(200);
2760          await advanceTimers(200);
2761          expect(Scheduler).toFlushAndYield(['A', 'L', 'B']);
2762          expect(ReactNoop.getChildren()).toEqual([span('B')]);
2763        });
2764
2765        await ReactNoop.act(async () => {
2766          transitionToPage('C');
2767          // Rendering C is a bit slower so we've already showed
2768          // the loading indicator.
2769          Scheduler.unstable_advanceTime(600);
2770          await advanceTimers(600);
2771          expect(Scheduler).toFlushAndYield(['B', 'L', 'C']);
2772          // We're technically done now but we haven't shown the
2773          // loading indicator for long enough yet so we'll suspend
2774          // while we keep it on the screen a bit longer.
2775          expect(ReactNoop.getChildren()).toEqual([span('B'), span('L')]);
2776          Scheduler.unstable_advanceTime(400);
2777          await advanceTimers(400);
2778          expect(ReactNoop.getChildren()).toEqual([span('C')]);
2779        });
2780
2781        await ReactNoop.act(async () => {
2782          transitionToPage('D');
2783          // Rendering D is very slow so we've already showed
2784          // the loading indicator.
2785          Scheduler.unstable_advanceTime(1000);
2786          await advanceTimers(1000);
2787          expect(Scheduler).toFlushAndYield(['C', 'L', 'D']);
2788          // However, since we exceeded the minimum time to show
2789          // the loading indicator, we commit immediately.
2790          expect(ReactNoop.getChildren()).toEqual([span('D')]);
2791        });
2792      });
2793
2794      it("suspended commit remains suspended even if there's another update at same expiration", async () => {
2795        // Regression test
2796        function App({text}) {
2797          return (
2798            <Suspense fallback="Loading...">
2799              <AsyncText ms={2000} text={text} />
2800            </Suspense>
2801          );
2802        }
2803
2804        const root = ReactNoop.createRoot();
2805        await ReactNoop.act(async () => {
2806          root.render(<App text="Initial" />);
2807        });
2808
2809        // Resolve initial render
2810        await ReactNoop.act(async () => {
2811          Scheduler.unstable_advanceTime(2000);
2812          await advanceTimers(2000);
2813        });
2814        expect(Scheduler).toHaveYielded([
2815          'Suspend! [Initial]',
2816          'Promise resolved [Initial]',
2817          'Initial',
2818        ]);
2819        expect(root).toMatchRenderedOutput(<span prop="Initial" />);
2820
2821        // Update. Since showing a fallback would hide content that's already
2822        // visible, it should suspend for a bit without committing.
2823        await ReactNoop.act(async () => {
2824          root.render(<App text="First update" />);
2825
2826          expect(Scheduler).toFlushAndYield(['Suspend! [First update]']);
2827          // Should not display a fallback
2828          expect(root).toMatchRenderedOutput(<span prop="Initial" />);
2829        });
2830
2831        // Update again. This should also suspend for a bit.
2832        await ReactNoop.act(async () => {
2833          root.render(<App text="Second update" />);
2834
2835          expect(Scheduler).toFlushAndYield(['Suspend! [Second update]']);
2836          // Should not display a fallback
2837          expect(root).toMatchRenderedOutput(<span prop="Initial" />);
2838        });
2839      });
2840
2841      it('regression test: resets current "debug phase" after suspending', async () => {
2842        function App() {
2843          return (
2844            <Suspense fallback="Loading...">
2845              <Foo suspend={false} />
2846            </Suspense>
2847          );
2848        }
2849
2850        const thenable = {then() {}};
2851
2852        let foo;
2853        class Foo extends React.Component {
2854          state = {suspend: false};
2855          render() {
2856            foo = this;
2857
2858            if (this.state.suspend) {
2859              Scheduler.unstable_yieldValue('Suspend!');
2860              throw thenable;
2861            }
2862
2863            return <Text text="Foo" />;
2864          }
2865        }
2866
2867        const root = ReactNoop.createRoot();
2868        await ReactNoop.act(async () => {
2869          root.render(<App />);
2870        });
2871
2872        expect(Scheduler).toHaveYielded(['Foo']);
2873
2874        await ReactNoop.act(async () => {
2875          foo.setState({suspend: true});
2876
2877          // In the regression that this covers, we would neglect to reset the
2878          // current debug phase after suspending (in the catch block), so React
2879          // thinks we're still inside the render phase.
2880          expect(Scheduler).toFlushAndYieldThrough(['Suspend!']);
2881