1. let React;
    
  2. let ReactNoop;
    
  3. let Scheduler;
    
  4. let act;
    
  5. let Suspense;
    
  6. let useEffect;
    
  7. let getCacheForType;
    
  8. 
    
  9. let caches;
    
  10. let seededCache;
    
  11. let assertLog;
    
  12. 
    
  13. describe('ReactSuspenseWithNoopRenderer', () => {
    
  14.   beforeEach(() => {
    
  15.     jest.resetModules();
    
  16. 
    
  17.     React = require('react');
    
  18.     ReactNoop = require('react-noop-renderer');
    
  19.     Scheduler = require('scheduler');
    
  20.     act = require('internal-test-utils').act;
    
  21.     Suspense = React.Suspense;
    
  22.     useEffect = React.useEffect;
    
  23. 
    
  24.     getCacheForType = React.unstable_getCacheForType;
    
  25. 
    
  26.     const InternalTestUtils = require('internal-test-utils');
    
  27.     assertLog = InternalTestUtils.assertLog;
    
  28. 
    
  29.     caches = [];
    
  30.     seededCache = null;
    
  31.   });
    
  32. 
    
  33.   function createTextCache() {
    
  34.     if (seededCache !== null) {
    
  35.       // Trick to seed a cache before it exists.
    
  36.       // TODO: Need a built-in API to seed data before the initial render (i.e.
    
  37.       // not a refresh because nothing has mounted yet).
    
  38.       const cache = seededCache;
    
  39.       seededCache = null;
    
  40.       return cache;
    
  41.     }
    
  42. 
    
  43.     const data = new Map();
    
  44.     const version = caches.length + 1;
    
  45.     const cache = {
    
  46.       version,
    
  47.       data,
    
  48.       resolve(text) {
    
  49.         const record = data.get(text);
    
  50.         if (record === undefined) {
    
  51.           const newRecord = {
    
  52.             status: 'resolved',
    
  53.             value: text,
    
  54.           };
    
  55.           data.set(text, newRecord);
    
  56.         } else if (record.status === 'pending') {
    
  57.           const thenable = record.value;
    
  58.           record.status = 'resolved';
    
  59.           record.value = text;
    
  60.           thenable.pings.forEach(t => t());
    
  61.         }
    
  62.       },
    
  63.       reject(text, error) {
    
  64.         const record = data.get(text);
    
  65.         if (record === undefined) {
    
  66.           const newRecord = {
    
  67.             status: 'rejected',
    
  68.             value: error,
    
  69.           };
    
  70.           data.set(text, newRecord);
    
  71.         } else if (record.status === 'pending') {
    
  72.           const thenable = record.value;
    
  73.           record.status = 'rejected';
    
  74.           record.value = error;
    
  75.           thenable.pings.forEach(t => t());
    
  76.         }
    
  77.       },
    
  78.     };
    
  79.     caches.push(cache);
    
  80.     return cache;
    
  81.   }
    
  82. 
    
  83.   function readText(text) {
    
  84.     const textCache = getCacheForType(createTextCache);
    
  85.     const record = textCache.data.get(text);
    
  86.     if (record !== undefined) {
    
  87.       switch (record.status) {
    
  88.         case 'pending':
    
  89.           Scheduler.log(`Suspend! [${text}]`);
    
  90.           throw record.value;
    
  91.         case 'rejected':
    
  92.           Scheduler.log(`Error! [${text}]`);
    
  93.           throw record.value;
    
  94.         case 'resolved':
    
  95.           return textCache.version;
    
  96.       }
    
  97.     } else {
    
  98.       Scheduler.log(`Suspend! [${text}]`);
    
  99. 
    
  100.       const thenable = {
    
  101.         pings: [],
    
  102.         then(resolve) {
    
  103.           if (newRecord.status === 'pending') {
    
  104.             thenable.pings.push(resolve);
    
  105.           } else {
    
  106.             Promise.resolve().then(() => resolve(newRecord.value));
    
  107.           }
    
  108.         },
    
  109.       };
    
  110. 
    
  111.       const newRecord = {
    
  112.         status: 'pending',
    
  113.         value: thenable,
    
  114.       };
    
  115.       textCache.data.set(text, newRecord);
    
  116. 
    
  117.       throw thenable;
    
  118.     }
    
  119.   }
    
  120. 
    
  121.   function resolveMostRecentTextCache(text) {
    
  122.     if (caches.length === 0) {
    
  123.       throw Error('Cache does not exist.');
    
  124.     } else {
    
  125.       // Resolve the most recently created cache. An older cache can by
    
  126.       // resolved with `caches[index].resolve(text)`.
    
  127.       caches[caches.length - 1].resolve(text);
    
  128.     }
    
  129.   }
    
  130. 
    
  131.   const resolveText = resolveMostRecentTextCache;
    
  132. 
    
  133.   // @gate experimental || www
    
  134.   it('regression: false positive for legacy suspense', async () => {
    
  135.     // Wrapping in memo because regular function components go through the
    
  136.     // mountIndeterminateComponent path, which acts like there's no `current`
    
  137.     // fiber even though there is. `memo` is not indeterminate, so it goes
    
  138.     // through the update path.
    
  139.     const Child = React.memo(({text}) => {
    
  140.       // If text hasn't resolved, this will throw and exit before the passive
    
  141.       // static effect flag is added by the useEffect call below.
    
  142.       readText(text);
    
  143. 
    
  144.       useEffect(() => {
    
  145.         Scheduler.log('Effect');
    
  146.       }, []);
    
  147. 
    
  148.       Scheduler.log(text);
    
  149.       return text;
    
  150.     });
    
  151. 
    
  152.     function App() {
    
  153.       return (
    
  154.         <Suspense fallback="Loading...">
    
  155.           <Child text="Async" />
    
  156.         </Suspense>
    
  157.       );
    
  158.     }
    
  159. 
    
  160.     const root = ReactNoop.createLegacyRoot(null);
    
  161. 
    
  162.     // On initial mount, the suspended component is committed in an incomplete
    
  163.     // state, without a passive static effect flag.
    
  164.     await act(() => {
    
  165.       root.render(<App />);
    
  166.     });
    
  167.     assertLog(['Suspend! [Async]']);
    
  168.     expect(root).toMatchRenderedOutput('Loading...');
    
  169. 
    
  170.     // When the promise resolves, a passive static effect flag is added. In the
    
  171.     // regression, the "missing expected static flag" would fire, because the
    
  172.     // previous fiber did not have one.
    
  173.     await act(() => {
    
  174.       resolveText('Async');
    
  175.     });
    
  176.     assertLog(['Async', 'Effect']);
    
  177.     expect(root).toMatchRenderedOutput('Async');
    
  178.   });
    
  179. });