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.  * @emails react-core
    
  8.  * @jest-environment ./scripts/jest/ReactDOMServerIntegrationEnvironment
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. let Activity;
    
  14. let React = require('react');
    
  15. let ReactDOM;
    
  16. let ReactDOMClient;
    
  17. let ReactDOMServer;
    
  18. let ReactFeatureFlags;
    
  19. let Scheduler;
    
  20. let Suspense;
    
  21. let SuspenseList;
    
  22. let useSyncExternalStore;
    
  23. let act;
    
  24. let IdleEventPriority;
    
  25. let waitForAll;
    
  26. let waitFor;
    
  27. let waitForPaint;
    
  28. let assertLog;
    
  29. 
    
  30. function normalizeCodeLocInfo(strOrErr) {
    
  31.   if (strOrErr && strOrErr.replace) {
    
  32.     return strOrErr.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
    
  33.       return '\n    in ' + name + ' (at **)';
    
  34.     });
    
  35.   }
    
  36.   return strOrErr;
    
  37. }
    
  38. 
    
  39. function dispatchMouseEvent(to, from) {
    
  40.   if (!to) {
    
  41.     to = null;
    
  42.   }
    
  43.   if (!from) {
    
  44.     from = null;
    
  45.   }
    
  46.   if (from) {
    
  47.     const mouseOutEvent = document.createEvent('MouseEvents');
    
  48.     mouseOutEvent.initMouseEvent(
    
  49.       'mouseout',
    
  50.       true,
    
  51.       true,
    
  52.       window,
    
  53.       0,
    
  54.       50,
    
  55.       50,
    
  56.       50,
    
  57.       50,
    
  58.       false,
    
  59.       false,
    
  60.       false,
    
  61.       false,
    
  62.       0,
    
  63.       to,
    
  64.     );
    
  65.     from.dispatchEvent(mouseOutEvent);
    
  66.   }
    
  67.   if (to) {
    
  68.     const mouseOverEvent = document.createEvent('MouseEvents');
    
  69.     mouseOverEvent.initMouseEvent(
    
  70.       'mouseover',
    
  71.       true,
    
  72.       true,
    
  73.       window,
    
  74.       0,
    
  75.       50,
    
  76.       50,
    
  77.       50,
    
  78.       50,
    
  79.       false,
    
  80.       false,
    
  81.       false,
    
  82.       false,
    
  83.       0,
    
  84.       from,
    
  85.     );
    
  86.     to.dispatchEvent(mouseOverEvent);
    
  87.   }
    
  88. }
    
  89. 
    
  90. class TestAppClass extends React.Component {
    
  91.   render() {
    
  92.     return (
    
  93.       <div>
    
  94.         <>{''}</>
    
  95.         <>{'Hello'}</>
    
  96.       </div>
    
  97.     );
    
  98.   }
    
  99. }
    
  100. 
    
  101. describe('ReactDOMServerPartialHydration', () => {
    
  102.   beforeEach(() => {
    
  103.     jest.resetModules();
    
  104. 
    
  105.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  106.     ReactFeatureFlags.enableSuspenseCallback = true;
    
  107.     ReactFeatureFlags.enableCreateEventHandleAPI = true;
    
  108. 
    
  109.     React = require('react');
    
  110.     ReactDOM = require('react-dom');
    
  111.     ReactDOMClient = require('react-dom/client');
    
  112.     act = require('internal-test-utils').act;
    
  113.     ReactDOMServer = require('react-dom/server');
    
  114.     Scheduler = require('scheduler');
    
  115.     Activity = React.unstable_Activity;
    
  116.     Suspense = React.Suspense;
    
  117.     useSyncExternalStore = React.useSyncExternalStore;
    
  118.     if (gate(flags => flags.enableSuspenseList)) {
    
  119.       SuspenseList = React.unstable_SuspenseList;
    
  120.     }
    
  121. 
    
  122.     const InternalTestUtils = require('internal-test-utils');
    
  123.     waitForAll = InternalTestUtils.waitForAll;
    
  124.     assertLog = InternalTestUtils.assertLog;
    
  125.     waitForPaint = InternalTestUtils.waitForPaint;
    
  126.     waitFor = InternalTestUtils.waitFor;
    
  127. 
    
  128.     IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
    
  129.   });
    
  130. 
    
  131.   // Note: This is based on a similar component we use in www. We can delete
    
  132.   // once the extra div wrapper is no longer necessary.
    
  133.   function LegacyHiddenDiv({children, mode}) {
    
  134.     return (
    
  135.       <div hidden={mode === 'hidden'}>
    
  136.         <React.unstable_LegacyHidden
    
  137.           mode={mode === 'hidden' ? 'unstable-defer-without-hiding' : mode}>
    
  138.           {children}
    
  139.         </React.unstable_LegacyHidden>
    
  140.       </div>
    
  141.     );
    
  142.   }
    
  143. 
    
  144.   it('hydrates a parent even if a child Suspense boundary is blocked', async () => {
    
  145.     let suspend = false;
    
  146.     let resolve;
    
  147.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  148.     const ref = React.createRef();
    
  149. 
    
  150.     function Child() {
    
  151.       if (suspend) {
    
  152.         throw promise;
    
  153.       } else {
    
  154.         return 'Hello';
    
  155.       }
    
  156.     }
    
  157. 
    
  158.     function App() {
    
  159.       return (
    
  160.         <div>
    
  161.           <Suspense fallback="Loading...">
    
  162.             <span ref={ref}>
    
  163.               <Child />
    
  164.             </span>
    
  165.           </Suspense>
    
  166.         </div>
    
  167.       );
    
  168.     }
    
  169. 
    
  170.     // First we render the final HTML. With the streaming renderer
    
  171.     // this may have suspense points on the server but here we want
    
  172.     // to test the completed HTML. Don't suspend on the server.
    
  173.     suspend = false;
    
  174.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  175. 
    
  176.     const container = document.createElement('div');
    
  177.     container.innerHTML = finalHTML;
    
  178. 
    
  179.     const span = container.getElementsByTagName('span')[0];
    
  180. 
    
  181.     // On the client we don't have all data yet but we want to start
    
  182.     // hydrating anyway.
    
  183.     suspend = true;
    
  184.     ReactDOMClient.hydrateRoot(container, <App />);
    
  185.     await waitForAll([]);
    
  186. 
    
  187.     expect(ref.current).toBe(null);
    
  188. 
    
  189.     // Resolving the promise should continue hydration
    
  190.     suspend = false;
    
  191.     resolve();
    
  192.     await promise;
    
  193.     await waitForAll([]);
    
  194. 
    
  195.     // We should now have hydrated with a ref on the existing span.
    
  196.     expect(ref.current).toBe(span);
    
  197.   });
    
  198. 
    
  199.   it('can hydrate siblings of a suspended component without errors', async () => {
    
  200.     let suspend = false;
    
  201.     let resolve;
    
  202.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  203.     function Child() {
    
  204.       if (suspend) {
    
  205.         throw promise;
    
  206.       } else {
    
  207.         return 'Hello';
    
  208.       }
    
  209.     }
    
  210. 
    
  211.     function App() {
    
  212.       return (
    
  213.         <Suspense fallback="Loading...">
    
  214.           <Child />
    
  215.           <Suspense fallback="Loading...">
    
  216.             <div>Hello</div>
    
  217.           </Suspense>
    
  218.         </Suspense>
    
  219.       );
    
  220.     }
    
  221. 
    
  222.     // First we render the final HTML. With the streaming renderer
    
  223.     // this may have suspense points on the server but here we want
    
  224.     // to test the completed HTML. Don't suspend on the server.
    
  225.     suspend = false;
    
  226.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  227. 
    
  228.     const container = document.createElement('div');
    
  229.     container.innerHTML = finalHTML;
    
  230.     expect(container.textContent).toBe('HelloHello');
    
  231. 
    
  232.     // On the client we don't have all data yet but we want to start
    
  233.     // hydrating anyway.
    
  234.     suspend = true;
    
  235.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  236.       onRecoverableError(error) {
    
  237.         Scheduler.log(error.message);
    
  238.       },
    
  239.     });
    
  240.     await waitForAll([]);
    
  241. 
    
  242.     // Expect the server-generated HTML to stay intact.
    
  243.     expect(container.textContent).toBe('HelloHello');
    
  244. 
    
  245.     // Resolving the promise should continue hydration
    
  246.     suspend = false;
    
  247.     resolve();
    
  248.     await promise;
    
  249.     await waitForAll([]);
    
  250.     // Hydration should not change anything.
    
  251.     expect(container.textContent).toBe('HelloHello');
    
  252.   });
    
  253. 
    
  254.   it('falls back to client rendering boundary on mismatch', async () => {
    
  255.     // We can't use the toErrorDev helper here because this is async.
    
  256.     const originalConsoleError = console.error;
    
  257.     const mockError = jest.fn();
    
  258.     console.error = (...args) => {
    
  259.       mockError(...args.map(normalizeCodeLocInfo));
    
  260.     };
    
  261.     let client = false;
    
  262.     let suspend = false;
    
  263.     let resolve;
    
  264.     const promise = new Promise(resolvePromise => {
    
  265.       resolve = () => {
    
  266.         suspend = false;
    
  267.         resolvePromise();
    
  268.       };
    
  269.     });
    
  270.     function Child() {
    
  271.       if (suspend) {
    
  272.         Scheduler.log('Suspend');
    
  273.         throw promise;
    
  274.       } else {
    
  275.         Scheduler.log('Hello');
    
  276.         return 'Hello';
    
  277.       }
    
  278.     }
    
  279.     function Component({shouldMismatch}) {
    
  280.       Scheduler.log('Component');
    
  281.       if (shouldMismatch && client) {
    
  282.         return <article>Mismatch</article>;
    
  283.       }
    
  284.       return <div>Component</div>;
    
  285.     }
    
  286.     function App() {
    
  287.       return (
    
  288.         <Suspense fallback="Loading...">
    
  289.           <Child />
    
  290.           <Component />
    
  291.           <Component />
    
  292.           <Component />
    
  293.           <Component shouldMismatch={true} />
    
  294.         </Suspense>
    
  295.       );
    
  296.     }
    
  297.     try {
    
  298.       const finalHTML = ReactDOMServer.renderToString(<App />);
    
  299.       const container = document.createElement('section');
    
  300.       container.innerHTML = finalHTML;
    
  301.       assertLog(['Hello', 'Component', 'Component', 'Component', 'Component']);
    
  302. 
    
  303.       expect(container.innerHTML).toBe(
    
  304.         '<!--$-->Hello<div>Component</div><div>Component</div><div>Component</div><div>Component</div><!--/$-->',
    
  305.       );
    
  306. 
    
  307.       suspend = true;
    
  308.       client = true;
    
  309. 
    
  310.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  311.         onRecoverableError(error) {
    
  312.           Scheduler.log(error.message);
    
  313.         },
    
  314.       });
    
  315.       await waitForAll(['Suspend']);
    
  316.       jest.runAllTimers();
    
  317. 
    
  318.       // Unchanged
    
  319.       expect(container.innerHTML).toBe(
    
  320.         '<!--$-->Hello<div>Component</div><div>Component</div><div>Component</div><div>Component</div><!--/$-->',
    
  321.       );
    
  322. 
    
  323.       suspend = false;
    
  324.       resolve();
    
  325.       await promise;
    
  326.       await waitForAll([
    
  327.         // first pass, mismatches at end
    
  328.         'Hello',
    
  329.         'Component',
    
  330.         'Component',
    
  331.         'Component',
    
  332.         'Component',
    
  333. 
    
  334.         // second pass as client render
    
  335.         'Hello',
    
  336.         'Component',
    
  337.         'Component',
    
  338.         'Component',
    
  339.         'Component',
    
  340. 
    
  341.         // Hydration mismatch is logged
    
  342.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  343.         'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  344.       ]);
    
  345. 
    
  346.       // Client rendered - suspense comment nodes removed
    
  347.       expect(container.innerHTML).toBe(
    
  348.         'Hello<div>Component</div><div>Component</div><div>Component</div><article>Mismatch</article>',
    
  349.       );
    
  350. 
    
  351.       if (__DEV__) {
    
  352.         const secondToLastCall =
    
  353.           mockError.mock.calls[mockError.mock.calls.length - 2];
    
  354.         expect(secondToLastCall).toEqual([
    
  355.           'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s',
    
  356.           'article',
    
  357.           'section',
    
  358.           '\n' +
    
  359.             '    in article (at **)\n' +
    
  360.             '    in Component (at **)\n' +
    
  361.             '    in Suspense (at **)\n' +
    
  362.             '    in App (at **)',
    
  363.         ]);
    
  364.       }
    
  365.     } finally {
    
  366.       console.error = originalConsoleError;
    
  367.     }
    
  368.   });
    
  369. 
    
  370.   it('calls the hydration callbacks after hydration or deletion', async () => {
    
  371.     let suspend = false;
    
  372.     let resolve;
    
  373.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  374.     function Child() {
    
  375.       if (suspend) {
    
  376.         throw promise;
    
  377.       } else {
    
  378.         return 'Hello';
    
  379.       }
    
  380.     }
    
  381. 
    
  382.     let suspend2 = false;
    
  383.     const promise2 = new Promise(() => {});
    
  384.     function Child2() {
    
  385.       if (suspend2) {
    
  386.         throw promise2;
    
  387.       } else {
    
  388.         return 'World';
    
  389.       }
    
  390.     }
    
  391. 
    
  392.     function App({value}) {
    
  393.       return (
    
  394.         <div>
    
  395.           <Suspense fallback="Loading...">
    
  396.             <Child />
    
  397.           </Suspense>
    
  398.           <Suspense fallback="Loading...">
    
  399.             <Child2 value={value} />
    
  400.           </Suspense>
    
  401.         </div>
    
  402.       );
    
  403.     }
    
  404. 
    
  405.     // First we render the final HTML. With the streaming renderer
    
  406.     // this may have suspense points on the server but here we want
    
  407.     // to test the completed HTML. Don't suspend on the server.
    
  408.     suspend = false;
    
  409.     suspend2 = false;
    
  410.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  411. 
    
  412.     const container = document.createElement('div');
    
  413.     container.innerHTML = finalHTML;
    
  414. 
    
  415.     const hydrated = [];
    
  416.     const deleted = [];
    
  417. 
    
  418.     // On the client we don't have all data yet but we want to start
    
  419.     // hydrating anyway.
    
  420.     suspend = true;
    
  421.     suspend2 = true;
    
  422.     const root = ReactDOMClient.hydrateRoot(container, <App />, {
    
  423.       onHydrated(node) {
    
  424.         hydrated.push(node);
    
  425.       },
    
  426.       onDeleted(node) {
    
  427.         deleted.push(node);
    
  428.       },
    
  429.       onRecoverableError(error) {
    
  430.         Scheduler.log(error.message);
    
  431.       },
    
  432.     });
    
  433.     await waitForAll([]);
    
  434. 
    
  435.     expect(hydrated.length).toBe(0);
    
  436.     expect(deleted.length).toBe(0);
    
  437. 
    
  438.     await act(async () => {
    
  439.       // Resolving the promise should continue hydration
    
  440.       suspend = false;
    
  441.       resolve();
    
  442.       await promise;
    
  443.     });
    
  444. 
    
  445.     expect(hydrated.length).toBe(1);
    
  446.     expect(deleted.length).toBe(0);
    
  447. 
    
  448.     // Performing an update should force it to delete the boundary
    
  449.     await act(() => {
    
  450.       root.render(<App value={true} />);
    
  451.     });
    
  452. 
    
  453.     expect(hydrated.length).toBe(1);
    
  454.     expect(deleted.length).toBe(1);
    
  455.   });
    
  456. 
    
  457.   it('hydrates an empty suspense boundary', async () => {
    
  458.     function App() {
    
  459.       return (
    
  460.         <div>
    
  461.           <Suspense fallback="Loading..." />
    
  462.           <div>Sibling</div>
    
  463.         </div>
    
  464.       );
    
  465.     }
    
  466. 
    
  467.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  468. 
    
  469.     const container = document.createElement('div');
    
  470.     container.innerHTML = finalHTML;
    
  471. 
    
  472.     ReactDOMClient.hydrateRoot(container, <App />);
    
  473.     await waitForAll([]);
    
  474. 
    
  475.     expect(container.innerHTML).toContain('<div>Sibling</div>');
    
  476.   });
    
  477. 
    
  478.   it('recovers with client render when server rendered additional nodes at suspense root', async () => {
    
  479.     function CheckIfHydrating({children}) {
    
  480.       // This is a trick to check whether we're hydrating or not, since React
    
  481.       // doesn't expose that information currently except
    
  482.       // via useSyncExternalStore.
    
  483.       let serverOrClient = '(unknown)';
    
  484.       useSyncExternalStore(
    
  485.         () => {},
    
  486.         () => {
    
  487.           serverOrClient = 'Client rendered';
    
  488.           return null;
    
  489.         },
    
  490.         () => {
    
  491.           serverOrClient = 'Server rendered';
    
  492.           return null;
    
  493.         },
    
  494.       );
    
  495.       Scheduler.log(serverOrClient);
    
  496.       return null;
    
  497.     }
    
  498. 
    
  499.     const ref = React.createRef();
    
  500.     function App({hasB}) {
    
  501.       return (
    
  502.         <div>
    
  503.           <Suspense fallback="Loading...">
    
  504.             <span ref={ref}>A</span>
    
  505.             {hasB ? <span>B</span> : null}
    
  506.             <CheckIfHydrating />
    
  507.           </Suspense>
    
  508.           <div>Sibling</div>
    
  509.         </div>
    
  510.       );
    
  511.     }
    
  512. 
    
  513.     const finalHTML = ReactDOMServer.renderToString(<App hasB={true} />);
    
  514.     assertLog(['Server rendered']);
    
  515. 
    
  516.     const container = document.createElement('div');
    
  517.     container.innerHTML = finalHTML;
    
  518. 
    
  519.     const span = container.getElementsByTagName('span')[0];
    
  520. 
    
  521.     expect(container.innerHTML).toContain('<span>A</span>');
    
  522.     expect(container.innerHTML).toContain('<span>B</span>');
    
  523.     expect(ref.current).toBe(null);
    
  524. 
    
  525.     await expect(async () => {
    
  526.       await act(() => {
    
  527.         ReactDOMClient.hydrateRoot(container, <App hasB={false} />, {
    
  528.           onRecoverableError(error) {
    
  529.             Scheduler.log(error.message);
    
  530.           },
    
  531.         });
    
  532.       });
    
  533.     }).toErrorDev('Did not expect server HTML to contain a <span> in <div>');
    
  534. 
    
  535.     expect(container.innerHTML).toContain('<span>A</span>');
    
  536.     expect(container.innerHTML).not.toContain('<span>B</span>');
    
  537. 
    
  538.     assertLog([
    
  539.       'Server rendered',
    
  540.       'Client rendered',
    
  541.       'There was an error while hydrating this Suspense boundary. ' +
    
  542.         'Switched to client rendering.',
    
  543.     ]);
    
  544.     expect(ref.current).not.toBe(span);
    
  545.   });
    
  546. 
    
  547.   it('recovers with client render when server rendered additional nodes at suspense root after unsuspending', async () => {
    
  548.     // We can't use the toErrorDev helper here because this is async.
    
  549.     const originalConsoleError = console.error;
    
  550.     const mockError = jest.fn();
    
  551.     console.error = (...args) => {
    
  552.       mockError(...args.map(normalizeCodeLocInfo));
    
  553.     };
    
  554. 
    
  555.     const ref = React.createRef();
    
  556.     let shouldSuspend = false;
    
  557.     let resolve;
    
  558.     const promise = new Promise(res => {
    
  559.       resolve = () => {
    
  560.         shouldSuspend = false;
    
  561.         res();
    
  562.       };
    
  563.     });
    
  564.     function Suspender() {
    
  565.       if (shouldSuspend) {
    
  566.         throw promise;
    
  567.       }
    
  568.       return <></>;
    
  569.     }
    
  570.     function App({hasB}) {
    
  571.       return (
    
  572.         <div>
    
  573.           <Suspense fallback="Loading...">
    
  574.             <Suspender />
    
  575.             <span ref={ref}>A</span>
    
  576.             {hasB ? <span>B</span> : null}
    
  577.           </Suspense>
    
  578.           <div>Sibling</div>
    
  579.         </div>
    
  580.       );
    
  581.     }
    
  582.     try {
    
  583.       const finalHTML = ReactDOMServer.renderToString(<App hasB={true} />);
    
  584. 
    
  585.       const container = document.createElement('div');
    
  586.       container.innerHTML = finalHTML;
    
  587. 
    
  588.       const span = container.getElementsByTagName('span')[0];
    
  589. 
    
  590.       expect(container.innerHTML).toContain('<span>A</span>');
    
  591.       expect(container.innerHTML).toContain('<span>B</span>');
    
  592.       expect(ref.current).toBe(null);
    
  593. 
    
  594.       shouldSuspend = true;
    
  595.       await act(() => {
    
  596.         ReactDOMClient.hydrateRoot(container, <App hasB={false} />);
    
  597.       });
    
  598. 
    
  599.       await act(() => {
    
  600.         resolve();
    
  601.       });
    
  602. 
    
  603.       expect(container.innerHTML).toContain('<span>A</span>');
    
  604.       expect(container.innerHTML).not.toContain('<span>B</span>');
    
  605.       expect(ref.current).not.toBe(span);
    
  606.       if (__DEV__) {
    
  607.         expect(mockError).toHaveBeenCalledWith(
    
  608.           'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s',
    
  609.           'span',
    
  610.           'div',
    
  611.           '\n' +
    
  612.             '    in Suspense (at **)\n' +
    
  613.             '    in div (at **)\n' +
    
  614.             '    in App (at **)',
    
  615.         );
    
  616.       }
    
  617.     } finally {
    
  618.       console.error = originalConsoleError;
    
  619.     }
    
  620.   });
    
  621. 
    
  622.   it('recovers with client render when server rendered additional nodes deep inside suspense root', async () => {
    
  623.     const ref = React.createRef();
    
  624.     function App({hasB}) {
    
  625.       return (
    
  626.         <div>
    
  627.           <Suspense fallback="Loading...">
    
  628.             <div>
    
  629.               <span ref={ref}>A</span>
    
  630.               {hasB ? <span>B</span> : null}
    
  631.             </div>
    
  632.           </Suspense>
    
  633.           <div>Sibling</div>
    
  634.         </div>
    
  635.       );
    
  636.     }
    
  637. 
    
  638.     const finalHTML = ReactDOMServer.renderToString(<App hasB={true} />);
    
  639. 
    
  640.     const container = document.createElement('div');
    
  641.     container.innerHTML = finalHTML;
    
  642. 
    
  643.     const span = container.getElementsByTagName('span')[0];
    
  644. 
    
  645.     expect(container.innerHTML).toContain('<span>A</span>');
    
  646.     expect(container.innerHTML).toContain('<span>B</span>');
    
  647.     expect(ref.current).toBe(null);
    
  648. 
    
  649.     await expect(async () => {
    
  650.       await act(() => {
    
  651.         ReactDOMClient.hydrateRoot(container, <App hasB={false} />, {
    
  652.           onRecoverableError(error) {
    
  653.             Scheduler.log(error.message);
    
  654.           },
    
  655.         });
    
  656.       });
    
  657.     }).toErrorDev('Did not expect server HTML to contain a <span> in <div>');
    
  658.     assertLog([
    
  659.       'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  660.       'There was an error while hydrating this Suspense boundary. Switched to client rendering.',
    
  661.     ]);
    
  662. 
    
  663.     expect(container.innerHTML).toContain('<span>A</span>');
    
  664.     expect(container.innerHTML).not.toContain('<span>B</span>');
    
  665.     expect(ref.current).not.toBe(span);
    
  666.   });
    
  667. 
    
  668.   it('calls the onDeleted hydration callback if the parent gets deleted', async () => {
    
  669.     let suspend = false;
    
  670.     const promise = new Promise(() => {});
    
  671.     function Child() {
    
  672.       if (suspend) {
    
  673.         throw promise;
    
  674.       } else {
    
  675.         return 'Hello';
    
  676.       }
    
  677.     }
    
  678. 
    
  679.     function App({deleted}) {
    
  680.       if (deleted) {
    
  681.         return null;
    
  682.       }
    
  683.       return (
    
  684.         <div>
    
  685.           <Suspense fallback="Loading...">
    
  686.             <Child />
    
  687.           </Suspense>
    
  688.         </div>
    
  689.       );
    
  690.     }
    
  691. 
    
  692.     suspend = false;
    
  693.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  694. 
    
  695.     const container = document.createElement('div');
    
  696.     container.innerHTML = finalHTML;
    
  697. 
    
  698.     const deleted = [];
    
  699. 
    
  700.     // On the client we don't have all data yet but we want to start
    
  701.     // hydrating anyway.
    
  702.     suspend = true;
    
  703.     const root = await act(() => {
    
  704.       return ReactDOMClient.hydrateRoot(container, <App />, {
    
  705.         onDeleted(node) {
    
  706.           deleted.push(node);
    
  707.         },
    
  708.       });
    
  709.     });
    
  710. 
    
  711.     expect(deleted.length).toBe(0);
    
  712. 
    
  713.     await act(() => {
    
  714.       root.render(<App deleted={true} />);
    
  715.     });
    
  716. 
    
  717.     // The callback should have been invoked.
    
  718.     expect(deleted.length).toBe(1);
    
  719.   });
    
  720. 
    
  721.   it('warns and replaces the boundary content in legacy mode', async () => {
    
  722.     let suspend = false;
    
  723.     let resolve;
    
  724.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  725.     const ref = React.createRef();
    
  726. 
    
  727.     function Child() {
    
  728.       if (suspend) {
    
  729.         throw promise;
    
  730.       } else {
    
  731.         return 'Hello';
    
  732.       }
    
  733.     }
    
  734. 
    
  735.     function App() {
    
  736.       return (
    
  737.         <div>
    
  738.           <Suspense fallback="Loading...">
    
  739.             <span ref={ref}>
    
  740.               <Child />
    
  741.             </span>
    
  742.           </Suspense>
    
  743.         </div>
    
  744.       );
    
  745.     }
    
  746. 
    
  747.     // Don't suspend on the server.
    
  748.     suspend = false;
    
  749.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  750. 
    
  751.     const container = document.createElement('div');
    
  752.     container.innerHTML = finalHTML;
    
  753. 
    
  754.     const span = container.getElementsByTagName('span')[0];
    
  755. 
    
  756.     // On the client we try to hydrate.
    
  757.     suspend = true;
    
  758.     await expect(async () => {
    
  759.       await act(() => {
    
  760.         ReactDOM.hydrate(<App />, container);
    
  761.       });
    
  762.     }).toErrorDev(
    
  763.       'Warning: Cannot hydrate Suspense in legacy mode. Switch from ' +
    
  764.         'ReactDOM.hydrate(element, container) to ' +
    
  765.         'ReactDOMClient.hydrateRoot(container, <App />)' +
    
  766.         '.render(element) or remove the Suspense components from the server ' +
    
  767.         'rendered components.' +
    
  768.         '\n    in Suspense (at **)' +
    
  769.         '\n    in div (at **)' +
    
  770.         '\n    in App (at **)',
    
  771.     );
    
  772. 
    
  773.     // We're now in loading state.
    
  774.     expect(container.textContent).toBe('Loading...');
    
  775. 
    
  776.     const span2 = container.getElementsByTagName('span')[0];
    
  777.     // This is a new node.
    
  778.     expect(span).not.toBe(span2);
    
  779. 
    
  780.     if (gate(flags => flags.dfsEffectsRefactor)) {
    
  781.       // The effects list refactor causes this to be null because the Suspense Activity's child
    
  782.       // is null. However, since we can't hydrate Suspense in legacy this change in behavior is ok
    
  783.       expect(ref.current).toBe(null);
    
  784.     } else {
    
  785.       expect(ref.current).toBe(span2);
    
  786.     }
    
  787. 
    
  788.     // Resolving the promise should render the final content.
    
  789.     suspend = false;
    
  790.     await act(() => resolve());
    
  791. 
    
  792.     // We should now have hydrated with a ref on the existing span.
    
  793.     expect(container.textContent).toBe('Hello');
    
  794.   });
    
  795. 
    
  796.   it('can insert siblings before the dehydrated boundary', async () => {
    
  797.     let suspend = false;
    
  798.     const promise = new Promise(() => {});
    
  799.     let showSibling;
    
  800. 
    
  801.     function Child() {
    
  802.       if (suspend) {
    
  803.         throw promise;
    
  804.       } else {
    
  805.         return 'Second';
    
  806.       }
    
  807.     }
    
  808. 
    
  809.     function Sibling() {
    
  810.       const [visible, setVisibilty] = React.useState(false);
    
  811.       showSibling = () => setVisibilty(true);
    
  812.       if (visible) {
    
  813.         return <div>First</div>;
    
  814.       }
    
  815.       return null;
    
  816.     }
    
  817. 
    
  818.     function App() {
    
  819.       return (
    
  820.         <div>
    
  821.           <Sibling />
    
  822.           <Suspense fallback="Loading...">
    
  823.             <span>
    
  824.               <Child />
    
  825.             </span>
    
  826.           </Suspense>
    
  827.         </div>
    
  828.       );
    
  829.     }
    
  830. 
    
  831.     suspend = false;
    
  832.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  833.     const container = document.createElement('div');
    
  834.     container.innerHTML = finalHTML;
    
  835. 
    
  836.     // On the client we don't have all data yet but we want to start
    
  837.     // hydrating anyway.
    
  838.     suspend = true;
    
  839. 
    
  840.     await act(() => {
    
  841.       ReactDOMClient.hydrateRoot(container, <App />);
    
  842.     });
    
  843. 
    
  844.     expect(container.firstChild.firstChild.tagName).not.toBe('DIV');
    
  845. 
    
  846.     // In this state, we can still update the siblings.
    
  847.     await act(() => showSibling());
    
  848. 
    
  849.     expect(container.firstChild.firstChild.tagName).toBe('DIV');
    
  850.     expect(container.firstChild.firstChild.textContent).toBe('First');
    
  851.   });
    
  852. 
    
  853.   it('can delete the dehydrated boundary before it is hydrated', async () => {
    
  854.     let suspend = false;
    
  855.     const promise = new Promise(() => {});
    
  856.     let hideMiddle;
    
  857. 
    
  858.     function Child() {
    
  859.       if (suspend) {
    
  860.         throw promise;
    
  861.       } else {
    
  862.         return (
    
  863.           <>
    
  864.             <div>Middle</div>
    
  865.             Some text
    
  866.           </>
    
  867.         );
    
  868.       }
    
  869.     }
    
  870. 
    
  871.     function App() {
    
  872.       const [visible, setVisibilty] = React.useState(true);
    
  873.       hideMiddle = () => setVisibilty(false);
    
  874. 
    
  875.       return (
    
  876.         <div>
    
  877.           <div>Before</div>
    
  878.           {visible ? (
    
  879.             <Suspense fallback="Loading...">
    
  880.               <Child />
    
  881.             </Suspense>
    
  882.           ) : null}
    
  883.           <div>After</div>
    
  884.         </div>
    
  885.       );
    
  886.     }
    
  887. 
    
  888.     suspend = false;
    
  889.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  890.     const container = document.createElement('div');
    
  891.     container.innerHTML = finalHTML;
    
  892. 
    
  893.     // On the client we don't have all data yet but we want to start
    
  894.     // hydrating anyway.
    
  895.     suspend = true;
    
  896.     await act(() => {
    
  897.       ReactDOMClient.hydrateRoot(container, <App />);
    
  898.     });
    
  899. 
    
  900.     expect(container.firstChild.children[1].textContent).toBe('Middle');
    
  901. 
    
  902.     // In this state, we can still delete the boundary.
    
  903.     await act(() => hideMiddle());
    
  904. 
    
  905.     expect(container.firstChild.children[1].textContent).toBe('After');
    
  906.   });
    
  907. 
    
  908.   it('blocks updates to hydrate the content first if props have changed', async () => {
    
  909.     let suspend = false;
    
  910.     let resolve;
    
  911.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  912.     const ref = React.createRef();
    
  913. 
    
  914.     function Child({text}) {
    
  915.       if (suspend) {
    
  916.         throw promise;
    
  917.       } else {
    
  918.         return text;
    
  919.       }
    
  920.     }
    
  921. 
    
  922.     function App({text, className}) {
    
  923.       return (
    
  924.         <div>
    
  925.           <Suspense fallback="Loading...">
    
  926.             <span ref={ref} className={className}>
    
  927.               <Child text={text} />
    
  928.             </span>
    
  929.           </Suspense>
    
  930.         </div>
    
  931.       );
    
  932.     }
    
  933. 
    
  934.     suspend = false;
    
  935.     const finalHTML = ReactDOMServer.renderToString(
    
  936.       <App text="Hello" className="hello" />,
    
  937.     );
    
  938.     const container = document.createElement('div');
    
  939.     container.innerHTML = finalHTML;
    
  940. 
    
  941.     const span = container.getElementsByTagName('span')[0];
    
  942. 
    
  943.     // On the client we don't have all data yet but we want to start
    
  944.     // hydrating anyway.
    
  945.     suspend = true;
    
  946.     const root = ReactDOMClient.hydrateRoot(
    
  947.       container,
    
  948.       <App text="Hello" className="hello" />,
    
  949.     );
    
  950.     await waitForAll([]);
    
  951. 
    
  952.     expect(ref.current).toBe(null);
    
  953.     expect(span.textContent).toBe('Hello');
    
  954. 
    
  955.     // Render an update, which will be higher or the same priority as pinging the hydration.
    
  956.     root.render(<App text="Hi" className="hi" />);
    
  957. 
    
  958.     // At the same time, resolving the promise so that rendering can complete.
    
  959.     // This should first complete the hydration and then flush the update onto the hydrated state.
    
  960.     await act(async () => {
    
  961.       suspend = false;
    
  962.       resolve();
    
  963.       await promise;
    
  964.     });
    
  965. 
    
  966.     // The new span should be the same since we should have successfully hydrated
    
  967.     // before changing it.
    
  968.     const newSpan = container.getElementsByTagName('span')[0];
    
  969.     expect(span).toBe(newSpan);
    
  970. 
    
  971.     // We should now have fully rendered with a ref on the new span.
    
  972.     expect(ref.current).toBe(span);
    
  973.     expect(span.textContent).toBe('Hi');
    
  974.     // If we ended up hydrating the existing content, we won't have properly
    
  975.     // patched up the tree, which might mean we haven't patched the className.
    
  976.     expect(span.className).toBe('hi');
    
  977.   });
    
  978. 
    
  979.   // @gate experimental || www
    
  980.   it('blocks updates to hydrate the content first if props changed at idle priority', async () => {
    
  981.     let suspend = false;
    
  982.     let resolve;
    
  983.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  984.     const ref = React.createRef();
    
  985. 
    
  986.     function Child({text}) {
    
  987.       if (suspend) {
    
  988.         throw promise;
    
  989.       } else {
    
  990.         return text;
    
  991.       }
    
  992.     }
    
  993. 
    
  994.     function App({text, className}) {
    
  995.       return (
    
  996.         <div>
    
  997.           <Suspense fallback="Loading...">
    
  998.             <span ref={ref} className={className}>
    
  999.               <Child text={text} />
    
  1000.             </span>
    
  1001.           </Suspense>
    
  1002.         </div>
    
  1003.       );
    
  1004.     }
    
  1005. 
    
  1006.     suspend = false;
    
  1007.     const finalHTML = ReactDOMServer.renderToString(
    
  1008.       <App text="Hello" className="hello" />,
    
  1009.     );
    
  1010.     const container = document.createElement('div');
    
  1011.     container.innerHTML = finalHTML;
    
  1012. 
    
  1013.     const span = container.getElementsByTagName('span')[0];
    
  1014. 
    
  1015.     // On the client we don't have all data yet but we want to start
    
  1016.     // hydrating anyway.
    
  1017.     suspend = true;
    
  1018.     const root = ReactDOMClient.hydrateRoot(
    
  1019.       container,
    
  1020.       <App text="Hello" className="hello" />,
    
  1021.     );
    
  1022.     await waitForAll([]);
    
  1023. 
    
  1024.     expect(ref.current).toBe(null);
    
  1025.     expect(span.textContent).toBe('Hello');
    
  1026. 
    
  1027.     // Schedule an update at idle priority
    
  1028.     ReactDOM.unstable_runWithPriority(IdleEventPriority, () => {
    
  1029.       root.render(<App text="Hi" className="hi" />);
    
  1030.     });
    
  1031. 
    
  1032.     // At the same time, resolving the promise so that rendering can complete.
    
  1033.     suspend = false;
    
  1034.     resolve();
    
  1035.     await promise;
    
  1036. 
    
  1037.     // This should first complete the hydration and then flush the update onto the hydrated state.
    
  1038.     await waitForAll([]);
    
  1039. 
    
  1040.     // The new span should be the same since we should have successfully hydrated
    
  1041.     // before changing it.
    
  1042.     const newSpan = container.getElementsByTagName('span')[0];
    
  1043.     expect(span).toBe(newSpan);
    
  1044. 
    
  1045.     // We should now have fully rendered with a ref on the new span.
    
  1046.     expect(ref.current).toBe(span);
    
  1047.     expect(span.textContent).toBe('Hi');
    
  1048.     // If we ended up hydrating the existing content, we won't have properly
    
  1049.     // patched up the tree, which might mean we haven't patched the className.
    
  1050.     expect(span.className).toBe('hi');
    
  1051.   });
    
  1052. 
    
  1053.   it('shows the fallback if props have changed before hydration completes and is still suspended', async () => {
    
  1054.     let suspend = false;
    
  1055.     let resolve;
    
  1056.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1057.     const ref = React.createRef();
    
  1058. 
    
  1059.     function Child({text}) {
    
  1060.       if (suspend) {
    
  1061.         throw promise;
    
  1062.       } else {
    
  1063.         return text;
    
  1064.       }
    
  1065.     }
    
  1066. 
    
  1067.     function App({text, className}) {
    
  1068.       return (
    
  1069.         <div>
    
  1070.           <Suspense fallback="Loading...">
    
  1071.             <span ref={ref} className={className}>
    
  1072.               <Child text={text} />
    
  1073.             </span>
    
  1074.           </Suspense>
    
  1075.         </div>
    
  1076.       );
    
  1077.     }
    
  1078. 
    
  1079.     suspend = false;
    
  1080.     const finalHTML = ReactDOMServer.renderToString(
    
  1081.       <App text="Hello" className="hello" />,
    
  1082.     );
    
  1083.     const container = document.createElement('div');
    
  1084.     container.innerHTML = finalHTML;
    
  1085. 
    
  1086.     // On the client we don't have all data yet but we want to start
    
  1087.     // hydrating anyway.
    
  1088.     suspend = true;
    
  1089.     const root = ReactDOMClient.hydrateRoot(
    
  1090.       container,
    
  1091.       <App text="Hello" className="hello" />,
    
  1092.       {
    
  1093.         onRecoverableError(error) {
    
  1094.           Scheduler.log(error.message);
    
  1095.         },
    
  1096.       },
    
  1097.     );
    
  1098.     await waitForAll([]);
    
  1099. 
    
  1100.     expect(ref.current).toBe(null);
    
  1101. 
    
  1102.     // Render an update, but leave it still suspended.
    
  1103.     await act(() => {
    
  1104.       root.render(<App text="Hi" className="hi" />);
    
  1105.     });
    
  1106. 
    
  1107.     // Flushing now should delete the existing content and show the fallback.
    
  1108. 
    
  1109.     expect(container.getElementsByTagName('span').length).toBe(0);
    
  1110.     expect(ref.current).toBe(null);
    
  1111.     expect(container.textContent).toBe('Loading...');
    
  1112. 
    
  1113.     // Unsuspending shows the content.
    
  1114.     await act(async () => {
    
  1115.       suspend = false;
    
  1116.       resolve();
    
  1117.       await promise;
    
  1118.     });
    
  1119. 
    
  1120.     const span = container.getElementsByTagName('span')[0];
    
  1121.     expect(span.textContent).toBe('Hi');
    
  1122.     expect(span.className).toBe('hi');
    
  1123.     expect(ref.current).toBe(span);
    
  1124.     expect(container.textContent).toBe('Hi');
    
  1125.   });
    
  1126. 
    
  1127.   it('treats missing fallback the same as if it was defined', async () => {
    
  1128.     // This is the same exact test as above but with a nested Suspense without a fallback.
    
  1129.     // This should be a noop.
    
  1130.     let suspend = false;
    
  1131.     let resolve;
    
  1132.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1133.     const ref = React.createRef();
    
  1134. 
    
  1135.     function Child({text}) {
    
  1136.       if (suspend) {
    
  1137.         throw promise;
    
  1138.       } else {
    
  1139.         return text;
    
  1140.       }
    
  1141.     }
    
  1142. 
    
  1143.     function App({text, className}) {
    
  1144.       return (
    
  1145.         <div>
    
  1146.           <Suspense fallback="Loading...">
    
  1147.             <span ref={ref} className={className}>
    
  1148.               <Suspense>
    
  1149.                 <Child text={text} />
    
  1150.               </Suspense>
    
  1151.             </span>
    
  1152.           </Suspense>
    
  1153.         </div>
    
  1154.       );
    
  1155.     }
    
  1156. 
    
  1157.     suspend = false;
    
  1158.     const finalHTML = ReactDOMServer.renderToString(
    
  1159.       <App text="Hello" className="hello" />,
    
  1160.     );
    
  1161.     const container = document.createElement('div');
    
  1162.     container.innerHTML = finalHTML;
    
  1163. 
    
  1164.     // On the client we don't have all data yet but we want to start
    
  1165.     // hydrating anyway.
    
  1166.     suspend = true;
    
  1167.     const root = ReactDOMClient.hydrateRoot(
    
  1168.       container,
    
  1169.       <App text="Hello" className="hello" />,
    
  1170.       {
    
  1171.         onRecoverableError(error) {
    
  1172.           Scheduler.log(error.message);
    
  1173.         },
    
  1174.       },
    
  1175.     );
    
  1176.     await waitForAll([]);
    
  1177. 
    
  1178.     const span = container.getElementsByTagName('span')[0];
    
  1179.     expect(ref.current).toBe(span);
    
  1180. 
    
  1181.     // Render an update, but leave it still suspended.
    
  1182.     // Flushing now should delete the existing content and show the fallback.
    
  1183.     await act(() => {
    
  1184.       root.render(<App text="Hi" className="hi" />);
    
  1185.     });
    
  1186. 
    
  1187.     expect(container.getElementsByTagName('span').length).toBe(1);
    
  1188.     expect(ref.current).toBe(span);
    
  1189.     expect(container.textContent).toBe('');
    
  1190. 
    
  1191.     // Unsuspending shows the content.
    
  1192.     await act(async () => {
    
  1193.       suspend = false;
    
  1194.       resolve();
    
  1195.       await promise;
    
  1196.     });
    
  1197. 
    
  1198.     expect(span.textContent).toBe('Hi');
    
  1199.     expect(span.className).toBe('hi');
    
  1200.     expect(ref.current).toBe(span);
    
  1201.     expect(container.textContent).toBe('Hi');
    
  1202.   });
    
  1203. 
    
  1204.   it('clears nested suspense boundaries if they did not hydrate yet', async () => {
    
  1205.     let suspend = false;
    
  1206.     let resolve;
    
  1207.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1208.     const ref = React.createRef();
    
  1209. 
    
  1210.     function Child({text}) {
    
  1211.       if (suspend) {
    
  1212.         throw promise;
    
  1213.       } else {
    
  1214.         return text;
    
  1215.       }
    
  1216.     }
    
  1217. 
    
  1218.     function App({text, className}) {
    
  1219.       return (
    
  1220.         <div>
    
  1221.           <Suspense fallback="Loading...">
    
  1222.             <Suspense fallback="Never happens">
    
  1223.               <Child text={text} />
    
  1224.             </Suspense>{' '}
    
  1225.             <span ref={ref} className={className}>
    
  1226.               <Child text={text} />
    
  1227.             </span>
    
  1228.           </Suspense>
    
  1229.         </div>
    
  1230.       );
    
  1231.     }
    
  1232. 
    
  1233.     suspend = false;
    
  1234.     const finalHTML = ReactDOMServer.renderToString(
    
  1235.       <App text="Hello" className="hello" />,
    
  1236.     );
    
  1237.     const container = document.createElement('div');
    
  1238.     container.innerHTML = finalHTML;
    
  1239. 
    
  1240.     // On the client we don't have all data yet but we want to start
    
  1241.     // hydrating anyway.
    
  1242.     suspend = true;
    
  1243.     const root = ReactDOMClient.hydrateRoot(
    
  1244.       container,
    
  1245.       <App text="Hello" className="hello" />,
    
  1246.       {
    
  1247.         onRecoverableError(error) {
    
  1248.           Scheduler.log(error.message);
    
  1249.         },
    
  1250.       },
    
  1251.     );
    
  1252.     await waitForAll([]);
    
  1253. 
    
  1254.     expect(ref.current).toBe(null);
    
  1255. 
    
  1256.     // Render an update, but leave it still suspended.
    
  1257.     // Flushing now should delete the existing content and show the fallback.
    
  1258.     await act(() => {
    
  1259.       root.render(<App text="Hi" className="hi" />);
    
  1260.     });
    
  1261. 
    
  1262.     expect(container.getElementsByTagName('span').length).toBe(0);
    
  1263.     expect(ref.current).toBe(null);
    
  1264.     expect(container.textContent).toBe('Loading...');
    
  1265. 
    
  1266.     // Unsuspending shows the content.
    
  1267.     await act(async () => {
    
  1268.       suspend = false;
    
  1269.       resolve();
    
  1270.       await promise;
    
  1271.     });
    
  1272. 
    
  1273.     await waitForAll([]);
    
  1274. 
    
  1275.     const span = container.getElementsByTagName('span')[0];
    
  1276.     expect(span.textContent).toBe('Hi');
    
  1277.     expect(span.className).toBe('hi');
    
  1278.     expect(ref.current).toBe(span);
    
  1279.     expect(container.textContent).toBe('Hi Hi');
    
  1280.   });
    
  1281. 
    
  1282.   it('hydrates first if props changed but we are able to resolve within a timeout', async () => {
    
  1283.     let suspend = false;
    
  1284.     let resolve;
    
  1285.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1286.     const ref = React.createRef();
    
  1287. 
    
  1288.     function Child({text}) {
    
  1289.       if (suspend) {
    
  1290.         throw promise;
    
  1291.       } else {
    
  1292.         return text;
    
  1293.       }
    
  1294.     }
    
  1295. 
    
  1296.     function App({text, className}) {
    
  1297.       return (
    
  1298.         <div>
    
  1299.           <Suspense fallback="Loading...">
    
  1300.             <span ref={ref} className={className}>
    
  1301.               <Child text={text} />
    
  1302.             </span>
    
  1303.           </Suspense>
    
  1304.         </div>
    
  1305.       );
    
  1306.     }
    
  1307. 
    
  1308.     suspend = false;
    
  1309.     const finalHTML = ReactDOMServer.renderToString(
    
  1310.       <App text="Hello" className="hello" />,
    
  1311.     );
    
  1312.     const container = document.createElement('div');
    
  1313.     container.innerHTML = finalHTML;
    
  1314. 
    
  1315.     const span = container.getElementsByTagName('span')[0];
    
  1316. 
    
  1317.     // On the client we don't have all data yet but we want to start
    
  1318.     // hydrating anyway.
    
  1319.     suspend = true;
    
  1320.     const root = ReactDOMClient.hydrateRoot(
    
  1321.       container,
    
  1322.       <App text="Hello" className="hello" />,
    
  1323.     );
    
  1324.     await waitForAll([]);
    
  1325. 
    
  1326.     expect(ref.current).toBe(null);
    
  1327.     expect(container.textContent).toBe('Hello');
    
  1328. 
    
  1329.     // Render an update with a long timeout.
    
  1330.     React.startTransition(() => root.render(<App text="Hi" className="hi" />));
    
  1331.     // This shouldn't force the fallback yet.
    
  1332.     await waitForAll([]);
    
  1333. 
    
  1334.     expect(ref.current).toBe(null);
    
  1335.     expect(container.textContent).toBe('Hello');
    
  1336. 
    
  1337.     // Resolving the promise so that rendering can complete.
    
  1338.     // This should first complete the hydration and then flush the update onto the hydrated state.
    
  1339.     suspend = false;
    
  1340.     await act(() => resolve());
    
  1341. 
    
  1342.     // The new span should be the same since we should have successfully hydrated
    
  1343.     // before changing it.
    
  1344.     const newSpan = container.getElementsByTagName('span')[0];
    
  1345.     expect(span).toBe(newSpan);
    
  1346. 
    
  1347.     // We should now have fully rendered with a ref on the new span.
    
  1348.     expect(ref.current).toBe(span);
    
  1349.     expect(container.textContent).toBe('Hi');
    
  1350.     // If we ended up hydrating the existing content, we won't have properly
    
  1351.     // patched up the tree, which might mean we haven't patched the className.
    
  1352.     expect(span.className).toBe('hi');
    
  1353.   });
    
  1354. 
    
  1355.   it('warns but works if setState is called before commit in a dehydrated component', async () => {
    
  1356.     let suspend = false;
    
  1357.     let resolve;
    
  1358.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1359. 
    
  1360.     let updateText;
    
  1361. 
    
  1362.     function Child() {
    
  1363.       const [state, setState] = React.useState('Hello');
    
  1364.       updateText = setState;
    
  1365.       Scheduler.log('Child');
    
  1366.       if (suspend) {
    
  1367.         throw promise;
    
  1368.       } else {
    
  1369.         return state;
    
  1370.       }
    
  1371.     }
    
  1372. 
    
  1373.     function Sibling() {
    
  1374.       Scheduler.log('Sibling');
    
  1375.       return null;
    
  1376.     }
    
  1377. 
    
  1378.     function App() {
    
  1379.       return (
    
  1380.         <div>
    
  1381.           <Suspense fallback="Loading...">
    
  1382.             <Child />
    
  1383.             <Sibling />
    
  1384.           </Suspense>
    
  1385.         </div>
    
  1386.       );
    
  1387.     }
    
  1388. 
    
  1389.     suspend = false;
    
  1390.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1391.     assertLog(['Child', 'Sibling']);
    
  1392. 
    
  1393.     const container = document.createElement('div');
    
  1394.     container.innerHTML = finalHTML;
    
  1395. 
    
  1396.     ReactDOMClient.hydrateRoot(
    
  1397.       container,
    
  1398.       <App text="Hello" className="hello" />,
    
  1399.     );
    
  1400. 
    
  1401.     await act(async () => {
    
  1402.       suspend = true;
    
  1403.       await waitFor(['Child']);
    
  1404. 
    
  1405.       // While we're part way through the hydration, we update the state.
    
  1406.       // This will schedule an update on the children of the suspense boundary.
    
  1407.       expect(() => updateText('Hi')).toErrorDev(
    
  1408.         "Can't perform a React state update on a component that hasn't mounted yet.",
    
  1409.       );
    
  1410. 
    
  1411.       // This will throw it away and rerender.
    
  1412.       await waitForAll(['Child']);
    
  1413. 
    
  1414.       expect(container.textContent).toBe('Hello');
    
  1415. 
    
  1416.       suspend = false;
    
  1417.       resolve();
    
  1418.       await promise;
    
  1419.     });
    
  1420.     assertLog(['Child', 'Sibling']);
    
  1421. 
    
  1422.     expect(container.textContent).toBe('Hello');
    
  1423.   });
    
  1424. 
    
  1425.   it('blocks the update to hydrate first if context has changed', async () => {
    
  1426.     let suspend = false;
    
  1427.     let resolve;
    
  1428.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1429.     const ref = React.createRef();
    
  1430.     const Context = React.createContext(null);
    
  1431. 
    
  1432.     function Child() {
    
  1433.       const {text, className} = React.useContext(Context);
    
  1434.       if (suspend) {
    
  1435.         throw promise;
    
  1436.       } else {
    
  1437.         return (
    
  1438.           <span ref={ref} className={className}>
    
  1439.             {text}
    
  1440.           </span>
    
  1441.         );
    
  1442.       }
    
  1443.     }
    
  1444. 
    
  1445.     const App = React.memo(function App() {
    
  1446.       return (
    
  1447.         <div>
    
  1448.           <Suspense fallback="Loading...">
    
  1449.             <Child />
    
  1450.           </Suspense>
    
  1451.         </div>
    
  1452.       );
    
  1453.     });
    
  1454. 
    
  1455.     suspend = false;
    
  1456.     const finalHTML = ReactDOMServer.renderToString(
    
  1457.       <Context.Provider value={{text: 'Hello', className: 'hello'}}>
    
  1458.         <App />
    
  1459.       </Context.Provider>,
    
  1460.     );
    
  1461.     const container = document.createElement('div');
    
  1462.     container.innerHTML = finalHTML;
    
  1463. 
    
  1464.     const span = container.getElementsByTagName('span')[0];
    
  1465. 
    
  1466.     // On the client we don't have all data yet but we want to start
    
  1467.     // hydrating anyway.
    
  1468.     suspend = true;
    
  1469.     const root = ReactDOMClient.hydrateRoot(
    
  1470.       container,
    
  1471.       <Context.Provider value={{text: 'Hello', className: 'hello'}}>
    
  1472.         <App />
    
  1473.       </Context.Provider>,
    
  1474.     );
    
  1475.     await waitForAll([]);
    
  1476. 
    
  1477.     expect(ref.current).toBe(null);
    
  1478.     expect(span.textContent).toBe('Hello');
    
  1479. 
    
  1480.     // Render an update, which will be higher or the same priority as pinging the hydration.
    
  1481.     root.render(
    
  1482.       <Context.Provider value={{text: 'Hi', className: 'hi'}}>
    
  1483.         <App />
    
  1484.       </Context.Provider>,
    
  1485.     );
    
  1486. 
    
  1487.     // At the same time, resolving the promise so that rendering can complete.
    
  1488.     // This should first complete the hydration and then flush the update onto the hydrated state.
    
  1489.     await act(async () => {
    
  1490.       suspend = false;
    
  1491.       resolve();
    
  1492.       await promise;
    
  1493.     });
    
  1494. 
    
  1495.     // Since this should have been hydrated, this should still be the same span.
    
  1496.     const newSpan = container.getElementsByTagName('span')[0];
    
  1497.     expect(newSpan).toBe(span);
    
  1498. 
    
  1499.     // We should now have fully rendered with a ref on the new span.
    
  1500.     expect(ref.current).toBe(span);
    
  1501.     expect(span.textContent).toBe('Hi');
    
  1502.     // If we ended up hydrating the existing content, we won't have properly
    
  1503.     // patched up the tree, which might mean we haven't patched the className.
    
  1504.     expect(span.className).toBe('hi');
    
  1505.   });
    
  1506. 
    
  1507.   it('shows the fallback if context has changed before hydration completes and is still suspended', async () => {
    
  1508.     let suspend = false;
    
  1509.     let resolve;
    
  1510.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1511.     const ref = React.createRef();
    
  1512.     const Context = React.createContext(null);
    
  1513. 
    
  1514.     function Child() {
    
  1515.       const {text, className} = React.useContext(Context);
    
  1516.       if (suspend) {
    
  1517.         throw promise;
    
  1518.       } else {
    
  1519.         return (
    
  1520.           <span ref={ref} className={className}>
    
  1521.             {text}
    
  1522.           </span>
    
  1523.         );
    
  1524.       }
    
  1525.     }
    
  1526. 
    
  1527.     const App = React.memo(function App() {
    
  1528.       return (
    
  1529.         <div>
    
  1530.           <Suspense fallback="Loading...">
    
  1531.             <Child />
    
  1532.           </Suspense>
    
  1533.         </div>
    
  1534.       );
    
  1535.     });
    
  1536. 
    
  1537.     suspend = false;
    
  1538.     const finalHTML = ReactDOMServer.renderToString(
    
  1539.       <Context.Provider value={{text: 'Hello', className: 'hello'}}>
    
  1540.         <App />
    
  1541.       </Context.Provider>,
    
  1542.     );
    
  1543.     const container = document.createElement('div');
    
  1544.     container.innerHTML = finalHTML;
    
  1545. 
    
  1546.     // On the client we don't have all data yet but we want to start
    
  1547.     // hydrating anyway.
    
  1548.     suspend = true;
    
  1549.     const root = ReactDOMClient.hydrateRoot(
    
  1550.       container,
    
  1551.       <Context.Provider value={{text: 'Hello', className: 'hello'}}>
    
  1552.         <App />
    
  1553.       </Context.Provider>,
    
  1554.       {
    
  1555.         onRecoverableError(error) {
    
  1556.           Scheduler.log(error.message);
    
  1557.         },
    
  1558.       },
    
  1559.     );
    
  1560.     await waitForAll([]);
    
  1561. 
    
  1562.     expect(ref.current).toBe(null);
    
  1563. 
    
  1564.     // Render an update, but leave it still suspended.
    
  1565.     // Flushing now should delete the existing content and show the fallback.
    
  1566.     await act(() => {
    
  1567.       root.render(
    
  1568.         <Context.Provider value={{text: 'Hi', className: 'hi'}}>
    
  1569.           <App />
    
  1570.         </Context.Provider>,
    
  1571.       );
    
  1572.     });
    
  1573. 
    
  1574.     expect(container.getElementsByTagName('span').length).toBe(0);
    
  1575.     expect(ref.current).toBe(null);
    
  1576.     expect(container.textContent).toBe('Loading...');
    
  1577. 
    
  1578.     // Unsuspending shows the content.
    
  1579.     await act(async () => {
    
  1580.       suspend = false;
    
  1581.       resolve();
    
  1582.       await promise;
    
  1583.     });
    
  1584. 
    
  1585.     const span = container.getElementsByTagName('span')[0];
    
  1586.     expect(span.textContent).toBe('Hi');
    
  1587.     expect(span.className).toBe('hi');
    
  1588.     expect(ref.current).toBe(span);
    
  1589.     expect(container.textContent).toBe('Hi');
    
  1590.   });
    
  1591. 
    
  1592.   it('replaces the fallback with client content if it is not rendered by the server', async () => {
    
  1593.     let suspend = false;
    
  1594.     const promise = new Promise(resolvePromise => {});
    
  1595.     const ref = React.createRef();
    
  1596. 
    
  1597.     function Child() {
    
  1598.       if (suspend) {
    
  1599.         throw promise;
    
  1600.       } else {
    
  1601.         return 'Hello';
    
  1602.       }
    
  1603.     }
    
  1604. 
    
  1605.     function App() {
    
  1606.       return (
    
  1607.         <div>
    
  1608.           <Suspense fallback="Loading...">
    
  1609.             <span ref={ref}>
    
  1610.               <Child />
    
  1611.             </span>
    
  1612.           </Suspense>
    
  1613.         </div>
    
  1614.       );
    
  1615.     }
    
  1616. 
    
  1617.     // First we render the final HTML. With the streaming renderer
    
  1618.     // this may have suspense points on the server but here we want
    
  1619.     // to test the completed HTML. Don't suspend on the server.
    
  1620.     suspend = true;
    
  1621.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1622.     const container = document.createElement('div');
    
  1623.     container.innerHTML = finalHTML;
    
  1624. 
    
  1625.     expect(container.getElementsByTagName('span').length).toBe(0);
    
  1626. 
    
  1627.     // On the client we have the data available quickly for some reason.
    
  1628.     suspend = false;
    
  1629.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  1630.       onRecoverableError(error) {
    
  1631.         Scheduler.log(error.message);
    
  1632.       },
    
  1633.     });
    
  1634.     if (__DEV__) {
    
  1635.       await waitForAll([
    
  1636.         'The server did not finish this Suspense boundary: The server used' +
    
  1637.           ' "renderToString" which does not support Suspense. If you intended' +
    
  1638.           ' for this Suspense boundary to render the fallback content on the' +
    
  1639.           ' server consider throwing an Error somewhere within the Suspense boundary.' +
    
  1640.           ' If you intended to have the server wait for the suspended component' +
    
  1641.           ' please switch to "renderToPipeableStream" which supports Suspense on the server',
    
  1642.       ]);
    
  1643.     } else {
    
  1644.       await waitForAll([
    
  1645.         'The server could not finish this Suspense boundary, likely due to ' +
    
  1646.           'an error during server rendering. Switched to client rendering.',
    
  1647.       ]);
    
  1648.     }
    
  1649.     jest.runAllTimers();
    
  1650. 
    
  1651.     expect(container.textContent).toBe('Hello');
    
  1652. 
    
  1653.     const span = container.getElementsByTagName('span')[0];
    
  1654.     expect(ref.current).toBe(span);
    
  1655.   });
    
  1656. 
    
  1657.   it('replaces the fallback within the suspended time if there is a nested suspense', async () => {
    
  1658.     let suspend = false;
    
  1659.     const promise = new Promise(resolvePromise => {});
    
  1660.     const ref = React.createRef();
    
  1661. 
    
  1662.     function Child() {
    
  1663.       if (suspend) {
    
  1664.         throw promise;
    
  1665.       } else {
    
  1666.         return 'Hello';
    
  1667.       }
    
  1668.     }
    
  1669. 
    
  1670.     function InnerChild() {
    
  1671.       // Always suspends indefinitely
    
  1672.       throw promise;
    
  1673.     }
    
  1674. 
    
  1675.     function App() {
    
  1676.       return (
    
  1677.         <div>
    
  1678.           <Suspense fallback="Loading...">
    
  1679.             <span ref={ref}>
    
  1680.               <Child />
    
  1681.             </span>
    
  1682.             <Suspense fallback={null}>
    
  1683.               <InnerChild />
    
  1684.             </Suspense>
    
  1685.           </Suspense>
    
  1686.         </div>
    
  1687.       );
    
  1688.     }
    
  1689. 
    
  1690.     // First we render the final HTML. With the streaming renderer
    
  1691.     // this may have suspense points on the server but here we want
    
  1692.     // to test the completed HTML. Don't suspend on the server.
    
  1693.     suspend = true;
    
  1694.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1695.     const container = document.createElement('div');
    
  1696.     container.innerHTML = finalHTML;
    
  1697. 
    
  1698.     expect(container.getElementsByTagName('span').length).toBe(0);
    
  1699. 
    
  1700.     // On the client we have the data available quickly for some reason.
    
  1701.     suspend = false;
    
  1702.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  1703.       onRecoverableError(error) {
    
  1704.         Scheduler.log(error.message);
    
  1705.       },
    
  1706.     });
    
  1707.     if (__DEV__) {
    
  1708.       await waitForAll([
    
  1709.         'The server did not finish this Suspense boundary: The server used' +
    
  1710.           ' "renderToString" which does not support Suspense. If you intended' +
    
  1711.           ' for this Suspense boundary to render the fallback content on the' +
    
  1712.           ' server consider throwing an Error somewhere within the Suspense boundary.' +
    
  1713.           ' If you intended to have the server wait for the suspended component' +
    
  1714.           ' please switch to "renderToPipeableStream" which supports Suspense on the server',
    
  1715.       ]);
    
  1716.     } else {
    
  1717.       await waitForAll([
    
  1718.         'The server could not finish this Suspense boundary, likely due to ' +
    
  1719.           'an error during server rendering. Switched to client rendering.',
    
  1720.       ]);
    
  1721.     }
    
  1722.     // This will have exceeded the suspended time so we should timeout.
    
  1723.     jest.advanceTimersByTime(500);
    
  1724.     // The boundary should longer be suspended for the middle content
    
  1725.     // even though the inner boundary is still suspended.
    
  1726. 
    
  1727.     expect(container.textContent).toBe('Hello');
    
  1728. 
    
  1729.     const span = container.getElementsByTagName('span')[0];
    
  1730.     expect(ref.current).toBe(span);
    
  1731.   });
    
  1732. 
    
  1733.   it('replaces the fallback within the suspended time if there is a nested suspense in a nested suspense', async () => {
    
  1734.     let suspend = false;
    
  1735.     const promise = new Promise(resolvePromise => {});
    
  1736.     const ref = React.createRef();
    
  1737. 
    
  1738.     function Child() {
    
  1739.       if (suspend) {
    
  1740.         throw promise;
    
  1741.       } else {
    
  1742.         return 'Hello';
    
  1743.       }
    
  1744.     }
    
  1745. 
    
  1746.     function InnerChild() {
    
  1747.       // Always suspends indefinitely
    
  1748.       throw promise;
    
  1749.     }
    
  1750. 
    
  1751.     function App() {
    
  1752.       return (
    
  1753.         <div>
    
  1754.           <Suspense fallback="Another layer">
    
  1755.             <Suspense fallback="Loading...">
    
  1756.               <span ref={ref}>
    
  1757.                 <Child />
    
  1758.               </span>
    
  1759.               <Suspense fallback={null}>
    
  1760.                 <InnerChild />
    
  1761.               </Suspense>
    
  1762.             </Suspense>
    
  1763.           </Suspense>
    
  1764.         </div>
    
  1765.       );
    
  1766.     }
    
  1767. 
    
  1768.     // First we render the final HTML. With the streaming renderer
    
  1769.     // this may have suspense points on the server but here we want
    
  1770.     // to test the completed HTML. Don't suspend on the server.
    
  1771.     suspend = true;
    
  1772.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1773.     const container = document.createElement('div');
    
  1774.     container.innerHTML = finalHTML;
    
  1775. 
    
  1776.     expect(container.getElementsByTagName('span').length).toBe(0);
    
  1777. 
    
  1778.     // On the client we have the data available quickly for some reason.
    
  1779.     suspend = false;
    
  1780.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  1781.       onRecoverableError(error) {
    
  1782.         Scheduler.log(error.message);
    
  1783.       },
    
  1784.     });
    
  1785.     if (__DEV__) {
    
  1786.       await waitForAll([
    
  1787.         'The server did not finish this Suspense boundary: The server used' +
    
  1788.           ' "renderToString" which does not support Suspense. If you intended' +
    
  1789.           ' for this Suspense boundary to render the fallback content on the' +
    
  1790.           ' server consider throwing an Error somewhere within the Suspense boundary.' +
    
  1791.           ' If you intended to have the server wait for the suspended component' +
    
  1792.           ' please switch to "renderToPipeableStream" which supports Suspense on the server',
    
  1793.       ]);
    
  1794.     } else {
    
  1795.       await waitForAll([
    
  1796.         'The server could not finish this Suspense boundary, likely due to ' +
    
  1797.           'an error during server rendering. Switched to client rendering.',
    
  1798.       ]);
    
  1799.     }
    
  1800.     // This will have exceeded the suspended time so we should timeout.
    
  1801.     jest.advanceTimersByTime(500);
    
  1802.     // The boundary should longer be suspended for the middle content
    
  1803.     // even though the inner boundary is still suspended.
    
  1804. 
    
  1805.     expect(container.textContent).toBe('Hello');
    
  1806. 
    
  1807.     const span = container.getElementsByTagName('span')[0];
    
  1808.     expect(ref.current).toBe(span);
    
  1809.   });
    
  1810. 
    
  1811.   // @gate enableSuspenseList
    
  1812.   it('shows inserted items in a SuspenseList before content is hydrated', async () => {
    
  1813.     let suspend = false;
    
  1814.     let resolve;
    
  1815.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1816.     const ref = React.createRef();
    
  1817. 
    
  1818.     function Child({children}) {
    
  1819.       if (suspend) {
    
  1820.         throw promise;
    
  1821.       } else {
    
  1822.         return children;
    
  1823.       }
    
  1824.     }
    
  1825. 
    
  1826.     // These are hoisted to avoid them from rerendering.
    
  1827.     const a = (
    
  1828.       <Suspense fallback="Loading A">
    
  1829.         <Child>
    
  1830.           <span>A</span>
    
  1831.         </Child>
    
  1832.       </Suspense>
    
  1833.     );
    
  1834.     const b = (
    
  1835.       <Suspense fallback="Loading B">
    
  1836.         <Child>
    
  1837.           <span ref={ref}>B</span>
    
  1838.         </Child>
    
  1839.       </Suspense>
    
  1840.     );
    
  1841. 
    
  1842.     function App({showMore}) {
    
  1843.       return (
    
  1844.         <SuspenseList revealOrder="forwards">
    
  1845.           {a}
    
  1846.           {b}
    
  1847.           {showMore ? (
    
  1848.             <Suspense fallback="Loading C">
    
  1849.               <span>C</span>
    
  1850.             </Suspense>
    
  1851.           ) : null}
    
  1852.         </SuspenseList>
    
  1853.       );
    
  1854.     }
    
  1855. 
    
  1856.     suspend = false;
    
  1857.     const html = ReactDOMServer.renderToString(<App showMore={false} />);
    
  1858. 
    
  1859.     const container = document.createElement('div');
    
  1860.     container.innerHTML = html;
    
  1861. 
    
  1862.     const spanB = container.getElementsByTagName('span')[1];
    
  1863. 
    
  1864.     suspend = true;
    
  1865.     const root = await act(() =>
    
  1866.       ReactDOMClient.hydrateRoot(container, <App showMore={false} />),
    
  1867.     );
    
  1868. 
    
  1869.     // We're not hydrated yet.
    
  1870.     expect(ref.current).toBe(null);
    
  1871.     expect(container.textContent).toBe('AB');
    
  1872. 
    
  1873.     // Add more rows before we've hydrated the first two.
    
  1874.     await act(() => {
    
  1875.       root.render(<App showMore={true} />);
    
  1876.     });
    
  1877. 
    
  1878.     // We're not hydrated yet.
    
  1879.     expect(ref.current).toBe(null);
    
  1880. 
    
  1881.     // Since the first two are already showing their final content
    
  1882.     // we should be able to show the real content.
    
  1883.     expect(container.textContent).toBe('ABC');
    
  1884. 
    
  1885.     suspend = false;
    
  1886.     await act(async () => {
    
  1887.       await resolve();
    
  1888.     });
    
  1889. 
    
  1890.     expect(container.textContent).toBe('ABC');
    
  1891.     // We've hydrated the same span.
    
  1892.     expect(ref.current).toBe(spanB);
    
  1893.   });
    
  1894. 
    
  1895.   // @gate enableSuspenseList
    
  1896.   it('shows is able to hydrate boundaries even if others in a list are pending', async () => {
    
  1897.     let suspend = false;
    
  1898.     let resolve;
    
  1899.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1900.     const ref = React.createRef();
    
  1901. 
    
  1902.     function Child({children}) {
    
  1903.       if (suspend) {
    
  1904.         throw promise;
    
  1905.       } else {
    
  1906.         return children;
    
  1907.       }
    
  1908.     }
    
  1909. 
    
  1910.     const promise2 = new Promise(() => {});
    
  1911.     function AlwaysSuspend() {
    
  1912.       throw promise2;
    
  1913.     }
    
  1914. 
    
  1915.     // This is hoisted to avoid them from rerendering.
    
  1916.     const a = (
    
  1917.       <Suspense fallback="Loading A">
    
  1918.         <Child>
    
  1919.           <span ref={ref}>A</span>
    
  1920.         </Child>
    
  1921.       </Suspense>
    
  1922.     );
    
  1923. 
    
  1924.     function App({showMore}) {
    
  1925.       return (
    
  1926.         <SuspenseList revealOrder="together">
    
  1927.           {a}
    
  1928.           {showMore ? (
    
  1929.             <Suspense fallback="Loading B">
    
  1930.               <AlwaysSuspend />
    
  1931.             </Suspense>
    
  1932.           ) : null}
    
  1933.         </SuspenseList>
    
  1934.       );
    
  1935.     }
    
  1936. 
    
  1937.     suspend = false;
    
  1938.     const html = ReactDOMServer.renderToString(<App showMore={false} />);
    
  1939. 
    
  1940.     const container = document.createElement('div');
    
  1941.     container.innerHTML = html;
    
  1942. 
    
  1943.     const spanA = container.getElementsByTagName('span')[0];
    
  1944. 
    
  1945.     suspend = true;
    
  1946.     const root = await act(() =>
    
  1947.       ReactDOMClient.hydrateRoot(container, <App showMore={false} />),
    
  1948.     );
    
  1949. 
    
  1950.     // We're not hydrated yet.
    
  1951.     expect(ref.current).toBe(null);
    
  1952.     expect(container.textContent).toBe('A');
    
  1953. 
    
  1954.     await act(async () => {
    
  1955.       // Add another row before we've hydrated the first one.
    
  1956.       root.render(<App showMore={true} />);
    
  1957.       // At the same time, we resolve the blocking promise.
    
  1958.       suspend = false;
    
  1959.       await resolve();
    
  1960.     });
    
  1961. 
    
  1962.     // We should have been able to hydrate the first row.
    
  1963.     expect(ref.current).toBe(spanA);
    
  1964.     // Even though we're still slowing B.
    
  1965.     expect(container.textContent).toBe('ALoading B');
    
  1966.   });
    
  1967. 
    
  1968.   // @gate enableSuspenseList
    
  1969.   it('clears server boundaries when SuspenseList runs out of time hydrating', async () => {
    
  1970.     let suspend = false;
    
  1971.     let resolve;
    
  1972.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1973. 
    
  1974.     const ref = React.createRef();
    
  1975. 
    
  1976.     function Child({children}) {
    
  1977.       if (suspend) {
    
  1978.         throw promise;
    
  1979.       } else {
    
  1980.         return children;
    
  1981.       }
    
  1982.     }
    
  1983. 
    
  1984.     function Before() {
    
  1985.       Scheduler.log('Before');
    
  1986.       return null;
    
  1987.     }
    
  1988. 
    
  1989.     function After() {
    
  1990.       Scheduler.log('After');
    
  1991.       return null;
    
  1992.     }
    
  1993. 
    
  1994.     function FirstRow() {
    
  1995.       return (
    
  1996.         <>
    
  1997.           <Before />
    
  1998.           <Suspense fallback="Loading A">
    
  1999.             <span>A</span>
    
  2000.           </Suspense>
    
  2001.           <After />
    
  2002.         </>
    
  2003.       );
    
  2004.     }
    
  2005. 
    
  2006.     function App() {
    
  2007.       return (
    
  2008.         <Suspense fallback={null}>
    
  2009.           <SuspenseList revealOrder="forwards" tail="hidden">
    
  2010.             <FirstRow />
    
  2011.             <Suspense fallback="Loading B">
    
  2012.               <Child>
    
  2013.                 <span ref={ref}>B</span>
    
  2014.               </Child>
    
  2015.             </Suspense>
    
  2016.           </SuspenseList>
    
  2017.         </Suspense>
    
  2018.       );
    
  2019.     }
    
  2020. 
    
  2021.     suspend = false;
    
  2022.     const html = ReactDOMServer.renderToString(<App />);
    
  2023.     assertLog(['Before', 'After']);
    
  2024. 
    
  2025.     const container = document.createElement('div');
    
  2026.     container.innerHTML = html;
    
  2027. 
    
  2028.     const b = container.getElementsByTagName('span')[1];
    
  2029.     expect(b.textContent).toBe('B');
    
  2030. 
    
  2031.     const root = ReactDOMClient.hydrateRoot(container, <App />);
    
  2032. 
    
  2033.     // Increase hydration priority to higher than "offscreen".
    
  2034.     root.unstable_scheduleHydration(b);
    
  2035. 
    
  2036.     suspend = true;
    
  2037. 
    
  2038.     await act(async () => {
    
  2039.       if (gate(flags => flags.forceConcurrentByDefaultForTesting)) {
    
  2040.         await waitFor(['Before']);
    
  2041.         // This took a long time to render.
    
  2042.         Scheduler.unstable_advanceTime(1000);
    
  2043.         await waitFor(['After']);
    
  2044.       } else {
    
  2045.         await waitFor(['Before', 'After']);
    
  2046.       }
    
  2047. 
    
  2048.       // This will cause us to skip the second row completely.
    
  2049.     });
    
  2050. 
    
  2051.     // We haven't hydrated the second child but the placeholder is still in the list.
    
  2052.     expect(ref.current).toBe(null);
    
  2053.     expect(container.textContent).toBe('AB');
    
  2054. 
    
  2055.     suspend = false;
    
  2056.     await act(async () => {
    
  2057.       // Resolve the boundary to be in its resolved final state.
    
  2058.       await resolve();
    
  2059.     });
    
  2060. 
    
  2061.     expect(container.textContent).toBe('AB');
    
  2062.     expect(ref.current).toBe(b);
    
  2063.   });
    
  2064. 
    
  2065.   // @gate enableSuspenseList
    
  2066.   it('clears server boundaries when SuspenseList suspends last row hydrating', async () => {
    
  2067.     let suspend = false;
    
  2068.     let resolve;
    
  2069.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2070. 
    
  2071.     function Child({children}) {
    
  2072.       if (suspend) {
    
  2073.         throw promise;
    
  2074.       } else {
    
  2075.         return children;
    
  2076.       }
    
  2077.     }
    
  2078. 
    
  2079.     function App() {
    
  2080.       return (
    
  2081.         <Suspense fallback={null}>
    
  2082.           <SuspenseList revealOrder="forwards" tail="hidden">
    
  2083.             <Suspense fallback="Loading A">
    
  2084.               <span>A</span>
    
  2085.             </Suspense>
    
  2086.             <Suspense fallback="Loading B">
    
  2087.               <Child>
    
  2088.                 <span>B</span>
    
  2089.               </Child>
    
  2090.             </Suspense>
    
  2091.           </SuspenseList>
    
  2092.         </Suspense>
    
  2093.       );
    
  2094.     }
    
  2095. 
    
  2096.     suspend = true;
    
  2097.     const html = ReactDOMServer.renderToString(<App />);
    
  2098. 
    
  2099.     const container = document.createElement('div');
    
  2100.     container.innerHTML = html;
    
  2101. 
    
  2102.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  2103.       onRecoverableError(error) {
    
  2104.         Scheduler.log(error.message);
    
  2105.       },
    
  2106.     });
    
  2107. 
    
  2108.     suspend = true;
    
  2109.     if (__DEV__) {
    
  2110.       await waitForAll([
    
  2111.         'The server did not finish this Suspense boundary: The server used' +
    
  2112.           ' "renderToString" which does not support Suspense. If you intended' +
    
  2113.           ' for this Suspense boundary to render the fallback content on the' +
    
  2114.           ' server consider throwing an Error somewhere within the Suspense boundary.' +
    
  2115.           ' If you intended to have the server wait for the suspended component' +
    
  2116.           ' please switch to "renderToPipeableStream" which supports Suspense on the server',
    
  2117.       ]);
    
  2118.     } else {
    
  2119.       await waitForAll([
    
  2120.         'The server could not finish this Suspense boundary, likely due to ' +
    
  2121.           'an error during server rendering. Switched to client rendering.',
    
  2122.       ]);
    
  2123.     }
    
  2124. 
    
  2125.     // We haven't hydrated the second child but the placeholder is still in the list.
    
  2126.     expect(container.textContent).toBe('ALoading B');
    
  2127. 
    
  2128.     suspend = false;
    
  2129.     await act(async () => {
    
  2130.       // Resolve the boundary to be in its resolved final state.
    
  2131.       await resolve();
    
  2132.     });
    
  2133. 
    
  2134.     expect(container.textContent).toBe('AB');
    
  2135.   });
    
  2136. 
    
  2137.   it('can client render nested boundaries', async () => {
    
  2138.     let suspend = false;
    
  2139.     const promise = new Promise(() => {});
    
  2140.     const ref = React.createRef();
    
  2141. 
    
  2142.     function Child() {
    
  2143.       if (suspend) {
    
  2144.         throw promise;
    
  2145.       } else {
    
  2146.         return 'Hello';
    
  2147.       }
    
  2148.     }
    
  2149. 
    
  2150.     function App() {
    
  2151.       return (
    
  2152.         <div>
    
  2153.           <Suspense
    
  2154.             fallback={
    
  2155.               <>
    
  2156.                 <Suspense fallback="Loading...">
    
  2157.                   <Child />
    
  2158.                 </Suspense>
    
  2159.                 <span>Inner Sibling</span>
    
  2160.               </>
    
  2161.             }>
    
  2162.             <Child />
    
  2163.           </Suspense>
    
  2164.           <span ref={ref}>Sibling</span>
    
  2165.         </div>
    
  2166.       );
    
  2167.     }
    
  2168. 
    
  2169.     suspend = true;
    
  2170.     const html = ReactDOMServer.renderToString(<App />);
    
  2171. 
    
  2172.     const container = document.createElement('div');
    
  2173.     container.innerHTML = html + '<!--unrelated comment-->';
    
  2174. 
    
  2175.     const span = container.getElementsByTagName('span')[1];
    
  2176. 
    
  2177.     suspend = false;
    
  2178.     ReactDOMClient.hydrateRoot(container, <App />, {
    
  2179.       onRecoverableError(error) {
    
  2180.         Scheduler.log(error.message);
    
  2181.       },
    
  2182.     });
    
  2183.     if (__DEV__) {
    
  2184.       await waitForAll([
    
  2185.         'The server did not finish this Suspense boundary: The server used' +
    
  2186.           ' "renderToString" which does not support Suspense. If you intended' +
    
  2187.           ' for this Suspense boundary to render the fallback content on the' +
    
  2188.           ' server consider throwing an Error somewhere within the Suspense boundary.' +
    
  2189.           ' If you intended to have the server wait for the suspended component' +
    
  2190.           ' please switch to "renderToPipeableStream" which supports Suspense on the server',
    
  2191.       ]);
    
  2192.     } else {
    
  2193.       await waitForAll([
    
  2194.         'The server could not finish this Suspense boundary, likely due to ' +
    
  2195.           'an error during server rendering. Switched to client rendering.',
    
  2196.       ]);
    
  2197.     }
    
  2198.     jest.runAllTimers();
    
  2199. 
    
  2200.     expect(ref.current).toBe(span);
    
  2201.     expect(span.parentNode).not.toBe(null);
    
  2202. 
    
  2203.     // It leaves non-React comments alone.
    
  2204.     expect(container.lastChild.nodeType).toBe(8);
    
  2205.     expect(container.lastChild.data).toBe('unrelated comment');
    
  2206.   });
    
  2207. 
    
  2208.   it('can hydrate TWO suspense boundaries', async () => {
    
  2209.     const ref1 = React.createRef();
    
  2210.     const ref2 = React.createRef();
    
  2211. 
    
  2212.     function App() {
    
  2213.       return (
    
  2214.         <div>
    
  2215.           <Suspense fallback="Loading 1...">
    
  2216.             <span ref={ref1}>1</span>
    
  2217.           </Suspense>
    
  2218.           <Suspense fallback="Loading 2...">
    
  2219.             <span ref={ref2}>2</span>
    
  2220.           </Suspense>
    
  2221.         </div>
    
  2222.       );
    
  2223.     }
    
  2224. 
    
  2225.     // First we render the final HTML. With the streaming renderer
    
  2226.     // this may have suspense points on the server but here we want
    
  2227.     // to test the completed HTML. Don't suspend on the server.
    
  2228.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2229. 
    
  2230.     const container = document.createElement('div');
    
  2231.     container.innerHTML = finalHTML;
    
  2232. 
    
  2233.     const span1 = container.getElementsByTagName('span')[0];
    
  2234.     const span2 = container.getElementsByTagName('span')[1];
    
  2235. 
    
  2236.     // On the client we don't have all data yet but we want to start
    
  2237.     // hydrating anyway.
    
  2238.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2239.     await waitForAll([]);
    
  2240. 
    
  2241.     expect(ref1.current).toBe(span1);
    
  2242.     expect(ref2.current).toBe(span2);
    
  2243.   });
    
  2244. 
    
  2245.   it('regenerates if it cannot hydrate before changes to props/context expire', async () => {
    
  2246.     let suspend = false;
    
  2247.     const promise = new Promise(resolvePromise => {});
    
  2248.     const ref = React.createRef();
    
  2249.     const ClassName = React.createContext(null);
    
  2250. 
    
  2251.     function Child({text}) {
    
  2252.       const className = React.useContext(ClassName);
    
  2253.       if (suspend && className !== 'hi' && text !== 'Hi') {
    
  2254.         // Never suspends on the newer data.
    
  2255.         throw promise;
    
  2256.       } else {
    
  2257.         return (
    
  2258.           <span ref={ref} className={className}>
    
  2259.             {text}
    
  2260.           </span>
    
  2261.         );
    
  2262.       }
    
  2263.     }
    
  2264. 
    
  2265.     function App({text, className}) {
    
  2266.       return (
    
  2267.         <div>
    
  2268.           <Suspense fallback="Loading...">
    
  2269.             <Child text={text} />
    
  2270.           </Suspense>
    
  2271.         </div>
    
  2272.       );
    
  2273.     }
    
  2274. 
    
  2275.     suspend = false;
    
  2276.     const finalHTML = ReactDOMServer.renderToString(
    
  2277.       <ClassName.Provider value={'hello'}>
    
  2278.         <App text="Hello" />
    
  2279.       </ClassName.Provider>,
    
  2280.     );
    
  2281.     const container = document.createElement('div');
    
  2282.     container.innerHTML = finalHTML;
    
  2283. 
    
  2284.     const span = container.getElementsByTagName('span')[0];
    
  2285. 
    
  2286.     // On the client we don't have all data yet but we want to start
    
  2287.     // hydrating anyway.
    
  2288.     suspend = true;
    
  2289.     const root = ReactDOMClient.hydrateRoot(
    
  2290.       container,
    
  2291.       <ClassName.Provider value={'hello'}>
    
  2292.         <App text="Hello" />
    
  2293.       </ClassName.Provider>,
    
  2294.       {
    
  2295.         onRecoverableError(error) {
    
  2296.           Scheduler.log(error.message);
    
  2297.         },
    
  2298.       },
    
  2299.     );
    
  2300.     await waitForAll([]);
    
  2301. 
    
  2302.     expect(ref.current).toBe(null);
    
  2303.     expect(span.textContent).toBe('Hello');
    
  2304. 
    
  2305.     // Render an update, which will be higher or the same priority as pinging the hydration.
    
  2306.     // The new update doesn't suspend.
    
  2307.     // Since we're still suspended on the original data, we can't hydrate.
    
  2308.     // This will force all expiration times to flush.
    
  2309.     await act(() => {
    
  2310.       root.render(
    
  2311.         <ClassName.Provider value={'hi'}>
    
  2312.           <App text="Hi" />
    
  2313.         </ClassName.Provider>,
    
  2314.       );
    
  2315.     });
    
  2316. 
    
  2317.     // This will now be a new span because we weren't able to hydrate before
    
  2318.     const newSpan = container.getElementsByTagName('span')[0];
    
  2319.     expect(newSpan).not.toBe(span);
    
  2320. 
    
  2321.     // We should now have fully rendered with a ref on the new span.
    
  2322.     expect(ref.current).toBe(newSpan);
    
  2323.     expect(newSpan.textContent).toBe('Hi');
    
  2324.     // If we ended up hydrating the existing content, we won't have properly
    
  2325.     // patched up the tree, which might mean we haven't patched the className.
    
  2326.     expect(newSpan.className).toBe('hi');
    
  2327.   });
    
  2328. 
    
  2329.   it('does not invoke an event on a hydrated node until it commits', async () => {
    
  2330.     let suspend = false;
    
  2331.     let resolve;
    
  2332.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2333. 
    
  2334.     function Sibling({text}) {
    
  2335.       if (suspend) {
    
  2336.         throw promise;
    
  2337.       } else {
    
  2338.         return 'Hello';
    
  2339.       }
    
  2340.     }
    
  2341. 
    
  2342.     let clicks = 0;
    
  2343. 
    
  2344.     function Button() {
    
  2345.       const [clicked, setClicked] = React.useState(false);
    
  2346.       if (clicked) {
    
  2347.         return null;
    
  2348.       }
    
  2349.       return (
    
  2350.         <a
    
  2351.           onClick={() => {
    
  2352.             setClicked(true);
    
  2353.             clicks++;
    
  2354.           }}>
    
  2355.           Click me
    
  2356.         </a>
    
  2357.       );
    
  2358.     }
    
  2359. 
    
  2360.     function App() {
    
  2361.       return (
    
  2362.         <div>
    
  2363.           <Suspense fallback="Loading...">
    
  2364.             <Button />
    
  2365.             <Sibling />
    
  2366.           </Suspense>
    
  2367.         </div>
    
  2368.       );
    
  2369.     }
    
  2370. 
    
  2371.     suspend = false;
    
  2372.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2373.     const container = document.createElement('div');
    
  2374.     container.innerHTML = finalHTML;
    
  2375. 
    
  2376.     // We need this to be in the document since we'll dispatch events on it.
    
  2377.     document.body.appendChild(container);
    
  2378. 
    
  2379.     const a = container.getElementsByTagName('a')[0];
    
  2380. 
    
  2381.     // On the client we don't have all data yet but we want to start
    
  2382.     // hydrating anyway.
    
  2383.     suspend = true;
    
  2384.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2385.     await waitForAll([]);
    
  2386. 
    
  2387.     expect(container.textContent).toBe('Click meHello');
    
  2388. 
    
  2389.     // We're now partially hydrated.
    
  2390.     await act(() => {
    
  2391.       a.click();
    
  2392.     });
    
  2393.     expect(clicks).toBe(0);
    
  2394. 
    
  2395.     // Resolving the promise so that rendering can complete.
    
  2396.     await act(async () => {
    
  2397.       suspend = false;
    
  2398.       resolve();
    
  2399.       await promise;
    
  2400.     });
    
  2401. 
    
  2402.     expect(clicks).toBe(0);
    
  2403.     expect(container.textContent).toBe('Click meHello');
    
  2404. 
    
  2405.     document.body.removeChild(container);
    
  2406.   });
    
  2407. 
    
  2408.   // @gate www
    
  2409.   it('does not invoke an event on a hydrated event handle until it commits', async () => {
    
  2410.     const setClick = ReactDOM.unstable_createEventHandle('click');
    
  2411.     let suspend = false;
    
  2412.     let isServerRendering = true;
    
  2413.     let resolve;
    
  2414.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2415. 
    
  2416.     function Sibling({text}) {
    
  2417.       if (suspend) {
    
  2418.         throw promise;
    
  2419.       } else {
    
  2420.         return 'Hello';
    
  2421.       }
    
  2422.     }
    
  2423. 
    
  2424.     const onEvent = jest.fn();
    
  2425. 
    
  2426.     function Button() {
    
  2427.       const ref = React.useRef(null);
    
  2428.       if (!isServerRendering) {
    
  2429.         React.useLayoutEffect(() => {
    
  2430.           return setClick(ref.current, onEvent);
    
  2431.         });
    
  2432.       }
    
  2433.       return <a ref={ref}>Click me</a>;
    
  2434.     }
    
  2435. 
    
  2436.     function App() {
    
  2437.       return (
    
  2438.         <div>
    
  2439.           <Suspense fallback="Loading...">
    
  2440.             <Button />
    
  2441.             <Sibling />
    
  2442.           </Suspense>
    
  2443.         </div>
    
  2444.       );
    
  2445.     }
    
  2446. 
    
  2447.     suspend = false;
    
  2448.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2449.     const container = document.createElement('div');
    
  2450.     container.innerHTML = finalHTML;
    
  2451. 
    
  2452.     // We need this to be in the document since we'll dispatch events on it.
    
  2453.     document.body.appendChild(container);
    
  2454. 
    
  2455.     const a = container.getElementsByTagName('a')[0];
    
  2456. 
    
  2457.     // On the client we don't have all data yet but we want to start
    
  2458.     // hydrating anyway.
    
  2459.     suspend = true;
    
  2460.     isServerRendering = false;
    
  2461.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2462. 
    
  2463.     // We'll do one click before hydrating.
    
  2464.     a.click();
    
  2465.     // This should be delayed.
    
  2466.     expect(onEvent).toHaveBeenCalledTimes(0);
    
  2467. 
    
  2468.     await waitForAll([]);
    
  2469. 
    
  2470.     // We're now partially hydrated.
    
  2471.     await act(() => {
    
  2472.       a.click();
    
  2473.     });
    
  2474.     // We should not have invoked the event yet because we're not
    
  2475.     // yet hydrated.
    
  2476.     expect(onEvent).toHaveBeenCalledTimes(0);
    
  2477. 
    
  2478.     // Resolving the promise so that rendering can complete.
    
  2479.     await act(async () => {
    
  2480.       suspend = false;
    
  2481.       resolve();
    
  2482.       await promise;
    
  2483.     });
    
  2484. 
    
  2485.     expect(onEvent).toHaveBeenCalledTimes(0);
    
  2486. 
    
  2487.     document.body.removeChild(container);
    
  2488.   });
    
  2489. 
    
  2490.   it('invokes discrete events on nested suspense boundaries in a root (legacy system)', async () => {
    
  2491.     let suspend = false;
    
  2492.     let resolve;
    
  2493.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2494. 
    
  2495.     let clicks = 0;
    
  2496. 
    
  2497.     function Button() {
    
  2498.       return (
    
  2499.         <a
    
  2500.           onClick={() => {
    
  2501.             clicks++;
    
  2502.           }}>
    
  2503.           Click me
    
  2504.         </a>
    
  2505.       );
    
  2506.     }
    
  2507. 
    
  2508.     function Child() {
    
  2509.       if (suspend) {
    
  2510.         throw promise;
    
  2511.       } else {
    
  2512.         return (
    
  2513.           <Suspense fallback="Loading...">
    
  2514.             <Button />
    
  2515.           </Suspense>
    
  2516.         );
    
  2517.       }
    
  2518.     }
    
  2519. 
    
  2520.     function App() {
    
  2521.       return (
    
  2522.         <Suspense fallback="Loading...">
    
  2523.           <Child />
    
  2524.         </Suspense>
    
  2525.       );
    
  2526.     }
    
  2527. 
    
  2528.     suspend = false;
    
  2529.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2530.     const container = document.createElement('div');
    
  2531.     container.innerHTML = finalHTML;
    
  2532. 
    
  2533.     // We need this to be in the document since we'll dispatch events on it.
    
  2534.     document.body.appendChild(container);
    
  2535. 
    
  2536.     const a = container.getElementsByTagName('a')[0];
    
  2537. 
    
  2538.     // On the client we don't have all data yet but we want to start
    
  2539.     // hydrating anyway.
    
  2540.     suspend = true;
    
  2541.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2542. 
    
  2543.     // We'll do one click before hydrating.
    
  2544.     await act(() => {
    
  2545.       a.click();
    
  2546.     });
    
  2547.     // This should be delayed.
    
  2548.     expect(clicks).toBe(0);
    
  2549. 
    
  2550.     await waitForAll([]);
    
  2551. 
    
  2552.     // We're now partially hydrated.
    
  2553.     await act(() => {
    
  2554.       a.click();
    
  2555.     });
    
  2556.     expect(clicks).toBe(0);
    
  2557. 
    
  2558.     // Resolving the promise so that rendering can complete.
    
  2559.     await act(async () => {
    
  2560.       suspend = false;
    
  2561.       resolve();
    
  2562.       await promise;
    
  2563.     });
    
  2564. 
    
  2565.     expect(clicks).toBe(0);
    
  2566. 
    
  2567.     document.body.removeChild(container);
    
  2568.   });
    
  2569. 
    
  2570.   // @gate www
    
  2571.   it('invokes discrete events on nested suspense boundaries in a root (createEventHandle)', async () => {
    
  2572.     let suspend = false;
    
  2573.     let isServerRendering = true;
    
  2574.     let resolve;
    
  2575.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2576. 
    
  2577.     const onEvent = jest.fn();
    
  2578.     const setClick = ReactDOM.unstable_createEventHandle('click');
    
  2579. 
    
  2580.     function Button() {
    
  2581.       const ref = React.useRef(null);
    
  2582. 
    
  2583.       if (!isServerRendering) {
    
  2584.         React.useLayoutEffect(() => {
    
  2585.           return setClick(ref.current, onEvent);
    
  2586.         });
    
  2587.       }
    
  2588. 
    
  2589.       return <a ref={ref}>Click me</a>;
    
  2590.     }
    
  2591. 
    
  2592.     function Child() {
    
  2593.       if (suspend) {
    
  2594.         throw promise;
    
  2595.       } else {
    
  2596.         return (
    
  2597.           <Suspense fallback="Loading...">
    
  2598.             <Button />
    
  2599.           </Suspense>
    
  2600.         );
    
  2601.       }
    
  2602.     }
    
  2603. 
    
  2604.     function App() {
    
  2605.       return (
    
  2606.         <Suspense fallback="Loading...">
    
  2607.           <Child />
    
  2608.         </Suspense>
    
  2609.       );
    
  2610.     }
    
  2611. 
    
  2612.     suspend = false;
    
  2613.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2614.     const container = document.createElement('div');
    
  2615.     container.innerHTML = finalHTML;
    
  2616. 
    
  2617.     // We need this to be in the document since we'll dispatch events on it.
    
  2618.     document.body.appendChild(container);
    
  2619. 
    
  2620.     const a = container.getElementsByTagName('a')[0];
    
  2621. 
    
  2622.     // On the client we don't have all data yet but we want to start
    
  2623.     // hydrating anyway.
    
  2624.     suspend = true;
    
  2625.     isServerRendering = false;
    
  2626.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2627. 
    
  2628.     // We'll do one click before hydrating.
    
  2629.     a.click();
    
  2630.     // This should be delayed.
    
  2631.     expect(onEvent).toHaveBeenCalledTimes(0);
    
  2632. 
    
  2633.     await waitForAll([]);
    
  2634. 
    
  2635.     // We're now partially hydrated.
    
  2636.     await act(() => {
    
  2637.       a.click();
    
  2638.     });
    
  2639.     // We should not have invoked the event yet because we're not
    
  2640.     // yet hydrated.
    
  2641.     expect(onEvent).toHaveBeenCalledTimes(0);
    
  2642. 
    
  2643.     // Resolving the promise so that rendering can complete.
    
  2644.     await act(async () => {
    
  2645.       suspend = false;
    
  2646.       resolve();
    
  2647.       await promise;
    
  2648.     });
    
  2649. 
    
  2650.     expect(onEvent).toHaveBeenCalledTimes(0);
    
  2651. 
    
  2652.     document.body.removeChild(container);
    
  2653.   });
    
  2654. 
    
  2655.   it('does not invoke the parent of dehydrated boundary event', async () => {
    
  2656.     let suspend = false;
    
  2657.     let resolve;
    
  2658.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2659. 
    
  2660.     let clicksOnParent = 0;
    
  2661.     let clicksOnChild = 0;
    
  2662. 
    
  2663.     function Child({text}) {
    
  2664.       if (suspend) {
    
  2665.         throw promise;
    
  2666.       } else {
    
  2667.         return (
    
  2668.           <span
    
  2669.             onClick={e => {
    
  2670.               // The stopPropagation is showing an example why invoking
    
  2671.               // the event on only a parent might not be correct.
    
  2672.               e.stopPropagation();
    
  2673.               clicksOnChild++;
    
  2674.             }}>
    
  2675.             Hello
    
  2676.           </span>
    
  2677.         );
    
  2678.       }
    
  2679.     }
    
  2680. 
    
  2681.     function App() {
    
  2682.       return (
    
  2683.         <div onClick={() => clicksOnParent++}>
    
  2684.           <Suspense fallback="Loading...">
    
  2685.             <Child />
    
  2686.           </Suspense>
    
  2687.         </div>
    
  2688.       );
    
  2689.     }
    
  2690. 
    
  2691.     suspend = false;
    
  2692.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2693.     const container = document.createElement('div');
    
  2694.     container.innerHTML = finalHTML;
    
  2695. 
    
  2696.     // We need this to be in the document since we'll dispatch events on it.
    
  2697.     document.body.appendChild(container);
    
  2698. 
    
  2699.     const span = container.getElementsByTagName('span')[0];
    
  2700. 
    
  2701.     // On the client we don't have all data yet but we want to start
    
  2702.     // hydrating anyway.
    
  2703.     suspend = true;
    
  2704.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2705.     await waitForAll([]);
    
  2706. 
    
  2707.     // We're now partially hydrated.
    
  2708.     await act(() => {
    
  2709.       span.click();
    
  2710.     });
    
  2711.     expect(clicksOnChild).toBe(0);
    
  2712.     expect(clicksOnParent).toBe(0);
    
  2713. 
    
  2714.     // Resolving the promise so that rendering can complete.
    
  2715.     await act(async () => {
    
  2716.       suspend = false;
    
  2717.       resolve();
    
  2718.       await promise;
    
  2719.     });
    
  2720. 
    
  2721.     expect(clicksOnChild).toBe(0);
    
  2722.     expect(clicksOnParent).toBe(0);
    
  2723. 
    
  2724.     document.body.removeChild(container);
    
  2725.   });
    
  2726. 
    
  2727.   it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => {
    
  2728.     let suspend = false;
    
  2729.     let resolve;
    
  2730.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2731. 
    
  2732.     let clicks = 0;
    
  2733.     const childSlotRef = React.createRef();
    
  2734. 
    
  2735.     function Parent() {
    
  2736.       return <div onClick={() => clicks++} ref={childSlotRef} />;
    
  2737.     }
    
  2738. 
    
  2739.     function Child({text}) {
    
  2740.       if (suspend) {
    
  2741.         throw promise;
    
  2742.       } else {
    
  2743.         return <a>Click me</a>;
    
  2744.       }
    
  2745.     }
    
  2746. 
    
  2747.     function App() {
    
  2748.       // The root is a Suspense boundary.
    
  2749.       return (
    
  2750.         <Suspense fallback="Loading...">
    
  2751.           <Child />
    
  2752.         </Suspense>
    
  2753.       );
    
  2754.     }
    
  2755. 
    
  2756.     suspend = false;
    
  2757.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2758. 
    
  2759.     const parentContainer = document.createElement('div');
    
  2760.     const childContainer = document.createElement('div');
    
  2761. 
    
  2762.     // We need this to be in the document since we'll dispatch events on it.
    
  2763.     document.body.appendChild(parentContainer);
    
  2764. 
    
  2765.     // We're going to use a different root as a parent.
    
  2766.     // This lets us detect whether an event goes through React's event system.
    
  2767.     const parentRoot = ReactDOMClient.createRoot(parentContainer);
    
  2768.     await act(() => parentRoot.render(<Parent />));
    
  2769. 
    
  2770.     childSlotRef.current.appendChild(childContainer);
    
  2771. 
    
  2772.     childContainer.innerHTML = finalHTML;
    
  2773. 
    
  2774.     const a = childContainer.getElementsByTagName('a')[0];
    
  2775. 
    
  2776.     suspend = true;
    
  2777. 
    
  2778.     // Hydrate asynchronously.
    
  2779.     await act(() => ReactDOMClient.hydrateRoot(childContainer, <App />));
    
  2780. 
    
  2781.     // The Suspense boundary is not yet hydrated.
    
  2782.     await act(() => {
    
  2783.       a.click();
    
  2784.     });
    
  2785.     expect(clicks).toBe(0);
    
  2786. 
    
  2787.     // Resolving the promise so that rendering can complete.
    
  2788.     await act(async () => {
    
  2789.       suspend = false;
    
  2790.       resolve();
    
  2791.       await promise;
    
  2792.     });
    
  2793. 
    
  2794.     expect(clicks).toBe(0);
    
  2795. 
    
  2796.     document.body.removeChild(parentContainer);
    
  2797.   });
    
  2798. 
    
  2799.   it('blocks only on the last continuous event (legacy system)', async () => {
    
  2800.     let suspend1 = false;
    
  2801.     let resolve1;
    
  2802.     const promise1 = new Promise(resolvePromise => (resolve1 = resolvePromise));
    
  2803.     let suspend2 = false;
    
  2804.     let resolve2;
    
  2805.     const promise2 = new Promise(resolvePromise => (resolve2 = resolvePromise));
    
  2806. 
    
  2807.     function First({text}) {
    
  2808.       if (suspend1) {
    
  2809.         throw promise1;
    
  2810.       } else {
    
  2811.         return 'Hello';
    
  2812.       }
    
  2813.     }
    
  2814. 
    
  2815.     function Second({text}) {
    
  2816.       if (suspend2) {
    
  2817.         throw promise2;
    
  2818.       } else {
    
  2819.         return 'World';
    
  2820.       }
    
  2821.     }
    
  2822. 
    
  2823.     const ops = [];
    
  2824. 
    
  2825.     function App() {
    
  2826.       return (
    
  2827.         <div>
    
  2828.           <Suspense fallback="Loading First...">
    
  2829.             <span
    
  2830.               onMouseEnter={() => ops.push('Mouse Enter First')}
    
  2831.               onMouseLeave={() => ops.push('Mouse Leave First')}
    
  2832.             />
    
  2833.             {/* We suspend after to test what happens when we eager
    
  2834.                 attach the listener. */}
    
  2835.             <First />
    
  2836.           </Suspense>
    
  2837.           <Suspense fallback="Loading Second...">
    
  2838.             <span
    
  2839.               onMouseEnter={() => ops.push('Mouse Enter Second')}
    
  2840.               onMouseLeave={() => ops.push('Mouse Leave Second')}>
    
  2841.               <Second />
    
  2842.             </span>
    
  2843.           </Suspense>
    
  2844.         </div>
    
  2845.       );
    
  2846.     }
    
  2847. 
    
  2848.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2849.     const container = document.createElement('div');
    
  2850.     container.innerHTML = finalHTML;
    
  2851. 
    
  2852.     // We need this to be in the document since we'll dispatch events on it.
    
  2853.     document.body.appendChild(container);
    
  2854. 
    
  2855.     const appDiv = container.getElementsByTagName('div')[0];
    
  2856.     const firstSpan = appDiv.getElementsByTagName('span')[0];
    
  2857.     const secondSpan = appDiv.getElementsByTagName('span')[1];
    
  2858.     expect(firstSpan.textContent).toBe('');
    
  2859.     expect(secondSpan.textContent).toBe('World');
    
  2860. 
    
  2861.     // On the client we don't have all data yet but we want to start
    
  2862.     // hydrating anyway.
    
  2863.     suspend1 = true;
    
  2864.     suspend2 = true;
    
  2865.     ReactDOMClient.hydrateRoot(container, <App />);
    
  2866. 
    
  2867.     await waitForAll([]);
    
  2868. 
    
  2869.     dispatchMouseEvent(appDiv, null);
    
  2870.     dispatchMouseEvent(firstSpan, appDiv);
    
  2871.     dispatchMouseEvent(secondSpan, firstSpan);
    
  2872. 
    
  2873.     // Neither target is yet hydrated.
    
  2874.     expect(ops).toEqual([]);
    
  2875. 
    
  2876.     // Resolving the second promise so that rendering can complete.
    
  2877.     suspend2 = false;
    
  2878.     resolve2();
    
  2879.     await promise2;
    
  2880. 
    
  2881.     await waitForAll([]);
    
  2882. 
    
  2883.     // We've unblocked the current hover target so we should be
    
  2884.     // able to replay it now.
    
  2885.     expect(ops).toEqual(['Mouse Enter Second']);
    
  2886. 
    
  2887.     // Resolving the first promise has no effect now.
    
  2888.     suspend1 = false;
    
  2889.     resolve1();
    
  2890.     await promise1;
    
  2891. 
    
  2892.     await waitForAll([]);
    
  2893. 
    
  2894.     expect(ops).toEqual(['Mouse Enter Second']);
    
  2895. 
    
  2896.     document.body.removeChild(container);
    
  2897.   });
    
  2898. 
    
  2899.   it('finishes normal pri work before continuing to hydrate a retry', async () => {
    
  2900.     let suspend = false;
    
  2901.     let resolve;
    
  2902.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2903.     const ref = React.createRef();
    
  2904. 
    
  2905.     function Child() {
    
  2906.       if (suspend) {
    
  2907.         throw promise;
    
  2908.       } else {
    
  2909.         Scheduler.log('Child');
    
  2910.         return 'Hello';
    
  2911.       }
    
  2912.     }
    
  2913. 
    
  2914.     function Sibling() {
    
  2915.       Scheduler.log('Sibling');
    
  2916.       React.useLayoutEffect(() => {
    
  2917.         Scheduler.log('Commit Sibling');
    
  2918.       });
    
  2919.       return 'World';
    
  2920.     }
    
  2921. 
    
  2922.     // Avoid rerendering the tree by hoisting it.
    
  2923.     const tree = (
    
  2924.       <Suspense fallback="Loading...">
    
  2925.         <span ref={ref}>
    
  2926.           <Child />
    
  2927.         </span>
    
  2928.       </Suspense>
    
  2929.     );
    
  2930. 
    
  2931.     function App({showSibling}) {
    
  2932.       return (
    
  2933.         <div>
    
  2934.           {tree}
    
  2935.           {showSibling ? <Sibling /> : null}
    
  2936.         </div>
    
  2937.       );
    
  2938.     }
    
  2939. 
    
  2940.     suspend = false;
    
  2941.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  2942.     assertLog(['Child']);
    
  2943. 
    
  2944.     const container = document.createElement('div');
    
  2945.     container.innerHTML = finalHTML;
    
  2946. 
    
  2947.     suspend = true;
    
  2948.     const root = ReactDOMClient.hydrateRoot(
    
  2949.       container,
    
  2950.       <App showSibling={false} />,
    
  2951.     );
    
  2952.     await waitForAll([]);
    
  2953. 
    
  2954.     expect(ref.current).toBe(null);
    
  2955.     expect(container.textContent).toBe('Hello');
    
  2956. 
    
  2957.     // Resolving the promise should continue hydration
    
  2958.     suspend = false;
    
  2959.     resolve();
    
  2960.     await promise;
    
  2961. 
    
  2962.     Scheduler.unstable_advanceTime(100);
    
  2963. 
    
  2964.     // Before we have a chance to flush it, we'll also render an update.
    
  2965.     root.render(<App showSibling={true} />);
    
  2966. 
    
  2967.     // When we flush we expect the Normal pri render to take priority
    
  2968.     // over hydration.
    
  2969.     await waitFor(['Sibling', 'Commit Sibling']);
    
  2970. 
    
  2971.     // We shouldn't have hydrated the child yet.
    
  2972.     expect(ref.current).toBe(null);
    
  2973.     // But we did have a chance to update the content.
    
  2974.     expect(container.textContent).toBe('HelloWorld');
    
  2975. 
    
  2976.     await waitForAll(['Child']);
    
  2977. 
    
  2978.     // Now we're hydrated.
    
  2979.     expect(ref.current).not.toBe(null);
    
  2980.   });
    
  2981. 
    
  2982.   it('regression test: does not overfire non-bubbling browser events', async () => {
    
  2983.     let suspend = false;
    
  2984.     let resolve;
    
  2985.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  2986. 
    
  2987.     function Sibling({text}) {
    
  2988.       if (suspend) {
    
  2989.         throw promise;
    
  2990.       } else {
    
  2991.         return 'Hello';
    
  2992.       }
    
  2993.     }
    
  2994. 
    
  2995.     let submits = 0;
    
  2996. 
    
  2997.     function Form() {
    
  2998.       const [submitted, setSubmitted] = React.useState(false);
    
  2999.       if (submitted) {
    
  3000.         return null;
    
  3001.       }
    
  3002.       return (
    
  3003.         <form
    
  3004.           onSubmit={() => {
    
  3005.             setSubmitted(true);
    
  3006.             submits++;
    
  3007.           }}>
    
  3008.           Click me
    
  3009.         </form>
    
  3010.       );
    
  3011.     }
    
  3012. 
    
  3013.     function App() {
    
  3014.       return (
    
  3015.         <div>
    
  3016.           <Suspense fallback="Loading...">
    
  3017.             <Form />
    
  3018.             <Sibling />
    
  3019.           </Suspense>
    
  3020.         </div>
    
  3021.       );
    
  3022.     }
    
  3023. 
    
  3024.     suspend = false;
    
  3025.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3026.     const container = document.createElement('div');
    
  3027.     container.innerHTML = finalHTML;
    
  3028. 
    
  3029.     // We need this to be in the document since we'll dispatch events on it.
    
  3030.     document.body.appendChild(container);
    
  3031. 
    
  3032.     const form = container.getElementsByTagName('form')[0];
    
  3033. 
    
  3034.     // On the client we don't have all data yet but we want to start
    
  3035.     // hydrating anyway.
    
  3036.     suspend = true;
    
  3037.     ReactDOMClient.hydrateRoot(container, <App />);
    
  3038.     await waitForAll([]);
    
  3039. 
    
  3040.     expect(container.textContent).toBe('Click meHello');
    
  3041. 
    
  3042.     // We're now partially hydrated.
    
  3043.     await act(() => {
    
  3044.       form.dispatchEvent(
    
  3045.         new window.Event('submit', {
    
  3046.           bubbles: true,
    
  3047.         }),
    
  3048.       );
    
  3049.     });
    
  3050.     expect(submits).toBe(0);
    
  3051. 
    
  3052.     // Resolving the promise so that rendering can complete.
    
  3053.     await act(async () => {
    
  3054.       suspend = false;
    
  3055.       resolve();
    
  3056.       await promise;
    
  3057.     });
    
  3058. 
    
  3059.     // discrete event not replayed
    
  3060.     expect(submits).toBe(0);
    
  3061.     expect(container.textContent).toBe('Click meHello');
    
  3062. 
    
  3063.     document.body.removeChild(container);
    
  3064.   });
    
  3065. 
    
  3066.   // This test fails, in both forks. Without a boundary, the deferred tree won't
    
  3067.   // re-enter hydration mode. It doesn't come up in practice because there's
    
  3068.   // always a parent Suspense boundary. But it's still a bug. Leaving for a
    
  3069.   // follow up.
    
  3070.   //
    
  3071.   // @gate FIXME
    
  3072.   it('hydrates a hidden subtree outside of a Suspense boundary', async () => {
    
  3073.     const ref = React.createRef();
    
  3074. 
    
  3075.     function App() {
    
  3076.       return (
    
  3077.         <LegacyHiddenDiv mode="hidden">
    
  3078.           <span ref={ref}>Hidden child</span>
    
  3079.         </LegacyHiddenDiv>
    
  3080.       );
    
  3081.     }
    
  3082. 
    
  3083.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3084. 
    
  3085.     const container = document.createElement('div');
    
  3086.     container.innerHTML = finalHTML;
    
  3087. 
    
  3088.     const span = container.getElementsByTagName('span')[0];
    
  3089.     expect(span.innerHTML).toBe('Hidden child');
    
  3090. 
    
  3091.     await act(() =>
    
  3092.       ReactDOMClient.hydrateRoot(container, <App />, {
    
  3093.         onRecoverableError(error) {
    
  3094.           Scheduler.log('Log recoverable error: ' + error.message);
    
  3095.         },
    
  3096.       }),
    
  3097.     );
    
  3098. 
    
  3099.     expect(ref.current).toBe(span);
    
  3100.     expect(span.innerHTML).toBe('Hidden child');
    
  3101.   });
    
  3102. 
    
  3103.   // @gate www
    
  3104.   it('renders a hidden LegacyHidden component inside a Suspense boundary', async () => {
    
  3105.     const ref = React.createRef();
    
  3106. 
    
  3107.     function App() {
    
  3108.       return (
    
  3109.         <Suspense fallback="Loading...">
    
  3110.           <LegacyHiddenDiv mode="hidden">
    
  3111.             <span ref={ref}>Hidden child</span>
    
  3112.           </LegacyHiddenDiv>
    
  3113.         </Suspense>
    
  3114.       );
    
  3115.     }
    
  3116. 
    
  3117.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3118. 
    
  3119.     const container = document.createElement('div');
    
  3120.     container.innerHTML = finalHTML;
    
  3121. 
    
  3122.     const span = container.getElementsByTagName('span')[0];
    
  3123.     expect(span.innerHTML).toBe('Hidden child');
    
  3124. 
    
  3125.     await act(() => ReactDOMClient.hydrateRoot(container, <App />));
    
  3126.     expect(ref.current).toBe(span);
    
  3127.     expect(span.innerHTML).toBe('Hidden child');
    
  3128.   });
    
  3129. 
    
  3130.   // @gate www
    
  3131.   it('renders a visible LegacyHidden component', async () => {
    
  3132.     const ref = React.createRef();
    
  3133. 
    
  3134.     function App() {
    
  3135.       return (
    
  3136.         <LegacyHiddenDiv mode="visible">
    
  3137.           <span ref={ref}>Hidden child</span>
    
  3138.         </LegacyHiddenDiv>
    
  3139.       );
    
  3140.     }
    
  3141. 
    
  3142.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3143. 
    
  3144.     const container = document.createElement('div');
    
  3145.     container.innerHTML = finalHTML;
    
  3146. 
    
  3147.     const span = container.getElementsByTagName('span')[0];
    
  3148. 
    
  3149.     await act(() => ReactDOMClient.hydrateRoot(container, <App />));
    
  3150.     expect(ref.current).toBe(span);
    
  3151.     expect(ref.current.innerHTML).toBe('Hidden child');
    
  3152.   });
    
  3153. 
    
  3154.   // @gate enableActivity
    
  3155.   it('a visible Activity component acts like a fragment', async () => {
    
  3156.     const ref = React.createRef();
    
  3157. 
    
  3158.     function App() {
    
  3159.       return (
    
  3160.         <Activity mode="visible">
    
  3161.           <span ref={ref}>Child</span>
    
  3162.         </Activity>
    
  3163.       );
    
  3164.     }
    
  3165. 
    
  3166.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3167.     assertLog([]);
    
  3168. 
    
  3169.     const container = document.createElement('div');
    
  3170.     container.innerHTML = finalHTML;
    
  3171. 
    
  3172.     // Visible Activity boundaries behave exactly like fragments: a
    
  3173.     // pure indirection.
    
  3174.     expect(container).toMatchInlineSnapshot(`
    
  3175.       <div>
    
  3176.         <span>
    
  3177.           Child
    
  3178.         </span>
    
  3179.       </div>
    
  3180.     `);
    
  3181. 
    
  3182.     const span = container.getElementsByTagName('span')[0];
    
  3183. 
    
  3184.     // The tree successfully hydrates
    
  3185.     ReactDOMClient.hydrateRoot(container, <App />);
    
  3186.     await waitForAll([]);
    
  3187.     expect(ref.current).toBe(span);
    
  3188.   });
    
  3189. 
    
  3190.   // @gate enableActivity
    
  3191.   it('a hidden Activity component is skipped over during server rendering', async () => {
    
  3192.     const visibleRef = React.createRef();
    
  3193. 
    
  3194.     function HiddenChild() {
    
  3195.       Scheduler.log('HiddenChild');
    
  3196.       return <span>Hidden</span>;
    
  3197.     }
    
  3198. 
    
  3199.     function App() {
    
  3200.       Scheduler.log('App');
    
  3201.       return (
    
  3202.         <>
    
  3203.           <span ref={visibleRef}>Visible</span>
    
  3204.           <Activity mode="hidden">
    
  3205.             <HiddenChild />
    
  3206.           </Activity>
    
  3207.         </>
    
  3208.       );
    
  3209.     }
    
  3210. 
    
  3211.     // During server rendering, the Child component should not be evaluated,
    
  3212.     // because it's inside a hidden tree.
    
  3213.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3214.     assertLog(['App']);
    
  3215. 
    
  3216.     const container = document.createElement('div');
    
  3217.     container.innerHTML = finalHTML;
    
  3218. 
    
  3219.     // The hidden child is not part of the server rendered HTML
    
  3220.     expect(container).toMatchInlineSnapshot(`
    
  3221.       <div>
    
  3222.         <span>
    
  3223.           Visible
    
  3224.         </span>
    
  3225.       </div>
    
  3226.     `);
    
  3227. 
    
  3228.     const visibleSpan = container.getElementsByTagName('span')[0];
    
  3229. 
    
  3230.     // The visible span successfully hydrates
    
  3231.     ReactDOMClient.hydrateRoot(container, <App />);
    
  3232.     await waitForPaint(['App']);
    
  3233.     expect(visibleRef.current).toBe(visibleSpan);
    
  3234. 
    
  3235.     // Subsequently, the hidden child is prerendered on the client
    
  3236.     await waitForPaint(['HiddenChild']);
    
  3237.     expect(container).toMatchInlineSnapshot(`
    
  3238.       <div>
    
  3239.         <span>
    
  3240.           Visible
    
  3241.         </span>
    
  3242.         <span
    
  3243.           style="display: none;"
    
  3244.         >
    
  3245.           Hidden
    
  3246.         </span>
    
  3247.       </div>
    
  3248.     `);
    
  3249.   });
    
  3250. 
    
  3251.   function itHydratesWithoutMismatch(msg, App) {
    
  3252.     it('hydrates without mismatch ' + msg, async () => {
    
  3253.       const container = document.createElement('div');
    
  3254.       document.body.appendChild(container);
    
  3255.       const finalHTML = ReactDOMServer.renderToString(<App />);
    
  3256.       container.innerHTML = finalHTML;
    
  3257. 
    
  3258.       await act(() => ReactDOMClient.hydrateRoot(container, <App />));
    
  3259.     });
    
  3260.   }
    
  3261. 
    
  3262.   itHydratesWithoutMismatch('an empty string with neighbors', function App() {
    
  3263.     return (
    
  3264.       <div>
    
  3265.         <div id="test">Test</div>
    
  3266.         {'' && <div>Test</div>}
    
  3267.         {'Test'}
    
  3268.       </div>
    
  3269.     );
    
  3270.   });
    
  3271. 
    
  3272.   itHydratesWithoutMismatch('an empty string', function App() {
    
  3273.     return '';
    
  3274.   });
    
  3275.   itHydratesWithoutMismatch(
    
  3276.     'an empty string simple in fragment',
    
  3277.     function App() {
    
  3278.       return (
    
  3279.         <>
    
  3280.           {''}
    
  3281.           {'sup'}
    
  3282.         </>
    
  3283.       );
    
  3284.     },
    
  3285.   );
    
  3286.   itHydratesWithoutMismatch(
    
  3287.     'an empty string simple in suspense',
    
  3288.     function App() {
    
  3289.       return <Suspense>{'' && false}</Suspense>;
    
  3290.     },
    
  3291.   );
    
  3292. 
    
  3293.   itHydratesWithoutMismatch('an empty string in class component', TestAppClass);
    
  3294. 
    
  3295.   it('fallback to client render on hydration mismatch at root', async () => {
    
  3296.     let suspend = true;
    
  3297.     let resolve;
    
  3298.     const promise = new Promise((res, rej) => {
    
  3299.       resolve = () => {
    
  3300.         suspend = false;
    
  3301.         res();
    
  3302.       };
    
  3303.     });
    
  3304.     function App({isClient}) {
    
  3305.       return (
    
  3306.         <>
    
  3307.           <Suspense fallback={<div>Loading</div>}>
    
  3308.             <ChildThatSuspends id={1} isClient={isClient} />
    
  3309.           </Suspense>
    
  3310.           {isClient ? <span>client</span> : <div>server</div>}
    
  3311.           <Suspense fallback={<div>Loading</div>}>
    
  3312.             <ChildThatSuspends id={2} isClient={isClient} />
    
  3313.           </Suspense>
    
  3314.         </>
    
  3315.       );
    
  3316.     }
    
  3317.     function ChildThatSuspends({id, isClient}) {
    
  3318.       if (isClient && suspend) {
    
  3319.         throw promise;
    
  3320.       }
    
  3321.       return <div>{id}</div>;
    
  3322.     }
    
  3323. 
    
  3324.     const finalHTML = ReactDOMServer.renderToString(<App isClient={false} />);
    
  3325. 
    
  3326.     const container = document.createElement('div');
    
  3327.     document.body.appendChild(container);
    
  3328.     container.innerHTML = finalHTML;
    
  3329. 
    
  3330.     await expect(async () => {
    
  3331.       await act(() => {
    
  3332.         ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  3333.           onRecoverableError(error) {
    
  3334.             Scheduler.log('Log recoverable error: ' + error.message);
    
  3335.           },
    
  3336.         });
    
  3337.       });
    
  3338.     }).toErrorDev(
    
  3339.       [
    
  3340.         'Warning: An error occurred during hydration. ' +
    
  3341.           'The server HTML was replaced with client content in <div>.',
    
  3342.         'Warning: Expected server HTML to contain a matching <span> in <div>.\n' +
    
  3343.           '    in span (at **)\n' +
    
  3344.           '    in App (at **)',
    
  3345.       ],
    
  3346.       {withoutStack: 1},
    
  3347.     );
    
  3348.     assertLog([
    
  3349.       'Log recoverable error: Hydration failed because the initial UI does not match what was rendered on the server.',
    
  3350.       'Log recoverable error: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  3351.     ]);
    
  3352. 
    
  3353.     // We show fallback state when mismatch happens at root
    
  3354.     expect(container.innerHTML).toEqual(
    
  3355.       '<div>Loading</div><span>client</span><div>Loading</div>',
    
  3356.     );
    
  3357. 
    
  3358.     await act(async () => {
    
  3359.       resolve();
    
  3360.       await promise;
    
  3361.     });
    
  3362. 
    
  3363.     expect(container.innerHTML).toEqual(
    
  3364.       '<div>1</div><span>client</span><div>2</div>',
    
  3365.     );
    
  3366.   });
    
  3367. 
    
  3368.   // @gate enableClientRenderFallbackOnTextMismatch
    
  3369.   it("falls back to client rendering when there's a text mismatch (direct text child)", async () => {
    
  3370.     function DirectTextChild({text}) {
    
  3371.       return <div>{text}</div>;
    
  3372.     }
    
  3373.     const container = document.createElement('div');
    
  3374.     container.innerHTML = ReactDOMServer.renderToString(
    
  3375.       <DirectTextChild text="good" />,
    
  3376.     );
    
  3377.     await expect(async () => {
    
  3378.       await act(() => {
    
  3379.         ReactDOMClient.hydrateRoot(container, <DirectTextChild text="bad" />, {
    
  3380.           onRecoverableError(error) {
    
  3381.             Scheduler.log(error.message);
    
  3382.           },
    
  3383.         });
    
  3384.       });
    
  3385.     }).toErrorDev(
    
  3386.       [
    
  3387.         'Text content did not match. Server: "good" Client: "bad"',
    
  3388.         'An error occurred during hydration. The server HTML was replaced with ' +
    
  3389.           'client content in <div>.',
    
  3390.       ],
    
  3391.       {withoutStack: 1},
    
  3392.     );
    
  3393.     assertLog([
    
  3394.       'Text content does not match server-rendered HTML.',
    
  3395.       'There was an error while hydrating. Because the error happened outside ' +
    
  3396.         'of a Suspense boundary, the entire root will switch to client rendering.',
    
  3397.     ]);
    
  3398.   });
    
  3399. 
    
  3400.   // @gate enableClientRenderFallbackOnTextMismatch
    
  3401.   it("falls back to client rendering when there's a text mismatch (text child with siblings)", async () => {
    
  3402.     function Sibling() {
    
  3403.       return 'Sibling';
    
  3404.     }
    
  3405. 
    
  3406.     function TextChildWithSibling({text}) {
    
  3407.       return (
    
  3408.         <div>
    
  3409.           <Sibling />
    
  3410.           {text}
    
  3411.         </div>
    
  3412.       );
    
  3413.     }
    
  3414.     const container2 = document.createElement('div');
    
  3415.     container2.innerHTML = ReactDOMServer.renderToString(
    
  3416.       <TextChildWithSibling text="good" />,
    
  3417.     );
    
  3418.     await expect(async () => {
    
  3419.       await act(() => {
    
  3420.         ReactDOMClient.hydrateRoot(
    
  3421.           container2,
    
  3422.           <TextChildWithSibling text="bad" />,
    
  3423.           {
    
  3424.             onRecoverableError(error) {
    
  3425.               Scheduler.log(error.message);
    
  3426.             },
    
  3427.           },
    
  3428.         );
    
  3429.       });
    
  3430.     }).toErrorDev(
    
  3431.       [
    
  3432.         'Text content did not match. Server: "good" Client: "bad"',
    
  3433.         'An error occurred during hydration. The server HTML was replaced with ' +
    
  3434.           'client content in <div>.',
    
  3435.       ],
    
  3436.       {withoutStack: 1},
    
  3437.     );
    
  3438.     assertLog([
    
  3439.       'Text content does not match server-rendered HTML.',
    
  3440.       'There was an error while hydrating. Because the error happened outside ' +
    
  3441.         'of a Suspense boundary, the entire root will switch to client rendering.',
    
  3442.     ]);
    
  3443.   });
    
  3444. });