1. let React;
    
  2. let ReactNoop;
    
  3. let Scheduler;
    
  4. let act;
    
  5. let Profiler;
    
  6. let Suspense;
    
  7. let SuspenseList;
    
  8. let waitForAll;
    
  9. let assertLog;
    
  10. let waitFor;
    
  11. 
    
  12. describe('ReactSuspenseList', () => {
    
  13.   beforeEach(() => {
    
  14.     jest.resetModules();
    
  15. 
    
  16.     React = require('react');
    
  17.     ReactNoop = require('react-noop-renderer');
    
  18.     Scheduler = require('scheduler');
    
  19.     Profiler = React.Profiler;
    
  20.     Suspense = React.Suspense;
    
  21.     if (gate(flags => flags.enableSuspenseList)) {
    
  22.       SuspenseList = React.unstable_SuspenseList;
    
  23.     }
    
  24. 
    
  25.     const InternalTestUtils = require('internal-test-utils');
    
  26.     waitForAll = InternalTestUtils.waitForAll;
    
  27.     assertLog = InternalTestUtils.assertLog;
    
  28.     waitFor = InternalTestUtils.waitFor;
    
  29.     act = InternalTestUtils.act;
    
  30.   });
    
  31. 
    
  32.   function Text(props) {
    
  33.     Scheduler.log(props.text);
    
  34.     return <span>{props.text}</span>;
    
  35.   }
    
  36. 
    
  37.   function createAsyncText(text) {
    
  38.     let resolved = false;
    
  39.     const Component = function () {
    
  40.       if (!resolved) {
    
  41.         Scheduler.log('Suspend! [' + text + ']');
    
  42.         throw promise;
    
  43.       }
    
  44.       return <Text text={text} />;
    
  45.     };
    
  46.     const promise = new Promise(resolve => {
    
  47.       Component.resolve = function () {
    
  48.         resolved = true;
    
  49.         return resolve();
    
  50.       };
    
  51.     });
    
  52.     return Component;
    
  53.   }
    
  54. 
    
  55.   // @gate enableSuspenseList
    
  56.   it('warns if an unsupported revealOrder option is used', async () => {
    
  57.     function Foo() {
    
  58.       return (
    
  59.         <SuspenseList revealOrder="something">
    
  60.           <Suspense fallback="Loading">Content</Suspense>
    
  61.         </SuspenseList>
    
  62.       );
    
  63.     }
    
  64. 
    
  65.     await expect(async () => {
    
  66.       await act(() => {
    
  67.         ReactNoop.render(<Foo />);
    
  68.       });
    
  69.     }).toErrorDev([
    
  70.       'Warning: "something" is not a supported revealOrder on ' +
    
  71.         '<SuspenseList />. Did you mean "together", "forwards" or "backwards"?' +
    
  72.         '\n    in SuspenseList (at **)' +
    
  73.         '\n    in Foo (at **)',
    
  74.     ]);
    
  75.   });
    
  76. 
    
  77.   // @gate enableSuspenseList
    
  78.   it('warns if a upper case revealOrder option is used', async () => {
    
  79.     function Foo() {
    
  80.       return (
    
  81.         <SuspenseList revealOrder="TOGETHER">
    
  82.           <Suspense fallback="Loading">Content</Suspense>
    
  83.         </SuspenseList>
    
  84.       );
    
  85.     }
    
  86. 
    
  87.     await expect(async () => {
    
  88.       await act(() => {
    
  89.         ReactNoop.render(<Foo />);
    
  90.       });
    
  91.     }).toErrorDev([
    
  92.       'Warning: "TOGETHER" is not a valid value for revealOrder on ' +
    
  93.         '<SuspenseList />. Use lowercase "together" instead.' +
    
  94.         '\n    in SuspenseList (at **)' +
    
  95.         '\n    in Foo (at **)',
    
  96.     ]);
    
  97.   });
    
  98. 
    
  99.   // @gate enableSuspenseList
    
  100.   it('warns if a misspelled revealOrder option is used', async () => {
    
  101.     function Foo() {
    
  102.       return (
    
  103.         <SuspenseList revealOrder="forward">
    
  104.           <Suspense fallback="Loading">Content</Suspense>
    
  105.         </SuspenseList>
    
  106.       );
    
  107.     }
    
  108. 
    
  109.     await expect(async () => {
    
  110.       await act(() => {
    
  111.         ReactNoop.render(<Foo />);
    
  112.       });
    
  113.     }).toErrorDev([
    
  114.       'Warning: "forward" is not a valid value for revealOrder on ' +
    
  115.         '<SuspenseList />. React uses the -s suffix in the spelling. ' +
    
  116.         'Use "forwards" instead.' +
    
  117.         '\n    in SuspenseList (at **)' +
    
  118.         '\n    in Foo (at **)',
    
  119.     ]);
    
  120.   });
    
  121. 
    
  122.   // @gate enableSuspenseList
    
  123.   it('warns if a single element is passed to a "forwards" list', async () => {
    
  124.     function Foo({children}) {
    
  125.       return <SuspenseList revealOrder="forwards">{children}</SuspenseList>;
    
  126.     }
    
  127. 
    
  128.     ReactNoop.render(<Foo />);
    
  129.     // No warning
    
  130.     await waitForAll([]);
    
  131. 
    
  132.     ReactNoop.render(<Foo>{null}</Foo>);
    
  133.     // No warning
    
  134.     await waitForAll([]);
    
  135. 
    
  136.     ReactNoop.render(<Foo>{false}</Foo>);
    
  137.     // No warning
    
  138.     await waitForAll([]);
    
  139. 
    
  140.     await expect(async () => {
    
  141.       await act(() => {
    
  142.         ReactNoop.render(
    
  143.           <Foo>
    
  144.             <Suspense fallback="Loading">Child</Suspense>
    
  145.           </Foo>,
    
  146.         );
    
  147.       });
    
  148.     }).toErrorDev([
    
  149.       'Warning: A single row was passed to a <SuspenseList revealOrder="forwards" />. ' +
    
  150.         'This is not useful since it needs multiple rows. ' +
    
  151.         'Did you mean to pass multiple children or an array?' +
    
  152.         '\n    in SuspenseList (at **)' +
    
  153.         '\n    in Foo (at **)',
    
  154.     ]);
    
  155.   });
    
  156. 
    
  157.   // @gate enableSuspenseList
    
  158.   it('warns if a single fragment is passed to a "backwards" list', async () => {
    
  159.     function Foo() {
    
  160.       return (
    
  161.         <SuspenseList revealOrder="backwards">
    
  162.           <>{[]}</>
    
  163.         </SuspenseList>
    
  164.       );
    
  165.     }
    
  166. 
    
  167.     await expect(async () => {
    
  168.       await act(() => {
    
  169.         ReactNoop.render(<Foo />);
    
  170.       });
    
  171.     }).toErrorDev([
    
  172.       'Warning: A single row was passed to a <SuspenseList revealOrder="backwards" />. ' +
    
  173.         'This is not useful since it needs multiple rows. ' +
    
  174.         'Did you mean to pass multiple children or an array?' +
    
  175.         '\n    in SuspenseList (at **)' +
    
  176.         '\n    in Foo (at **)',
    
  177.     ]);
    
  178.   });
    
  179. 
    
  180.   // @gate enableSuspenseList
    
  181.   it('warns if a nested array is passed to a "forwards" list', async () => {
    
  182.     function Foo({items}) {
    
  183.       return (
    
  184.         <SuspenseList revealOrder="forwards">
    
  185.           {items.map(name => (
    
  186.             <Suspense key={name} fallback="Loading">
    
  187.               {name}
    
  188.             </Suspense>
    
  189.           ))}
    
  190.           <div>Tail</div>
    
  191.         </SuspenseList>
    
  192.       );
    
  193.     }
    
  194. 
    
  195.     await expect(async () => {
    
  196.       await act(() => {
    
  197.         ReactNoop.render(<Foo items={['A', 'B']} />);
    
  198.       });
    
  199.     }).toErrorDev([
    
  200.       'Warning: A nested array was passed to row #0 in <SuspenseList />. ' +
    
  201.         'Wrap it in an additional SuspenseList to configure its revealOrder: ' +
    
  202.         '<SuspenseList revealOrder=...> ... ' +
    
  203.         '<SuspenseList revealOrder=...>{array}</SuspenseList> ... ' +
    
  204.         '</SuspenseList>' +
    
  205.         '\n    in SuspenseList (at **)' +
    
  206.         '\n    in Foo (at **)',
    
  207.     ]);
    
  208.   });
    
  209. 
    
  210.   // @gate enableSuspenseList
    
  211.   it('shows content independently by default', async () => {
    
  212.     const A = createAsyncText('A');
    
  213.     const B = createAsyncText('B');
    
  214.     const C = createAsyncText('C');
    
  215. 
    
  216.     function Foo() {
    
  217.       return (
    
  218.         <SuspenseList>
    
  219.           <Suspense fallback={<Text text="Loading A" />}>
    
  220.             <A />
    
  221.           </Suspense>
    
  222.           <Suspense fallback={<Text text="Loading B" />}>
    
  223.             <B />
    
  224.           </Suspense>
    
  225.           <Suspense fallback={<Text text="Loading C" />}>
    
  226.             <C />
    
  227.           </Suspense>
    
  228.         </SuspenseList>
    
  229.       );
    
  230.     }
    
  231. 
    
  232.     await A.resolve();
    
  233. 
    
  234.     ReactNoop.render(<Foo />);
    
  235. 
    
  236.     await waitForAll([
    
  237.       'A',
    
  238.       'Suspend! [B]',
    
  239.       'Loading B',
    
  240.       'Suspend! [C]',
    
  241.       'Loading C',
    
  242.     ]);
    
  243. 
    
  244.     expect(ReactNoop).toMatchRenderedOutput(
    
  245.       <>
    
  246.         <span>A</span>
    
  247.         <span>Loading B</span>
    
  248.         <span>Loading C</span>
    
  249.       </>,
    
  250.     );
    
  251. 
    
  252.     await act(() => C.resolve());
    
  253.     assertLog(['C']);
    
  254. 
    
  255.     expect(ReactNoop).toMatchRenderedOutput(
    
  256.       <>
    
  257.         <span>A</span>
    
  258.         <span>Loading B</span>
    
  259.         <span>C</span>
    
  260.       </>,
    
  261.     );
    
  262. 
    
  263.     await act(() => B.resolve());
    
  264.     assertLog(['B']);
    
  265. 
    
  266.     expect(ReactNoop).toMatchRenderedOutput(
    
  267.       <>
    
  268.         <span>A</span>
    
  269.         <span>B</span>
    
  270.         <span>C</span>
    
  271.       </>,
    
  272.     );
    
  273.   });
    
  274. 
    
  275.   // @gate enableSuspenseList
    
  276.   it('shows content independently in legacy mode regardless of option', async () => {
    
  277.     const A = createAsyncText('A');
    
  278.     const B = createAsyncText('B');
    
  279.     const C = createAsyncText('C');
    
  280. 
    
  281.     function Foo() {
    
  282.       return (
    
  283.         <SuspenseList revealOrder="together">
    
  284.           <Suspense fallback={<Text text="Loading A" />}>
    
  285.             <A />
    
  286.           </Suspense>
    
  287.           <Suspense fallback={<Text text="Loading B" />}>
    
  288.             <B />
    
  289.           </Suspense>
    
  290.           <Suspense fallback={<Text text="Loading C" />}>
    
  291.             <C />
    
  292.           </Suspense>
    
  293.         </SuspenseList>
    
  294.       );
    
  295.     }
    
  296. 
    
  297.     await A.resolve();
    
  298. 
    
  299.     ReactNoop.renderLegacySyncRoot(<Foo />);
    
  300. 
    
  301.     assertLog(['A', 'Suspend! [B]', 'Loading B', 'Suspend! [C]', 'Loading C']);
    
  302. 
    
  303.     expect(ReactNoop).toMatchRenderedOutput(
    
  304.       <>
    
  305.         <span>A</span>
    
  306.         <span>Loading B</span>
    
  307.         <span>Loading C</span>
    
  308.       </>,
    
  309.     );
    
  310. 
    
  311.     await act(() => {
    
  312.       C.resolve();
    
  313.     });
    
  314. 
    
  315.     assertLog(['C']);
    
  316. 
    
  317.     expect(ReactNoop).toMatchRenderedOutput(
    
  318.       <>
    
  319.         <span>A</span>
    
  320.         <span>Loading B</span>
    
  321.         <span>C</span>
    
  322.       </>,
    
  323.     );
    
  324. 
    
  325.     await act(() => {
    
  326.       B.resolve();
    
  327.     });
    
  328. 
    
  329.     assertLog(['B']);
    
  330. 
    
  331.     expect(ReactNoop).toMatchRenderedOutput(
    
  332.       <>
    
  333.         <span>A</span>
    
  334.         <span>B</span>
    
  335.         <span>C</span>
    
  336.       </>,
    
  337.     );
    
  338.   });
    
  339. 
    
  340.   // @gate enableSuspenseList
    
  341.   it('displays all "together"', async () => {
    
  342.     const A = createAsyncText('A');
    
  343.     const B = createAsyncText('B');
    
  344.     const C = createAsyncText('C');
    
  345. 
    
  346.     function Foo() {
    
  347.       return (
    
  348.         <SuspenseList revealOrder="together">
    
  349.           <Suspense fallback={<Text text="Loading A" />}>
    
  350.             <A />
    
  351.           </Suspense>
    
  352.           <Suspense fallback={<Text text="Loading B" />}>
    
  353.             <B />
    
  354.           </Suspense>
    
  355.           <Suspense fallback={<Text text="Loading C" />}>
    
  356.             <C />
    
  357.           </Suspense>
    
  358.         </SuspenseList>
    
  359.       );
    
  360.     }
    
  361. 
    
  362.     await A.resolve();
    
  363. 
    
  364.     ReactNoop.render(<Foo />);
    
  365. 
    
  366.     await waitForAll([
    
  367.       'A',
    
  368.       'Suspend! [B]',
    
  369.       'Loading B',
    
  370.       'Suspend! [C]',
    
  371.       'Loading C',
    
  372.       'Loading A',
    
  373.       'Loading B',
    
  374.       'Loading C',
    
  375.     ]);
    
  376. 
    
  377.     expect(ReactNoop).toMatchRenderedOutput(
    
  378.       <>
    
  379.         <span>Loading A</span>
    
  380.         <span>Loading B</span>
    
  381.         <span>Loading C</span>
    
  382.       </>,
    
  383.     );
    
  384. 
    
  385.     await act(() => B.resolve());
    
  386.     assertLog(['A', 'B', 'Suspend! [C]']);
    
  387. 
    
  388.     expect(ReactNoop).toMatchRenderedOutput(
    
  389.       <>
    
  390.         <span>Loading A</span>
    
  391.         <span>Loading B</span>
    
  392.         <span>Loading C</span>
    
  393.       </>,
    
  394.     );
    
  395. 
    
  396.     await act(() => C.resolve());
    
  397.     assertLog(['A', 'B', 'C']);
    
  398. 
    
  399.     expect(ReactNoop).toMatchRenderedOutput(
    
  400.       <>
    
  401.         <span>A</span>
    
  402.         <span>B</span>
    
  403.         <span>C</span>
    
  404.       </>,
    
  405.     );
    
  406.   });
    
  407. 
    
  408.   // @gate enableSuspenseList
    
  409.   it('displays all "together" even when nested as siblings', async () => {
    
  410.     const A = createAsyncText('A');
    
  411.     const B = createAsyncText('B');
    
  412.     const C = createAsyncText('C');
    
  413. 
    
  414.     function Foo() {
    
  415.       return (
    
  416.         <SuspenseList revealOrder="together">
    
  417.           <div>
    
  418.             <Suspense fallback={<Text text="Loading A" />}>
    
  419.               <A />
    
  420.             </Suspense>
    
  421.             <Suspense fallback={<Text text="Loading B" />}>
    
  422.               <B />
    
  423.             </Suspense>
    
  424.           </div>
    
  425.           <div>
    
  426.             <Suspense fallback={<Text text="Loading C" />}>
    
  427.               <C />
    
  428.             </Suspense>
    
  429.           </div>
    
  430.         </SuspenseList>
    
  431.       );
    
  432.     }
    
  433. 
    
  434.     await A.resolve();
    
  435. 
    
  436.     ReactNoop.render(<Foo />);
    
  437. 
    
  438.     await waitForAll([
    
  439.       'A',
    
  440.       'Suspend! [B]',
    
  441.       'Loading B',
    
  442.       'Suspend! [C]',
    
  443.       'Loading C',
    
  444.       'Loading A',
    
  445.       'Loading B',
    
  446.       'Loading C',
    
  447.     ]);
    
  448. 
    
  449.     expect(ReactNoop).toMatchRenderedOutput(
    
  450.       <>
    
  451.         <div>
    
  452.           <span>Loading A</span>
    
  453.           <span>Loading B</span>
    
  454.         </div>
    
  455.         <div>
    
  456.           <span>Loading C</span>
    
  457.         </div>
    
  458.       </>,
    
  459.     );
    
  460. 
    
  461.     await act(() => B.resolve());
    
  462.     assertLog(['A', 'B', 'Suspend! [C]']);
    
  463. 
    
  464.     expect(ReactNoop).toMatchRenderedOutput(
    
  465.       <>
    
  466.         <div>
    
  467.           <span>Loading A</span>
    
  468.           <span>Loading B</span>
    
  469.         </div>
    
  470.         <div>
    
  471.           <span>Loading C</span>
    
  472.         </div>
    
  473.       </>,
    
  474.     );
    
  475. 
    
  476.     await act(() => C.resolve());
    
  477.     assertLog(['A', 'B', 'C']);
    
  478. 
    
  479.     expect(ReactNoop).toMatchRenderedOutput(
    
  480.       <>
    
  481.         <div>
    
  482.           <span>A</span>
    
  483.           <span>B</span>
    
  484.         </div>
    
  485.         <div>
    
  486.           <span>C</span>
    
  487.         </div>
    
  488.       </>,
    
  489.     );
    
  490.   });
    
  491. 
    
  492.   // @gate enableSuspenseList
    
  493.   it('displays all "together" in nested SuspenseLists', async () => {
    
  494.     const A = createAsyncText('A');
    
  495.     const B = createAsyncText('B');
    
  496.     const C = createAsyncText('C');
    
  497. 
    
  498.     function Foo() {
    
  499.       return (
    
  500.         <SuspenseList revealOrder="together">
    
  501.           <Suspense fallback={<Text text="Loading A" />}>
    
  502.             <A />
    
  503.           </Suspense>
    
  504.           <SuspenseList revealOrder="together">
    
  505.             <Suspense fallback={<Text text="Loading B" />}>
    
  506.               <B />
    
  507.             </Suspense>
    
  508.             <Suspense fallback={<Text text="Loading C" />}>
    
  509.               <C />
    
  510.             </Suspense>
    
  511.           </SuspenseList>
    
  512.         </SuspenseList>
    
  513.       );
    
  514.     }
    
  515. 
    
  516.     await A.resolve();
    
  517.     await B.resolve();
    
  518. 
    
  519.     ReactNoop.render(<Foo />);
    
  520. 
    
  521.     await waitForAll([
    
  522.       'A',
    
  523.       'B',
    
  524.       'Suspend! [C]',
    
  525.       'Loading C',
    
  526.       'Loading B',
    
  527.       'Loading C',
    
  528.       'Loading A',
    
  529.       'Loading B',
    
  530.       'Loading C',
    
  531.     ]);
    
  532. 
    
  533.     expect(ReactNoop).toMatchRenderedOutput(
    
  534.       <>
    
  535.         <span>Loading A</span>
    
  536.         <span>Loading B</span>
    
  537.         <span>Loading C</span>
    
  538.       </>,
    
  539.     );
    
  540. 
    
  541.     await act(() => C.resolve());
    
  542.     assertLog(['A', 'B', 'C']);
    
  543. 
    
  544.     expect(ReactNoop).toMatchRenderedOutput(
    
  545.       <>
    
  546.         <span>A</span>
    
  547.         <span>B</span>
    
  548.         <span>C</span>
    
  549.       </>,
    
  550.     );
    
  551.   });
    
  552. 
    
  553.   // @gate enableSuspenseList
    
  554.   it('displays all "together" in nested SuspenseLists where the inner is default', async () => {
    
  555.     const A = createAsyncText('A');
    
  556.     const B = createAsyncText('B');
    
  557.     const C = createAsyncText('C');
    
  558. 
    
  559.     function Foo() {
    
  560.       return (
    
  561.         <SuspenseList revealOrder="together">
    
  562.           <Suspense fallback={<Text text="Loading A" />}>
    
  563.             <A />
    
  564.           </Suspense>
    
  565.           <SuspenseList>
    
  566.             <Suspense fallback={<Text text="Loading B" />}>
    
  567.               <B />
    
  568.             </Suspense>
    
  569.             <Suspense fallback={<Text text="Loading C" />}>
    
  570.               <C />
    
  571.             </Suspense>
    
  572.           </SuspenseList>
    
  573.         </SuspenseList>
    
  574.       );
    
  575.     }
    
  576. 
    
  577.     await A.resolve();
    
  578.     await B.resolve();
    
  579. 
    
  580.     ReactNoop.render(<Foo />);
    
  581. 
    
  582.     await waitForAll([
    
  583.       'A',
    
  584.       'B',
    
  585.       'Suspend! [C]',
    
  586.       'Loading C',
    
  587.       'Loading A',
    
  588.       'Loading B',
    
  589.       'Loading C',
    
  590.     ]);
    
  591. 
    
  592.     expect(ReactNoop).toMatchRenderedOutput(
    
  593.       <>
    
  594.         <span>Loading A</span>
    
  595.         <span>Loading B</span>
    
  596.         <span>Loading C</span>
    
  597.       </>,
    
  598.     );
    
  599. 
    
  600.     await act(() => C.resolve());
    
  601.     assertLog(['A', 'B', 'C']);
    
  602. 
    
  603.     expect(ReactNoop).toMatchRenderedOutput(
    
  604.       <>
    
  605.         <span>A</span>
    
  606.         <span>B</span>
    
  607.         <span>C</span>
    
  608.       </>,
    
  609.     );
    
  610.   });
    
  611. 
    
  612.   // @gate enableSuspenseList
    
  613.   it('displays all "together" during an update', async () => {
    
  614.     const A = createAsyncText('A');
    
  615.     const B = createAsyncText('B');
    
  616.     const C = createAsyncText('C');
    
  617.     const D = createAsyncText('D');
    
  618. 
    
  619.     function Foo({step}) {
    
  620.       return (
    
  621.         <SuspenseList revealOrder="together">
    
  622.           {step === 0 && (
    
  623.             <Suspense fallback={<Text text="Loading A" />}>
    
  624.               <A />
    
  625.             </Suspense>
    
  626.           )}
    
  627.           {step === 0 && (
    
  628.             <Suspense fallback={<Text text="Loading B" />}>
    
  629.               <B />
    
  630.             </Suspense>
    
  631.           )}
    
  632.           {step === 1 && (
    
  633.             <Suspense fallback={<Text text="Loading C" />}>
    
  634.               <C />
    
  635.             </Suspense>
    
  636.           )}
    
  637.           {step === 1 && (
    
  638.             <Suspense fallback={<Text text="Loading D" />}>
    
  639.               <D />
    
  640.             </Suspense>
    
  641.           )}
    
  642.         </SuspenseList>
    
  643.       );
    
  644.     }
    
  645. 
    
  646.     // Mount
    
  647.     await A.resolve();
    
  648.     ReactNoop.render(<Foo step={0} />);
    
  649.     await waitForAll([
    
  650.       'A',
    
  651.       'Suspend! [B]',
    
  652.       'Loading B',
    
  653.       'Loading A',
    
  654.       'Loading B',
    
  655.     ]);
    
  656.     expect(ReactNoop).toMatchRenderedOutput(
    
  657.       <>
    
  658.         <span>Loading A</span>
    
  659.         <span>Loading B</span>
    
  660.       </>,
    
  661.     );
    
  662.     await act(() => B.resolve());
    
  663.     assertLog(['A', 'B']);
    
  664.     expect(ReactNoop).toMatchRenderedOutput(
    
  665.       <>
    
  666.         <span>A</span>
    
  667.         <span>B</span>
    
  668.       </>,
    
  669.     );
    
  670. 
    
  671.     // Update
    
  672.     await C.resolve();
    
  673.     ReactNoop.render(<Foo step={1} />);
    
  674.     await waitForAll([
    
  675.       'C',
    
  676.       'Suspend! [D]',
    
  677.       'Loading D',
    
  678.       'Loading C',
    
  679.       'Loading D',
    
  680.     ]);
    
  681.     expect(ReactNoop).toMatchRenderedOutput(
    
  682.       <>
    
  683.         <span>Loading C</span>
    
  684.         <span>Loading D</span>
    
  685.       </>,
    
  686.     );
    
  687.     await act(() => D.resolve());
    
  688.     assertLog(['C', 'D']);
    
  689.     expect(ReactNoop).toMatchRenderedOutput(
    
  690.       <>
    
  691.         <span>C</span>
    
  692.         <span>D</span>
    
  693.       </>,
    
  694.     );
    
  695.   });
    
  696. 
    
  697.   // @gate enableSuspenseList && enableSuspenseAvoidThisFallback
    
  698.   it('avoided boundaries can be coordinate with SuspenseList', async () => {
    
  699.     const A = createAsyncText('A');
    
  700.     const B = createAsyncText('B');
    
  701.     const C = createAsyncText('C');
    
  702. 
    
  703.     function Foo({showMore}) {
    
  704.       return (
    
  705.         <Suspense fallback={<Text text="Loading" />}>
    
  706.           <SuspenseList revealOrder="together">
    
  707.             <Suspense
    
  708.               unstable_avoidThisFallback={true}
    
  709.               fallback={<Text text="Loading A" />}>
    
  710.               <A />
    
  711.             </Suspense>
    
  712.             {showMore ? (
    
  713.               <>
    
  714.                 <Suspense
    
  715.                   unstable_avoidThisFallback={true}
    
  716.                   fallback={<Text text="Loading B" />}>
    
  717.                   <B />
    
  718.                 </Suspense>
    
  719.                 <Suspense
    
  720.                   unstable_avoidThisFallback={true}
    
  721.                   fallback={<Text text="Loading C" />}>
    
  722.                   <C />
    
  723.                 </Suspense>
    
  724.               </>
    
  725.             ) : null}
    
  726.           </SuspenseList>
    
  727.         </Suspense>
    
  728.       );
    
  729.     }
    
  730. 
    
  731.     ReactNoop.render(<Foo />);
    
  732. 
    
  733.     await waitForAll(['Suspend! [A]', 'Loading']);
    
  734. 
    
  735.     expect(ReactNoop).toMatchRenderedOutput(<span>Loading</span>);
    
  736. 
    
  737.     await act(() => A.resolve());
    
  738.     assertLog(['A']);
    
  739. 
    
  740.     expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
    
  741. 
    
  742.     // Let's do an update that should consult the avoided boundaries.
    
  743.     ReactNoop.render(<Foo showMore={true} />);
    
  744. 
    
  745.     await waitForAll([
    
  746.       'A',
    
  747.       'Suspend! [B]',
    
  748.       'Loading B',
    
  749.       'Suspend! [C]',
    
  750.       'Loading C',
    
  751.       'A',
    
  752.       'Loading B',
    
  753.       'Loading C',
    
  754.     ]);
    
  755. 
    
  756.     // This will suspend, since the boundaries are avoided. Give them
    
  757.     // time to display their loading states.
    
  758.     jest.advanceTimersByTime(500);
    
  759. 
    
  760.     // A is already showing content so it doesn't turn into a fallback.
    
  761.     expect(ReactNoop).toMatchRenderedOutput(
    
  762.       <>
    
  763.         <span>A</span>
    
  764.         <span>Loading B</span>
    
  765.         <span>Loading C</span>
    
  766.       </>,
    
  767.     );
    
  768. 
    
  769.     await act(() => B.resolve());
    
  770.     assertLog(['B', 'Suspend! [C]']);
    
  771. 
    
  772.     // Even though we could now show B, we're still waiting on C.
    
  773.     expect(ReactNoop).toMatchRenderedOutput(
    
  774.       <>
    
  775.         <span>A</span>
    
  776.         <span>Loading B</span>
    
  777.         <span>Loading C</span>
    
  778.       </>,
    
  779.     );
    
  780. 
    
  781.     await act(() => C.resolve());
    
  782.     assertLog(['B', 'C']);
    
  783. 
    
  784.     expect(ReactNoop).toMatchRenderedOutput(
    
  785.       <>
    
  786.         <span>A</span>
    
  787.         <span>B</span>
    
  788.         <span>C</span>
    
  789.       </>,
    
  790.     );
    
  791.   });
    
  792. 
    
  793.   // @gate enableSuspenseList
    
  794.   it('boundaries without fallbacks can be coordinate with SuspenseList', async () => {
    
  795.     const A = createAsyncText('A');
    
  796.     const B = createAsyncText('B');
    
  797.     const C = createAsyncText('C');
    
  798. 
    
  799.     function Foo({showMore}) {
    
  800.       return (
    
  801.         <Suspense fallback={<Text text="Loading" />}>
    
  802.           <SuspenseList revealOrder="together">
    
  803.             <Suspense>
    
  804.               <A />
    
  805.             </Suspense>
    
  806.             {showMore ? (
    
  807.               <>
    
  808.                 <Suspense>
    
  809.                   <B />
    
  810.                 </Suspense>
    
  811.                 <Suspense>
    
  812.                   <C />
    
  813.                 </Suspense>
    
  814.               </>
    
  815.             ) : null}
    
  816.           </SuspenseList>
    
  817.         </Suspense>
    
  818.       );
    
  819.     }
    
  820. 
    
  821.     ReactNoop.render(<Foo />);
    
  822. 
    
  823.     await waitForAll([
    
  824.       'Suspend! [A]',
    
  825.       // null
    
  826.     ]);
    
  827. 
    
  828.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  829. 
    
  830.     await act(() => A.resolve());
    
  831.     assertLog(['A']);
    
  832. 
    
  833.     expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
    
  834. 
    
  835.     // Let's do an update that should consult the avoided boundaries.
    
  836.     ReactNoop.render(<Foo showMore={true} />);
    
  837. 
    
  838.     await waitForAll([
    
  839.       'A',
    
  840.       'Suspend! [B]',
    
  841.       // null
    
  842.       'Suspend! [C]',
    
  843.       // null
    
  844.       'A',
    
  845.       // null
    
  846.       // null
    
  847.     ]);
    
  848. 
    
  849.     // This will suspend, since the boundaries are avoided. Give them
    
  850.     // time to display their loading states.
    
  851.     jest.advanceTimersByTime(500);
    
  852. 
    
  853.     // A is already showing content so it doesn't turn into a fallback.
    
  854.     expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
    
  855. 
    
  856.     await act(() => B.resolve());
    
  857.     assertLog(['B', 'Suspend! [C]']);
    
  858. 
    
  859.     // Even though we could now show B, we're still waiting on C.
    
  860.     expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
    
  861. 
    
  862.     await act(() => C.resolve());
    
  863.     assertLog(['B', 'C']);
    
  864. 
    
  865.     expect(ReactNoop).toMatchRenderedOutput(
    
  866.       <>
    
  867.         <span>A</span>
    
  868.         <span>B</span>
    
  869.         <span>C</span>
    
  870.       </>,
    
  871.     );
    
  872.   });
    
  873. 
    
  874.   // @gate enableSuspenseList
    
  875.   it('displays each items in "forwards" order', async () => {
    
  876.     const A = createAsyncText('A');
    
  877.     const B = createAsyncText('B');
    
  878.     const C = createAsyncText('C');
    
  879. 
    
  880.     function Foo() {
    
  881.       return (
    
  882.         <SuspenseList revealOrder="forwards">
    
  883.           <Suspense fallback={<Text text="Loading A" />}>
    
  884.             <A />
    
  885.           </Suspense>
    
  886.           <Suspense fallback={<Text text="Loading B" />}>
    
  887.             <B />
    
  888.           </Suspense>
    
  889.           <Suspense fallback={<Text text="Loading C" />}>
    
  890.             <C />
    
  891.           </Suspense>
    
  892.         </SuspenseList>
    
  893.       );
    
  894.     }
    
  895. 
    
  896.     await C.resolve();
    
  897. 
    
  898.     ReactNoop.render(<Foo />);
    
  899. 
    
  900.     await waitForAll(['Suspend! [A]', 'Loading A', 'Loading B', 'Loading C']);
    
  901. 
    
  902.     expect(ReactNoop).toMatchRenderedOutput(
    
  903.       <>
    
  904.         <span>Loading A</span>
    
  905.         <span>Loading B</span>
    
  906.         <span>Loading C</span>
    
  907.       </>,
    
  908.     );
    
  909. 
    
  910.     await act(() => A.resolve());
    
  911.     assertLog(['A', 'Suspend! [B]']);
    
  912. 
    
  913.     expect(ReactNoop).toMatchRenderedOutput(
    
  914.       <>
    
  915.         <span>A</span>
    
  916.         <span>Loading B</span>
    
  917.         <span>Loading C</span>
    
  918.       </>,
    
  919.     );
    
  920. 
    
  921.     await act(() => B.resolve());
    
  922.     assertLog(['B', 'C']);
    
  923. 
    
  924.     expect(ReactNoop).toMatchRenderedOutput(
    
  925.       <>
    
  926.         <span>A</span>
    
  927.         <span>B</span>
    
  928.         <span>C</span>
    
  929.       </>,
    
  930.     );
    
  931.   });
    
  932. 
    
  933.   // @gate enableSuspenseList
    
  934.   it('displays each items in "backwards" order', async () => {
    
  935.     const A = createAsyncText('A');
    
  936.     const B = createAsyncText('B');
    
  937.     const C = createAsyncText('C');
    
  938. 
    
  939.     function Foo() {
    
  940.       return (
    
  941.         <SuspenseList revealOrder="backwards">
    
  942.           <Suspense fallback={<Text text="Loading A" />}>
    
  943.             <A />
    
  944.           </Suspense>
    
  945.           <Suspense fallback={<Text text="Loading B" />}>
    
  946.             <B />
    
  947.           </Suspense>
    
  948.           <Suspense fallback={<Text text="Loading C" />}>
    
  949.             <C />
    
  950.           </Suspense>
    
  951.         </SuspenseList>
    
  952.       );
    
  953.     }
    
  954. 
    
  955.     await A.resolve();
    
  956. 
    
  957.     ReactNoop.render(<Foo />);
    
  958. 
    
  959.     await waitForAll(['Suspend! [C]', 'Loading C', 'Loading B', 'Loading A']);
    
  960. 
    
  961.     expect(ReactNoop).toMatchRenderedOutput(
    
  962.       <>
    
  963.         <span>Loading A</span>
    
  964.         <span>Loading B</span>
    
  965.         <span>Loading C</span>
    
  966.       </>,
    
  967.     );
    
  968. 
    
  969.     await act(() => C.resolve());
    
  970.     assertLog(['C', 'Suspend! [B]']);
    
  971. 
    
  972.     expect(ReactNoop).toMatchRenderedOutput(
    
  973.       <>
    
  974.         <span>Loading A</span>
    
  975.         <span>Loading B</span>
    
  976.         <span>C</span>
    
  977.       </>,
    
  978.     );
    
  979. 
    
  980.     await act(() => B.resolve());
    
  981.     assertLog(['B', 'A']);
    
  982. 
    
  983.     expect(ReactNoop).toMatchRenderedOutput(
    
  984.       <>
    
  985.         <span>A</span>
    
  986.         <span>B</span>
    
  987.         <span>C</span>
    
  988.       </>,
    
  989.     );
    
  990.   });
    
  991. 
    
  992.   // @gate enableSuspenseList
    
  993.   it('displays added row at the top "together" and the bottom in "forwards" order', async () => {
    
  994.     const A = createAsyncText('A');
    
  995.     const B = createAsyncText('B');
    
  996.     const C = createAsyncText('C');
    
  997.     const D = createAsyncText('D');
    
  998.     const E = createAsyncText('E');
    
  999.     const F = createAsyncText('F');
    
  1000. 
    
  1001.     function Foo({items}) {
    
  1002.       return (
    
  1003.         <SuspenseList revealOrder="forwards">
    
  1004.           {items.map(([key, Component]) => (
    
  1005.             <Suspense key={key} fallback={<Text text={'Loading ' + key} />}>
    
  1006.               <Component />
    
  1007.             </Suspense>
    
  1008.           ))}
    
  1009.         </SuspenseList>
    
  1010.       );
    
  1011.     }
    
  1012. 
    
  1013.     await B.resolve();
    
  1014.     await D.resolve();
    
  1015. 
    
  1016.     ReactNoop.render(
    
  1017.       <Foo
    
  1018.         items={[
    
  1019.           ['B', B],
    
  1020.           ['D', D],
    
  1021.         ]}
    
  1022.       />,
    
  1023.     );
    
  1024. 
    
  1025.     await waitForAll(['B', 'D']);
    
  1026. 
    
  1027.     expect(ReactNoop).toMatchRenderedOutput(
    
  1028.       <>
    
  1029.         <span>B</span>
    
  1030.         <span>D</span>
    
  1031.       </>,
    
  1032.     );
    
  1033. 
    
  1034.     // Insert items in the beginning, middle and end.
    
  1035.     ReactNoop.render(
    
  1036.       <Foo
    
  1037.         items={[
    
  1038.           ['A', A],
    
  1039.           ['B', B],
    
  1040.           ['C', C],
    
  1041.           ['D', D],
    
  1042.           ['E', E],
    
  1043.           ['F', F],
    
  1044.         ]}
    
  1045.       />,
    
  1046.     );
    
  1047. 
    
  1048.     await waitForAll([
    
  1049.       'Suspend! [A]',
    
  1050.       'Loading A',
    
  1051.       'B',
    
  1052.       'Suspend! [C]',
    
  1053.       'Loading C',
    
  1054.       'D',
    
  1055.       'Loading A',
    
  1056.       'B',
    
  1057.       'Loading C',
    
  1058.       'D',
    
  1059.       'Loading E',
    
  1060.       'Loading F',
    
  1061.     ]);
    
  1062. 
    
  1063.     expect(ReactNoop).toMatchRenderedOutput(
    
  1064.       <>
    
  1065.         <span>Loading A</span>
    
  1066.         <span>B</span>
    
  1067.         <span>Loading C</span>
    
  1068.         <span>D</span>
    
  1069.         <span>Loading E</span>
    
  1070.         <span>Loading F</span>
    
  1071.       </>,
    
  1072.     );
    
  1073. 
    
  1074.     await act(() => A.resolve());
    
  1075.     assertLog(['A', 'Suspend! [C]']);
    
  1076. 
    
  1077.     // Even though we could show A, it is still in a fallback state because
    
  1078.     // C is not yet resolved. We need to resolve everything in the head first.
    
  1079.     expect(ReactNoop).toMatchRenderedOutput(
    
  1080.       <>
    
  1081.         <span>Loading A</span>
    
  1082.         <span>B</span>
    
  1083.         <span>Loading C</span>
    
  1084.         <span>D</span>
    
  1085.         <span>Loading E</span>
    
  1086.         <span>Loading F</span>
    
  1087.       </>,
    
  1088.     );
    
  1089. 
    
  1090.     await act(() => C.resolve());
    
  1091.     assertLog(['A', 'C', 'Suspend! [E]']);
    
  1092. 
    
  1093.     // We can now resolve the full head.
    
  1094.     expect(ReactNoop).toMatchRenderedOutput(
    
  1095.       <>
    
  1096.         <span>A</span>
    
  1097.         <span>B</span>
    
  1098.         <span>C</span>
    
  1099.         <span>D</span>
    
  1100.         <span>Loading E</span>
    
  1101.         <span>Loading F</span>
    
  1102.       </>,
    
  1103.     );
    
  1104. 
    
  1105.     await act(() => E.resolve());
    
  1106.     assertLog(['E', 'Suspend! [F]']);
    
  1107. 
    
  1108.     // In the tail we can resolve one-by-one.
    
  1109.     expect(ReactNoop).toMatchRenderedOutput(
    
  1110.       <>
    
  1111.         <span>A</span>
    
  1112.         <span>B</span>
    
  1113.         <span>C</span>
    
  1114.         <span>D</span>
    
  1115.         <span>E</span>
    
  1116.         <span>Loading F</span>
    
  1117.       </>,
    
  1118.     );
    
  1119. 
    
  1120.     await act(() => F.resolve());
    
  1121.     assertLog(['F']);
    
  1122.     expect(ReactNoop).toMatchRenderedOutput(
    
  1123.       <>
    
  1124.         <span>A</span>
    
  1125.         <span>B</span>
    
  1126.         <span>C</span>
    
  1127.         <span>D</span>
    
  1128.         <span>E</span>
    
  1129.         <span>F</span>
    
  1130.       </>,
    
  1131.     );
    
  1132. 
    
  1133.     // We can also delete some items.
    
  1134.     ReactNoop.render(
    
  1135.       <Foo
    
  1136.         items={[
    
  1137.           ['D', D],
    
  1138.           ['E', E],
    
  1139.           ['F', F],
    
  1140.         ]}
    
  1141.       />,
    
  1142.     );
    
  1143. 
    
  1144.     await waitForAll(['D', 'E', 'F']);
    
  1145. 
    
  1146.     expect(ReactNoop).toMatchRenderedOutput(
    
  1147.       <>
    
  1148.         <span>D</span>
    
  1149.         <span>E</span>
    
  1150.         <span>F</span>
    
  1151.       </>,
    
  1152.     );
    
  1153.   });
    
  1154. 
    
  1155.   // @gate enableSuspenseList
    
  1156.   it('displays added row at the top "together" and the bottom in "backwards" order', async () => {
    
  1157.     const A = createAsyncText('A');
    
  1158.     const B = createAsyncText('B');
    
  1159.     const D = createAsyncText('D');
    
  1160.     const F = createAsyncText('F');
    
  1161. 
    
  1162.     function createSyncText(text) {
    
  1163.       return function () {
    
  1164.         return <Text text={text} />;
    
  1165.       };
    
  1166.     }
    
  1167. 
    
  1168.     const As = createSyncText('A');
    
  1169.     const Bs = createSyncText('B');
    
  1170.     const Cs = createSyncText('C');
    
  1171.     const Ds = createSyncText('D');
    
  1172.     const Es = createSyncText('E');
    
  1173.     const Fs = createSyncText('F');
    
  1174. 
    
  1175.     function Foo({items}) {
    
  1176.       return (
    
  1177.         <SuspenseList revealOrder="backwards">
    
  1178.           {items.map(([key, Component]) => (
    
  1179.             <Suspense key={key} fallback={<Text text={'Loading ' + key} />}>
    
  1180.               <Component />
    
  1181.             </Suspense>
    
  1182.           ))}
    
  1183.         </SuspenseList>
    
  1184.       );
    
  1185.     }
    
  1186. 
    
  1187.     // The first pass doesn't suspend.
    
  1188.     ReactNoop.render(
    
  1189.       <Foo
    
  1190.         items={[
    
  1191.           ['A', As],
    
  1192.           ['B', Bs],
    
  1193.           ['C', Cs],
    
  1194.           ['D', Ds],
    
  1195.           ['E', Es],
    
  1196.           ['F', Fs],
    
  1197.         ]}
    
  1198.       />,
    
  1199.     );
    
  1200.     await waitForAll(['F', 'E', 'D', 'C', 'B', 'A']);
    
  1201.     expect(ReactNoop).toMatchRenderedOutput(
    
  1202.       <>
    
  1203.         <span>A</span>
    
  1204.         <span>B</span>
    
  1205.         <span>C</span>
    
  1206.         <span>D</span>
    
  1207.         <span>E</span>
    
  1208.         <span>F</span>
    
  1209.       </>,
    
  1210.     );
    
  1211. 
    
  1212.     // Update items in the beginning, middle and end to start suspending.
    
  1213.     ReactNoop.render(
    
  1214.       <Foo
    
  1215.         items={[
    
  1216.           ['A', A],
    
  1217.           ['B', B],
    
  1218.           ['C', Cs],
    
  1219.           ['D', D],
    
  1220.           ['E', Es],
    
  1221.           ['F', F],
    
  1222.         ]}
    
  1223.       />,
    
  1224.     );
    
  1225. 
    
  1226.     await waitForAll([
    
  1227.       'Suspend! [A]',
    
  1228.       'Loading A',
    
  1229.       'Suspend! [B]',
    
  1230.       'Loading B',
    
  1231.       'C',
    
  1232.       'Suspend! [D]',
    
  1233.       'Loading D',
    
  1234.       'E',
    
  1235.       'Suspend! [F]',
    
  1236.       'Loading F',
    
  1237.       'Suspend! [A]',
    
  1238.       'Loading A',
    
  1239.       'Suspend! [B]',
    
  1240.       'Loading B',
    
  1241.       'C',
    
  1242.       'Suspend! [D]',
    
  1243.       'Loading D',
    
  1244.       'E',
    
  1245.       'Suspend! [F]',
    
  1246.       'Loading F',
    
  1247.     ]);
    
  1248. 
    
  1249.     // This will suspend, since the boundaries are avoided. Give them
    
  1250.     // time to display their loading states.
    
  1251.     jest.advanceTimersByTime(500);
    
  1252. 
    
  1253.     expect(ReactNoop).toMatchRenderedOutput(
    
  1254.       <>
    
  1255.         <span hidden={true}>A</span>
    
  1256.         <span>Loading A</span>
    
  1257.         <span hidden={true}>B</span>
    
  1258.         <span>Loading B</span>
    
  1259.         <span>C</span>
    
  1260.         <span hidden={true}>D</span>
    
  1261.         <span>Loading D</span>
    
  1262.         <span>E</span>
    
  1263.         <span hidden={true}>F</span>
    
  1264.         <span>Loading F</span>
    
  1265.       </>,
    
  1266.     );
    
  1267. 
    
  1268.     await F.resolve();
    
  1269. 
    
  1270.     await waitForAll(['Suspend! [D]', 'F']);
    
  1271. 
    
  1272.     // Even though we could show F, it is still in a fallback state because
    
  1273.     // E is not yet resolved. We need to resolve everything in the head first.
    
  1274.     expect(ReactNoop).toMatchRenderedOutput(
    
  1275.       <>
    
  1276.         <span hidden={true}>A</span>
    
  1277.         <span>Loading A</span>
    
  1278.         <span hidden={true}>B</span>
    
  1279.         <span>Loading B</span>
    
  1280.         <span>C</span>
    
  1281.         <span hidden={true}>D</span>
    
  1282.         <span>Loading D</span>
    
  1283.         <span>E</span>
    
  1284.         <span hidden={true}>F</span>
    
  1285.         <span>Loading F</span>
    
  1286.       </>,
    
  1287.     );
    
  1288. 
    
  1289.     await act(() => D.resolve());
    
  1290.     assertLog(['D', 'F', 'Suspend! [B]']);
    
  1291. 
    
  1292.     // We can now resolve the full head.
    
  1293.     expect(ReactNoop).toMatchRenderedOutput(
    
  1294.       <>
    
  1295.         <span hidden={true}>A</span>
    
  1296.         <span>Loading A</span>
    
  1297.         <span hidden={true}>B</span>
    
  1298.         <span>Loading B</span>
    
  1299.         <span>C</span>
    
  1300.         <span>D</span>
    
  1301.         <span>E</span>
    
  1302.         <span>F</span>
    
  1303.       </>,
    
  1304.     );
    
  1305. 
    
  1306.     await act(() => B.resolve());
    
  1307.     assertLog(['B', 'Suspend! [A]']);
    
  1308. 
    
  1309.     // In the tail we can resolve one-by-one.
    
  1310.     expect(ReactNoop).toMatchRenderedOutput(
    
  1311.       <>
    
  1312.         <span hidden={true}>A</span>
    
  1313.         <span>Loading A</span>
    
  1314.         <span>B</span>
    
  1315.         <span>C</span>
    
  1316.         <span>D</span>
    
  1317.         <span>E</span>
    
  1318.         <span>F</span>
    
  1319.       </>,
    
  1320.     );
    
  1321. 
    
  1322.     await act(() => A.resolve());
    
  1323.     assertLog(['A']);
    
  1324. 
    
  1325.     expect(ReactNoop).toMatchRenderedOutput(
    
  1326.       <>
    
  1327.         <span>A</span>
    
  1328.         <span>B</span>
    
  1329.         <span>C</span>
    
  1330.         <span>D</span>
    
  1331.         <span>E</span>
    
  1332.         <span>F</span>
    
  1333.       </>,
    
  1334.     );
    
  1335.   });
    
  1336. 
    
  1337.   // @gate enableSuspenseList
    
  1338.   it('switches to rendering fallbacks if the tail takes long CPU time', async () => {
    
  1339.     function Foo() {
    
  1340.       return (
    
  1341.         <SuspenseList revealOrder="forwards">
    
  1342.           <Suspense fallback={<Text text="Loading A" />}>
    
  1343.             <Text text="A" />
    
  1344.           </Suspense>
    
  1345.           <Suspense fallback={<Text text="Loading B" />}>
    
  1346.             <Text text="B" />
    
  1347.           </Suspense>
    
  1348.           <Suspense fallback={<Text text="Loading C" />}>
    
  1349.             <Text text="C" />
    
  1350.           </Suspense>
    
  1351.         </SuspenseList>
    
  1352.       );
    
  1353.     }
    
  1354. 
    
  1355.     // This render is only CPU bound. Nothing suspends.
    
  1356.     await act(async () => {
    
  1357.       React.startTransition(() => {
    
  1358.         ReactNoop.render(<Foo />);
    
  1359.       });
    
  1360. 
    
  1361.       await waitFor(['A']);
    
  1362. 
    
  1363.       Scheduler.unstable_advanceTime(200);
    
  1364.       jest.advanceTimersByTime(200);
    
  1365. 
    
  1366.       await waitFor(['B']);
    
  1367. 
    
  1368.       Scheduler.unstable_advanceTime(300);
    
  1369.       jest.advanceTimersByTime(300);
    
  1370. 
    
  1371.       // We've still not been able to show anything on the screen even though
    
  1372.       // we have two items ready.
    
  1373.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  1374. 
    
  1375.       // Time has now elapsed for so long that we're just going to give up
    
  1376.       // rendering the rest of the content. So that we can at least show
    
  1377.       // something.
    
  1378.       await waitFor([
    
  1379.         'Loading C',
    
  1380.         'C', // I'll flush through into the next render so that the first commits.
    
  1381.       ]);
    
  1382. 
    
  1383.       expect(ReactNoop).toMatchRenderedOutput(
    
  1384.         <>
    
  1385.           <span>A</span>
    
  1386.           <span>B</span>
    
  1387.           <span>Loading C</span>
    
  1388.         </>,
    
  1389.       );
    
  1390.     });
    
  1391.     // Then we do a second pass to commit the last item.
    
  1392.     assertLog([]);
    
  1393.     expect(ReactNoop).toMatchRenderedOutput(
    
  1394.       <>
    
  1395.         <span>A</span>
    
  1396.         <span>B</span>
    
  1397.         <span>C</span>
    
  1398.       </>,
    
  1399.     );
    
  1400.   });
    
  1401. 
    
  1402.   // @gate enableSuspenseList
    
  1403.   it('only shows one loading state at a time for "collapsed" tail insertions', async () => {
    
  1404.     const A = createAsyncText('A');
    
  1405.     const B = createAsyncText('B');
    
  1406.     const C = createAsyncText('C');
    
  1407. 
    
  1408.     function Foo() {
    
  1409.       return (
    
  1410.         <SuspenseList revealOrder="forwards" tail="collapsed">
    
  1411.           <Suspense fallback={<Text text="Loading A" />}>
    
  1412.             <A />
    
  1413.           </Suspense>
    
  1414.           <Suspense fallback={<Text text="Loading B" />}>
    
  1415.             <B />
    
  1416.           </Suspense>
    
  1417.           <Suspense fallback={<Text text="Loading C" />}>
    
  1418.             <C />
    
  1419.           </Suspense>
    
  1420.         </SuspenseList>
    
  1421.       );
    
  1422.     }
    
  1423. 
    
  1424.     ReactNoop.render(<Foo />);
    
  1425. 
    
  1426.     await waitForAll(['Suspend! [A]', 'Loading A']);
    
  1427. 
    
  1428.     expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
    
  1429. 
    
  1430.     await A.resolve();
    
  1431. 
    
  1432.     await waitForAll(['A', 'Suspend! [B]', 'Loading B']);
    
  1433. 
    
  1434.     // Incremental loading is suspended.
    
  1435.     jest.advanceTimersByTime(500);
    
  1436. 
    
  1437.     expect(ReactNoop).toMatchRenderedOutput(
    
  1438.       <>
    
  1439.         <span>A</span>
    
  1440.         <span>Loading B</span>
    
  1441.       </>,
    
  1442.     );
    
  1443. 
    
  1444.     await B.resolve();
    
  1445. 
    
  1446.     await waitForAll(['B', 'Suspend! [C]', 'Loading C']);
    
  1447. 
    
  1448.     // Incremental loading is suspended.
    
  1449.     jest.advanceTimersByTime(500);
    
  1450. 
    
  1451.     expect(ReactNoop).toMatchRenderedOutput(
    
  1452.       <>
    
  1453.         <span>A</span>
    
  1454.         <span>B</span>
    
  1455.         <span>Loading C</span>
    
  1456.       </>,
    
  1457.     );
    
  1458. 
    
  1459.     await act(() => C.resolve());
    
  1460.     await assertLog(['C']);
    
  1461. 
    
  1462.     expect(ReactNoop).toMatchRenderedOutput(
    
  1463.       <>
    
  1464.         <span>A</span>
    
  1465.         <span>B</span>
    
  1466.         <span>C</span>
    
  1467.       </>,
    
  1468.     );
    
  1469.   });
    
  1470. 
    
  1471.   // @gate enableSuspenseList
    
  1472.   it('warns if an unsupported tail option is used', async () => {
    
  1473.     function Foo() {
    
  1474.       return (
    
  1475.         <SuspenseList revealOrder="forwards" tail="collapse">
    
  1476.           <Suspense fallback="Loading">A</Suspense>
    
  1477.           <Suspense fallback="Loading">B</Suspense>
    
  1478.         </SuspenseList>
    
  1479.       );
    
  1480.     }
    
  1481. 
    
  1482.     await expect(async () => {
    
  1483.       await act(() => {
    
  1484.         ReactNoop.render(<Foo />);
    
  1485.       });
    
  1486.     }).toErrorDev([
    
  1487.       'Warning: "collapse" is not a supported value for tail on ' +
    
  1488.         '<SuspenseList />. Did you mean "collapsed" or "hidden"?' +
    
  1489.         '\n    in SuspenseList (at **)' +
    
  1490.         '\n    in Foo (at **)',
    
  1491.     ]);
    
  1492.   });
    
  1493. 
    
  1494.   // @gate enableSuspenseList
    
  1495.   it('warns if a tail option is used with "together"', async () => {
    
  1496.     function Foo() {
    
  1497.       return (
    
  1498.         <SuspenseList revealOrder="together" tail="collapsed">
    
  1499.           <Suspense fallback="Loading">Content</Suspense>
    
  1500.         </SuspenseList>
    
  1501.       );
    
  1502.     }
    
  1503. 
    
  1504.     await expect(async () => {
    
  1505.       await act(() => {
    
  1506.         ReactNoop.render(<Foo />);
    
  1507.       });
    
  1508.     }).toErrorDev([
    
  1509.       'Warning: <SuspenseList tail="collapsed" /> is only valid if ' +
    
  1510.         'revealOrder is "forwards" or "backwards". ' +
    
  1511.         'Did you mean to specify revealOrder="forwards"?' +
    
  1512.         '\n    in SuspenseList (at **)' +
    
  1513.         '\n    in Foo (at **)',
    
  1514.     ]);
    
  1515.   });
    
  1516. 
    
  1517.   // @gate enableSuspenseList
    
  1518.   it('renders one "collapsed" fallback even if CPU time elapsed', async () => {
    
  1519.     function Foo() {
    
  1520.       return (
    
  1521.         <SuspenseList revealOrder="forwards" tail="collapsed">
    
  1522.           <Suspense fallback={<Text text="Loading A" />}>
    
  1523.             <Text text="A" />
    
  1524.           </Suspense>
    
  1525.           <Suspense fallback={<Text text="Loading B" />}>
    
  1526.             <Text text="B" />
    
  1527.           </Suspense>
    
  1528.           <Suspense fallback={<Text text="Loading C" />}>
    
  1529.             <Text text="C" />
    
  1530.           </Suspense>
    
  1531.           <Suspense fallback={<Text text="Loading D" />}>
    
  1532.             <Text text="D" />
    
  1533.           </Suspense>
    
  1534.         </SuspenseList>
    
  1535.       );
    
  1536.     }
    
  1537. 
    
  1538.     // This render is only CPU bound. Nothing suspends.
    
  1539.     await act(async () => {
    
  1540.       React.startTransition(() => {
    
  1541.         ReactNoop.render(<Foo />);
    
  1542.       });
    
  1543. 
    
  1544.       await waitFor(['A']);
    
  1545. 
    
  1546.       Scheduler.unstable_advanceTime(200);
    
  1547.       jest.advanceTimersByTime(200);
    
  1548. 
    
  1549.       await waitFor(['B']);
    
  1550. 
    
  1551.       Scheduler.unstable_advanceTime(300);
    
  1552.       jest.advanceTimersByTime(300);
    
  1553. 
    
  1554.       // We've still not been able to show anything on the screen even though
    
  1555.       // we have two items ready.
    
  1556.       expect(ReactNoop).toMatchRenderedOutput(null);
    
  1557. 
    
  1558.       // Time has now elapsed for so long that we're just going to give up
    
  1559.       // rendering the rest of the content. So that we can at least show
    
  1560.       // something.
    
  1561.       await waitFor([
    
  1562.         'Loading C',
    
  1563.         'C', // I'll flush through into the next render so that the first commits.
    
  1564.       ]);
    
  1565. 
    
  1566.       expect(ReactNoop).toMatchRenderedOutput(
    
  1567.         <>
    
  1568.           <span>A</span>
    
  1569.           <span>B</span>
    
  1570.           <span>Loading C</span>
    
  1571.         </>,
    
  1572.       );
    
  1573.     });
    
  1574.     // Then we do a second pass to commit the last two items.
    
  1575.     assertLog(['D']);
    
  1576.     expect(ReactNoop).toMatchRenderedOutput(
    
  1577.       <>
    
  1578.         <span>A</span>
    
  1579.         <span>B</span>
    
  1580.         <span>C</span>
    
  1581.         <span>D</span>
    
  1582.       </>,
    
  1583.     );
    
  1584.   });
    
  1585. 
    
  1586.   // @gate enableSuspenseList
    
  1587.   it('adding to the middle does not collapse insertions (forwards)', async () => {
    
  1588.     const A = createAsyncText('A');
    
  1589.     const B = createAsyncText('B');
    
  1590.     const C = createAsyncText('C');
    
  1591.     const D = createAsyncText('D');
    
  1592.     const E = createAsyncText('E');
    
  1593.     const F = createAsyncText('F');
    
  1594. 
    
  1595.     function Foo({items}) {
    
  1596.       return (
    
  1597.         <SuspenseList revealOrder="forwards" tail="collapsed">
    
  1598.           {items.map(([key, Component]) => (
    
  1599.             <Suspense key={key} fallback={<Text text={'Loading ' + key} />}>
    
  1600.               <Component />
    
  1601.             </Suspense>
    
  1602.           ))}
    
  1603.         </SuspenseList>
    
  1604.       );
    
  1605.     }
    
  1606. 
    
  1607.     ReactNoop.render(
    
  1608.       <Foo
    
  1609.         items={[
    
  1610.           ['A', A],
    
  1611.           ['D', D],
    
  1612.         ]}
    
  1613.       />,
    
  1614.     );
    
  1615. 
    
  1616.     await A.resolve();
    
  1617.     await D.resolve();
    
  1618. 
    
  1619.     await waitForAll(['A', 'D']);
    
  1620. 
    
  1621.     // First render commits A and D.
    
  1622.     expect(ReactNoop).toMatchRenderedOutput(
    
  1623.       <>
    
  1624.         <span>A</span>
    
  1625.         <span>D</span>
    
  1626.       </>,
    
  1627.     );
    
  1628. 
    
  1629.     // For the second render, we're going to insert items in the middle and end.
    
  1630.     ReactNoop.render(
    
  1631.       <Foo
    
  1632.         items={[
    
  1633.           ['A', A],
    
  1634.           ['B', B],
    
  1635.           ['C', C],
    
  1636.           ['D', D],
    
  1637.           ['E', E],
    
  1638.           ['F', F],
    
  1639.         ]}
    
  1640.       />,
    
  1641.     );
    
  1642. 
    
  1643.     await waitForAll([
    
  1644.       'A',
    
  1645.       'Suspend! [B]',
    
  1646.       'Loading B',
    
  1647.       'Suspend! [C]',
    
  1648.       'Loading C',
    
  1649.       'D',
    
  1650.       'A',
    
  1651.       'Loading B',
    
  1652.       'Loading C',
    
  1653.       'D',
    
  1654.       'Loading E',
    
  1655.     ]);
    
  1656. 
    
  1657.     // B and C don't get collapsed, but F gets collapsed with E.
    
  1658.     expect(ReactNoop).toMatchRenderedOutput(
    
  1659.       <>
    
  1660.         <span>A</span>
    
  1661.         <span>Loading B</span>
    
  1662.         <span>Loading C</span>
    
  1663.         <span>D</span>
    
  1664.         <span>Loading E</span>
    
  1665.       </>,
    
  1666.     );
    
  1667. 
    
  1668.     await B.resolve();
    
  1669. 
    
  1670.     await waitForAll(['B', 'Suspend! [C]']);
    
  1671. 
    
  1672.     // Incremental loading is suspended.
    
  1673.     jest.advanceTimersByTime(500);
    
  1674. 
    
  1675.     // Even though B is unsuspended, it's still in loading state because
    
  1676.     // it is blocked by C.
    
  1677.     expect(ReactNoop).toMatchRenderedOutput(
    
  1678.       <>
    
  1679.         <span>A</span>
    
  1680.         <span>Loading B</span>
    
  1681.         <span>Loading C</span>
    
  1682.         <span>D</span>
    
  1683.         <span>Loading E</span>
    
  1684.       </>,
    
  1685.     );
    
  1686. 
    
  1687.     await C.resolve();
    
  1688.     await E.resolve();
    
  1689. 
    
  1690.     await waitForAll(['B', 'C', 'E', 'Suspend! [F]', 'Loading F']);
    
  1691. 
    
  1692.     jest.advanceTimersByTime(500);
    
  1693. 
    
  1694.     expect(ReactNoop).toMatchRenderedOutput(
    
  1695.       <>
    
  1696.         <span>A</span>
    
  1697.         <span>B</span>
    
  1698.         <span>C</span>
    
  1699.         <span>D</span>
    
  1700.         <span>E</span>
    
  1701.         <span>Loading F</span>
    
  1702.       </>,
    
  1703.     );
    
  1704. 
    
  1705.     await F.resolve();
    
  1706. 
    
  1707.     await waitForAll(['F']);
    
  1708. 
    
  1709.     jest.advanceTimersByTime(500);
    
  1710. 
    
  1711.     expect(ReactNoop).toMatchRenderedOutput(
    
  1712.       <>
    
  1713.         <span>A</span>
    
  1714.         <span>B</span>
    
  1715.         <span>C</span>
    
  1716.         <span>D</span>
    
  1717.         <span>E</span>
    
  1718.         <span>F</span>
    
  1719.       </>,
    
  1720.     );
    
  1721.   });
    
  1722. 
    
  1723.   // @gate enableSuspenseList
    
  1724.   it('adding to the middle does not collapse insertions (backwards)', async () => {
    
  1725.     const A = createAsyncText('A');
    
  1726.     const B = createAsyncText('B');
    
  1727.     const C = createAsyncText('C');
    
  1728.     const D = createAsyncText('D');
    
  1729.     const E = createAsyncText('E');
    
  1730.     const F = createAsyncText('F');
    
  1731. 
    
  1732.     function Foo({items}) {
    
  1733.       return (
    
  1734.         <SuspenseList revealOrder="backwards" tail="collapsed">
    
  1735.           {items.map(([key, Component]) => (
    
  1736.             <Suspense key={key} fallback={<Text text={'Loading ' + key} />}>
    
  1737.               <Component />
    
  1738.             </Suspense>
    
  1739.           ))}
    
  1740.         </SuspenseList>
    
  1741.       );
    
  1742.     }
    
  1743. 
    
  1744.     ReactNoop.render(
    
  1745.       <Foo
    
  1746.         items={[
    
  1747.           ['C', C],
    
  1748.           ['F', F],
    
  1749.         ]}
    
  1750.       />,
    
  1751.     );
    
  1752. 
    
  1753.     await C.resolve();
    
  1754.     await F.resolve();
    
  1755. 
    
  1756.     await waitForAll(['F', 'C']);
    
  1757. 
    
  1758.     // First render commits C and F.
    
  1759.     expect(ReactNoop).toMatchRenderedOutput(
    
  1760.       <>
    
  1761.         <span>C</span>
    
  1762.         <span>F</span>
    
  1763.       </>,
    
  1764.     );
    
  1765. 
    
  1766.     // For the second render, we're going to insert items in the middle and end.
    
  1767.     ReactNoop.render(
    
  1768.       <Foo
    
  1769.         items={[
    
  1770.           ['A', A],
    
  1771.           ['B', B],
    
  1772.           ['C', C],
    
  1773.           ['D', D],
    
  1774.           ['E', E],
    
  1775.           ['F', F],
    
  1776.         ]}
    
  1777.       />,
    
  1778.     );
    
  1779. 
    
  1780.     await waitForAll([
    
  1781.       'C',
    
  1782.       'Suspend! [D]',
    
  1783.       'Loading D',
    
  1784.       'Suspend! [E]',
    
  1785.       'Loading E',
    
  1786.       'F',
    
  1787.       'C',
    
  1788.       'Loading D',
    
  1789.       'Loading E',
    
  1790.       'F',
    
  1791.       'Loading B',
    
  1792.     ]);
    
  1793. 
    
  1794.     // D and E don't get collapsed, but A gets collapsed with B.
    
  1795.     expect(ReactNoop).toMatchRenderedOutput(
    
  1796.       <>
    
  1797.         <span>Loading B</span>
    
  1798.         <span>C</span>
    
  1799.         <span>Loading D</span>
    
  1800.         <span>Loading E</span>
    
  1801.         <span>F</span>
    
  1802.       </>,
    
  1803.     );
    
  1804. 
    
  1805.     await D.resolve();
    
  1806. 
    
  1807.     await waitForAll(['D', 'Suspend! [E]']);
    
  1808. 
    
  1809.     // Incremental loading is suspended.
    
  1810.     jest.advanceTimersByTime(500);
    
  1811. 
    
  1812.     // Even though D is unsuspended, it's still in loading state because
    
  1813.     // it is blocked by E.
    
  1814.     expect(ReactNoop).toMatchRenderedOutput(
    
  1815.       <>
    
  1816.         <span>Loading B</span>
    
  1817.         <span>C</span>
    
  1818.         <span>Loading D</span>
    
  1819.         <span>Loading E</span>
    
  1820.         <span>F</span>
    
  1821.       </>,
    
  1822.     );
    
  1823. 
    
  1824.     await C.resolve();
    
  1825.     await E.resolve();
    
  1826. 
    
  1827.     await B.resolve();
    
  1828.     await C.resolve();
    
  1829.     await D.resolve();
    
  1830.     await E.resolve();
    
  1831. 
    
  1832.     await waitForAll(['D', 'E', 'B', 'Suspend! [A]', 'Loading A']);
    
  1833. 
    
  1834.     jest.advanceTimersByTime(500);
    
  1835. 
    
  1836.     expect(ReactNoop).toMatchRenderedOutput(
    
  1837.       <>
    
  1838.         <span>Loading A</span>
    
  1839.         <span>B</span>
    
  1840.         <span>C</span>
    
  1841.         <span>D</span>
    
  1842.         <span>E</span>
    
  1843.         <span>F</span>
    
  1844.       </>,
    
  1845.     );
    
  1846. 
    
  1847.     await A.resolve();
    
  1848. 
    
  1849.     await waitForAll(['A']);
    
  1850. 
    
  1851.     jest.advanceTimersByTime(500);
    
  1852. 
    
  1853.     expect(ReactNoop).toMatchRenderedOutput(
    
  1854.       <>
    
  1855.         <span>A</span>
    
  1856.         <span>B</span>
    
  1857.         <span>C</span>
    
  1858.         <span>D</span>
    
  1859.         <span>E</span>
    
  1860.         <span>F</span>
    
  1861.       </>,
    
  1862.     );
    
  1863.   });
    
  1864. 
    
  1865.   // @gate enableSuspenseList
    
  1866.   it('adding to the middle of committed tail does not collapse insertions', async () => {
    
  1867.     const A = createAsyncText('A');
    
  1868.     const B = createAsyncText('B');
    
  1869.     const C = createAsyncText('C');
    
  1870.     const D = createAsyncText('D');
    
  1871.     const E = createAsyncText('E');
    
  1872.     const F = createAsyncText('F');
    
  1873. 
    
  1874.     function SyncD() {
    
  1875.       return <Text text="D" />;
    
  1876.     }
    
  1877. 
    
  1878.     function Foo({items}) {
    
  1879.       return (
    
  1880.         <SuspenseList revealOrder="forwards" tail="collapsed">
    
  1881.           {items.map(([key, Component]) => (
    
  1882.             <Suspense key={key} fallback={<Text text={'Loading ' + key} />}>
    
  1883.               <Component />
    
  1884.             </Suspense>
    
  1885.           ))}
    
  1886.         </SuspenseList>
    
  1887.       );
    
  1888.     }
    
  1889. 
    
  1890.     ReactNoop.render(
    
  1891.       <Foo
    
  1892.         items={[
    
  1893.           ['A', A],
    
  1894.           ['D', SyncD],
    
  1895.         ]}
    
  1896.       />,
    
  1897.     );
    
  1898. 
    
  1899.     await A.resolve();
    
  1900. 
    
  1901.     await waitForAll(['A', 'D']);
    
  1902. 
    
  1903.     // First render commits A and D.
    
  1904.     expect(ReactNoop).toMatchRenderedOutput(
    
  1905.       <>
    
  1906.         <span>A</span>
    
  1907.         <span>D</span>
    
  1908.       </>,
    
  1909.     );
    
  1910. 
    
  1911.     // For the second render, we're going to insert items in the middle and end.
    
  1912.     // Note that D now suspends even though it didn't in the first pass.
    
  1913.     ReactNoop.render(
    
  1914.       <Foo
    
  1915.         items={[
    
  1916.           ['A', A],
    
  1917.           ['B', B],
    
  1918.           ['C', C],
    
  1919.           ['D', D],
    
  1920.           ['E', E],
    
  1921.           ['F', F],
    
  1922.         ]}
    
  1923.       />,
    
  1924.     );
    
  1925. 
    
  1926.     await waitForAll([
    
  1927.       'A',
    
  1928.       'Suspend! [B]',
    
  1929.       'Loading B',
    
  1930.       'Suspend! [C]',
    
  1931.       'Loading C',
    
  1932.       'Suspend! [D]',
    
  1933.       'Loading D',
    
  1934.       'A',
    
  1935.       'Loading B',
    
  1936.       'Loading C',
    
  1937.       'Suspend! [D]',
    
  1938.       'Loading D',
    
  1939.       'Loading E',
    
  1940.     ]);
    
  1941. 
    
  1942.     // This is suspended due to the update to D causing a loading state.
    
  1943.     jest.advanceTimersByTime(500);
    
  1944. 
    
  1945.     // B and C don't get collapsed, but F gets collapsed with E.
    
  1946.     // Even though everything in the bottom of the list is suspended, we don't
    
  1947.     // collapse them because D was an update. Not an insertion.
    
  1948.     expect(ReactNoop).toMatchRenderedOutput(
    
  1949.       <>
    
  1950.         <span>A</span>
    
  1951.         <span>Loading B</span>
    
  1952.         <span>Loading C</span>
    
  1953.         <span hidden={true}>D</span>
    
  1954.         <span>Loading D</span>
    
  1955.         <span>Loading E</span>
    
  1956.       </>,
    
  1957.     );
    
  1958. 
    
  1959.     await B.resolve();
    
  1960. 
    
  1961.     await waitForAll(['B', 'Suspend! [C]']);
    
  1962. 
    
  1963.     // Incremental loading is suspended.
    
  1964.     jest.advanceTimersByTime(500);
    
  1965. 
    
  1966.     // B is able to unblock here because it's part of the tail.
    
  1967.     // If D was still visible it wouldn't be part of the tail
    
  1968.     // and would be blocked on C like in the other test.
    
  1969.     expect(ReactNoop).toMatchRenderedOutput(
    
  1970.       <>
    
  1971.         <span>A</span>
    
  1972.         <span>B</span>
    
  1973.         <span>Loading C</span>
    
  1974.         <span hidden={true}>D</span>
    
  1975.         <span>Loading D</span>
    
  1976.         <span>Loading E</span>
    
  1977.       </>,
    
  1978.     );
    
  1979. 
    
  1980.     await C.resolve();
    
  1981.     await D.resolve();
    
  1982.     await E.resolve();
    
  1983. 
    
  1984.     await waitForAll(['C', 'D', 'E', 'Suspend! [F]', 'Loading F']);
    
  1985. 
    
  1986.     jest.advanceTimersByTime(500);
    
  1987. 
    
  1988.     expect(ReactNoop).toMatchRenderedOutput(
    
  1989.       <>
    
  1990.         <span>A</span>
    
  1991.         <span>B</span>
    
  1992.         <span>C</span>
    
  1993.         <span>D</span>
    
  1994.         <span>E</span>
    
  1995.         <span>Loading F</span>
    
  1996.       </>,
    
  1997.     );
    
  1998. 
    
  1999.     await F.resolve();
    
  2000. 
    
  2001.     await waitForAll(['F']);
    
  2002. 
    
  2003.     jest.advanceTimersByTime(500);
    
  2004. 
    
  2005.     expect(ReactNoop).toMatchRenderedOutput(
    
  2006.       <>
    
  2007.         <span>A</span>
    
  2008.         <span>B</span>
    
  2009.         <span>C</span>
    
  2010.         <span>D</span>
    
  2011.         <span>E</span>
    
  2012.         <span>F</span>
    
  2013.       </>,
    
  2014.     );
    
  2015.   });
    
  2016. 
    
  2017.   // @gate enableSuspenseList
    
  2018.   it('only shows no initial loading state "hidden" tail insertions', async () => {
    
  2019.     const A = createAsyncText('A');
    
  2020.     const B = createAsyncText('B');
    
  2021.     const C = createAsyncText('C');
    
  2022. 
    
  2023.     function Foo() {
    
  2024.       return (
    
  2025.         <SuspenseList revealOrder="forwards" tail="hidden">
    
  2026.           <Suspense fallback={<Text text="Loading A" />}>
    
  2027.             <A />
    
  2028.           </Suspense>
    
  2029.           <Suspense fallback={<Text text="Loading B" />}>
    
  2030.             <B />
    
  2031.           </Suspense>
    
  2032.           <Suspense fallback={<Text text="Loading C" />}>
    
  2033.             <C />
    
  2034.           </Suspense>
    
  2035.         </SuspenseList>
    
  2036.       );
    
  2037.     }
    
  2038. 
    
  2039.     ReactNoop.render(<Foo />);
    
  2040. 
    
  2041.     await waitForAll(['Suspend! [A]', 'Loading A']);
    
  2042. 
    
  2043.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  2044. 
    
  2045.     await A.resolve();
    
  2046. 
    
  2047.     await waitForAll(['A', 'Suspend! [B]', 'Loading B']);
    
  2048. 
    
  2049.     // Incremental loading is suspended.
    
  2050.     jest.advanceTimersByTime(500);
    
  2051. 
    
  2052.     expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
    
  2053. 
    
  2054.     await act(() => B.resolve());
    
  2055.     assertLog(['B', 'Suspend! [C]', 'Loading C']);
    
  2056. 
    
  2057.     // Incremental loading is suspended.
    
  2058.     jest.advanceTimersByTime(500);
    
  2059. 
    
  2060.     expect(ReactNoop).toMatchRenderedOutput(
    
  2061.       <>
    
  2062.         <span>A</span>
    
  2063.         <span>B</span>
    
  2064.       </>,
    
  2065.     );
    
  2066. 
    
  2067.     await act(() => C.resolve());
    
  2068.     assertLog(['C']);
    
  2069. 
    
  2070.     expect(ReactNoop).toMatchRenderedOutput(
    
  2071.       <>
    
  2072.         <span>A</span>
    
  2073.         <span>B</span>
    
  2074.         <span>C</span>
    
  2075.       </>,
    
  2076.     );
    
  2077.   });
    
  2078. 
    
  2079.   // @gate enableSuspenseList
    
  2080.   it('eventually resolves a nested forwards suspense list', async () => {
    
  2081.     const B = createAsyncText('B');
    
  2082. 
    
  2083.     function Foo() {
    
  2084.       return (
    
  2085.         <SuspenseList revealOrder="together">
    
  2086.           <SuspenseList revealOrder="forwards">
    
  2087.             <Suspense fallback={<Text text="Loading A" />}>
    
  2088.               <Text text="A" />
    
  2089.             </Suspense>
    
  2090.             <Suspense fallback={<Text text="Loading B" />}>
    
  2091.               <B />
    
  2092.             </Suspense>
    
  2093.             <Suspense fallback={<Text text="Loading C" />}>
    
  2094.               <Text text="C" />
    
  2095.             </Suspense>
    
  2096.           </SuspenseList>
    
  2097.           <Suspense fallback={<Text text="Loading D" />}>
    
  2098.             <Text text="D" />
    
  2099.           </Suspense>
    
  2100.         </SuspenseList>
    
  2101.       );
    
  2102.     }
    
  2103. 
    
  2104.     ReactNoop.render(<Foo />);
    
  2105. 
    
  2106.     await waitForAll([
    
  2107.       'A',
    
  2108.       'Suspend! [B]',
    
  2109.       'Loading B',
    
  2110.       'Loading C',
    
  2111.       'D',
    
  2112.       // The second pass forces the fallbacks
    
  2113.       'Loading A',
    
  2114.       'Loading B',
    
  2115.       'Loading C',
    
  2116.       'Loading D',
    
  2117.     ]);
    
  2118. 
    
  2119.     expect(ReactNoop).toMatchRenderedOutput(
    
  2120.       <>
    
  2121.         <span>Loading A</span>
    
  2122.         <span>Loading B</span>
    
  2123.         <span>Loading C</span>
    
  2124.         <span>Loading D</span>
    
  2125.       </>,
    
  2126.     );
    
  2127. 
    
  2128.     await act(() => B.resolve());
    
  2129.     assertLog(['A', 'B', 'C', 'D']);
    
  2130. 
    
  2131.     expect(ReactNoop).toMatchRenderedOutput(
    
  2132.       <>
    
  2133.         <span>A</span>
    
  2134.         <span>B</span>
    
  2135.         <span>C</span>
    
  2136.         <span>D</span>
    
  2137.       </>,
    
  2138.     );
    
  2139.   });
    
  2140. 
    
  2141.   // @gate enableSuspenseList
    
  2142.   it('eventually resolves a nested forwards suspense list with a hidden tail', async () => {
    
  2143.     const B = createAsyncText('B');
    
  2144. 
    
  2145.     function Foo() {
    
  2146.       return (
    
  2147.         <SuspenseList revealOrder="together">
    
  2148.           <SuspenseList revealOrder="forwards" tail="hidden">
    
  2149.             <Suspense fallback={<Text text="Loading A" />}>
    
  2150.               <Text text="A" />
    
  2151.             </Suspense>
    
  2152.             <Suspense fallback={<Text text="Loading B" />}>
    
  2153.               <B />
    
  2154.             </Suspense>
    
  2155.           </SuspenseList>
    
  2156.           <Suspense fallback={<Text text="Loading C" />}>
    
  2157.             <Text text="C" />
    
  2158.           </Suspense>
    
  2159.         </SuspenseList>
    
  2160.       );
    
  2161.     }
    
  2162. 
    
  2163.     ReactNoop.render(<Foo />);
    
  2164. 
    
  2165.     await waitForAll(['A', 'Suspend! [B]', 'Loading B', 'C', 'Loading C']);
    
  2166. 
    
  2167.     expect(ReactNoop).toMatchRenderedOutput(<span>Loading C</span>);
    
  2168. 
    
  2169.     await act(() => B.resolve());
    
  2170.     assertLog(['A', 'B', 'C']);
    
  2171. 
    
  2172.     expect(ReactNoop).toMatchRenderedOutput(
    
  2173.       <>
    
  2174.         <span>A</span>
    
  2175.         <span>B</span>
    
  2176.         <span>C</span>
    
  2177.       </>,
    
  2178.     );
    
  2179.   });
    
  2180. 
    
  2181.   // @gate enableSuspenseList
    
  2182.   it('eventually resolves two nested forwards suspense lists with a hidden tail', async () => {
    
  2183.     const B = createAsyncText('B');
    
  2184. 
    
  2185.     function Foo({showB}) {
    
  2186.       return (
    
  2187.         <SuspenseList revealOrder="forwards">
    
  2188.           <SuspenseList revealOrder="forwards" tail="hidden">
    
  2189.             <Suspense fallback={<Text text="Loading A" />}>
    
  2190.               <Text text="A" />
    
  2191.             </Suspense>
    
  2192.             {showB ? (
    
  2193.               <Suspense fallback={<Text text="Loading B" />}>
    
  2194.                 <B />
    
  2195.               </Suspense>
    
  2196.             ) : null}
    
  2197.           </SuspenseList>
    
  2198.           <Suspense fallback={<Text text="Loading C" />}>
    
  2199.             <Text text="C" />
    
  2200.           </Suspense>
    
  2201.         </SuspenseList>
    
  2202.       );
    
  2203.     }
    
  2204. 
    
  2205.     ReactNoop.render(<Foo showB={false} />);
    
  2206. 
    
  2207.     await waitForAll(['A', 'C']);
    
  2208. 
    
  2209.     expect(ReactNoop).toMatchRenderedOutput(
    
  2210.       <>
    
  2211.         <span>A</span>
    
  2212.         <span>C</span>
    
  2213.       </>,
    
  2214.     );
    
  2215. 
    
  2216.     // Showing the B later means that C has already committed
    
  2217.     // so we're now effectively in "together" mode for the head.
    
  2218.     ReactNoop.render(<Foo showB={true} />);
    
  2219. 
    
  2220.     await waitForAll(['A', 'Suspend! [B]', 'Loading B', 'C', 'A', 'C']);
    
  2221. 
    
  2222.     expect(ReactNoop).toMatchRenderedOutput(
    
  2223.       <>
    
  2224.         <span>A</span>
    
  2225.         <span>C</span>
    
  2226.       </>,
    
  2227.     );
    
  2228. 
    
  2229.     await act(() => B.resolve());
    
  2230.     assertLog(['B']);
    
  2231. 
    
  2232.     expect(ReactNoop).toMatchRenderedOutput(
    
  2233.       <>
    
  2234.         <span>A</span>
    
  2235.         <span>B</span>
    
  2236.         <span>C</span>
    
  2237.       </>,
    
  2238.     );
    
  2239.   });
    
  2240. 
    
  2241.   // @gate enableSuspenseList
    
  2242.   it('can do unrelated adjacent updates', async () => {
    
  2243.     let updateAdjacent;
    
  2244.     function Adjacent() {
    
  2245.       const [text, setText] = React.useState('-');
    
  2246.       updateAdjacent = setText;
    
  2247.       return <Text text={text} />;
    
  2248.     }
    
  2249. 
    
  2250.     function Foo() {
    
  2251.       return (
    
  2252.         <div>
    
  2253.           <SuspenseList revealOrder="forwards">
    
  2254.             <Text text="A" />
    
  2255.             <Text text="B" />
    
  2256.           </SuspenseList>
    
  2257.           <Adjacent />
    
  2258.         </div>
    
  2259.       );
    
  2260.     }
    
  2261. 
    
  2262.     ReactNoop.render(<Foo />);
    
  2263. 
    
  2264.     await waitForAll(['A', 'B', '-']);
    
  2265. 
    
  2266.     expect(ReactNoop).toMatchRenderedOutput(
    
  2267.       <div>
    
  2268.         <span>A</span>
    
  2269.         <span>B</span>
    
  2270.         <span>-</span>
    
  2271.       </div>,
    
  2272.     );
    
  2273. 
    
  2274.     // Update the row adjacent to the list
    
  2275.     await act(() => updateAdjacent('C'));
    
  2276. 
    
  2277.     assertLog(['C']);
    
  2278. 
    
  2279.     expect(ReactNoop).toMatchRenderedOutput(
    
  2280.       <div>
    
  2281.         <span>A</span>
    
  2282.         <span>B</span>
    
  2283.         <span>C</span>
    
  2284.       </div>,
    
  2285.     );
    
  2286.   });
    
  2287. 
    
  2288.   // @gate enableSuspenseList
    
  2289.   it('is able to re-suspend the last rows during an update with hidden', async () => {
    
  2290.     const AsyncB = createAsyncText('B');
    
  2291. 
    
  2292.     let setAsyncB;
    
  2293. 
    
  2294.     function B() {
    
  2295.       const [shouldBeAsync, setAsync] = React.useState(false);
    
  2296.       setAsyncB = setAsync;
    
  2297. 
    
  2298.       return shouldBeAsync ? (
    
  2299.         <Suspense fallback={<Text text="Loading B" />}>
    
  2300.           <AsyncB />
    
  2301.         </Suspense>
    
  2302.       ) : (
    
  2303.         <Text text="Sync B" />
    
  2304.       );
    
  2305.     }
    
  2306. 
    
  2307.     function Foo({updateList}) {
    
  2308.       return (
    
  2309.         <SuspenseList revealOrder="forwards" tail="hidden">
    
  2310.           <Suspense key="A" fallback={<Text text="Loading A" />}>
    
  2311.             <Text text="A" />
    
  2312.           </Suspense>
    
  2313.           <B key="B" updateList={updateList} />
    
  2314.         </SuspenseList>
    
  2315.       );
    
  2316.     }
    
  2317. 
    
  2318.     ReactNoop.render(<Foo />);
    
  2319. 
    
  2320.     await waitForAll(['A', 'Sync B']);
    
  2321. 
    
  2322.     expect(ReactNoop).toMatchRenderedOutput(
    
  2323.       <>
    
  2324.         <span>A</span>
    
  2325.         <span>Sync B</span>
    
  2326.       </>,
    
  2327.     );
    
  2328. 
    
  2329.     const previousInst = setAsyncB;
    
  2330. 
    
  2331.     // During an update we suspend on B.
    
  2332.     await act(() => setAsyncB(true));
    
  2333. 
    
  2334.     assertLog([
    
  2335.       'Suspend! [B]',
    
  2336.       'Loading B',
    
  2337.       // The second pass is the "force hide" pass
    
  2338.       'Loading B',
    
  2339.     ]);
    
  2340. 
    
  2341.     expect(ReactNoop).toMatchRenderedOutput(
    
  2342.       <>
    
  2343.         <span>A</span>
    
  2344.         <span>Loading B</span>
    
  2345.       </>,
    
  2346.     );
    
  2347. 
    
  2348.     // Before we resolve we'll rerender the whole list.
    
  2349.     // This should leave the tree intact.
    
  2350.     await act(() => ReactNoop.render(<Foo updateList={true} />));
    
  2351. 
    
  2352.     assertLog(['A', 'Suspend! [B]', 'Loading B']);
    
  2353. 
    
  2354.     expect(ReactNoop).toMatchRenderedOutput(
    
  2355.       <>
    
  2356.         <span>A</span>
    
  2357.         <span>Loading B</span>
    
  2358.       </>,
    
  2359.     );
    
  2360. 
    
  2361.     await act(() => AsyncB.resolve());
    
  2362.     assertLog(['B']);
    
  2363. 
    
  2364.     expect(ReactNoop).toMatchRenderedOutput(
    
  2365.       <>
    
  2366.         <span>A</span>
    
  2367.         <span>B</span>
    
  2368.       </>,
    
  2369.     );
    
  2370. 
    
  2371.     // This should be the same instance. I.e. it didn't
    
  2372.     // remount.
    
  2373.     expect(previousInst).toBe(setAsyncB);
    
  2374.   });
    
  2375. 
    
  2376.   // @gate enableSuspenseList
    
  2377.   it('is able to re-suspend the last rows during an update with hidden', async () => {
    
  2378.     const AsyncB = createAsyncText('B');
    
  2379. 
    
  2380.     let setAsyncB;
    
  2381. 
    
  2382.     function B() {
    
  2383.       const [shouldBeAsync, setAsync] = React.useState(false);
    
  2384.       setAsyncB = setAsync;
    
  2385. 
    
  2386.       return shouldBeAsync ? (
    
  2387.         <Suspense fallback={<Text text="Loading B" />}>
    
  2388.           <AsyncB />
    
  2389.         </Suspense>
    
  2390.       ) : (
    
  2391.         <Text text="Sync B" />
    
  2392.       );
    
  2393.     }
    
  2394. 
    
  2395.     function Foo({updateList}) {
    
  2396.       return (
    
  2397.         <SuspenseList revealOrder="forwards" tail="hidden">
    
  2398.           <Suspense key="A" fallback={<Text text="Loading A" />}>
    
  2399.             <Text text="A" />
    
  2400.           </Suspense>
    
  2401.           <B key="B" updateList={updateList} />
    
  2402.         </SuspenseList>
    
  2403.       );
    
  2404.     }
    
  2405. 
    
  2406.     ReactNoop.render(<Foo />);
    
  2407. 
    
  2408.     await waitForAll(['A', 'Sync B']);
    
  2409. 
    
  2410.     expect(ReactNoop).toMatchRenderedOutput(
    
  2411.       <>
    
  2412.         <span>A</span>
    
  2413.         <span>Sync B</span>
    
  2414.       </>,
    
  2415.     );
    
  2416. 
    
  2417.     const previousInst = setAsyncB;
    
  2418. 
    
  2419.     // During an update we suspend on B.
    
  2420.     await act(() => setAsyncB(true));
    
  2421. 
    
  2422.     assertLog([
    
  2423.       'Suspend! [B]',
    
  2424.       'Loading B',
    
  2425.       // The second pass is the "force hide" pass
    
  2426.       'Loading B',
    
  2427.     ]);
    
  2428. 
    
  2429.     expect(ReactNoop).toMatchRenderedOutput(
    
  2430.       <>
    
  2431.         <span>A</span>
    
  2432.         <span>Loading B</span>
    
  2433.       </>,
    
  2434.     );
    
  2435. 
    
  2436.     // Before we resolve we'll rerender the whole list.
    
  2437.     // This should leave the tree intact.
    
  2438.     await act(() => ReactNoop.render(<Foo updateList={true} />));
    
  2439. 
    
  2440.     assertLog(['A', 'Suspend! [B]', 'Loading B']);
    
  2441. 
    
  2442.     expect(ReactNoop).toMatchRenderedOutput(
    
  2443.       <>
    
  2444.         <span>A</span>
    
  2445.         <span>Loading B</span>
    
  2446.       </>,
    
  2447.     );
    
  2448. 
    
  2449.     await act(() => AsyncB.resolve());
    
  2450.     assertLog(['B']);
    
  2451. 
    
  2452.     expect(ReactNoop).toMatchRenderedOutput(
    
  2453.       <>
    
  2454.         <span>A</span>
    
  2455.         <span>B</span>
    
  2456.       </>,
    
  2457.     );
    
  2458. 
    
  2459.     // This should be the same instance. I.e. it didn't
    
  2460.     // remount.
    
  2461.     expect(previousInst).toBe(setAsyncB);
    
  2462.   });
    
  2463. 
    
  2464.   // @gate enableSuspenseList
    
  2465.   it('is able to interrupt a partially rendered tree and continue later', async () => {
    
  2466.     const AsyncA = createAsyncText('A');
    
  2467. 
    
  2468.     let updateLowPri;
    
  2469.     let updateHighPri;
    
  2470. 
    
  2471.     function Bar() {
    
  2472.       const [highPriState, setHighPriState] = React.useState(false);
    
  2473.       updateHighPri = setHighPriState;
    
  2474.       return highPriState ? <AsyncA /> : null;
    
  2475.     }
    
  2476. 
    
  2477.     function Foo() {
    
  2478.       const [lowPriState, setLowPriState] = React.useState(false);
    
  2479.       updateLowPri = setLowPriState;
    
  2480.       return (
    
  2481.         <SuspenseList revealOrder="forwards" tail="hidden">
    
  2482.           <Suspense key="A" fallback={<Text text="Loading A" />}>
    
  2483.             <Bar />
    
  2484.           </Suspense>
    
  2485.           {lowPriState ? <Text text="B" /> : null}
    
  2486.           {lowPriState ? <Text text="C" /> : null}
    
  2487.           {lowPriState ? <Text text="D" /> : null}
    
  2488.         </SuspenseList>
    
  2489.       );
    
  2490.     }
    
  2491. 
    
  2492.     ReactNoop.render(<Foo />);
    
  2493. 
    
  2494.     await waitForAll([]);
    
  2495. 
    
  2496.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  2497. 
    
  2498.     await act(async () => {
    
  2499.       // Add a few items at the end.
    
  2500.       React.startTransition(() => {
    
  2501.         updateLowPri(true);
    
  2502.       });
    
  2503. 
    
  2504.       // Flush partially through.
    
  2505.       await waitFor(['B', 'C']);
    
  2506. 
    
  2507.       // Schedule another update at higher priority.
    
  2508.       ReactNoop.flushSync(() => updateHighPri(true));
    
  2509. 
    
  2510.       // That will intercept the previous render.
    
  2511.       assertLog([
    
  2512.         'Suspend! [A]',
    
  2513.         'Loading A',
    
  2514.         // Re-render at forced.
    
  2515.         'Suspend! [A]',
    
  2516.         'Loading A',
    
  2517.       ]);
    
  2518.       expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
    
  2519. 
    
  2520.       // Try again on low-pri.
    
  2521.       await waitForAll(['Suspend! [A]', 'Loading A']);
    
  2522.       expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
    
  2523.     });
    
  2524. 
    
  2525.     await act(() => AsyncA.resolve());
    
  2526.     assertLog(['A', 'B', 'C', 'D']);
    
  2527. 
    
  2528.     expect(ReactNoop).toMatchRenderedOutput(
    
  2529.       <>
    
  2530.         <span>A</span>
    
  2531.         <span>B</span>
    
  2532.         <span>C</span>
    
  2533.         <span>D</span>
    
  2534.       </>,
    
  2535.     );
    
  2536.   });
    
  2537. 
    
  2538.   // @gate enableSuspenseList
    
  2539.   it('can resume class components when revealed together', async () => {
    
  2540.     const A = createAsyncText('A');
    
  2541.     const B = createAsyncText('B');
    
  2542. 
    
  2543.     class ClassComponent extends React.Component {
    
  2544.       render() {
    
  2545.         return this.props.children;
    
  2546.       }
    
  2547.     }
    
  2548. 
    
  2549.     function Foo() {
    
  2550.       return (
    
  2551.         <Suspense fallback={<Text text="Loading" />}>
    
  2552.           <SuspenseList revealOrder="together">
    
  2553.             <ClassComponent>
    
  2554.               <Suspense fallback={<Text text="Loading A" />}>
    
  2555.                 <A />
    
  2556.               </Suspense>
    
  2557.             </ClassComponent>
    
  2558.             <ClassComponent>
    
  2559.               <Suspense fallback={<Text text="Loading B" />}>
    
  2560.                 <B />
    
  2561.               </Suspense>
    
  2562.             </ClassComponent>
    
  2563.           </SuspenseList>
    
  2564.         </Suspense>
    
  2565.       );
    
  2566.     }
    
  2567. 
    
  2568.     await A.resolve();
    
  2569. 
    
  2570.     ReactNoop.render(<Foo />);
    
  2571. 
    
  2572.     await waitForAll([
    
  2573.       'A',
    
  2574.       'Suspend! [B]',
    
  2575.       'Loading B',
    
  2576.       'Loading A',
    
  2577.       'Loading B',
    
  2578.     ]);
    
  2579. 
    
  2580.     expect(ReactNoop).toMatchRenderedOutput(
    
  2581.       <>
    
  2582.         <span>Loading A</span>
    
  2583.         <span>Loading B</span>
    
  2584.       </>,
    
  2585.     );
    
  2586. 
    
  2587.     await B.resolve();
    
  2588. 
    
  2589.     ReactNoop.render(<Foo />);
    
  2590. 
    
  2591.     await waitForAll(['A', 'B']);
    
  2592. 
    
  2593.     expect(ReactNoop).toMatchRenderedOutput(
    
  2594.       <>
    
  2595.         <span>A</span>
    
  2596.         <span>B</span>
    
  2597.       </>,
    
  2598.     );
    
  2599.   });
    
  2600. 
    
  2601.   // @gate enableSuspenseList
    
  2602.   it('should be able to progressively show CPU expensive rows with two pass rendering', async () => {
    
  2603.     function TwoPass({text}) {
    
  2604.       const [pass, setPass] = React.useState(0);
    
  2605.       React.useLayoutEffect(() => {
    
  2606.         Scheduler.log('Mount ' + text);
    
  2607.         setPass(1);
    
  2608.       }, []);
    
  2609.       return <Text text={pass === 0 ? 'First Pass ' + text : text} />;
    
  2610.     }
    
  2611. 
    
  2612.     function Sleep({time, children}) {
    
  2613.       Scheduler.unstable_advanceTime(time);
    
  2614.       return children;
    
  2615.     }
    
  2616. 
    
  2617.     function App() {
    
  2618.       Scheduler.log('App');
    
  2619.       return (
    
  2620.         <SuspenseList revealOrder="forwards" tail="hidden">
    
  2621.           <Suspense fallback={<Text text="Loading A" />}>
    
  2622.             <Sleep time={600}>
    
  2623.               <TwoPass text="A" />
    
  2624.             </Sleep>
    
  2625.           </Suspense>
    
  2626.           <Suspense fallback={<Text text="Loading B" />}>
    
  2627.             <Sleep time={600}>
    
  2628.               <TwoPass text="B" />
    
  2629.             </Sleep>
    
  2630.           </Suspense>
    
  2631.           <Sleep time={600}>
    
  2632.             <Text text="C" />
    
  2633.           </Sleep>
    
  2634.         </SuspenseList>
    
  2635.       );
    
  2636.     }
    
  2637. 
    
  2638.     React.startTransition(() => {
    
  2639.       ReactNoop.render(<App />);
    
  2640.     });
    
  2641. 
    
  2642.     await waitFor(['App', 'First Pass A', 'Mount A', 'A']);
    
  2643.     expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
    
  2644. 
    
  2645.     await waitFor(['First Pass B', 'Mount B', 'B']);
    
  2646.     expect(ReactNoop).toMatchRenderedOutput(
    
  2647.       <>
    
  2648.         <span>A</span>
    
  2649.         <span>B</span>
    
  2650.       </>,
    
  2651.     );
    
  2652. 
    
  2653.     await waitForAll(['C']);
    
  2654.     expect(ReactNoop).toMatchRenderedOutput(
    
  2655.       <>
    
  2656.         <span>A</span>
    
  2657.         <span>B</span>
    
  2658.         <span>C</span>
    
  2659.       </>,
    
  2660.     );
    
  2661.   });
    
  2662. 
    
  2663.   // @gate enableSuspenseList
    
  2664.   it('should be able to progressively show rows with two pass rendering and visible', async () => {
    
  2665.     function TwoPass({text}) {
    
  2666.       const [pass, setPass] = React.useState(0);
    
  2667.       React.useLayoutEffect(() => {
    
  2668.         Scheduler.log('Mount ' + text);
    
  2669.         setPass(1);
    
  2670.       }, []);
    
  2671.       return <Text text={pass === 0 ? 'First Pass ' + text : text} />;
    
  2672.     }
    
  2673. 
    
  2674.     function Sleep({time, children}) {
    
  2675.       Scheduler.unstable_advanceTime(time);
    
  2676.       return children;
    
  2677.     }
    
  2678. 
    
  2679.     function App() {
    
  2680.       Scheduler.log('App');
    
  2681.       return (
    
  2682.         <SuspenseList revealOrder="forwards">
    
  2683.           <Suspense fallback={<Text text="Loading A" />}>
    
  2684.             <Sleep time={600}>
    
  2685.               <TwoPass text="A" />
    
  2686.             </Sleep>
    
  2687.           </Suspense>
    
  2688.           <Suspense fallback={<Text text="Loading B" />}>
    
  2689.             <Sleep time={600}>
    
  2690.               <TwoPass text="B" />
    
  2691.             </Sleep>
    
  2692.           </Suspense>
    
  2693.           <Suspense fallback={<Text text="Loading C" />}>
    
  2694.             <Sleep time={600}>
    
  2695.               <Text text="C" />
    
  2696.             </Sleep>
    
  2697.           </Suspense>
    
  2698.         </SuspenseList>
    
  2699.       );
    
  2700.     }
    
  2701. 
    
  2702.     React.startTransition(() => {
    
  2703.       ReactNoop.render(<App />);
    
  2704.     });
    
  2705. 
    
  2706.     await waitFor([
    
  2707.       'App',
    
  2708.       'First Pass A',
    
  2709.       'Loading B',
    
  2710.       'Loading C',
    
  2711.       'Mount A',
    
  2712.       'A',
    
  2713.     ]);
    
  2714.     expect(ReactNoop).toMatchRenderedOutput(
    
  2715.       <>
    
  2716.         <span>A</span>
    
  2717.         <span>Loading B</span>
    
  2718.         <span>Loading C</span>
    
  2719.       </>,
    
  2720.     );
    
  2721. 
    
  2722.     await waitFor(['First Pass B', 'Mount B', 'B']);
    
  2723.     expect(ReactNoop).toMatchRenderedOutput(
    
  2724.       <>
    
  2725.         <span>A</span>
    
  2726.         <span>B</span>
    
  2727.         <span>Loading C</span>
    
  2728.       </>,
    
  2729.     );
    
  2730. 
    
  2731.     await waitForAll(['C']);
    
  2732.     expect(ReactNoop).toMatchRenderedOutput(
    
  2733.       <>
    
  2734.         <span>A</span>
    
  2735.         <span>B</span>
    
  2736.         <span>C</span>
    
  2737.       </>,
    
  2738.     );
    
  2739.   });
    
  2740. 
    
  2741.   // @gate enableProfilerTimer
    
  2742.   // @gate enableSuspenseList
    
  2743.   it('counts the actual duration when profiling a SuspenseList', async () => {
    
  2744.     // Order of parameters: id, phase, actualDuration, treeBaseDuration
    
  2745.     const onRender = jest.fn();
    
  2746. 
    
  2747.     const Fallback = () => {
    
  2748.       Scheduler.log('Fallback');
    
  2749.       Scheduler.unstable_advanceTime(3);
    
  2750.       return <span>Loading...</span>;
    
  2751.     };
    
  2752. 
    
  2753.     const A = createAsyncText('A');
    
  2754.     const B = createAsyncText('B');
    
  2755.     const C = createAsyncText('C');
    
  2756.     const D = createAsyncText('D');
    
  2757.     await A.resolve();
    
  2758.     await B.resolve();
    
  2759. 
    
  2760.     function Sleep({time, children}) {
    
  2761.       Scheduler.unstable_advanceTime(time);
    
  2762.       return children;
    
  2763.     }
    
  2764. 
    
  2765.     function App({addRow, suspendTail}) {
    
  2766.       Scheduler.log('App');
    
  2767.       return (
    
  2768.         <Profiler id="root" onRender={onRender}>
    
  2769.           <SuspenseList revealOrder="forwards">
    
  2770.             <Suspense fallback={<Fallback />}>
    
  2771.               <Sleep time={1}>
    
  2772.                 <A />
    
  2773.               </Sleep>
    
  2774.             </Suspense>
    
  2775.             <Suspense fallback={<Fallback />}>
    
  2776.               <Sleep time={4}>
    
  2777.                 <B />
    
  2778.               </Sleep>
    
  2779.             </Suspense>
    
  2780.             <Suspense fallback={<Fallback />}>
    
  2781.               <Sleep time={5}>{suspendTail ? <C /> : <Text text="C" />}</Sleep>
    
  2782.             </Suspense>
    
  2783.             {addRow ? (
    
  2784.               <Suspense fallback={<Fallback />}>
    
  2785.                 <Sleep time={12}>
    
  2786.                   <D />
    
  2787.                 </Sleep>
    
  2788.               </Suspense>
    
  2789.             ) : null}
    
  2790.           </SuspenseList>
    
  2791.         </Profiler>
    
  2792.       );
    
  2793.     }
    
  2794. 
    
  2795.     ReactNoop.render(<App suspendTail={true} />);
    
  2796. 
    
  2797.     await waitForAll(['App', 'A', 'B', 'Suspend! [C]', 'Fallback']);
    
  2798.     expect(ReactNoop).toMatchRenderedOutput(
    
  2799.       <>
    
  2800.         <span>A</span>
    
  2801.         <span>B</span>
    
  2802.         <span>Loading...</span>
    
  2803.       </>,
    
  2804.     );
    
  2805.     expect(onRender).toHaveBeenCalledTimes(1);
    
  2806. 
    
  2807.     // The treeBaseDuration should be the time to render each child. The last
    
  2808.     // one counts the fallback time.
    
  2809.     // The actualDuration should also include the 5ms spent rendering the
    
  2810.     // last suspended row.
    
  2811. 
    
  2812.     // actualDuration
    
  2813.     expect(onRender.mock.calls[0][2]).toBe(1 + 4 + 5 + 3);
    
  2814.     // treeBaseDuration
    
  2815.     expect(onRender.mock.calls[0][3]).toBe(1 + 4 + 3);
    
  2816. 
    
  2817.     ReactNoop.render(<App suspendTail={false} />);
    
  2818. 
    
  2819.     await waitForAll(['App', 'A', 'B', 'C']);
    
  2820. 
    
  2821.     expect(ReactNoop).toMatchRenderedOutput(
    
  2822.       <>
    
  2823.         <span>A</span>
    
  2824.         <span>B</span>
    
  2825.         <span>C</span>
    
  2826.       </>,
    
  2827.     );
    
  2828.     expect(onRender).toHaveBeenCalledTimes(2);
    
  2829. 
    
  2830.     // actualDuration
    
  2831.     expect(onRender.mock.calls[1][2]).toBe(1 + 4 + 5);
    
  2832.     // treeBaseDuration
    
  2833.     expect(onRender.mock.calls[1][3]).toBe(1 + 4 + 5);
    
  2834. 
    
  2835.     ReactNoop.render(<App addRow={true} suspendTail={true} />);
    
  2836. 
    
  2837.     await waitForAll([
    
  2838.       'App',
    
  2839.       'A',
    
  2840.       'B',
    
  2841.       'Suspend! [C]',
    
  2842.       'Fallback',
    
  2843.       // We rendered in together mode for the head, now we re-render with forced suspense.
    
  2844.       'A',
    
  2845.       'B',
    
  2846.       'Suspend! [C]',
    
  2847.       'Fallback',
    
  2848.       // Lastly we render the tail.
    
  2849.       'Fallback',
    
  2850.     ]);
    
  2851. 
    
  2852.     // Flush suspended time.
    
  2853.     jest.advanceTimersByTime(1000);
    
  2854. 
    
  2855.     expect(ReactNoop).toMatchRenderedOutput(
    
  2856.       <>
    
  2857.         <span>A</span>
    
  2858.         <span>B</span>
    
  2859.         <span hidden={true}>C</span>
    
  2860.         <span>Loading...</span>
    
  2861.         <span>Loading...</span>
    
  2862.       </>,
    
  2863.     );
    
  2864.     expect(onRender).toHaveBeenCalledTimes(3);
    
  2865. 
    
  2866.     // The treeBaseDuration should be the time to render the first two
    
  2867.     // children and then two fallbacks.
    
  2868.     // The actualDuration should also include rendering the content of
    
  2869.     // the first fallback, as well as the second pass to render the head
    
  2870.     // with force fallback mode.
    
  2871. 
    
  2872.     // actualDuration
    
  2873.     expect(onRender.mock.calls[2][2]).toBe((1 + 4 + 5 + 3) * 2 + 3);
    
  2874.     // treeBaseDuration
    
  2875.     expect(onRender.mock.calls[2][3]).toBe(1 + 4 + 3 + 3);
    
  2876. 
    
  2877.     await act(() => C.resolve());
    
  2878.     assertLog(['C', 'Suspend! [D]']);
    
  2879.     expect(ReactNoop).toMatchRenderedOutput(
    
  2880.       <>
    
  2881.         <span>A</span>
    
  2882.         <span>B</span>
    
  2883.         <span>C</span>
    
  2884.         <span>Loading...</span>
    
  2885.       </>,
    
  2886.     );
    
  2887.     expect(onRender).toHaveBeenCalledTimes(4);
    
  2888. 
    
  2889.     // actualDuration
    
  2890.     expect(onRender.mock.calls[3][2]).toBe(5 + 12);
    
  2891.     // treeBaseDuration
    
  2892.     expect(onRender.mock.calls[3][3]).toBe(1 + 4 + 5 + 3);
    
  2893.   });
    
  2894. 
    
  2895.   // @gate enableSuspenseList
    
  2896.   it('propagates despite a memo bailout', async () => {
    
  2897.     const A = createAsyncText('A');
    
  2898.     const B = createAsyncText('B');
    
  2899.     const C = createAsyncText('C');
    
  2900. 
    
  2901.     const Bailout = React.memo(({children}) => {
    
  2902.       return children;
    
  2903.     });
    
  2904. 
    
  2905.     function Foo() {
    
  2906.       // To test the part that relies on context propagation,
    
  2907.       // we need to bailout *above* the Suspense's parent.
    
  2908.       // Several layers of Bailout wrappers help verify we're
    
  2909.       // marking updates all the way to the propagation root.
    
  2910.       return (
    
  2911.         <SuspenseList revealOrder="forwards">
    
  2912.           <Bailout>
    
  2913.             <Bailout>
    
  2914.               <Bailout>
    
  2915.                 <Bailout>
    
  2916.                   <Suspense fallback={<Text text="Loading A" />}>
    
  2917.                     <A />
    
  2918.                   </Suspense>
    
  2919.                 </Bailout>
    
  2920.               </Bailout>
    
  2921.             </Bailout>
    
  2922.           </Bailout>
    
  2923.           <Bailout>
    
  2924.             <Bailout>
    
  2925.               <Bailout>
    
  2926.                 <Bailout>
    
  2927.                   <Suspense fallback={<Text text="Loading B" />}>
    
  2928.                     <B />
    
  2929.                   </Suspense>
    
  2930.                 </Bailout>
    
  2931.               </Bailout>
    
  2932.             </Bailout>
    
  2933.           </Bailout>
    
  2934.           <Bailout>
    
  2935.             <Bailout>
    
  2936.               <Bailout>
    
  2937.                 <Bailout>
    
  2938.                   <Suspense fallback={<Text text="Loading C" />}>
    
  2939.                     <C />
    
  2940.                   </Suspense>
    
  2941.                 </Bailout>
    
  2942.               </Bailout>
    
  2943.             </Bailout>
    
  2944.           </Bailout>
    
  2945.         </SuspenseList>
    
  2946.       );
    
  2947.     }
    
  2948. 
    
  2949.     await C.resolve();
    
  2950. 
    
  2951.     ReactNoop.render(<Foo />);
    
  2952. 
    
  2953.     await waitForAll(['Suspend! [A]', 'Loading A', 'Loading B', 'Loading C']);
    
  2954. 
    
  2955.     expect(ReactNoop).toMatchRenderedOutput(
    
  2956.       <>
    
  2957.         <span>Loading A</span>
    
  2958.         <span>Loading B</span>
    
  2959.         <span>Loading C</span>
    
  2960.       </>,
    
  2961.     );
    
  2962. 
    
  2963.     await act(() => A.resolve());
    
  2964.     assertLog(['A', 'Suspend! [B]']);
    
  2965.     expect(ReactNoop).toMatchRenderedOutput(
    
  2966.       <>
    
  2967.         <span>A</span>
    
  2968.         <span>Loading B</span>
    
  2969.         <span>Loading C</span>
    
  2970.       </>,
    
  2971.     );
    
  2972. 
    
  2973.     await act(() => B.resolve());
    
  2974.     assertLog(['B', 'C']);
    
  2975.     expect(ReactNoop).toMatchRenderedOutput(
    
  2976.       <>
    
  2977.         <span>A</span>
    
  2978.         <span>B</span>
    
  2979.         <span>C</span>
    
  2980.       </>,
    
  2981.     );
    
  2982.   });
    
  2983. 
    
  2984.   // @gate enableSuspenseList
    
  2985.   it(
    
  2986.     'regression test: SuspenseList should never force boundaries deeper than ' +
    
  2987.       'a single level into fallback mode',
    
  2988.     async () => {
    
  2989.       const A = createAsyncText('A');
    
  2990. 
    
  2991.       function UnreachableFallback() {
    
  2992.         throw new Error('Should never be thrown!');
    
  2993.       }
    
  2994. 
    
  2995.       function Repro({update}) {
    
  2996.         return (
    
  2997.           <SuspenseList revealOrder="forwards">
    
  2998.             {update && (
    
  2999.               <Suspense fallback={<Text text="Loading A..." />}>
    
  3000.                 <A />
    
  3001.               </Suspense>
    
  3002.             )}
    
  3003.             <Suspense fallback={<Text text="Loading B..." />}>
    
  3004.               {update ? (
    
  3005.                 <Suspense fallback={<UnreachableFallback />}>
    
  3006.                   <Text text="B2" />
    
  3007.                 </Suspense>
    
  3008.               ) : (
    
  3009.                 <Text text="B1" />
    
  3010.               )}
    
  3011.             </Suspense>
    
  3012.             <Suspense fallback={<Text text="Loading C..." />}>
    
  3013.               <Text text="C" />
    
  3014.             </Suspense>
    
  3015.             {update && (
    
  3016.               <Suspense fallback={<Text text="Loading D..." />}>
    
  3017.                 <Text text="D" />
    
  3018.               </Suspense>
    
  3019.             )}
    
  3020.           </SuspenseList>
    
  3021.         );
    
  3022.       }
    
  3023. 
    
  3024.       // Initial mount. Only two rows are mounted, B and C.
    
  3025.       const root = ReactNoop.createRoot();
    
  3026.       await act(() => root.render(<Repro update={false} />));
    
  3027.       assertLog(['B1', 'C']);
    
  3028.       expect(root).toMatchRenderedOutput(
    
  3029.         <>
    
  3030.           <span>B1</span>
    
  3031.           <span>C</span>
    
  3032.         </>,
    
  3033.       );
    
  3034. 
    
  3035.       // During the update, a few things happen simultaneously:
    
  3036.       // - A new row, A, is inserted into the head. This row suspends.
    
  3037.       // - The context in row B is replaced. The new content contains a nested
    
  3038.       //   Suspense boundary.
    
  3039.       // - A new row, D, is inserted into the tail.
    
  3040.       await act(() => root.render(<Repro update={true} />));
    
  3041.       assertLog([
    
  3042.         // During the first pass, the new row, A, suspends. This means any new
    
  3043.         // rows in the tail should be forced into fallback mode.
    
  3044.         'Suspend! [A]',
    
  3045.         'Loading A...',
    
  3046.         'B2',
    
  3047.         'C',
    
  3048. 
    
  3049.         // A second pass is used to render the fallbacks in the tail.
    
  3050.         //
    
  3051.         // Rows B and C were already mounted, so they should not be forced into
    
  3052.         // fallback mode.
    
  3053.         //
    
  3054.         // In the regression that this test was written for, the inner
    
  3055.         // Suspense boundary around B2 was incorrectly activated. Only the
    
  3056.         // nearest fallbacks per row should be activated, and only if they
    
  3057.         // haven't already mounted.
    
  3058.         'Loading A...',
    
  3059.         'B2',
    
  3060.         'C',
    
  3061. 
    
  3062.         // D is part of the tail, so it should show a fallback.
    
  3063.         'Loading D...',
    
  3064.       ]);
    
  3065.       expect(root).toMatchRenderedOutput(
    
  3066.         <>
    
  3067.           <span>Loading A...</span>
    
  3068.           <span>B2</span>
    
  3069.           <span>C</span>
    
  3070.           <span>Loading D...</span>
    
  3071.         </>,
    
  3072.       );
    
  3073. 
    
  3074.       // Now finish loading A.
    
  3075.       await act(() => A.resolve());
    
  3076.       assertLog(['A', 'D']);
    
  3077.       expect(root).toMatchRenderedOutput(
    
  3078.         <>
    
  3079.           <span>A</span>
    
  3080.           <span>B2</span>
    
  3081.           <span>C</span>
    
  3082.           <span>D</span>
    
  3083.         </>,
    
  3084.       );
    
  3085.     },
    
  3086.   );
    
  3087. });