1. let React;
    
  2. let ReactNoop;
    
  3. let Scheduler;
    
  4. let act;
    
  5. let useState;
    
  6. let useContext;
    
  7. let Suspense;
    
  8. let SuspenseList;
    
  9. let getCacheForType;
    
  10. let caches;
    
  11. let seededCache;
    
  12. let assertLog;
    
  13. 
    
  14. describe('ReactLazyContextPropagation', () => {
    
  15.   beforeEach(() => {
    
  16.     jest.resetModules();
    
  17. 
    
  18.     React = require('react');
    
  19.     ReactNoop = require('react-noop-renderer');
    
  20.     Scheduler = require('scheduler');
    
  21.     act = require('internal-test-utils').act;
    
  22.     useState = React.useState;
    
  23.     useContext = React.useContext;
    
  24.     Suspense = React.Suspense;
    
  25.     if (gate(flags => flags.enableSuspenseList)) {
    
  26.       SuspenseList = React.unstable_SuspenseList;
    
  27.     }
    
  28. 
    
  29.     const InternalTestUtils = require('internal-test-utils');
    
  30.     assertLog = InternalTestUtils.assertLog;
    
  31. 
    
  32.     getCacheForType = React.unstable_getCacheForType;
    
  33. 
    
  34.     caches = [];
    
  35.     seededCache = null;
    
  36.   });
    
  37. 
    
  38.   // NOTE: These tests are not specific to the lazy propagation (as opposed to
    
  39.   // eager propagation). The behavior should be the same in both
    
  40.   // implementations. These are tests that are more relevant to the lazy
    
  41.   // propagation implementation, though.
    
  42. 
    
  43.   function createTextCache() {
    
  44.     if (seededCache !== null) {
    
  45.       // Trick to seed a cache before it exists.
    
  46.       // TODO: Need a built-in API to seed data before the initial render (i.e.
    
  47.       // not a refresh because nothing has mounted yet).
    
  48.       const cache = seededCache;
    
  49.       seededCache = null;
    
  50.       return cache;
    
  51.     }
    
  52. 
    
  53.     const data = new Map();
    
  54.     const version = caches.length + 1;
    
  55.     const cache = {
    
  56.       version,
    
  57.       data,
    
  58.       resolve(text) {
    
  59.         const record = data.get(text);
    
  60.         if (record === undefined) {
    
  61.           const newRecord = {
    
  62.             status: 'resolved',
    
  63.             value: text,
    
  64.           };
    
  65.           data.set(text, newRecord);
    
  66.         } else if (record.status === 'pending') {
    
  67.           const thenable = record.value;
    
  68.           record.status = 'resolved';
    
  69.           record.value = text;
    
  70.           thenable.pings.forEach(t => t());
    
  71.         }
    
  72.       },
    
  73.       reject(text, error) {
    
  74.         const record = data.get(text);
    
  75.         if (record === undefined) {
    
  76.           const newRecord = {
    
  77.             status: 'rejected',
    
  78.             value: error,
    
  79.           };
    
  80.           data.set(text, newRecord);
    
  81.         } else if (record.status === 'pending') {
    
  82.           const thenable = record.value;
    
  83.           record.status = 'rejected';
    
  84.           record.value = error;
    
  85.           thenable.pings.forEach(t => t());
    
  86.         }
    
  87.       },
    
  88.     };
    
  89.     caches.push(cache);
    
  90.     return cache;
    
  91.   }
    
  92. 
    
  93.   function readText(text) {
    
  94.     const textCache = getCacheForType(createTextCache);
    
  95.     const record = textCache.data.get(text);
    
  96.     if (record !== undefined) {
    
  97.       switch (record.status) {
    
  98.         case 'pending':
    
  99.           Scheduler.log(`Suspend! [${text}]`);
    
  100.           throw record.value;
    
  101.         case 'rejected':
    
  102.           Scheduler.log(`Error! [${text}]`);
    
  103.           throw record.value;
    
  104.         case 'resolved':
    
  105.           return textCache.version;
    
  106.       }
    
  107.     } else {
    
  108.       Scheduler.log(`Suspend! [${text}]`);
    
  109. 
    
  110.       const thenable = {
    
  111.         pings: [],
    
  112.         then(resolve) {
    
  113.           if (newRecord.status === 'pending') {
    
  114.             thenable.pings.push(resolve);
    
  115.           } else {
    
  116.             Promise.resolve().then(() => resolve(newRecord.value));
    
  117.           }
    
  118.         },
    
  119.       };
    
  120. 
    
  121.       const newRecord = {
    
  122.         status: 'pending',
    
  123.         value: thenable,
    
  124.       };
    
  125.       textCache.data.set(text, newRecord);
    
  126. 
    
  127.       throw thenable;
    
  128.     }
    
  129.   }
    
  130. 
    
  131.   function Text({text}) {
    
  132.     Scheduler.log(text);
    
  133.     return text;
    
  134.   }
    
  135. 
    
  136.   // function AsyncText({text, showVersion}) {
    
  137.   //   const version = readText(text);
    
  138.   //   const fullText = showVersion ? `${text} [v${version}]` : text;
    
  139.   //   Scheduler.log(fullText);
    
  140.   //   return text;
    
  141.   // }
    
  142. 
    
  143.   function seedNextTextCache(text) {
    
  144.     if (seededCache === null) {
    
  145.       seededCache = createTextCache();
    
  146.     }
    
  147.     seededCache.resolve(text);
    
  148.   }
    
  149. 
    
  150.   function resolveMostRecentTextCache(text) {
    
  151.     if (caches.length === 0) {
    
  152.       throw Error('Cache does not exist.');
    
  153.     } else {
    
  154.       // Resolve the most recently created cache. An older cache can by
    
  155.       // resolved with `caches[index].resolve(text)`.
    
  156.       caches[caches.length - 1].resolve(text);
    
  157.     }
    
  158.   }
    
  159. 
    
  160.   const resolveText = resolveMostRecentTextCache;
    
  161. 
    
  162.   // function rejectMostRecentTextCache(text, error) {
    
  163.   //   if (caches.length === 0) {
    
  164.   //     throw Error('Cache does not exist.');
    
  165.   //   } else {
    
  166.   //     // Resolve the most recently created cache. An older cache can by
    
  167.   //     // resolved with `caches[index].reject(text, error)`.
    
  168.   //     caches[caches.length - 1].reject(text, error);
    
  169.   //   }
    
  170.   // }
    
  171. 
    
  172.   test(
    
  173.     'context change should prevent bailout of memoized component (useMemo -> ' +
    
  174.       'no intermediate fiber)',
    
  175.     async () => {
    
  176.       const root = ReactNoop.createRoot();
    
  177. 
    
  178.       const Context = React.createContext(0);
    
  179. 
    
  180.       let setValue;
    
  181.       function App() {
    
  182.         const [value, _setValue] = useState(0);
    
  183.         setValue = _setValue;
    
  184. 
    
  185.         // NOTE: It's an important part of this test that we're memoizing the
    
  186.         // props of the Consumer component, as opposed to wrapping in an
    
  187.         // additional memoized fiber, because the implementation propagates
    
  188.         // context changes whenever a fiber bails out.
    
  189.         const consumer = React.useMemo(() => <Consumer />, []);
    
  190. 
    
  191.         return <Context.Provider value={value}>{consumer}</Context.Provider>;
    
  192.       }
    
  193. 
    
  194.       function Consumer() {
    
  195.         const value = useContext(Context);
    
  196.         // Even though Consumer is memoized, Consumer should re-render
    
  197.         // DeepChild whenever the context value changes. Otherwise DeepChild
    
  198.         // won't receive the new value.
    
  199.         return <DeepChild value={value} />;
    
  200.       }
    
  201. 
    
  202.       function DeepChild({value}) {
    
  203.         return <Text text={value} />;
    
  204.       }
    
  205. 
    
  206.       await act(() => {
    
  207.         root.render(<App />);
    
  208.       });
    
  209.       assertLog([0]);
    
  210.       expect(root).toMatchRenderedOutput('0');
    
  211. 
    
  212.       await act(() => {
    
  213.         setValue(1);
    
  214.       });
    
  215.       assertLog([1]);
    
  216.       expect(root).toMatchRenderedOutput('1');
    
  217.     },
    
  218.   );
    
  219. 
    
  220.   test('context change should prevent bailout of memoized component (memo HOC)', async () => {
    
  221.     const root = ReactNoop.createRoot();
    
  222. 
    
  223.     const Context = React.createContext(0);
    
  224. 
    
  225.     let setValue;
    
  226.     function App() {
    
  227.       const [value, _setValue] = useState(0);
    
  228.       setValue = _setValue;
    
  229.       return (
    
  230.         <Context.Provider value={value}>
    
  231.           <Consumer />
    
  232.         </Context.Provider>
    
  233.       );
    
  234.     }
    
  235. 
    
  236.     const Consumer = React.memo(() => {
    
  237.       const value = useContext(Context);
    
  238.       // Even though Consumer is memoized, Consumer should re-render
    
  239.       // DeepChild whenever the context value changes. Otherwise DeepChild
    
  240.       // won't receive the new value.
    
  241.       return <DeepChild value={value} />;
    
  242.     });
    
  243. 
    
  244.     function DeepChild({value}) {
    
  245.       return <Text text={value} />;
    
  246.     }
    
  247. 
    
  248.     await act(() => {
    
  249.       root.render(<App />);
    
  250.     });
    
  251.     assertLog([0]);
    
  252.     expect(root).toMatchRenderedOutput('0');
    
  253. 
    
  254.     await act(() => {
    
  255.       setValue(1);
    
  256.     });
    
  257.     assertLog([1]);
    
  258.     expect(root).toMatchRenderedOutput('1');
    
  259.   });
    
  260. 
    
  261.   test('context change should prevent bailout of memoized component (PureComponent)', async () => {
    
  262.     const root = ReactNoop.createRoot();
    
  263. 
    
  264.     const Context = React.createContext(0);
    
  265. 
    
  266.     let setValue;
    
  267.     function App() {
    
  268.       const [value, _setValue] = useState(0);
    
  269.       setValue = _setValue;
    
  270.       return (
    
  271.         <Context.Provider value={value}>
    
  272.           <Consumer />
    
  273.         </Context.Provider>
    
  274.       );
    
  275.     }
    
  276. 
    
  277.     class Consumer extends React.PureComponent {
    
  278.       static contextType = Context;
    
  279.       render() {
    
  280.         // Even though Consumer is memoized, Consumer should re-render
    
  281.         // DeepChild whenever the context value changes. Otherwise DeepChild
    
  282.         // won't receive the new value.
    
  283.         return <DeepChild value={this.context} />;
    
  284.       }
    
  285.     }
    
  286. 
    
  287.     function DeepChild({value}) {
    
  288.       return <Text text={value} />;
    
  289.     }
    
  290. 
    
  291.     await act(() => {
    
  292.       root.render(<App />);
    
  293.     });
    
  294.     assertLog([0]);
    
  295.     expect(root).toMatchRenderedOutput('0');
    
  296. 
    
  297.     await act(() => {
    
  298.       setValue(1);
    
  299.     });
    
  300.     assertLog([1]);
    
  301.     expect(root).toMatchRenderedOutput('1');
    
  302.   });
    
  303. 
    
  304.   test("context consumer bails out if context hasn't changed", async () => {
    
  305.     const root = ReactNoop.createRoot();
    
  306. 
    
  307.     const Context = React.createContext(0);
    
  308. 
    
  309.     function App() {
    
  310.       return (
    
  311.         <Context.Provider value={0}>
    
  312.           <Consumer />
    
  313.         </Context.Provider>
    
  314.       );
    
  315.     }
    
  316. 
    
  317.     let setOtherValue;
    
  318.     const Consumer = React.memo(() => {
    
  319.       const value = useContext(Context);
    
  320. 
    
  321.       const [, _setOtherValue] = useState(0);
    
  322.       setOtherValue = _setOtherValue;
    
  323. 
    
  324.       Scheduler.log('Consumer');
    
  325. 
    
  326.       return <Text text={value} />;
    
  327.     });
    
  328. 
    
  329.     await act(() => {
    
  330.       root.render(<App />);
    
  331.     });
    
  332.     assertLog(['Consumer', 0]);
    
  333.     expect(root).toMatchRenderedOutput('0');
    
  334. 
    
  335.     await act(() => {
    
  336.       // Intentionally calling setState to some other arbitrary value before
    
  337.       // setting it back to the current one. That way an update is scheduled,
    
  338.       // but we'll bail out during render when nothing has changed.
    
  339.       setOtherValue(1);
    
  340.       setOtherValue(0);
    
  341.     });
    
  342.     // NOTE: If this didn't yield anything, that indicates that we never visited
    
  343.     // the consumer during the render phase, which probably means the eager
    
  344.     // bailout mechanism kicked in. Because we're testing the _lazy_ bailout
    
  345.     // mechanism, update this test to foil the _eager_ bailout, somehow. Perhaps
    
  346.     // by switching to useReducer.
    
  347.     assertLog(['Consumer']);
    
  348.     expect(root).toMatchRenderedOutput('0');
    
  349.   });
    
  350. 
    
  351.   // @gate enableLegacyCache
    
  352.   test('context is propagated across retries', async () => {
    
  353.     const root = ReactNoop.createRoot();
    
  354. 
    
  355.     const Context = React.createContext('A');
    
  356. 
    
  357.     let setContext;
    
  358.     function App() {
    
  359.       const [value, setValue] = useState('A');
    
  360.       setContext = setValue;
    
  361.       return (
    
  362.         <Context.Provider value={value}>
    
  363.           <Suspense fallback={<Text text="Loading..." />}>
    
  364.             <Async />
    
  365.           </Suspense>
    
  366.           <Text text={value} />
    
  367.         </Context.Provider>
    
  368.       );
    
  369.     }
    
  370. 
    
  371.     function Async() {
    
  372.       const value = useContext(Context);
    
  373.       readText(value);
    
  374. 
    
  375.       // When `readText` suspends, we haven't yet visited Indirection and all
    
  376.       // of its children. They won't get rendered until a later retry.
    
  377.       return <Indirection />;
    
  378.     }
    
  379. 
    
  380.     const Indirection = React.memo(() => {
    
  381.       // This child must always be consistent with the sibling Text component.
    
  382.       return <DeepChild />;
    
  383.     });
    
  384. 
    
  385.     function DeepChild() {
    
  386.       const value = useContext(Context);
    
  387.       return <Text text={value} />;
    
  388.     }
    
  389. 
    
  390.     await seedNextTextCache('A');
    
  391.     await act(() => {
    
  392.       root.render(<App />);
    
  393.     });
    
  394.     assertLog(['A', 'A']);
    
  395.     expect(root).toMatchRenderedOutput('AA');
    
  396. 
    
  397.     await act(() => {
    
  398.       // Intentionally not wrapping in startTransition, so that the fallback
    
  399.       // the fallback displays despite this being a refresh.
    
  400.       setContext('B');
    
  401.     });
    
  402.     assertLog(['Suspend! [B]', 'Loading...', 'B']);
    
  403.     expect(root).toMatchRenderedOutput('Loading...B');
    
  404. 
    
  405.     await act(async () => {
    
  406.       await resolveText('B');
    
  407.     });
    
  408.     assertLog(['B']);
    
  409.     expect(root).toMatchRenderedOutput('BB');
    
  410.   });
    
  411. 
    
  412.   // @gate enableLegacyCache
    
  413.   test('multiple contexts are propagated across retries', async () => {
    
  414.     // Same as previous test, but with multiple context providers
    
  415.     const root = ReactNoop.createRoot();
    
  416. 
    
  417.     const Context1 = React.createContext('A');
    
  418.     const Context2 = React.createContext('A');
    
  419. 
    
  420.     let setContext;
    
  421.     function App() {
    
  422.       const [value, setValue] = useState('A');
    
  423.       setContext = setValue;
    
  424.       return (
    
  425.         <Context1.Provider value={value}>
    
  426.           <Context2.Provider value={value}>
    
  427.             <Suspense fallback={<Text text="Loading..." />}>
    
  428.               <Async />
    
  429.             </Suspense>
    
  430.             <Text text={value} />
    
  431.           </Context2.Provider>
    
  432.         </Context1.Provider>
    
  433.       );
    
  434.     }
    
  435. 
    
  436.     function Async() {
    
  437.       const value = useContext(Context1);
    
  438.       readText(value);
    
  439. 
    
  440.       // When `readText` suspends, we haven't yet visited Indirection and all
    
  441.       // of its children. They won't get rendered until a later retry.
    
  442.       return (
    
  443.         <>
    
  444.           <Indirection1 />
    
  445.           <Indirection2 />
    
  446.         </>
    
  447.       );
    
  448.     }
    
  449. 
    
  450.     const Indirection1 = React.memo(() => {
    
  451.       // This child must always be consistent with the sibling Text component.
    
  452.       return <DeepChild1 />;
    
  453.     });
    
  454. 
    
  455.     const Indirection2 = React.memo(() => {
    
  456.       // This child must always be consistent with the sibling Text component.
    
  457.       return <DeepChild2 />;
    
  458.     });
    
  459. 
    
  460.     function DeepChild1() {
    
  461.       const value = useContext(Context1);
    
  462.       return <Text text={value} />;
    
  463.     }
    
  464. 
    
  465.     function DeepChild2() {
    
  466.       const value = useContext(Context2);
    
  467.       return <Text text={value} />;
    
  468.     }
    
  469. 
    
  470.     await seedNextTextCache('A');
    
  471.     await act(() => {
    
  472.       root.render(<App />);
    
  473.     });
    
  474.     assertLog(['A', 'A', 'A']);
    
  475.     expect(root).toMatchRenderedOutput('AAA');
    
  476. 
    
  477.     await act(() => {
    
  478.       // Intentionally not wrapping in startTransition, so that the fallback
    
  479.       // the fallback displays despite this being a refresh.
    
  480.       setContext('B');
    
  481.     });
    
  482.     assertLog(['Suspend! [B]', 'Loading...', 'B']);
    
  483.     expect(root).toMatchRenderedOutput('Loading...B');
    
  484. 
    
  485.     await act(async () => {
    
  486.       await resolveText('B');
    
  487.     });
    
  488.     assertLog(['B', 'B']);
    
  489.     expect(root).toMatchRenderedOutput('BBB');
    
  490.   });
    
  491. 
    
  492.   // @gate enableLegacyCache
    
  493.   test('context is propagated across retries (legacy)', async () => {
    
  494.     const root = ReactNoop.createLegacyRoot();
    
  495. 
    
  496.     const Context = React.createContext('A');
    
  497. 
    
  498.     let setContext;
    
  499.     function App() {
    
  500.       const [value, setValue] = useState('A');
    
  501.       setContext = setValue;
    
  502.       return (
    
  503.         <Context.Provider value={value}>
    
  504.           <Suspense fallback={<Text text="Loading..." />}>
    
  505.             <Async />
    
  506.           </Suspense>
    
  507.           <Text text={value} />
    
  508.         </Context.Provider>
    
  509.       );
    
  510.     }
    
  511. 
    
  512.     function Async() {
    
  513.       const value = useContext(Context);
    
  514.       readText(value);
    
  515. 
    
  516.       // When `readText` suspends, we haven't yet visited Indirection and all
    
  517.       // of its children. They won't get rendered until a later retry.
    
  518.       return <Indirection />;
    
  519.     }
    
  520. 
    
  521.     const Indirection = React.memo(() => {
    
  522.       // This child must always be consistent with the sibling Text component.
    
  523.       return <DeepChild />;
    
  524.     });
    
  525. 
    
  526.     function DeepChild() {
    
  527.       const value = useContext(Context);
    
  528.       return <Text text={value} />;
    
  529.     }
    
  530. 
    
  531.     await seedNextTextCache('A');
    
  532.     await act(() => {
    
  533.       root.render(<App />);
    
  534.     });
    
  535.     assertLog(['A', 'A']);
    
  536.     expect(root).toMatchRenderedOutput('AA');
    
  537. 
    
  538.     await act(() => {
    
  539.       // Intentionally not wrapping in startTransition, so that the fallback
    
  540.       // the fallback displays despite this being a refresh.
    
  541.       setContext('B');
    
  542.     });
    
  543.     assertLog(['Suspend! [B]', 'Loading...', 'B']);
    
  544.     expect(root).toMatchRenderedOutput('Loading...B');
    
  545. 
    
  546.     await act(async () => {
    
  547.       await resolveText('B');
    
  548.     });
    
  549.     assertLog(['B']);
    
  550.     expect(root).toMatchRenderedOutput('BB');
    
  551.   });
    
  552. 
    
  553.   // @gate www
    
  554.   test('context is propagated through offscreen trees', async () => {
    
  555.     const LegacyHidden = React.unstable_LegacyHidden;
    
  556. 
    
  557.     const root = ReactNoop.createRoot();
    
  558. 
    
  559.     const Context = React.createContext('A');
    
  560. 
    
  561.     let setContext;
    
  562.     function App() {
    
  563.       const [value, setValue] = useState('A');
    
  564.       setContext = setValue;
    
  565.       return (
    
  566.         <Context.Provider value={value}>
    
  567.           <LegacyHidden mode="hidden">
    
  568.             <Indirection />
    
  569.           </LegacyHidden>
    
  570.           <Text text={value} />
    
  571.         </Context.Provider>
    
  572.       );
    
  573.     }
    
  574. 
    
  575.     const Indirection = React.memo(() => {
    
  576.       // This child must always be consistent with the sibling Text component.
    
  577.       return <DeepChild />;
    
  578.     });
    
  579. 
    
  580.     function DeepChild() {
    
  581.       const value = useContext(Context);
    
  582.       return <Text text={value} />;
    
  583.     }
    
  584. 
    
  585.     await seedNextTextCache('A');
    
  586.     await act(() => {
    
  587.       root.render(<App />);
    
  588.     });
    
  589.     assertLog(['A', 'A']);
    
  590.     expect(root).toMatchRenderedOutput('AA');
    
  591. 
    
  592.     await act(() => {
    
  593.       setContext('B');
    
  594.     });
    
  595.     assertLog(['B', 'B']);
    
  596.     expect(root).toMatchRenderedOutput('BB');
    
  597.   });
    
  598. 
    
  599.   // @gate www
    
  600.   test('multiple contexts are propagated across through offscreen trees', async () => {
    
  601.     // Same as previous test, but with multiple context providers
    
  602.     const LegacyHidden = React.unstable_LegacyHidden;
    
  603. 
    
  604.     const root = ReactNoop.createRoot();
    
  605. 
    
  606.     const Context1 = React.createContext('A');
    
  607.     const Context2 = React.createContext('A');
    
  608. 
    
  609.     let setContext;
    
  610.     function App() {
    
  611.       const [value, setValue] = useState('A');
    
  612.       setContext = setValue;
    
  613.       return (
    
  614.         <Context1.Provider value={value}>
    
  615.           <Context2.Provider value={value}>
    
  616.             <LegacyHidden mode="hidden">
    
  617.               <Indirection1 />
    
  618.               <Indirection2 />
    
  619.             </LegacyHidden>
    
  620.             <Text text={value} />
    
  621.           </Context2.Provider>
    
  622.         </Context1.Provider>
    
  623.       );
    
  624.     }
    
  625. 
    
  626.     const Indirection1 = React.memo(() => {
    
  627.       // This child must always be consistent with the sibling Text component.
    
  628.       return <DeepChild1 />;
    
  629.     });
    
  630. 
    
  631.     const Indirection2 = React.memo(() => {
    
  632.       // This child must always be consistent with the sibling Text component.
    
  633.       return <DeepChild2 />;
    
  634.     });
    
  635. 
    
  636.     function DeepChild1() {
    
  637.       const value = useContext(Context1);
    
  638.       return <Text text={value} />;
    
  639.     }
    
  640. 
    
  641.     function DeepChild2() {
    
  642.       const value = useContext(Context2);
    
  643.       return <Text text={value} />;
    
  644.     }
    
  645. 
    
  646.     await seedNextTextCache('A');
    
  647.     await act(() => {
    
  648.       root.render(<App />);
    
  649.     });
    
  650.     assertLog(['A', 'A', 'A']);
    
  651.     expect(root).toMatchRenderedOutput('AAA');
    
  652. 
    
  653.     await act(() => {
    
  654.       setContext('B');
    
  655.     });
    
  656.     assertLog(['B', 'B', 'B']);
    
  657.     expect(root).toMatchRenderedOutput('BBB');
    
  658.   });
    
  659. 
    
  660.   // @gate enableSuspenseList
    
  661.   test('contexts are propagated through SuspenseList', async () => {
    
  662.     // This kinda tests an implementation detail. SuspenseList has an early
    
  663.     // bailout that doesn't use `bailoutOnAlreadyFinishedWork`. It probably
    
  664.     // should just use that function, though.
    
  665.     const Context = React.createContext('A');
    
  666. 
    
  667.     let setContext;
    
  668.     function App() {
    
  669.       const [value, setValue] = useState('A');
    
  670.       setContext = setValue;
    
  671.       const children = React.useMemo(
    
  672.         () => (
    
  673.           <SuspenseList revealOrder="forwards">
    
  674.             <Child />
    
  675.             <Child />
    
  676.           </SuspenseList>
    
  677.         ),
    
  678.         [],
    
  679.       );
    
  680.       return <Context.Provider value={value}>{children}</Context.Provider>;
    
  681.     }
    
  682. 
    
  683.     function Child() {
    
  684.       const value = useContext(Context);
    
  685.       return <Text text={value} />;
    
  686.     }
    
  687. 
    
  688.     const root = ReactNoop.createRoot();
    
  689.     await act(() => {
    
  690.       root.render(<App />);
    
  691.     });
    
  692.     assertLog(['A', 'A']);
    
  693.     expect(root).toMatchRenderedOutput('AA');
    
  694. 
    
  695.     await act(() => {
    
  696.       setContext('B');
    
  697.     });
    
  698.     assertLog(['B', 'B']);
    
  699.     expect(root).toMatchRenderedOutput('BB');
    
  700.   });
    
  701. 
    
  702.   test('nested bailouts', async () => {
    
  703.     // Lazy context propagation will stop propagating when it hits the first
    
  704.     // match. If we bail out again inside that tree, we must resume propagating.
    
  705. 
    
  706.     const Context = React.createContext('A');
    
  707. 
    
  708.     let setContext;
    
  709.     function App() {
    
  710.       const [value, setValue] = useState('A');
    
  711.       setContext = setValue;
    
  712.       return (
    
  713.         <Context.Provider value={value}>
    
  714.           <ChildIndirection />
    
  715.         </Context.Provider>
    
  716.       );
    
  717.     }
    
  718. 
    
  719.     const ChildIndirection = React.memo(() => {
    
  720.       return <Child />;
    
  721.     });
    
  722. 
    
  723.     function Child() {
    
  724.       const value = useContext(Context);
    
  725.       return (
    
  726.         <>
    
  727.           <Text text={value} />
    
  728.           <DeepChildIndirection />
    
  729.         </>
    
  730.       );
    
  731.     }
    
  732. 
    
  733.     const DeepChildIndirection = React.memo(() => {
    
  734.       return <DeepChild />;
    
  735.     });
    
  736. 
    
  737.     function DeepChild() {
    
  738.       const value = useContext(Context);
    
  739.       return <Text text={value} />;
    
  740.     }
    
  741. 
    
  742.     const root = ReactNoop.createRoot();
    
  743.     await act(() => {
    
  744.       root.render(<App />);
    
  745.     });
    
  746.     assertLog(['A', 'A']);
    
  747.     expect(root).toMatchRenderedOutput('AA');
    
  748. 
    
  749.     await act(() => {
    
  750.       setContext('B');
    
  751.     });
    
  752.     assertLog(['B', 'B']);
    
  753.     expect(root).toMatchRenderedOutput('BB');
    
  754.   });
    
  755. 
    
  756.   // @gate enableLegacyCache
    
  757.   test('nested bailouts across retries', async () => {
    
  758.     // Lazy context propagation will stop propagating when it hits the first
    
  759.     // match. If we bail out again inside that tree, we must resume propagating.
    
  760. 
    
  761.     const Context = React.createContext('A');
    
  762. 
    
  763.     let setContext;
    
  764.     function App() {
    
  765.       const [value, setValue] = useState('A');
    
  766.       setContext = setValue;
    
  767.       return (
    
  768.         <Context.Provider value={value}>
    
  769.           <Suspense fallback={<Text text="Loading..." />}>
    
  770.             <Async value={value} />
    
  771.           </Suspense>
    
  772.         </Context.Provider>
    
  773.       );
    
  774.     }
    
  775. 
    
  776.     function Async({value}) {
    
  777.       // When this suspends, we won't be able to visit its children during the
    
  778.       // current render. So we must take extra care to propagate the context
    
  779.       // change in such a way that they're aren't lost when we retry in a
    
  780.       // later render.
    
  781.       readText(value);
    
  782.       return <Child value={value} />;
    
  783.     }
    
  784. 
    
  785.     function Child() {
    
  786.       const value = useContext(Context);
    
  787.       return (
    
  788.         <>
    
  789.           <Text text={value} />
    
  790.           <DeepChildIndirection />
    
  791.         </>
    
  792.       );
    
  793.     }
    
  794. 
    
  795.     const DeepChildIndirection = React.memo(() => {
    
  796.       return <DeepChild />;
    
  797.     });
    
  798. 
    
  799.     function DeepChild() {
    
  800.       const value = useContext(Context);
    
  801.       return <Text text={value} />;
    
  802.     }
    
  803. 
    
  804.     const root = ReactNoop.createRoot();
    
  805.     await seedNextTextCache('A');
    
  806.     await act(() => {
    
  807.       root.render(<App />);
    
  808.     });
    
  809.     assertLog(['A', 'A']);
    
  810.     expect(root).toMatchRenderedOutput('AA');
    
  811. 
    
  812.     await act(() => {
    
  813.       setContext('B');
    
  814.     });
    
  815.     assertLog(['Suspend! [B]', 'Loading...']);
    
  816.     expect(root).toMatchRenderedOutput('Loading...');
    
  817. 
    
  818.     await act(async () => {
    
  819.       await resolveText('B');
    
  820.     });
    
  821.     assertLog(['B', 'B']);
    
  822.     expect(root).toMatchRenderedOutput('BB');
    
  823.   });
    
  824. 
    
  825.   // @gate www
    
  826.   test('nested bailouts through offscreen trees', async () => {
    
  827.     // Lazy context propagation will stop propagating when it hits the first
    
  828.     // match. If we bail out again inside that tree, we must resume propagating.
    
  829. 
    
  830.     const LegacyHidden = React.unstable_LegacyHidden;
    
  831. 
    
  832.     const Context = React.createContext('A');
    
  833. 
    
  834.     let setContext;
    
  835.     function App() {
    
  836.       const [value, setValue] = useState('A');
    
  837.       setContext = setValue;
    
  838.       return (
    
  839.         <Context.Provider value={value}>
    
  840.           <LegacyHidden mode="hidden">
    
  841.             <Child />
    
  842.           </LegacyHidden>
    
  843.         </Context.Provider>
    
  844.       );
    
  845.     }
    
  846. 
    
  847.     function Child() {
    
  848.       const value = useContext(Context);
    
  849.       return (
    
  850.         <>
    
  851.           <Text text={value} />
    
  852.           <DeepChildIndirection />
    
  853.         </>
    
  854.       );
    
  855.     }
    
  856. 
    
  857.     const DeepChildIndirection = React.memo(() => {
    
  858.       return <DeepChild />;
    
  859.     });
    
  860. 
    
  861.     function DeepChild() {
    
  862.       const value = useContext(Context);
    
  863.       return <Text text={value} />;
    
  864.     }
    
  865. 
    
  866.     const root = ReactNoop.createRoot();
    
  867.     await act(() => {
    
  868.       root.render(<App />);
    
  869.     });
    
  870.     assertLog(['A', 'A']);
    
  871.     expect(root).toMatchRenderedOutput('AA');
    
  872. 
    
  873.     await act(() => {
    
  874.       setContext('B');
    
  875.     });
    
  876.     assertLog(['B', 'B']);
    
  877.     expect(root).toMatchRenderedOutput('BB');
    
  878.   });
    
  879. 
    
  880.   test('finds context consumers in multiple sibling branches', async () => {
    
  881.     // This test confirms that when we find a matching context consumer during
    
  882.     // propagation, we continue propagating to its sibling branches.
    
  883. 
    
  884.     const Context = React.createContext('A');
    
  885. 
    
  886.     let setContext;
    
  887.     function App() {
    
  888.       const [value, setValue] = useState('A');
    
  889.       setContext = setValue;
    
  890.       return (
    
  891.         <Context.Provider value={value}>
    
  892.           <Blah />
    
  893.         </Context.Provider>
    
  894.       );
    
  895.     }
    
  896. 
    
  897.     const Blah = React.memo(() => {
    
  898.       return (
    
  899.         <>
    
  900.           <Indirection />
    
  901.           <Indirection />
    
  902.         </>
    
  903.       );
    
  904.     });
    
  905. 
    
  906.     const Indirection = React.memo(() => {
    
  907.       return <Child />;
    
  908.     });
    
  909. 
    
  910.     function Child() {
    
  911.       const value = useContext(Context);
    
  912.       return <Text text={value} />;
    
  913.     }
    
  914. 
    
  915.     const root = ReactNoop.createRoot();
    
  916.     await act(() => {
    
  917.       root.render(<App />);
    
  918.     });
    
  919.     assertLog(['A', 'A']);
    
  920.     expect(root).toMatchRenderedOutput('AA');
    
  921. 
    
  922.     await act(() => {
    
  923.       setContext('B');
    
  924.     });
    
  925.     assertLog(['B', 'B']);
    
  926.     expect(root).toMatchRenderedOutput('BB');
    
  927.   });
    
  928. });