How to use advanceTimers method in Playwright Internal

Best JavaScript code snippet using playwright-internal

ReactSuspenseWithNoopRenderer-test.internal.js

Source:ReactSuspenseWithNoopRenderer-test.internal.js Github

copy

Full Screen

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

Full Screen

Full Screen

ReactSuspense-test.internal.js

Source:ReactSuspense-test.internal.js Github

copy

Full Screen

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

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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 {

Full Screen

Using AI Code Generation

copy

Full Screen

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(

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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

Full Screen

Using AI Code Generation

copy

Full Screen

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 ({

Full Screen

Playwright tutorial

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.

Chapters:

  1. What is Playwright : Playwright is comparatively new but has gained good popularity. Get to know some history of the Playwright with some interesting facts connected with it.
  2. How To Install Playwright : Learn in detail about what basic configuration and dependencies are required for installing Playwright and run a test. Get a step-by-step direction for installing the Playwright automation framework.
  3. Playwright Futuristic Features: Launched in 2020, Playwright gained huge popularity quickly because of some obliging features such as Playwright Test Generator and Inspector, Playwright Reporter, Playwright auto-waiting mechanism and etc. Read up on those features to master Playwright testing.
  4. What is Component Testing: Component testing in Playwright is a unique feature that allows a tester to test a single component of a web application without integrating them with other elements. Learn how to perform Component testing on the Playwright automation framework.
  5. Inputs And Buttons In Playwright: Every website has Input boxes and buttons; learn about testing inputs and buttons with different scenarios and examples.
  6. Functions and Selectors in Playwright: Learn how to launch the Chromium browser with Playwright. Also, gain a better understanding of some important functions like “BrowserContext,” which allows you to run multiple browser sessions, and “newPage” which interacts with a page.
  7. Handling Alerts and Dropdowns in Playwright : Playwright interact with different types of alerts and pop-ups, such as simple, confirmation, and prompt, and different types of dropdowns, such as single selector and multi-selector get your hands-on with handling alerts and dropdown in Playright testing.
  8. Playwright vs Puppeteer: Get to know about the difference between two testing frameworks and how they are different than one another, which browsers they support, and what features they provide.
  9. Run Playwright Tests on LambdaTest: Playwright testing with LambdaTest leverages test performance to the utmost. You can run multiple Playwright tests in Parallel with the LammbdaTest test cloud. Get a step-by-step guide to run your Playwright test on the LambdaTest platform.
  10. Playwright Python Tutorial: Playwright automation framework support all major languages such as Python, JavaScript, TypeScript, .NET and etc. However, there are various advantages to Python end-to-end testing with Playwright because of its versatile utility. Get the hang of Playwright python testing with this chapter.
  11. Playwright End To End Testing Tutorial: Get your hands on with Playwright end-to-end testing and learn to use some exciting features such as TraceViewer, Debugging, Networking, Component testing, Visual testing, and many more.
  12. Playwright Video Tutorial: Watch the video tutorials on Playwright testing from experts and get a consecutive in-depth explanation of Playwright automation testing.

Run Playwright Internal automation tests on LambdaTest cloud grid

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

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful