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.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. import {createEventTarget} from 'dom-event-testing-library';
    
  13. 
    
  14. let React;
    
  15. let ReactDOM;
    
  16. let ReactDOMClient;
    
  17. let ReactDOMServer;
    
  18. let ReactFeatureFlags;
    
  19. let Scheduler;
    
  20. let Suspense;
    
  21. let act;
    
  22. let assertLog;
    
  23. let waitForAll;
    
  24. let waitFor;
    
  25. let waitForPaint;
    
  26. 
    
  27. let IdleEventPriority;
    
  28. let ContinuousEventPriority;
    
  29. 
    
  30. function dispatchMouseHoverEvent(to, from) {
    
  31.   if (!to) {
    
  32.     to = null;
    
  33.   }
    
  34.   if (!from) {
    
  35.     from = null;
    
  36.   }
    
  37.   if (from) {
    
  38.     const mouseOutEvent = document.createEvent('MouseEvents');
    
  39.     mouseOutEvent.initMouseEvent(
    
  40.       'mouseout',
    
  41.       true,
    
  42.       true,
    
  43.       window,
    
  44.       0,
    
  45.       50,
    
  46.       50,
    
  47.       50,
    
  48.       50,
    
  49.       false,
    
  50.       false,
    
  51.       false,
    
  52.       false,
    
  53.       0,
    
  54.       to,
    
  55.     );
    
  56.     from.dispatchEvent(mouseOutEvent);
    
  57.   }
    
  58.   if (to) {
    
  59.     const mouseOverEvent = document.createEvent('MouseEvents');
    
  60.     mouseOverEvent.initMouseEvent(
    
  61.       'mouseover',
    
  62.       true,
    
  63.       true,
    
  64.       window,
    
  65.       0,
    
  66.       50,
    
  67.       50,
    
  68.       50,
    
  69.       50,
    
  70.       false,
    
  71.       false,
    
  72.       false,
    
  73.       false,
    
  74.       0,
    
  75.       from,
    
  76.     );
    
  77.     to.dispatchEvent(mouseOverEvent);
    
  78.   }
    
  79. }
    
  80. 
    
  81. function dispatchClickEvent(target) {
    
  82.   const mouseOutEvent = document.createEvent('MouseEvents');
    
  83.   mouseOutEvent.initMouseEvent(
    
  84.     'click',
    
  85.     true,
    
  86.     true,
    
  87.     window,
    
  88.     0,
    
  89.     50,
    
  90.     50,
    
  91.     50,
    
  92.     50,
    
  93.     false,
    
  94.     false,
    
  95.     false,
    
  96.     false,
    
  97.     0,
    
  98.     target,
    
  99.   );
    
  100.   return target.dispatchEvent(mouseOutEvent);
    
  101. }
    
  102. 
    
  103. // TODO: There's currently no React DOM API to opt into Idle priority updates,
    
  104. // and there's no native DOM event that maps to idle priority, so this is a
    
  105. // temporary workaround. Need something like ReactDOM.unstable_IdleUpdates.
    
  106. function TODO_scheduleIdleDOMSchedulerTask(fn) {
    
  107.   ReactDOM.unstable_runWithPriority(IdleEventPriority, () => {
    
  108.     const prevEvent = window.event;
    
  109.     window.event = {type: 'message'};
    
  110.     try {
    
  111.       fn();
    
  112.     } finally {
    
  113.       window.event = prevEvent;
    
  114.     }
    
  115.   });
    
  116. }
    
  117. 
    
  118. function TODO_scheduleContinuousSchedulerTask(fn) {
    
  119.   ReactDOM.unstable_runWithPriority(ContinuousEventPriority, () => {
    
  120.     const prevEvent = window.event;
    
  121.     window.event = {type: 'message'};
    
  122.     try {
    
  123.       fn();
    
  124.     } finally {
    
  125.       window.event = prevEvent;
    
  126.     }
    
  127.   });
    
  128. }
    
  129. 
    
  130. describe('ReactDOMServerSelectiveHydration', () => {
    
  131.   beforeEach(() => {
    
  132.     jest.resetModules();
    
  133. 
    
  134.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  135.     ReactFeatureFlags.enableCreateEventHandleAPI = true;
    
  136.     React = require('react');
    
  137.     ReactDOM = require('react-dom');
    
  138.     ReactDOMClient = require('react-dom/client');
    
  139.     ReactDOMServer = require('react-dom/server');
    
  140.     act = require('internal-test-utils').act;
    
  141.     Scheduler = require('scheduler');
    
  142.     Suspense = React.Suspense;
    
  143. 
    
  144.     const InternalTestUtils = require('internal-test-utils');
    
  145.     assertLog = InternalTestUtils.assertLog;
    
  146.     waitForAll = InternalTestUtils.waitForAll;
    
  147.     waitFor = InternalTestUtils.waitFor;
    
  148.     waitForPaint = InternalTestUtils.waitForPaint;
    
  149. 
    
  150.     IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
    
  151.     ContinuousEventPriority =
    
  152.       require('react-reconciler/constants').ContinuousEventPriority;
    
  153.   });
    
  154. 
    
  155.   it('hydrates the target boundary synchronously during a click', async () => {
    
  156.     function Child({text}) {
    
  157.       Scheduler.log(text);
    
  158.       return (
    
  159.         <span
    
  160.           onClick={e => {
    
  161.             e.preventDefault();
    
  162.             Scheduler.log('Clicked ' + text);
    
  163.           }}>
    
  164.           {text}
    
  165.         </span>
    
  166.       );
    
  167.     }
    
  168. 
    
  169.     function App() {
    
  170.       Scheduler.log('App');
    
  171.       return (
    
  172.         <div>
    
  173.           <Suspense fallback="Loading...">
    
  174.             <Child text="A" />
    
  175.           </Suspense>
    
  176.           <Suspense fallback="Loading...">
    
  177.             <Child text="B" />
    
  178.           </Suspense>
    
  179.         </div>
    
  180.       );
    
  181.     }
    
  182. 
    
  183.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  184. 
    
  185.     assertLog(['App', 'A', 'B']);
    
  186. 
    
  187.     const container = document.createElement('div');
    
  188.     // We need this to be in the document since we'll dispatch events on it.
    
  189.     document.body.appendChild(container);
    
  190. 
    
  191.     container.innerHTML = finalHTML;
    
  192. 
    
  193.     const span = container.getElementsByTagName('span')[1];
    
  194. 
    
  195.     ReactDOMClient.hydrateRoot(container, <App />);
    
  196. 
    
  197.     // Nothing has been hydrated so far.
    
  198.     assertLog([]);
    
  199. 
    
  200.     // This should synchronously hydrate the root App and the second suspense
    
  201.     // boundary.
    
  202.     const result = dispatchClickEvent(span);
    
  203. 
    
  204.     // The event should have been canceled because we called preventDefault.
    
  205.     expect(result).toBe(false);
    
  206. 
    
  207.     // We rendered App, B and then invoked the event without rendering A.
    
  208.     assertLog(['App', 'B', 'Clicked B']);
    
  209. 
    
  210.     // After continuing the scheduler, we finally hydrate A.
    
  211.     await waitForAll(['A']);
    
  212. 
    
  213.     document.body.removeChild(container);
    
  214.   });
    
  215. 
    
  216.   it('hydrates at higher pri if sync did not work first time', async () => {
    
  217.     let suspend = false;
    
  218.     let resolve;
    
  219.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  220. 
    
  221.     function Child({text}) {
    
  222.       if ((text === 'A' || text === 'D') && suspend) {
    
  223.         throw promise;
    
  224.       }
    
  225.       Scheduler.log(text);
    
  226.       return (
    
  227.         <span
    
  228.           onClick={e => {
    
  229.             e.preventDefault();
    
  230.             Scheduler.log('Clicked ' + text);
    
  231.           }}>
    
  232.           {text}
    
  233.         </span>
    
  234.       );
    
  235.     }
    
  236. 
    
  237.     function App() {
    
  238.       Scheduler.log('App');
    
  239.       return (
    
  240.         <div>
    
  241.           <Suspense fallback="Loading...">
    
  242.             <Child text="A" />
    
  243.           </Suspense>
    
  244.           <Suspense fallback="Loading...">
    
  245.             <Child text="B" />
    
  246.           </Suspense>
    
  247.           <Suspense fallback="Loading...">
    
  248.             <Child text="C" />
    
  249.           </Suspense>
    
  250.           <Suspense fallback="Loading...">
    
  251.             <Child text="D" />
    
  252.           </Suspense>
    
  253.         </div>
    
  254.       );
    
  255.     }
    
  256. 
    
  257.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  258. 
    
  259.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  260. 
    
  261.     const container = document.createElement('div');
    
  262.     // We need this to be in the document since we'll dispatch events on it.
    
  263.     document.body.appendChild(container);
    
  264. 
    
  265.     container.innerHTML = finalHTML;
    
  266. 
    
  267.     const spanD = container.getElementsByTagName('span')[3];
    
  268. 
    
  269.     suspend = true;
    
  270. 
    
  271.     // A and D will be suspended. We'll click on D which should take
    
  272.     // priority, after we unsuspend.
    
  273.     ReactDOMClient.hydrateRoot(container, <App />);
    
  274. 
    
  275.     // Nothing has been hydrated so far.
    
  276.     assertLog([]);
    
  277. 
    
  278.     // This click target cannot be hydrated yet because it's suspended.
    
  279.     await act(() => {
    
  280.       const result = dispatchClickEvent(spanD);
    
  281.       expect(result).toBe(true);
    
  282.     });
    
  283.     assertLog([
    
  284.       'App',
    
  285.       // Continuing rendering will render B next.
    
  286.       'B',
    
  287.       'C',
    
  288.     ]);
    
  289. 
    
  290.     await act(async () => {
    
  291.       suspend = false;
    
  292.       resolve();
    
  293.       await promise;
    
  294.     });
    
  295. 
    
  296.     assertLog(['D', 'A']);
    
  297. 
    
  298.     document.body.removeChild(container);
    
  299.   });
    
  300. 
    
  301.   it('hydrates at higher pri for secondary discrete events', async () => {
    
  302.     let suspend = false;
    
  303.     let resolve;
    
  304.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  305. 
    
  306.     function Child({text}) {
    
  307.       if ((text === 'A' || text === 'D') && suspend) {
    
  308.         throw promise;
    
  309.       }
    
  310.       Scheduler.log(text);
    
  311.       return (
    
  312.         <span
    
  313.           onClick={e => {
    
  314.             e.preventDefault();
    
  315.             Scheduler.log('Clicked ' + text);
    
  316.           }}>
    
  317.           {text}
    
  318.         </span>
    
  319.       );
    
  320.     }
    
  321. 
    
  322.     function App() {
    
  323.       Scheduler.log('App');
    
  324.       return (
    
  325.         <div>
    
  326.           <Suspense fallback="Loading...">
    
  327.             <Child text="A" />
    
  328.           </Suspense>
    
  329.           <Suspense fallback="Loading...">
    
  330.             <Child text="B" />
    
  331.           </Suspense>
    
  332.           <Suspense fallback="Loading...">
    
  333.             <Child text="C" />
    
  334.           </Suspense>
    
  335.           <Suspense fallback="Loading...">
    
  336.             <Child text="D" />
    
  337.           </Suspense>
    
  338.         </div>
    
  339.       );
    
  340.     }
    
  341. 
    
  342.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  343. 
    
  344.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  345. 
    
  346.     const container = document.createElement('div');
    
  347.     // We need this to be in the document since we'll dispatch events on it.
    
  348.     document.body.appendChild(container);
    
  349. 
    
  350.     container.innerHTML = finalHTML;
    
  351. 
    
  352.     const spanA = container.getElementsByTagName('span')[0];
    
  353.     const spanC = container.getElementsByTagName('span')[2];
    
  354.     const spanD = container.getElementsByTagName('span')[3];
    
  355. 
    
  356.     suspend = true;
    
  357. 
    
  358.     // A and D will be suspended. We'll click on D which should take
    
  359.     // priority, after we unsuspend.
    
  360.     ReactDOMClient.hydrateRoot(container, <App />);
    
  361. 
    
  362.     // Nothing has been hydrated so far.
    
  363.     assertLog([]);
    
  364. 
    
  365.     // This click target cannot be hydrated yet because the first is Suspended.
    
  366.     dispatchClickEvent(spanA);
    
  367.     dispatchClickEvent(spanC);
    
  368.     dispatchClickEvent(spanD);
    
  369. 
    
  370.     assertLog(['App', 'C', 'Clicked C']);
    
  371. 
    
  372.     await act(async () => {
    
  373.       suspend = false;
    
  374.       resolve();
    
  375.       await promise;
    
  376.     });
    
  377. 
    
  378.     assertLog([
    
  379.       'A',
    
  380.       'D',
    
  381.       // B should render last since it wasn't clicked.
    
  382.       'B',
    
  383.     ]);
    
  384. 
    
  385.     document.body.removeChild(container);
    
  386.   });
    
  387. 
    
  388.   // @gate www
    
  389.   it('hydrates the target boundary synchronously during a click (createEventHandle)', async () => {
    
  390.     const setClick = ReactDOM.unstable_createEventHandle('click');
    
  391.     let isServerRendering = true;
    
  392. 
    
  393.     function Child({text}) {
    
  394.       const ref = React.useRef(null);
    
  395.       Scheduler.log(text);
    
  396.       if (!isServerRendering) {
    
  397.         React.useLayoutEffect(() => {
    
  398.           return setClick(ref.current, () => {
    
  399.             Scheduler.log('Clicked ' + text);
    
  400.           });
    
  401.         });
    
  402.       }
    
  403. 
    
  404.       return <span ref={ref}>{text}</span>;
    
  405.     }
    
  406. 
    
  407.     function App() {
    
  408.       Scheduler.log('App');
    
  409.       return (
    
  410.         <div>
    
  411.           <Suspense fallback="Loading...">
    
  412.             <Child text="A" />
    
  413.           </Suspense>
    
  414.           <Suspense fallback="Loading...">
    
  415.             <Child text="B" />
    
  416.           </Suspense>
    
  417.         </div>
    
  418.       );
    
  419.     }
    
  420. 
    
  421.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  422. 
    
  423.     assertLog(['App', 'A', 'B']);
    
  424. 
    
  425.     const container = document.createElement('div');
    
  426.     // We need this to be in the document since we'll dispatch events on it.
    
  427.     document.body.appendChild(container);
    
  428. 
    
  429.     container.innerHTML = finalHTML;
    
  430. 
    
  431.     isServerRendering = false;
    
  432. 
    
  433.     ReactDOMClient.hydrateRoot(container, <App />);
    
  434. 
    
  435.     // Nothing has been hydrated so far.
    
  436.     assertLog([]);
    
  437. 
    
  438.     const span = container.getElementsByTagName('span')[1];
    
  439. 
    
  440.     const target = createEventTarget(span);
    
  441. 
    
  442.     // This should synchronously hydrate the root App and the second suspense
    
  443.     // boundary.
    
  444.     target.virtualclick();
    
  445. 
    
  446.     // We rendered App, B and then invoked the event without rendering A.
    
  447.     assertLog(['App', 'B', 'Clicked B']);
    
  448. 
    
  449.     // After continuing the scheduler, we finally hydrate A.
    
  450.     await waitForAll(['A']);
    
  451. 
    
  452.     document.body.removeChild(container);
    
  453.   });
    
  454. 
    
  455.   // @gate www
    
  456.   it('hydrates at higher pri if sync did not work first time (createEventHandle)', async () => {
    
  457.     let suspend = false;
    
  458.     let isServerRendering = true;
    
  459.     let resolve;
    
  460.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  461.     const setClick = ReactDOM.unstable_createEventHandle('click');
    
  462. 
    
  463.     function Child({text}) {
    
  464.       const ref = React.useRef(null);
    
  465.       if ((text === 'A' || text === 'D') && suspend) {
    
  466.         throw promise;
    
  467.       }
    
  468.       Scheduler.log(text);
    
  469. 
    
  470.       if (!isServerRendering) {
    
  471.         React.useLayoutEffect(() => {
    
  472.           return setClick(ref.current, () => {
    
  473.             Scheduler.log('Clicked ' + text);
    
  474.           });
    
  475.         });
    
  476.       }
    
  477. 
    
  478.       return <span ref={ref}>{text}</span>;
    
  479.     }
    
  480. 
    
  481.     function App() {
    
  482.       Scheduler.log('App');
    
  483.       return (
    
  484.         <div>
    
  485.           <Suspense fallback="Loading...">
    
  486.             <Child text="A" />
    
  487.           </Suspense>
    
  488.           <Suspense fallback="Loading...">
    
  489.             <Child text="B" />
    
  490.           </Suspense>
    
  491.           <Suspense fallback="Loading...">
    
  492.             <Child text="C" />
    
  493.           </Suspense>
    
  494.           <Suspense fallback="Loading...">
    
  495.             <Child text="D" />
    
  496.           </Suspense>
    
  497.         </div>
    
  498.       );
    
  499.     }
    
  500. 
    
  501.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  502. 
    
  503.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  504. 
    
  505.     const container = document.createElement('div');
    
  506.     // We need this to be in the document since we'll dispatch events on it.
    
  507.     document.body.appendChild(container);
    
  508. 
    
  509.     container.innerHTML = finalHTML;
    
  510. 
    
  511.     const spanD = container.getElementsByTagName('span')[3];
    
  512. 
    
  513.     suspend = true;
    
  514.     isServerRendering = false;
    
  515. 
    
  516.     // A and D will be suspended. We'll click on D which should take
    
  517.     // priority, after we unsuspend.
    
  518.     ReactDOMClient.hydrateRoot(container, <App />);
    
  519. 
    
  520.     // Nothing has been hydrated so far.
    
  521.     assertLog([]);
    
  522. 
    
  523.     // Continuing rendering will render B next.
    
  524.     await act(() => {
    
  525.       const target = createEventTarget(spanD);
    
  526.       target.virtualclick();
    
  527.     });
    
  528.     assertLog(['App', 'B', 'C']);
    
  529. 
    
  530.     // After the click, we should prioritize D and the Click first,
    
  531.     // and only after that render A and C.
    
  532.     await act(async () => {
    
  533.       suspend = false;
    
  534.       resolve();
    
  535.       await promise;
    
  536.     });
    
  537. 
    
  538.     // no replay
    
  539.     assertLog(['D', 'A']);
    
  540. 
    
  541.     document.body.removeChild(container);
    
  542.   });
    
  543. 
    
  544.   // @gate www
    
  545.   it('hydrates at higher pri for secondary discrete events (createEventHandle)', async () => {
    
  546.     const setClick = ReactDOM.unstable_createEventHandle('click');
    
  547.     let suspend = false;
    
  548.     let isServerRendering = true;
    
  549.     let resolve;
    
  550.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  551. 
    
  552.     function Child({text}) {
    
  553.       const ref = React.useRef(null);
    
  554.       if ((text === 'A' || text === 'D') && suspend) {
    
  555.         throw promise;
    
  556.       }
    
  557.       Scheduler.log(text);
    
  558. 
    
  559.       if (!isServerRendering) {
    
  560.         React.useLayoutEffect(() => {
    
  561.           return setClick(ref.current, () => {
    
  562.             Scheduler.log('Clicked ' + text);
    
  563.           });
    
  564.         });
    
  565.       }
    
  566.       return <span ref={ref}>{text}</span>;
    
  567.     }
    
  568. 
    
  569.     function App() {
    
  570.       Scheduler.log('App');
    
  571.       return (
    
  572.         <div>
    
  573.           <Suspense fallback="Loading...">
    
  574.             <Child text="A" />
    
  575.           </Suspense>
    
  576.           <Suspense fallback="Loading...">
    
  577.             <Child text="B" />
    
  578.           </Suspense>
    
  579.           <Suspense fallback="Loading...">
    
  580.             <Child text="C" />
    
  581.           </Suspense>
    
  582.           <Suspense fallback="Loading...">
    
  583.             <Child text="D" />
    
  584.           </Suspense>
    
  585.         </div>
    
  586.       );
    
  587.     }
    
  588. 
    
  589.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  590. 
    
  591.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  592. 
    
  593.     const container = document.createElement('div');
    
  594.     // We need this to be in the document since we'll dispatch events on it.
    
  595.     document.body.appendChild(container);
    
  596. 
    
  597.     container.innerHTML = finalHTML;
    
  598. 
    
  599.     const spanA = container.getElementsByTagName('span')[0];
    
  600.     const spanC = container.getElementsByTagName('span')[2];
    
  601.     const spanD = container.getElementsByTagName('span')[3];
    
  602. 
    
  603.     suspend = true;
    
  604.     isServerRendering = false;
    
  605. 
    
  606.     // A and D will be suspended. We'll click on D which should take
    
  607.     // priority, after we unsuspend.
    
  608.     ReactDOMClient.hydrateRoot(container, <App />);
    
  609. 
    
  610.     // Nothing has been hydrated so far.
    
  611.     assertLog([]);
    
  612. 
    
  613.     // This click target cannot be hydrated yet because the first is Suspended.
    
  614.     createEventTarget(spanA).virtualclick();
    
  615.     createEventTarget(spanC).virtualclick();
    
  616.     createEventTarget(spanD).virtualclick();
    
  617. 
    
  618.     assertLog(['App', 'C', 'Clicked C']);
    
  619. 
    
  620.     await act(async () => {
    
  621.       suspend = false;
    
  622.       resolve();
    
  623.       await promise;
    
  624.     });
    
  625. 
    
  626.     assertLog([
    
  627.       'A',
    
  628.       'D',
    
  629.       // B should render last since it wasn't clicked.
    
  630.       'B',
    
  631.     ]);
    
  632. 
    
  633.     document.body.removeChild(container);
    
  634.   });
    
  635. 
    
  636.   it('hydrates the hovered targets as higher priority for continuous events', async () => {
    
  637.     let suspend = false;
    
  638.     let resolve;
    
  639.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  640.     function Child({text}) {
    
  641.       if ((text === 'A' || text === 'D') && suspend) {
    
  642.         throw promise;
    
  643.       }
    
  644.       Scheduler.log(text);
    
  645.       return (
    
  646.         <span
    
  647.           onClick={e => {
    
  648.             e.preventDefault();
    
  649.             Scheduler.log('Clicked ' + text);
    
  650.           }}
    
  651.           onMouseEnter={e => {
    
  652.             e.preventDefault();
    
  653.             Scheduler.log('Hover ' + text);
    
  654.           }}>
    
  655.           {text}
    
  656.         </span>
    
  657.       );
    
  658.     }
    
  659. 
    
  660.     function App() {
    
  661.       Scheduler.log('App');
    
  662.       return (
    
  663.         <div>
    
  664.           <Suspense fallback="Loading...">
    
  665.             <Child text="A" />
    
  666.           </Suspense>
    
  667.           <Suspense fallback="Loading...">
    
  668.             <Child text="B" />
    
  669.           </Suspense>
    
  670.           <Suspense fallback="Loading...">
    
  671.             <Child text="C" />
    
  672.           </Suspense>
    
  673.           <Suspense fallback="Loading...">
    
  674.             <Child text="D" />
    
  675.           </Suspense>
    
  676.         </div>
    
  677.       );
    
  678.     }
    
  679.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  680.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  681.     const container = document.createElement('div');
    
  682.     // We need this to be in the document since we'll dispatch events on it.
    
  683.     document.body.appendChild(container);
    
  684. 
    
  685.     container.innerHTML = finalHTML;
    
  686. 
    
  687.     const spanB = container.getElementsByTagName('span')[1];
    
  688.     const spanC = container.getElementsByTagName('span')[2];
    
  689.     const spanD = container.getElementsByTagName('span')[3];
    
  690. 
    
  691.     suspend = true;
    
  692. 
    
  693.     // A and D will be suspended. We'll click on D which should take
    
  694.     // priority, after we unsuspend.
    
  695.     ReactDOMClient.hydrateRoot(container, <App />);
    
  696. 
    
  697.     // Nothing has been hydrated so far.
    
  698.     assertLog([]);
    
  699. 
    
  700.     await act(() => {
    
  701.       // Click D
    
  702.       dispatchMouseHoverEvent(spanD, null);
    
  703.       dispatchClickEvent(spanD);
    
  704. 
    
  705.       // Hover over B and then C.
    
  706.       dispatchMouseHoverEvent(spanB, spanD);
    
  707.       dispatchMouseHoverEvent(spanC, spanB);
    
  708. 
    
  709.       assertLog(['App']);
    
  710. 
    
  711.       suspend = false;
    
  712.       resolve();
    
  713.     });
    
  714. 
    
  715.     // We should prioritize hydrating D first because we clicked it.
    
  716.     // but event isnt replayed
    
  717.     assertLog([
    
  718.       'D',
    
  719.       'B', // Ideally this should be later.
    
  720.       'C',
    
  721.       'Hover C',
    
  722.       'A',
    
  723.     ]);
    
  724. 
    
  725.     document.body.removeChild(container);
    
  726.   });
    
  727. 
    
  728.   it('replays capture phase for continuous events and respects stopPropagation', async () => {
    
  729.     let suspend = false;
    
  730.     let resolve;
    
  731.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  732. 
    
  733.     function Child({text}) {
    
  734.       if ((text === 'A' || text === 'D') && suspend) {
    
  735.         throw promise;
    
  736.       }
    
  737.       Scheduler.log(text);
    
  738.       return (
    
  739.         <span
    
  740.           id={text}
    
  741.           onClickCapture={e => {
    
  742.             e.preventDefault();
    
  743.             Scheduler.log('Capture Clicked ' + text);
    
  744.           }}
    
  745.           onClick={e => {
    
  746.             e.preventDefault();
    
  747.             Scheduler.log('Clicked ' + text);
    
  748.           }}
    
  749.           onMouseEnter={e => {
    
  750.             e.preventDefault();
    
  751.             Scheduler.log('Mouse Enter ' + text);
    
  752.           }}
    
  753.           onMouseOut={e => {
    
  754.             e.preventDefault();
    
  755.             Scheduler.log('Mouse Out ' + text);
    
  756.           }}
    
  757.           onMouseOutCapture={e => {
    
  758.             e.preventDefault();
    
  759.             e.stopPropagation();
    
  760.             Scheduler.log('Mouse Out Capture ' + text);
    
  761.           }}
    
  762.           onMouseOverCapture={e => {
    
  763.             e.preventDefault();
    
  764.             e.stopPropagation();
    
  765.             Scheduler.log('Mouse Over Capture ' + text);
    
  766.           }}
    
  767.           onMouseOver={e => {
    
  768.             e.preventDefault();
    
  769.             Scheduler.log('Mouse Over ' + text);
    
  770.           }}>
    
  771.           <div
    
  772.             onMouseOverCapture={e => {
    
  773.               e.preventDefault();
    
  774.               Scheduler.log('Mouse Over Capture Inner ' + text);
    
  775.             }}>
    
  776.             {text}
    
  777.           </div>
    
  778.         </span>
    
  779.       );
    
  780.     }
    
  781. 
    
  782.     function App() {
    
  783.       Scheduler.log('App');
    
  784.       return (
    
  785.         <div
    
  786.           onClickCapture={e => {
    
  787.             e.preventDefault();
    
  788.             Scheduler.log('Capture Clicked Parent');
    
  789.           }}
    
  790.           onMouseOverCapture={e => {
    
  791.             Scheduler.log('Mouse Over Capture Parent');
    
  792.           }}>
    
  793.           <Suspense fallback="Loading...">
    
  794.             <Child text="A" />
    
  795.           </Suspense>
    
  796.           <Suspense fallback="Loading...">
    
  797.             <Child text="B" />
    
  798.           </Suspense>
    
  799.           <Suspense fallback="Loading...">
    
  800.             <Child text="C" />
    
  801.           </Suspense>
    
  802.           <Suspense fallback="Loading...">
    
  803.             <Child text="D" />
    
  804.           </Suspense>
    
  805.         </div>
    
  806.       );
    
  807.     }
    
  808. 
    
  809.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  810. 
    
  811.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  812. 
    
  813.     const container = document.createElement('div');
    
  814.     // We need this to be in the document since we'll dispatch events on it.
    
  815.     document.body.appendChild(container);
    
  816. 
    
  817.     container.innerHTML = finalHTML;
    
  818. 
    
  819.     const spanB = document.getElementById('B').firstChild;
    
  820.     const spanC = document.getElementById('C').firstChild;
    
  821.     const spanD = document.getElementById('D').firstChild;
    
  822. 
    
  823.     suspend = true;
    
  824. 
    
  825.     // A and D will be suspended. We'll click on D which should take
    
  826.     // priority, after we unsuspend.
    
  827.     ReactDOMClient.hydrateRoot(container, <App />);
    
  828. 
    
  829.     // Nothing has been hydrated so far.
    
  830.     assertLog([]);
    
  831. 
    
  832.     await act(async () => {
    
  833.       // Click D
    
  834.       dispatchMouseHoverEvent(spanD, null);
    
  835.       dispatchClickEvent(spanD);
    
  836.       // Hover over B and then C.
    
  837.       dispatchMouseHoverEvent(spanB, spanD);
    
  838.       dispatchMouseHoverEvent(spanC, spanB);
    
  839. 
    
  840.       assertLog(['App']);
    
  841. 
    
  842.       suspend = false;
    
  843.       resolve();
    
  844.     });
    
  845. 
    
  846.     // We should prioritize hydrating D first because we clicked it.
    
  847.     // but event isnt replayed
    
  848.     assertLog([
    
  849.       'D',
    
  850.       'B', // Ideally this should be later.
    
  851.       'C',
    
  852.       // Mouse out events aren't replayed
    
  853.       // 'Mouse Out Capture B',
    
  854.       // 'Mouse Out B',
    
  855.       'Mouse Over Capture Parent',
    
  856.       'Mouse Over Capture C',
    
  857.       // Stop propagation stops these
    
  858.       // 'Mouse Over Capture Inner C',
    
  859.       // 'Mouse Over C',
    
  860.       'A',
    
  861.     ]);
    
  862. 
    
  863.     // This test shows existing quirk where stopPropagation on mouseout
    
  864.     // prevents mouseEnter from firing
    
  865.     dispatchMouseHoverEvent(spanC, spanB);
    
  866.     assertLog([
    
  867.       'Mouse Out Capture B',
    
  868.       // stopPropagation stops these
    
  869.       // 'Mouse Out B',
    
  870.       // 'Mouse Enter C',
    
  871.       'Mouse Over Capture Parent',
    
  872.       'Mouse Over Capture C',
    
  873.       // Stop propagation stops these
    
  874.       // 'Mouse Over Capture Inner C',
    
  875.       // 'Mouse Over C',
    
  876.     ]);
    
  877. 
    
  878.     document.body.removeChild(container);
    
  879.   });
    
  880. 
    
  881.   describe('can handle replaying events as part of multiple instances of React', () => {
    
  882.     let resolveInner;
    
  883.     let resolveOuter;
    
  884.     let innerPromise;
    
  885.     let outerPromise;
    
  886.     let OuterScheduler;
    
  887.     let InnerScheduler;
    
  888.     let innerDiv;
    
  889. 
    
  890.     let OuterTestUtils;
    
  891.     let InnerTestUtils;
    
  892. 
    
  893.     beforeEach(async () => {
    
  894.       document.body.innerHTML = '';
    
  895.       jest.resetModules();
    
  896.       let OuterReactDOMClient;
    
  897.       let InnerReactDOMClient;
    
  898. 
    
  899.       jest.isolateModules(() => {
    
  900.         OuterReactDOMClient = require('react-dom/client');
    
  901.         OuterScheduler = require('scheduler');
    
  902.         OuterTestUtils = require('internal-test-utils');
    
  903.       });
    
  904.       jest.isolateModules(() => {
    
  905.         InnerReactDOMClient = require('react-dom/client');
    
  906.         InnerScheduler = require('scheduler');
    
  907.         InnerTestUtils = require('internal-test-utils');
    
  908.       });
    
  909. 
    
  910.       expect(OuterReactDOMClient).not.toBe(InnerReactDOMClient);
    
  911.       expect(OuterScheduler).not.toBe(InnerScheduler);
    
  912. 
    
  913.       const outerContainer = document.createElement('div');
    
  914.       const innerContainer = document.createElement('div');
    
  915. 
    
  916.       let suspendOuter = false;
    
  917.       outerPromise = new Promise(res => {
    
  918.         resolveOuter = () => {
    
  919.           suspendOuter = false;
    
  920.           res();
    
  921.         };
    
  922.       });
    
  923. 
    
  924.       function Outer() {
    
  925.         if (suspendOuter) {
    
  926.           OuterScheduler.log('Suspend Outer');
    
  927.           throw outerPromise;
    
  928.         }
    
  929.         OuterScheduler.log('Outer');
    
  930.         const innerRoot = outerContainer.querySelector('#inner-root');
    
  931.         return (
    
  932.           <div
    
  933.             id="inner-root"
    
  934.             onMouseEnter={() => {
    
  935.               Scheduler.log('Outer Mouse Enter');
    
  936.             }}
    
  937.             dangerouslySetInnerHTML={{
    
  938.               __html: innerRoot ? innerRoot.innerHTML : '',
    
  939.             }}
    
  940.           />
    
  941.         );
    
  942.       }
    
  943.       const OuterApp = () => {
    
  944.         return (
    
  945.           <Suspense fallback={<div>Loading</div>}>
    
  946.             <Outer />
    
  947.           </Suspense>
    
  948.         );
    
  949.       };
    
  950. 
    
  951.       let suspendInner = false;
    
  952.       innerPromise = new Promise(res => {
    
  953.         resolveInner = () => {
    
  954.           suspendInner = false;
    
  955.           res();
    
  956.         };
    
  957.       });
    
  958.       function Inner() {
    
  959.         if (suspendInner) {
    
  960.           InnerScheduler.log('Suspend Inner');
    
  961.           throw innerPromise;
    
  962.         }
    
  963.         InnerScheduler.log('Inner');
    
  964.         return (
    
  965.           <div
    
  966.             id="inner"
    
  967.             onMouseEnter={() => {
    
  968.               Scheduler.log('Inner Mouse Enter');
    
  969.             }}
    
  970.           />
    
  971.         );
    
  972.       }
    
  973.       const InnerApp = () => {
    
  974.         return (
    
  975.           <Suspense fallback={<div>Loading</div>}>
    
  976.             <Inner />
    
  977.           </Suspense>
    
  978.         );
    
  979.       };
    
  980. 
    
  981.       document.body.appendChild(outerContainer);
    
  982.       const outerHTML = ReactDOMServer.renderToString(<OuterApp />);
    
  983.       outerContainer.innerHTML = outerHTML;
    
  984. 
    
  985.       const innerWrapper = document.querySelector('#inner-root');
    
  986.       innerWrapper.appendChild(innerContainer);
    
  987.       const innerHTML = ReactDOMServer.renderToString(<InnerApp />);
    
  988.       innerContainer.innerHTML = innerHTML;
    
  989. 
    
  990.       OuterTestUtils.assertLog(['Outer']);
    
  991.       InnerTestUtils.assertLog(['Inner']);
    
  992. 
    
  993.       suspendOuter = true;
    
  994.       suspendInner = true;
    
  995. 
    
  996.       await OuterTestUtils.act(() =>
    
  997.         OuterReactDOMClient.hydrateRoot(outerContainer, <OuterApp />),
    
  998.       );
    
  999.       await InnerTestUtils.act(() =>
    
  1000.         InnerReactDOMClient.hydrateRoot(innerContainer, <InnerApp />),
    
  1001.       );
    
  1002. 
    
  1003.       OuterTestUtils.assertLog(['Suspend Outer']);
    
  1004.       InnerTestUtils.assertLog(['Suspend Inner']);
    
  1005. 
    
  1006.       innerDiv = document.querySelector('#inner');
    
  1007. 
    
  1008.       dispatchClickEvent(innerDiv);
    
  1009. 
    
  1010.       await act(() => {
    
  1011.         jest.runAllTimers();
    
  1012.         Scheduler.unstable_flushAllWithoutAsserting();
    
  1013.         OuterScheduler.unstable_flushAllWithoutAsserting();
    
  1014.         InnerScheduler.unstable_flushAllWithoutAsserting();
    
  1015.       });
    
  1016. 
    
  1017.       OuterTestUtils.assertLog(['Suspend Outer']);
    
  1018. 
    
  1019.       // InnerApp doesn't see the event because OuterApp calls stopPropagation in
    
  1020.       // capture phase since the event is blocked on suspended component
    
  1021.       InnerTestUtils.assertLog([]);
    
  1022. 
    
  1023.       assertLog([]);
    
  1024.     });
    
  1025.     afterEach(async () => {
    
  1026.       document.body.innerHTML = '';
    
  1027.     });
    
  1028. 
    
  1029.     it('Inner hydrates first then Outer', async () => {
    
  1030.       dispatchMouseHoverEvent(innerDiv);
    
  1031. 
    
  1032.       await InnerTestUtils.act(async () => {
    
  1033.         await OuterTestUtils.act(() => {
    
  1034.           resolveInner();
    
  1035.         });
    
  1036.       });
    
  1037. 
    
  1038.       OuterTestUtils.assertLog(['Suspend Outer']);
    
  1039.       // Inner App renders because it is unblocked
    
  1040.       InnerTestUtils.assertLog(['Inner']);
    
  1041.       // No event is replayed yet
    
  1042.       assertLog([]);
    
  1043. 
    
  1044.       dispatchMouseHoverEvent(innerDiv);
    
  1045.       OuterTestUtils.assertLog([]);
    
  1046.       InnerTestUtils.assertLog([]);
    
  1047.       // No event is replayed yet
    
  1048.       assertLog([]);
    
  1049. 
    
  1050.       await InnerTestUtils.act(async () => {
    
  1051.         await OuterTestUtils.act(() => {
    
  1052.           resolveOuter();
    
  1053. 
    
  1054.           // Nothing happens to inner app yet.
    
  1055.           // Its blocked on the outer app replaying the event
    
  1056.           InnerTestUtils.assertLog([]);
    
  1057.           // Outer hydrates and schedules Replay
    
  1058.           OuterTestUtils.waitFor(['Outer']);
    
  1059.           // No event is replayed yet
    
  1060.           assertLog([]);
    
  1061.         });
    
  1062.       });
    
  1063. 
    
  1064.       // fire scheduled Replay
    
  1065. 
    
  1066.       // First Inner Mouse Enter fires then Outer Mouse Enter
    
  1067.       assertLog(['Inner Mouse Enter', 'Outer Mouse Enter']);
    
  1068.     });
    
  1069. 
    
  1070.     it('Outer hydrates first then Inner', async () => {
    
  1071.       dispatchMouseHoverEvent(innerDiv);
    
  1072. 
    
  1073.       await act(async () => {
    
  1074.         resolveOuter();
    
  1075.         await outerPromise;
    
  1076.         Scheduler.unstable_flushAllWithoutAsserting();
    
  1077.         OuterScheduler.unstable_flushAllWithoutAsserting();
    
  1078.         InnerScheduler.unstable_flushAllWithoutAsserting();
    
  1079.       });
    
  1080. 
    
  1081.       // Outer resolves and scheduled replay
    
  1082.       OuterTestUtils.assertLog(['Outer']);
    
  1083.       // Inner App is still blocked
    
  1084.       InnerTestUtils.assertLog([]);
    
  1085. 
    
  1086.       // Replay outer event
    
  1087.       await act(() => {
    
  1088.         Scheduler.unstable_flushAllWithoutAsserting();
    
  1089.         OuterScheduler.unstable_flushAllWithoutAsserting();
    
  1090.         InnerScheduler.unstable_flushAllWithoutAsserting();
    
  1091.       });
    
  1092. 
    
  1093.       // Inner is still blocked so when Outer replays the event in capture phase
    
  1094.       // inner ends up caling stopPropagation
    
  1095.       assertLog([]);
    
  1096.       OuterTestUtils.assertLog([]);
    
  1097.       InnerTestUtils.assertLog(['Suspend Inner']);
    
  1098. 
    
  1099.       dispatchMouseHoverEvent(innerDiv);
    
  1100.       OuterTestUtils.assertLog([]);
    
  1101.       InnerTestUtils.assertLog([]);
    
  1102.       assertLog([]);
    
  1103. 
    
  1104.       await act(async () => {
    
  1105.         resolveInner();
    
  1106.         await innerPromise;
    
  1107.         Scheduler.unstable_flushAllWithoutAsserting();
    
  1108.         OuterScheduler.unstable_flushAllWithoutAsserting();
    
  1109.         InnerScheduler.unstable_flushAllWithoutAsserting();
    
  1110.       });
    
  1111. 
    
  1112.       // Inner hydrates
    
  1113.       InnerTestUtils.assertLog(['Inner']);
    
  1114.       // Outer was hydrated earlier
    
  1115.       OuterTestUtils.assertLog([]);
    
  1116. 
    
  1117.       await act(() => {
    
  1118.         Scheduler.unstable_flushAllWithoutAsserting();
    
  1119.         OuterScheduler.unstable_flushAllWithoutAsserting();
    
  1120.         InnerScheduler.unstable_flushAllWithoutAsserting();
    
  1121.       });
    
  1122. 
    
  1123.       // First Inner Mouse Enter fires then Outer Mouse Enter
    
  1124.       assertLog(['Inner Mouse Enter', 'Outer Mouse Enter']);
    
  1125.     });
    
  1126.   });
    
  1127. 
    
  1128.   it('replays event with null target when tree is dismounted', async () => {
    
  1129.     let suspend = false;
    
  1130.     let resolve;
    
  1131.     const promise = new Promise(resolvePromise => {
    
  1132.       resolve = () => {
    
  1133.         suspend = false;
    
  1134.         resolvePromise();
    
  1135.       };
    
  1136.     });
    
  1137. 
    
  1138.     function Child() {
    
  1139.       if (suspend) {
    
  1140.         throw promise;
    
  1141.       }
    
  1142.       Scheduler.log('Child');
    
  1143.       return (
    
  1144.         <div
    
  1145.           onMouseOver={() => {
    
  1146.             Scheduler.log('on mouse over');
    
  1147.           }}>
    
  1148.           Child
    
  1149.         </div>
    
  1150.       );
    
  1151.     }
    
  1152. 
    
  1153.     function App() {
    
  1154.       return (
    
  1155.         <Suspense>
    
  1156.           <Child />
    
  1157.         </Suspense>
    
  1158.       );
    
  1159.     }
    
  1160. 
    
  1161.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1162.     assertLog(['Child']);
    
  1163. 
    
  1164.     const container = document.createElement('div');
    
  1165. 
    
  1166.     document.body.appendChild(container);
    
  1167.     container.innerHTML = finalHTML;
    
  1168.     suspend = true;
    
  1169. 
    
  1170.     ReactDOMClient.hydrateRoot(container, <App />);
    
  1171. 
    
  1172.     const childDiv = container.firstElementChild;
    
  1173. 
    
  1174.     await act(async () => {
    
  1175.       dispatchMouseHoverEvent(childDiv);
    
  1176. 
    
  1177.       // Not hydrated so event is saved for replay and stopPropagation is called
    
  1178.       assertLog([]);
    
  1179. 
    
  1180.       resolve();
    
  1181.       await waitFor(['Child']);
    
  1182. 
    
  1183.       ReactDOM.flushSync(() => {
    
  1184.         container.removeChild(childDiv);
    
  1185. 
    
  1186.         const container2 = document.createElement('div');
    
  1187.         container2.addEventListener('mouseover', () => {
    
  1188.           Scheduler.log('container2 mouse over');
    
  1189.         });
    
  1190.         container2.appendChild(childDiv);
    
  1191.       });
    
  1192.     });
    
  1193. 
    
  1194.     // Even though the tree is remove the event is still dispatched with native event handler
    
  1195.     // on the container firing.
    
  1196.     assertLog(['container2 mouse over']);
    
  1197. 
    
  1198.     document.body.removeChild(container);
    
  1199.   });
    
  1200. 
    
  1201.   it('hydrates the last target path first for continuous events', async () => {
    
  1202.     let suspend = false;
    
  1203.     let resolve;
    
  1204.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1205. 
    
  1206.     function Child({text}) {
    
  1207.       if ((text === 'A' || text === 'D') && suspend) {
    
  1208.         throw promise;
    
  1209.       }
    
  1210.       Scheduler.log(text);
    
  1211.       return (
    
  1212.         <span
    
  1213.           onMouseEnter={e => {
    
  1214.             e.preventDefault();
    
  1215.             Scheduler.log('Hover ' + text);
    
  1216.           }}>
    
  1217.           {text}
    
  1218.         </span>
    
  1219.       );
    
  1220.     }
    
  1221. 
    
  1222.     function App() {
    
  1223.       Scheduler.log('App');
    
  1224.       return (
    
  1225.         <div>
    
  1226.           <Suspense fallback="Loading...">
    
  1227.             <Child text="A" />
    
  1228.           </Suspense>
    
  1229.           <Suspense fallback="Loading...">
    
  1230.             <div>
    
  1231.               <Suspense fallback="Loading...">
    
  1232.                 <Child text="B" />
    
  1233.               </Suspense>
    
  1234.             </div>
    
  1235.             <Child text="C" />
    
  1236.           </Suspense>
    
  1237.           <Suspense fallback="Loading...">
    
  1238.             <Child text="D" />
    
  1239.           </Suspense>
    
  1240.         </div>
    
  1241.       );
    
  1242.     }
    
  1243. 
    
  1244.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1245. 
    
  1246.     assertLog(['App', 'A', 'B', 'C', 'D']);
    
  1247. 
    
  1248.     const container = document.createElement('div');
    
  1249.     // We need this to be in the document since we'll dispatch events on it.
    
  1250.     document.body.appendChild(container);
    
  1251. 
    
  1252.     container.innerHTML = finalHTML;
    
  1253. 
    
  1254.     const spanB = container.getElementsByTagName('span')[1];
    
  1255.     const spanC = container.getElementsByTagName('span')[2];
    
  1256.     const spanD = container.getElementsByTagName('span')[3];
    
  1257. 
    
  1258.     suspend = true;
    
  1259. 
    
  1260.     // A and D will be suspended. We'll click on D which should take
    
  1261.     // priority, after we unsuspend.
    
  1262.     ReactDOMClient.hydrateRoot(container, <App />);
    
  1263. 
    
  1264.     // Nothing has been hydrated so far.
    
  1265.     assertLog([]);
    
  1266. 
    
  1267.     // Hover over B and then C.
    
  1268.     dispatchMouseHoverEvent(spanB, spanD);
    
  1269.     dispatchMouseHoverEvent(spanC, spanB);
    
  1270. 
    
  1271.     await act(async () => {
    
  1272.       suspend = false;
    
  1273.       resolve();
    
  1274.       await promise;
    
  1275.     });
    
  1276. 
    
  1277.     // We should prioritize hydrating D first because we clicked it.
    
  1278.     // Next we should hydrate C since that's the current hover target.
    
  1279.     // Next it doesn't matter if we hydrate A or B first but as an
    
  1280.     // implementation detail we're currently hydrating B first since
    
  1281.     // we at one point hovered over it and we never deprioritized it.
    
  1282.     assertLog(['App', 'C', 'Hover C', 'A', 'B', 'D']);
    
  1283. 
    
  1284.     document.body.removeChild(container);
    
  1285.   });
    
  1286. 
    
  1287.   it('hydrates the last explicitly hydrated target at higher priority', async () => {
    
  1288.     function Child({text}) {
    
  1289.       Scheduler.log(text);
    
  1290.       return <span>{text}</span>;
    
  1291.     }
    
  1292. 
    
  1293.     function App() {
    
  1294.       Scheduler.log('App');
    
  1295.       return (
    
  1296.         <div>
    
  1297.           <Suspense fallback="Loading...">
    
  1298.             <Child text="A" />
    
  1299.           </Suspense>
    
  1300.           <Suspense fallback="Loading...">
    
  1301.             <Child text="B" />
    
  1302.           </Suspense>
    
  1303.           <Suspense fallback="Loading...">
    
  1304.             <Child text="C" />
    
  1305.           </Suspense>
    
  1306.         </div>
    
  1307.       );
    
  1308.     }
    
  1309. 
    
  1310.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1311. 
    
  1312.     assertLog(['App', 'A', 'B', 'C']);
    
  1313. 
    
  1314.     const container = document.createElement('div');
    
  1315.     container.innerHTML = finalHTML;
    
  1316. 
    
  1317.     const spanB = container.getElementsByTagName('span')[1];
    
  1318.     const spanC = container.getElementsByTagName('span')[2];
    
  1319. 
    
  1320.     const root = ReactDOMClient.hydrateRoot(container, <App />);
    
  1321. 
    
  1322.     // Nothing has been hydrated so far.
    
  1323.     assertLog([]);
    
  1324. 
    
  1325.     // Increase priority of B and then C.
    
  1326.     root.unstable_scheduleHydration(spanB);
    
  1327.     root.unstable_scheduleHydration(spanC);
    
  1328. 
    
  1329.     // We should prioritize hydrating C first because the last added
    
  1330.     // gets highest priority followed by the next added.
    
  1331.     await waitForAll(['App', 'C', 'B', 'A']);
    
  1332.   });
    
  1333. 
    
  1334.   // @gate experimental || www
    
  1335.   it('hydrates before an update even if hydration moves away from it', async () => {
    
  1336.     function Child({text}) {
    
  1337.       Scheduler.log(text);
    
  1338.       return <span>{text}</span>;
    
  1339.     }
    
  1340.     const ChildWithBoundary = React.memo(function ({text}) {
    
  1341.       return (
    
  1342.         <Suspense fallback="Loading...">
    
  1343.           <Child text={text} />
    
  1344.           <Child text={text.toLowerCase()} />
    
  1345.         </Suspense>
    
  1346.       );
    
  1347.     });
    
  1348. 
    
  1349.     function App({a}) {
    
  1350.       Scheduler.log('App');
    
  1351.       React.useEffect(() => {
    
  1352.         Scheduler.log('Commit');
    
  1353.       });
    
  1354.       return (
    
  1355.         <div>
    
  1356.           <ChildWithBoundary text={a} />
    
  1357.           <ChildWithBoundary text="B" />
    
  1358.           <ChildWithBoundary text="C" />
    
  1359.         </div>
    
  1360.       );
    
  1361.     }
    
  1362. 
    
  1363.     const finalHTML = ReactDOMServer.renderToString(<App a="A" />);
    
  1364. 
    
  1365.     assertLog(['App', 'A', 'a', 'B', 'b', 'C', 'c']);
    
  1366. 
    
  1367.     const container = document.createElement('div');
    
  1368.     container.innerHTML = finalHTML;
    
  1369. 
    
  1370.     // We need this to be in the document since we'll dispatch events on it.
    
  1371.     document.body.appendChild(container);
    
  1372. 
    
  1373.     const spanA = container.getElementsByTagName('span')[0];
    
  1374.     const spanB = container.getElementsByTagName('span')[2];
    
  1375.     const spanC = container.getElementsByTagName('span')[4];
    
  1376. 
    
  1377.     await act(async () => {
    
  1378.       const root = ReactDOMClient.hydrateRoot(container, <App a="A" />);
    
  1379.       // Hydrate the shell.
    
  1380.       await waitFor(['App', 'Commit']);
    
  1381. 
    
  1382.       // Render an update at Idle priority that needs to update A.
    
  1383. 
    
  1384.       TODO_scheduleIdleDOMSchedulerTask(() => {
    
  1385.         root.render(<App a="AA" />);
    
  1386.       });
    
  1387. 
    
  1388.       // Start rendering. This will force the first boundary to hydrate
    
  1389.       // by scheduling it at one higher pri than Idle.
    
  1390.       await waitFor([
    
  1391.         'App',
    
  1392. 
    
  1393.         // Start hydrating A
    
  1394.         'A',
    
  1395.       ]);
    
  1396. 
    
  1397.       // Hover over A which (could) schedule at one higher pri than Idle.
    
  1398.       dispatchMouseHoverEvent(spanA, null);
    
  1399. 
    
  1400.       // Before, we're done we now switch to hover over B.
    
  1401.       // This is meant to test that this doesn't cause us to forget that
    
  1402.       // we still have to hydrate A. The first boundary.
    
  1403.       // This also tests that we don't do the -1 down-prioritization of
    
  1404.       // continuous hover events because that would decrease its priority
    
  1405.       // to Idle.
    
  1406.       dispatchMouseHoverEvent(spanB, spanA);
    
  1407. 
    
  1408.       // Also click C to prioritize that even higher which resets the
    
  1409.       // priority levels.
    
  1410.       dispatchClickEvent(spanC);
    
  1411. 
    
  1412.       assertLog([
    
  1413.         // Hydrate C first since we clicked it.
    
  1414.         'C',
    
  1415.         'c',
    
  1416.       ]);
    
  1417. 
    
  1418.       await waitForAll([
    
  1419.         // Finish hydration of A since we forced it to hydrate.
    
  1420.         'A',
    
  1421.         'a',
    
  1422.         // Also, hydrate B since we hovered over it.
    
  1423.         // It's not important which one comes first. A or B.
    
  1424.         // As long as they both happen before the Idle update.
    
  1425.         'B',
    
  1426.         'b',
    
  1427.         // Begin the Idle update again.
    
  1428.         'App',
    
  1429.         'AA',
    
  1430.         'aa',
    
  1431.         'Commit',
    
  1432.       ]);
    
  1433.     });
    
  1434. 
    
  1435.     const spanA2 = container.getElementsByTagName('span')[0];
    
  1436.     // This is supposed to have been hydrated, not replaced.
    
  1437.     expect(spanA).toBe(spanA2);
    
  1438. 
    
  1439.     document.body.removeChild(container);
    
  1440.   });
    
  1441. 
    
  1442.   it('fires capture event handlers and native events if content is hydratable during discrete event', async () => {
    
  1443.     spyOnDev(console, 'error');
    
  1444.     function Child({text}) {
    
  1445.       Scheduler.log(text);
    
  1446.       const ref = React.useRef();
    
  1447.       React.useLayoutEffect(() => {
    
  1448.         if (!ref.current) {
    
  1449.           return;
    
  1450.         }
    
  1451.         ref.current.onclick = () => {
    
  1452.           Scheduler.log('Native Click ' + text);
    
  1453.         };
    
  1454.       }, [text]);
    
  1455.       return (
    
  1456.         <span
    
  1457.           ref={ref}
    
  1458.           onClickCapture={() => {
    
  1459.             Scheduler.log('Capture Clicked ' + text);
    
  1460.           }}
    
  1461.           onClick={e => {
    
  1462.             Scheduler.log('Clicked ' + text);
    
  1463.           }}>
    
  1464.           {text}
    
  1465.         </span>
    
  1466.       );
    
  1467.     }
    
  1468. 
    
  1469.     function App() {
    
  1470.       Scheduler.log('App');
    
  1471.       return (
    
  1472.         <div>
    
  1473.           <Suspense fallback="Loading...">
    
  1474.             <Child text="A" />
    
  1475.           </Suspense>
    
  1476.           <Suspense fallback="Loading...">
    
  1477.             <Child text="B" />
    
  1478.           </Suspense>
    
  1479.         </div>
    
  1480.       );
    
  1481.     }
    
  1482. 
    
  1483.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1484. 
    
  1485.     assertLog(['App', 'A', 'B']);
    
  1486. 
    
  1487.     const container = document.createElement('div');
    
  1488.     // We need this to be in the document since we'll dispatch events on it.
    
  1489.     document.body.appendChild(container);
    
  1490. 
    
  1491.     container.innerHTML = finalHTML;
    
  1492. 
    
  1493.     const span = container.getElementsByTagName('span')[1];
    
  1494. 
    
  1495.     ReactDOMClient.hydrateRoot(container, <App />);
    
  1496. 
    
  1497.     // Nothing has been hydrated so far.
    
  1498.     assertLog([]);
    
  1499. 
    
  1500.     // This should synchronously hydrate the root App and the second suspense
    
  1501.     // boundary.
    
  1502.     dispatchClickEvent(span);
    
  1503. 
    
  1504.     // We rendered App, B and then invoked the event without rendering A.
    
  1505.     assertLog(['App', 'B', 'Capture Clicked B', 'Native Click B', 'Clicked B']);
    
  1506. 
    
  1507.     // After continuing the scheduler, we finally hydrate A.
    
  1508.     await waitForAll(['A']);
    
  1509. 
    
  1510.     document.body.removeChild(container);
    
  1511.   });
    
  1512. 
    
  1513.   it('does not propagate discrete event if it cannot be synchronously hydrated', async () => {
    
  1514.     let triggeredParent = false;
    
  1515.     let triggeredChild = false;
    
  1516.     let suspend = false;
    
  1517.     const promise = new Promise(() => {});
    
  1518.     function Child() {
    
  1519.       if (suspend) {
    
  1520.         throw promise;
    
  1521.       }
    
  1522.       Scheduler.log('Child');
    
  1523.       return (
    
  1524.         <span
    
  1525.           onClickCapture={e => {
    
  1526.             e.stopPropagation();
    
  1527.             triggeredChild = true;
    
  1528.           }}>
    
  1529.           Click me
    
  1530.         </span>
    
  1531.       );
    
  1532.     }
    
  1533.     function App() {
    
  1534.       const onClick = () => {
    
  1535.         triggeredParent = true;
    
  1536.       };
    
  1537.       Scheduler.log('App');
    
  1538.       return (
    
  1539.         <div
    
  1540.           ref={n => {
    
  1541.             if (n) n.onclick = onClick;
    
  1542.           }}
    
  1543.           onClick={onClick}>
    
  1544.           <Suspense fallback={null}>
    
  1545.             <Child />
    
  1546.           </Suspense>
    
  1547.         </div>
    
  1548.       );
    
  1549.     }
    
  1550.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1551. 
    
  1552.     assertLog(['App', 'Child']);
    
  1553. 
    
  1554.     const container = document.createElement('div');
    
  1555.     document.body.appendChild(container);
    
  1556.     container.innerHTML = finalHTML;
    
  1557. 
    
  1558.     suspend = true;
    
  1559. 
    
  1560.     ReactDOMClient.hydrateRoot(container, <App />);
    
  1561.     // Nothing has been hydrated so far.
    
  1562.     assertLog([]);
    
  1563. 
    
  1564.     const span = container.getElementsByTagName('span')[0];
    
  1565.     dispatchClickEvent(span);
    
  1566. 
    
  1567.     assertLog(['App']);
    
  1568. 
    
  1569.     dispatchClickEvent(span);
    
  1570. 
    
  1571.     expect(triggeredParent).toBe(false);
    
  1572.     expect(triggeredChild).toBe(false);
    
  1573.   });
    
  1574. 
    
  1575.   it('can attempt sync hydration if suspended root is still concurrently rendering', async () => {
    
  1576.     let suspend = false;
    
  1577.     let resolve;
    
  1578.     const promise = new Promise(resolvePromise => (resolve = resolvePromise));
    
  1579.     function Child({text}) {
    
  1580.       if (suspend) {
    
  1581.         throw promise;
    
  1582.       }
    
  1583.       Scheduler.log(text);
    
  1584.       return (
    
  1585.         <span
    
  1586.           onClick={e => {
    
  1587.             e.preventDefault();
    
  1588.             Scheduler.log('Clicked ' + text);
    
  1589.           }}>
    
  1590.           {text}
    
  1591.         </span>
    
  1592.       );
    
  1593.     }
    
  1594. 
    
  1595.     function App() {
    
  1596.       Scheduler.log('App');
    
  1597.       return (
    
  1598.         <div>
    
  1599.           <Child text="A" />
    
  1600.         </div>
    
  1601.       );
    
  1602.     }
    
  1603. 
    
  1604.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1605. 
    
  1606.     assertLog(['App', 'A']);
    
  1607. 
    
  1608.     const container = document.createElement('div');
    
  1609.     // We need this to be in the document since we'll dispatch events on it.
    
  1610.     document.body.appendChild(container);
    
  1611. 
    
  1612.     container.innerHTML = finalHTML;
    
  1613. 
    
  1614.     const span = container.getElementsByTagName('span')[0];
    
  1615. 
    
  1616.     // We suspend on the client.
    
  1617.     suspend = true;
    
  1618. 
    
  1619.     React.startTransition(() => {
    
  1620.       ReactDOMClient.hydrateRoot(container, <App />);
    
  1621.     });
    
  1622.     await waitFor(['App']);
    
  1623. 
    
  1624.     // This should attempt to synchronously hydrate the root, then pause
    
  1625.     // because it still suspended
    
  1626.     const result = dispatchClickEvent(span);
    
  1627.     assertLog(['App']);
    
  1628.     // The event should not have been cancelled because we didn't hydrate.
    
  1629.     expect(result).toBe(true);
    
  1630. 
    
  1631.     // Finish loading the data
    
  1632.     await act(async () => {
    
  1633.       suspend = false;
    
  1634.       await resolve();
    
  1635.     });
    
  1636. 
    
  1637.     // The app should have successfully hydrated and rendered
    
  1638.     assertLog(['App', 'A']);
    
  1639. 
    
  1640.     document.body.removeChild(container);
    
  1641.   });
    
  1642. 
    
  1643.   it('can force hydration in response to sync update', async () => {
    
  1644.     function Child({text}) {
    
  1645.       Scheduler.log(`Child ${text}`);
    
  1646.       return <span ref={ref => (spanRef = ref)}>{text}</span>;
    
  1647.     }
    
  1648.     function App({text}) {
    
  1649.       Scheduler.log(`App ${text}`);
    
  1650.       return (
    
  1651.         <div>
    
  1652.           <Suspense fallback={null}>
    
  1653.             <Child text={text} />
    
  1654.           </Suspense>
    
  1655.         </div>
    
  1656.       );
    
  1657.     }
    
  1658. 
    
  1659.     let spanRef;
    
  1660.     const finalHTML = ReactDOMServer.renderToString(<App text="A" />);
    
  1661.     assertLog(['App A', 'Child A']);
    
  1662.     const container = document.createElement('div');
    
  1663.     document.body.appendChild(container);
    
  1664.     container.innerHTML = finalHTML;
    
  1665.     const initialSpan = container.getElementsByTagName('span')[0];
    
  1666.     const root = ReactDOMClient.hydrateRoot(container, <App text="A" />);
    
  1667.     await waitForPaint(['App A']);
    
  1668. 
    
  1669.     await act(() => {
    
  1670.       ReactDOM.flushSync(() => {
    
  1671.         root.render(<App text="B" />);
    
  1672.       });
    
  1673.     });
    
  1674.     assertLog(['App B', 'Child A', 'App B', 'Child B']);
    
  1675.     expect(initialSpan).toBe(spanRef);
    
  1676.   });
    
  1677. 
    
  1678.   // @gate experimental || www
    
  1679.   it('can force hydration in response to continuous update', async () => {
    
  1680.     function Child({text}) {
    
  1681.       Scheduler.log(`Child ${text}`);
    
  1682.       return <span ref={ref => (spanRef = ref)}>{text}</span>;
    
  1683.     }
    
  1684.     function App({text}) {
    
  1685.       Scheduler.log(`App ${text}`);
    
  1686.       return (
    
  1687.         <div>
    
  1688.           <Suspense fallback={null}>
    
  1689.             <Child text={text} />
    
  1690.           </Suspense>
    
  1691.         </div>
    
  1692.       );
    
  1693.     }
    
  1694. 
    
  1695.     let spanRef;
    
  1696.     const finalHTML = ReactDOMServer.renderToString(<App text="A" />);
    
  1697.     assertLog(['App A', 'Child A']);
    
  1698.     const container = document.createElement('div');
    
  1699.     document.body.appendChild(container);
    
  1700.     container.innerHTML = finalHTML;
    
  1701.     const initialSpan = container.getElementsByTagName('span')[0];
    
  1702.     const root = ReactDOMClient.hydrateRoot(container, <App text="A" />);
    
  1703.     await waitForPaint(['App A']);
    
  1704. 
    
  1705.     await act(() => {
    
  1706.       TODO_scheduleContinuousSchedulerTask(() => {
    
  1707.         root.render(<App text="B" />);
    
  1708.       });
    
  1709.     });
    
  1710. 
    
  1711.     assertLog(['App B', 'Child A', 'App B', 'Child B']);
    
  1712.     expect(initialSpan).toBe(spanRef);
    
  1713.   });
    
  1714. 
    
  1715.   it('can force hydration in response to default update', async () => {
    
  1716.     function Child({text}) {
    
  1717.       Scheduler.log(`Child ${text}`);
    
  1718.       return <span ref={ref => (spanRef = ref)}>{text}</span>;
    
  1719.     }
    
  1720.     function App({text}) {
    
  1721.       Scheduler.log(`App ${text}`);
    
  1722.       return (
    
  1723.         <div>
    
  1724.           <Suspense fallback={null}>
    
  1725.             <Child text={text} />
    
  1726.           </Suspense>
    
  1727.         </div>
    
  1728.       );
    
  1729.     }
    
  1730. 
    
  1731.     let spanRef;
    
  1732.     const finalHTML = ReactDOMServer.renderToString(<App text="A" />);
    
  1733.     assertLog(['App A', 'Child A']);
    
  1734.     const container = document.createElement('div');
    
  1735.     document.body.appendChild(container);
    
  1736.     container.innerHTML = finalHTML;
    
  1737.     const initialSpan = container.getElementsByTagName('span')[0];
    
  1738.     const root = ReactDOMClient.hydrateRoot(container, <App text="A" />);
    
  1739.     await waitForPaint(['App A']);
    
  1740.     await act(() => {
    
  1741.       root.render(<App text="B" />);
    
  1742.     });
    
  1743.     assertLog(['App B', 'Child A', 'App B', 'Child B']);
    
  1744.     expect(initialSpan).toBe(spanRef);
    
  1745.   });
    
  1746. 
    
  1747.   // @gate experimental || www
    
  1748.   it('regression test: can unwind context on selective hydration interruption', async () => {
    
  1749.     const Context = React.createContext('DefaultContext');
    
  1750. 
    
  1751.     function ContextReader(props) {
    
  1752.       const value = React.useContext(Context);
    
  1753.       Scheduler.log(value);
    
  1754.       return null;
    
  1755.     }
    
  1756. 
    
  1757.     function Child({text}) {
    
  1758.       Scheduler.log(text);
    
  1759.       return <span>{text}</span>;
    
  1760.     }
    
  1761.     const ChildWithBoundary = React.memo(function ({text}) {
    
  1762.       return (
    
  1763.         <Suspense fallback="Loading...">
    
  1764.           <Child text={text} />
    
  1765.         </Suspense>
    
  1766.       );
    
  1767.     });
    
  1768. 
    
  1769.     function App({a}) {
    
  1770.       Scheduler.log('App');
    
  1771.       React.useEffect(() => {
    
  1772.         Scheduler.log('Commit');
    
  1773.       });
    
  1774.       return (
    
  1775.         <>
    
  1776.           <Context.Provider value="SiblingContext">
    
  1777.             <ChildWithBoundary text={a} />
    
  1778.           </Context.Provider>
    
  1779.           <ContextReader />
    
  1780.         </>
    
  1781.       );
    
  1782.     }
    
  1783.     const finalHTML = ReactDOMServer.renderToString(<App a="A" />);
    
  1784.     assertLog(['App', 'A', 'DefaultContext']);
    
  1785.     const container = document.createElement('div');
    
  1786.     container.innerHTML = finalHTML;
    
  1787.     document.body.appendChild(container);
    
  1788. 
    
  1789.     const spanA = container.getElementsByTagName('span')[0];
    
  1790. 
    
  1791.     await act(async () => {
    
  1792.       const root = ReactDOMClient.hydrateRoot(container, <App a="A" />);
    
  1793.       await waitFor(['App', 'DefaultContext', 'Commit']);
    
  1794. 
    
  1795.       TODO_scheduleIdleDOMSchedulerTask(() => {
    
  1796.         root.render(<App a="AA" />);
    
  1797.       });
    
  1798.       await waitFor(['App', 'A']);
    
  1799. 
    
  1800.       dispatchClickEvent(spanA);
    
  1801.       assertLog(['A']);
    
  1802.       await waitForAll(['App', 'AA', 'DefaultContext', 'Commit']);
    
  1803.     });
    
  1804.   });
    
  1805. 
    
  1806.   it('regression test: can unwind context on selective hydration interruption for sync updates', async () => {
    
  1807.     const Context = React.createContext('DefaultContext');
    
  1808. 
    
  1809.     function ContextReader(props) {
    
  1810.       const value = React.useContext(Context);
    
  1811.       Scheduler.log(value);
    
  1812.       return null;
    
  1813.     }
    
  1814. 
    
  1815.     function Child({text}) {
    
  1816.       Scheduler.log(text);
    
  1817.       return <span>{text}</span>;
    
  1818.     }
    
  1819.     const ChildWithBoundary = React.memo(function ({text}) {
    
  1820.       return (
    
  1821.         <Suspense fallback="Loading...">
    
  1822.           <Child text={text} />
    
  1823.         </Suspense>
    
  1824.       );
    
  1825.     });
    
  1826. 
    
  1827.     function App({a}) {
    
  1828.       Scheduler.log('App');
    
  1829.       React.useEffect(() => {
    
  1830.         Scheduler.log('Commit');
    
  1831.       });
    
  1832.       return (
    
  1833.         <>
    
  1834.           <Context.Provider value="SiblingContext">
    
  1835.             <ChildWithBoundary text={a} />
    
  1836.           </Context.Provider>
    
  1837.           <ContextReader />
    
  1838.         </>
    
  1839.       );
    
  1840.     }
    
  1841.     const finalHTML = ReactDOMServer.renderToString(<App a="A" />);
    
  1842.     assertLog(['App', 'A', 'DefaultContext']);
    
  1843.     const container = document.createElement('div');
    
  1844.     container.innerHTML = finalHTML;
    
  1845. 
    
  1846.     await act(async () => {
    
  1847.       const root = ReactDOMClient.hydrateRoot(container, <App a="A" />);
    
  1848.       await waitFor(['App', 'DefaultContext', 'Commit']);
    
  1849. 
    
  1850.       ReactDOM.flushSync(() => {
    
  1851.         root.render(<App a="AA" />);
    
  1852.       });
    
  1853.       assertLog(['App', 'A', 'App', 'AA', 'DefaultContext', 'Commit']);
    
  1854.     });
    
  1855.   });
    
  1856. 
    
  1857.   it('regression: selective hydration does not contribute to "maximum update limit" count', async () => {
    
  1858.     const outsideRef = React.createRef(null);
    
  1859.     const insideRef = React.createRef(null);
    
  1860.     function Child() {
    
  1861.       return (
    
  1862.         <Suspense fallback="Loading...">
    
  1863.           <div ref={insideRef} />
    
  1864.         </Suspense>
    
  1865.       );
    
  1866.     }
    
  1867. 
    
  1868.     let setIsMounted = false;
    
  1869.     function App() {
    
  1870.       const [isMounted, setState] = React.useState(false);
    
  1871.       setIsMounted = setState;
    
  1872. 
    
  1873.       const children = [];
    
  1874.       for (let i = 0; i < 100; i++) {
    
  1875.         children.push(<Child key={i} isMounted={isMounted} />);
    
  1876.       }
    
  1877. 
    
  1878.       return <div ref={outsideRef}>{children}</div>;
    
  1879.     }
    
  1880. 
    
  1881.     const finalHTML = ReactDOMServer.renderToString(<App />);
    
  1882.     const container = document.createElement('div');
    
  1883.     container.innerHTML = finalHTML;
    
  1884. 
    
  1885.     await act(async () => {
    
  1886.       ReactDOMClient.hydrateRoot(container, <App />);
    
  1887. 
    
  1888.       // Commit just the shell
    
  1889.       await waitForPaint([]);
    
  1890. 
    
  1891.       // Assert that the shell has hydrated, but not the children
    
  1892.       expect(outsideRef.current).not.toBe(null);
    
  1893.       expect(insideRef.current).toBe(null);
    
  1894. 
    
  1895.       // Update the shell synchronously. The update will flow into the children,
    
  1896.       // which haven't hydrated yet. This will trigger a cascade of commits
    
  1897.       // caused by selective hydration. However, since there's really only one
    
  1898.       // update, it should not be treated as an update loop.
    
  1899.       // NOTE: It's unfortunate that every sibling boundary is separately
    
  1900.       // committed in this case. We should be able to commit everything in a
    
  1901.       // render phase, which we could do if we had resumable context stacks.
    
  1902.       ReactDOM.flushSync(() => {
    
  1903.         setIsMounted(true);
    
  1904.       });
    
  1905.     });
    
  1906. 
    
  1907.     // Should have successfully hydrated with no errors.
    
  1908.     expect(insideRef.current).not.toBe(null);
    
  1909.   });
    
  1910. });