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!!