1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. let React;
    
  10. let ReactNoop;
    
  11. let Scheduler;
    
  12. let Suspense;
    
  13. let getCacheForType;
    
  14. let caches;
    
  15. let seededCache;
    
  16. let waitForAll;
    
  17. 
    
  18. describe('ReactSuspenseFallback', () => {
    
  19.   beforeEach(() => {
    
  20.     jest.resetModules();
    
  21. 
    
  22.     React = require('react');
    
  23.     ReactNoop = require('react-noop-renderer');
    
  24.     Scheduler = require('scheduler');
    
  25.     Suspense = React.Suspense;
    
  26.     getCacheForType = React.unstable_getCacheForType;
    
  27.     caches = [];
    
  28.     seededCache = null;
    
  29. 
    
  30.     const InternalTestUtils = require('internal-test-utils');
    
  31.     waitForAll = InternalTestUtils.waitForAll;
    
  32.   });
    
  33. 
    
  34.   function createTextCache() {
    
  35.     if (seededCache !== null) {
    
  36.       // Trick to seed a cache before it exists.
    
  37.       // TODO: Need a built-in API to seed data before the initial render (i.e.
    
  38.       // not a refresh because nothing has mounted yet).
    
  39.       const cache = seededCache;
    
  40.       seededCache = null;
    
  41.       return cache;
    
  42.     }
    
  43. 
    
  44.     const data = new Map();
    
  45.     const version = caches.length + 1;
    
  46.     const cache = {
    
  47.       version,
    
  48.       data,
    
  49.       resolve(text) {
    
  50.         const record = data.get(text);
    
  51.         if (record === undefined) {
    
  52.           const newRecord = {
    
  53.             status: 'resolved',
    
  54.             value: text,
    
  55.           };
    
  56.           data.set(text, newRecord);
    
  57.         } else if (record.status === 'pending') {
    
  58.           const thenable = record.value;
    
  59.           record.status = 'resolved';
    
  60.           record.value = text;
    
  61.           thenable.pings.forEach(t => t());
    
  62.         }
    
  63.       },
    
  64.       reject(text, error) {
    
  65.         const record = data.get(text);
    
  66.         if (record === undefined) {
    
  67.           const newRecord = {
    
  68.             status: 'rejected',
    
  69.             value: error,
    
  70.           };
    
  71.           data.set(text, newRecord);
    
  72.         } else if (record.status === 'pending') {
    
  73.           const thenable = record.value;
    
  74.           record.status = 'rejected';
    
  75.           record.value = error;
    
  76.           thenable.pings.forEach(t => t());
    
  77.         }
    
  78.       },
    
  79.     };
    
  80.     caches.push(cache);
    
  81.     return cache;
    
  82.   }
    
  83. 
    
  84.   function readText(text) {
    
  85.     const textCache = getCacheForType(createTextCache);
    
  86.     const record = textCache.data.get(text);
    
  87.     if (record !== undefined) {
    
  88.       switch (record.status) {
    
  89.         case 'pending':
    
  90.           Scheduler.log(`Suspend! [${text}]`);
    
  91.           throw record.value;
    
  92.         case 'rejected':
    
  93.           Scheduler.log(`Error! [${text}]`);
    
  94.           throw record.value;
    
  95.         case 'resolved':
    
  96.           return textCache.version;
    
  97.       }
    
  98.     } else {
    
  99.       Scheduler.log(`Suspend! [${text}]`);
    
  100. 
    
  101.       const thenable = {
    
  102.         pings: [],
    
  103.         then(resolve) {
    
  104.           if (newRecord.status === 'pending') {
    
  105.             thenable.pings.push(resolve);
    
  106.           } else {
    
  107.             Promise.resolve().then(() => resolve(newRecord.value));
    
  108.           }
    
  109.         },
    
  110.       };
    
  111. 
    
  112.       const newRecord = {
    
  113.         status: 'pending',
    
  114.         value: thenable,
    
  115.       };
    
  116.       textCache.data.set(text, newRecord);
    
  117. 
    
  118.       throw thenable;
    
  119.     }
    
  120.   }
    
  121. 
    
  122.   function Text({text}) {
    
  123.     Scheduler.log(text);
    
  124.     return <span prop={text} />;
    
  125.   }
    
  126. 
    
  127.   function AsyncText({text, showVersion}) {
    
  128.     const version = readText(text);
    
  129.     const fullText = showVersion ? `${text} [v${version}]` : text;
    
  130.     Scheduler.log(fullText);
    
  131.     return <span prop={fullText} />;
    
  132.   }
    
  133. 
    
  134.   // @gate enableLegacyCache
    
  135.   it('suspends and shows fallback', async () => {
    
  136.     ReactNoop.render(
    
  137.       <Suspense fallback={<Text text="Loading..." />}>
    
  138.         <AsyncText text="A" ms={100} />
    
  139.       </Suspense>,
    
  140.     );
    
  141. 
    
  142.     await waitForAll(['Suspend! [A]', 'Loading...']);
    
  143.     expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
    
  144.   });
    
  145. 
    
  146.   // @gate enableLegacyCache
    
  147.   it('suspends and shows null fallback', async () => {
    
  148.     ReactNoop.render(
    
  149.       <Suspense fallback={null}>
    
  150.         <AsyncText text="A" ms={100} />
    
  151.       </Suspense>,
    
  152.     );
    
  153. 
    
  154.     await waitForAll([
    
  155.       'Suspend! [A]',
    
  156.       // null
    
  157.     ]);
    
  158.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  159.   });
    
  160. 
    
  161.   // @gate enableLegacyCache
    
  162.   it('suspends and shows undefined fallback', async () => {
    
  163.     ReactNoop.render(
    
  164.       <Suspense>
    
  165.         <AsyncText text="A" ms={100} />
    
  166.       </Suspense>,
    
  167.     );
    
  168. 
    
  169.     await waitForAll([
    
  170.       'Suspend! [A]',
    
  171.       // null
    
  172.     ]);
    
  173.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  174.   });
    
  175. 
    
  176.   // @gate enableLegacyCache
    
  177.   it('suspends and shows inner fallback', async () => {
    
  178.     ReactNoop.render(
    
  179.       <Suspense fallback={<Text text="Should not show..." />}>
    
  180.         <Suspense fallback={<Text text="Loading..." />}>
    
  181.           <AsyncText text="A" ms={100} />
    
  182.         </Suspense>
    
  183.       </Suspense>,
    
  184.     );
    
  185. 
    
  186.     await waitForAll(['Suspend! [A]', 'Loading...']);
    
  187.     expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
    
  188.   });
    
  189. 
    
  190.   // @gate enableLegacyCache
    
  191.   it('suspends and shows inner undefined fallback', async () => {
    
  192.     ReactNoop.render(
    
  193.       <Suspense fallback={<Text text="Should not show..." />}>
    
  194.         <Suspense>
    
  195.           <AsyncText text="A" ms={100} />
    
  196.         </Suspense>
    
  197.       </Suspense>,
    
  198.     );
    
  199. 
    
  200.     await waitForAll([
    
  201.       'Suspend! [A]',
    
  202.       // null
    
  203.     ]);
    
  204.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  205.   });
    
  206. 
    
  207.   // @gate enableLegacyCache
    
  208.   it('suspends and shows inner null fallback', async () => {
    
  209.     ReactNoop.render(
    
  210.       <Suspense fallback={<Text text="Should not show..." />}>
    
  211.         <Suspense fallback={null}>
    
  212.           <AsyncText text="A" ms={100} />
    
  213.         </Suspense>
    
  214.       </Suspense>,
    
  215.     );
    
  216. 
    
  217.     await waitForAll([
    
  218.       'Suspend! [A]',
    
  219.       // null
    
  220.     ]);
    
  221.     expect(ReactNoop).toMatchRenderedOutput(null);
    
  222.   });
    
  223. });