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