1. let React;
    
  2. let ReactNoop;
    
  3. let Scheduler;
    
  4. let act;
    
  5. let Suspense;
    
  6. let useState;
    
  7. let textCache;
    
  8. 
    
  9. let readText;
    
  10. let resolveText;
    
  11. // let rejectText;
    
  12. 
    
  13. let assertLog;
    
  14. let waitForPaint;
    
  15. 
    
  16. describe('ReactSuspenseWithNoopRenderer', () => {
    
  17.   beforeEach(() => {
    
  18.     jest.resetModules();
    
  19. 
    
  20.     React = require('react');
    
  21.     ReactNoop = require('react-noop-renderer');
    
  22.     Scheduler = require('scheduler');
    
  23.     act = require('internal-test-utils').act;
    
  24.     Suspense = React.Suspense;
    
  25.     useState = React.useState;
    
  26. 
    
  27.     const InternalTestUtils = require('internal-test-utils');
    
  28.     assertLog = InternalTestUtils.assertLog;
    
  29.     waitForPaint = InternalTestUtils.waitForPaint;
    
  30. 
    
  31.     textCache = new Map();
    
  32. 
    
  33.     readText = text => {
    
  34.       const record = textCache.get(text);
    
  35.       if (record !== undefined) {
    
  36.         switch (record.status) {
    
  37.           case 'pending':
    
  38.             throw record.promise;
    
  39.           case 'rejected':
    
  40.             throw Error('Failed to load: ' + text);
    
  41.           case 'resolved':
    
  42.             return text;
    
  43.         }
    
  44.       } else {
    
  45.         let ping;
    
  46.         const promise = new Promise(resolve => (ping = resolve));
    
  47.         const newRecord = {
    
  48.           status: 'pending',
    
  49.           ping: ping,
    
  50.           promise,
    
  51.         };
    
  52.         textCache.set(text, newRecord);
    
  53.         throw promise;
    
  54.       }
    
  55.     };
    
  56. 
    
  57.     resolveText = text => {
    
  58.       const record = textCache.get(text);
    
  59.       if (record !== undefined) {
    
  60.         if (record.status === 'pending') {
    
  61.           record.ping();
    
  62.           record.ping = null;
    
  63.           record.status = 'resolved';
    
  64.           record.promise = null;
    
  65.         }
    
  66.       } else {
    
  67.         const newRecord = {
    
  68.           ping: null,
    
  69.           status: 'resolved',
    
  70.           promise: null,
    
  71.         };
    
  72.         textCache.set(text, newRecord);
    
  73.       }
    
  74.     };
    
  75. 
    
  76.     // rejectText = text => {
    
  77.     //   const record = textCache.get(text);
    
  78.     //   if (record !== undefined) {
    
  79.     //     if (record.status === 'pending') {
    
  80.     //       Scheduler.log(`Promise rejected [${text}]`);
    
  81.     //       record.ping();
    
  82.     //       record.status = 'rejected';
    
  83.     //       clearTimeout(record.promise._timer);
    
  84.     //       record.promise = null;
    
  85.     //     }
    
  86.     //   } else {
    
  87.     //     const newRecord = {
    
  88.     //       ping: null,
    
  89.     //       status: 'rejected',
    
  90.     //       promise: null,
    
  91.     //     };
    
  92.     //     textCache.set(text, newRecord);
    
  93.     //   }
    
  94.     // };
    
  95.   });
    
  96. 
    
  97.   function Text(props) {
    
  98.     Scheduler.log(props.text);
    
  99.     return props.text;
    
  100.   }
    
  101. 
    
  102.   function AsyncText(props) {
    
  103.     const text = props.text;
    
  104.     try {
    
  105.       readText(text);
    
  106.       Scheduler.log(text);
    
  107.       return text;
    
  108.     } catch (promise) {
    
  109.       if (typeof promise.then === 'function') {
    
  110.         Scheduler.log(`Suspend! [${text}]`);
    
  111.       } else {
    
  112.         Scheduler.log(`Error! [${text}]`);
    
  113.       }
    
  114.       throw promise;
    
  115.     }
    
  116.   }
    
  117. 
    
  118.   // @gate enableCPUSuspense
    
  119.   it('skips CPU-bound trees on initial mount', async () => {
    
  120.     function App() {
    
  121.       return (
    
  122.         <>
    
  123.           <Text text="Outer" />
    
  124.           <div>
    
  125.             <Suspense
    
  126.               unstable_expectedLoadTime={2000}
    
  127.               fallback={<Text text="Loading..." />}>
    
  128.               <Text text="Inner" />
    
  129.             </Suspense>
    
  130.           </div>
    
  131.         </>
    
  132.       );
    
  133.     }
    
  134. 
    
  135.     const root = ReactNoop.createRoot();
    
  136.     await act(async () => {
    
  137.       root.render(<App />);
    
  138.       await waitForPaint(['Outer', 'Loading...']);
    
  139.       expect(root).toMatchRenderedOutput(
    
  140.         <>
    
  141.           Outer
    
  142.           <div>Loading...</div>
    
  143.         </>,
    
  144.       );
    
  145.     });
    
  146.     // Inner contents finish in separate commit from outer
    
  147.     assertLog(['Inner']);
    
  148.     expect(root).toMatchRenderedOutput(
    
  149.       <>
    
  150.         Outer
    
  151.         <div>Inner</div>
    
  152.       </>,
    
  153.     );
    
  154.   });
    
  155. 
    
  156.   // @gate enableCPUSuspense
    
  157.   it('does not skip CPU-bound trees during updates', async () => {
    
  158.     let setCount;
    
  159. 
    
  160.     function App() {
    
  161.       const [count, _setCount] = useState(0);
    
  162.       setCount = _setCount;
    
  163.       return (
    
  164.         <>
    
  165.           <Text text="Outer" />
    
  166.           <div>
    
  167.             <Suspense
    
  168.               unstable_expectedLoadTime={2000}
    
  169.               fallback={<Text text="Loading..." />}>
    
  170.               <Text text={`Inner [${count}]`} />
    
  171.             </Suspense>
    
  172.           </div>
    
  173.         </>
    
  174.       );
    
  175.     }
    
  176. 
    
  177.     // Initial mount
    
  178.     const root = ReactNoop.createRoot();
    
  179.     await act(() => {
    
  180.       root.render(<App />);
    
  181.     });
    
  182.     // Inner contents finish in separate commit from outer
    
  183.     assertLog(['Outer', 'Loading...', 'Inner [0]']);
    
  184.     expect(root).toMatchRenderedOutput(
    
  185.       <>
    
  186.         Outer
    
  187.         <div>Inner [0]</div>
    
  188.       </>,
    
  189.     );
    
  190. 
    
  191.     // Update
    
  192.     await act(() => {
    
  193.       setCount(1);
    
  194.     });
    
  195.     // Entire update finishes in a single commit
    
  196.     assertLog(['Outer', 'Inner [1]']);
    
  197.     expect(root).toMatchRenderedOutput(
    
  198.       <>
    
  199.         Outer
    
  200.         <div>Inner [1]</div>
    
  201.       </>,
    
  202.     );
    
  203.   });
    
  204. 
    
  205.   // @gate enableCPUSuspense
    
  206.   it('suspend inside CPU-bound tree', async () => {
    
  207.     function App() {
    
  208.       return (
    
  209.         <>
    
  210.           <Text text="Outer" />
    
  211.           <div>
    
  212.             <Suspense
    
  213.               unstable_expectedLoadTime={2000}
    
  214.               fallback={<Text text="Loading..." />}>
    
  215.               <AsyncText text="Inner" />
    
  216.             </Suspense>
    
  217.           </div>
    
  218.         </>
    
  219.       );
    
  220.     }
    
  221. 
    
  222.     const root = ReactNoop.createRoot();
    
  223.     await act(async () => {
    
  224.       root.render(<App />);
    
  225.       await waitForPaint(['Outer', 'Loading...']);
    
  226.       expect(root).toMatchRenderedOutput(
    
  227.         <>
    
  228.           Outer
    
  229.           <div>Loading...</div>
    
  230.         </>,
    
  231.       );
    
  232.     });
    
  233.     // Inner contents suspended, so we continue showing a fallback.
    
  234.     assertLog(['Suspend! [Inner]']);
    
  235.     expect(root).toMatchRenderedOutput(
    
  236.       <>
    
  237.         Outer
    
  238.         <div>Loading...</div>
    
  239.       </>,
    
  240.     );
    
  241. 
    
  242.     // Resolve the data and finish rendering
    
  243.     await act(async () => {
    
  244.       await resolveText('Inner');
    
  245.     });
    
  246.     assertLog(['Inner']);
    
  247.     expect(root).toMatchRenderedOutput(
    
  248.       <>
    
  249.         Outer
    
  250.         <div>Inner</div>
    
  251.       </>,
    
  252.     );
    
  253.   });
    
  254. 
    
  255.   // @gate enableCPUSuspense
    
  256.   it('nested CPU-bound trees', async () => {
    
  257.     function App() {
    
  258.       return (
    
  259.         <>
    
  260.           <Text text="A" />
    
  261.           <div>
    
  262.             <Suspense
    
  263.               unstable_expectedLoadTime={2000}
    
  264.               fallback={<Text text="Loading B..." />}>
    
  265.               <Text text="B" />
    
  266.               <div>
    
  267.                 <Suspense
    
  268.                   unstable_expectedLoadTime={2000}
    
  269.                   fallback={<Text text="Loading C..." />}>
    
  270.                   <Text text="C" />
    
  271.                 </Suspense>
    
  272.               </div>
    
  273.             </Suspense>
    
  274.           </div>
    
  275.         </>
    
  276.       );
    
  277.     }
    
  278. 
    
  279.     const root = ReactNoop.createRoot();
    
  280.     await act(() => {
    
  281.       root.render(<App />);
    
  282.     });
    
  283.     // Each level commits separately
    
  284.     assertLog(['A', 'Loading B...', 'B', 'Loading C...', 'C']);
    
  285.     expect(root).toMatchRenderedOutput(
    
  286.       <>
    
  287.         A
    
  288.         <div>
    
  289.           B<div>C</div>
    
  290.         </div>
    
  291.       </>,
    
  292.     );
    
  293.   });
    
  294. });