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 JSDOM;
    
  14. let Stream;
    
  15. let Scheduler;
    
  16. let React;
    
  17. let ReactDOMClient;
    
  18. let ReactDOMFizzServer;
    
  19. let document;
    
  20. let writable;
    
  21. let container;
    
  22. let buffer = '';
    
  23. let hasErrored = false;
    
  24. let fatalError = undefined;
    
  25. let waitForAll;
    
  26. 
    
  27. describe('ReactDOMFizzServerHydrationWarning', () => {
    
  28.   beforeEach(() => {
    
  29.     jest.resetModules();
    
  30.     JSDOM = require('jsdom').JSDOM;
    
  31.     Scheduler = require('scheduler');
    
  32.     React = require('react');
    
  33.     ReactDOMClient = require('react-dom/client');
    
  34.     ReactDOMFizzServer = require('react-dom/server');
    
  35.     Stream = require('stream');
    
  36. 
    
  37.     const InternalTestUtils = require('internal-test-utils');
    
  38.     waitForAll = InternalTestUtils.waitForAll;
    
  39. 
    
  40.     // Test Environment
    
  41.     const jsdom = new JSDOM(
    
  42.       '<!DOCTYPE html><html><head></head><body><div id="container">',
    
  43.       {
    
  44.         runScripts: 'dangerously',
    
  45.       },
    
  46.     );
    
  47.     document = jsdom.window.document;
    
  48.     container = document.getElementById('container');
    
  49. 
    
  50.     buffer = '';
    
  51.     hasErrored = false;
    
  52. 
    
  53.     writable = new Stream.PassThrough();
    
  54.     writable.setEncoding('utf8');
    
  55.     writable.on('data', chunk => {
    
  56.       buffer += chunk;
    
  57.     });
    
  58.     writable.on('error', error => {
    
  59.       hasErrored = true;
    
  60.       fatalError = error;
    
  61.     });
    
  62.   });
    
  63. 
    
  64.   async function act(callback) {
    
  65.     await callback();
    
  66.     // Await one turn around the event loop.
    
  67.     // This assumes that we'll flush everything we have so far.
    
  68.     await new Promise(resolve => {
    
  69.       setImmediate(resolve);
    
  70.     });
    
  71.     if (hasErrored) {
    
  72.       throw fatalError;
    
  73.     }
    
  74.     // JSDOM doesn't support stream HTML parser so we need to give it a proper fragment.
    
  75.     // We also want to execute any scripts that are embedded.
    
  76.     // We assume that we have now received a proper fragment of HTML.
    
  77.     const bufferedContent = buffer;
    
  78.     buffer = '';
    
  79.     const fakeBody = document.createElement('body');
    
  80.     fakeBody.innerHTML = bufferedContent;
    
  81.     while (fakeBody.firstChild) {
    
  82.       const node = fakeBody.firstChild;
    
  83.       if (node.nodeName === 'SCRIPT') {
    
  84.         const script = document.createElement('script');
    
  85.         script.textContent = node.textContent;
    
  86.         fakeBody.removeChild(node);
    
  87.         container.appendChild(script);
    
  88.       } else {
    
  89.         container.appendChild(node);
    
  90.       }
    
  91.     }
    
  92.   }
    
  93. 
    
  94.   function getVisibleChildren(element) {
    
  95.     const children = [];
    
  96.     let node = element.firstChild;
    
  97.     while (node) {
    
  98.       if (node.nodeType === 1) {
    
  99.         if (
    
  100.           node.tagName !== 'SCRIPT' &&
    
  101.           node.tagName !== 'TEMPLATE' &&
    
  102.           node.tagName !== 'template' &&
    
  103.           !node.hasAttribute('hidden') &&
    
  104.           !node.hasAttribute('aria-hidden')
    
  105.         ) {
    
  106.           const props = {};
    
  107.           const attributes = node.attributes;
    
  108.           for (let i = 0; i < attributes.length; i++) {
    
  109.             if (
    
  110.               attributes[i].name === 'id' &&
    
  111.               attributes[i].value.includes(':')
    
  112.             ) {
    
  113.               // We assume this is a React added ID that's a non-visual implementation detail.
    
  114.               continue;
    
  115.             }
    
  116.             props[attributes[i].name] = attributes[i].value;
    
  117.           }
    
  118.           props.children = getVisibleChildren(node);
    
  119.           children.push(React.createElement(node.tagName.toLowerCase(), props));
    
  120.         }
    
  121.       } else if (node.nodeType === 3) {
    
  122.         children.push(node.data);
    
  123.       }
    
  124.       node = node.nextSibling;
    
  125.     }
    
  126.     return children.length === 0
    
  127.       ? undefined
    
  128.       : children.length === 1
    
  129.       ? children[0]
    
  130.       : children;
    
  131.   }
    
  132. 
    
  133.   // @gate enableClientRenderFallbackOnTextMismatch
    
  134.   it('suppresses but does not fix text mismatches with suppressHydrationWarning', async () => {
    
  135.     function App({isClient}) {
    
  136.       return (
    
  137.         <div>
    
  138.           <span suppressHydrationWarning={true}>
    
  139.             {isClient ? 'Client Text' : 'Server Text'}
    
  140.           </span>
    
  141.           <span suppressHydrationWarning={true}>{isClient ? 2 : 1}</span>
    
  142.         </div>
    
  143.       );
    
  144.     }
    
  145.     await act(() => {
    
  146.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  147.         <App isClient={false} />,
    
  148.       );
    
  149.       pipe(writable);
    
  150.     });
    
  151.     expect(getVisibleChildren(container)).toEqual(
    
  152.       <div>
    
  153.         <span>Server Text</span>
    
  154.         <span>1</span>
    
  155.       </div>,
    
  156.     );
    
  157.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  158.       onRecoverableError(error) {
    
  159.         // Don't miss a hydration error. There should be none.
    
  160.         Scheduler.log(error.message);
    
  161.       },
    
  162.     });
    
  163.     await waitForAll([]);
    
  164.     // The text mismatch should be *silently* fixed. Even in production.
    
  165.     expect(getVisibleChildren(container)).toEqual(
    
  166.       <div>
    
  167.         <span>Server Text</span>
    
  168.         <span>1</span>
    
  169.       </div>,
    
  170.     );
    
  171.   });
    
  172. 
    
  173.   // @gate !enableClientRenderFallbackOnTextMismatch
    
  174.   it('suppresses and fixes text mismatches with suppressHydrationWarning', async () => {
    
  175.     function App({isClient}) {
    
  176.       return (
    
  177.         <div>
    
  178.           <span suppressHydrationWarning={true}>
    
  179.             {isClient ? 'Client Text' : 'Server Text'}
    
  180.           </span>
    
  181.           <span suppressHydrationWarning={true}>{isClient ? 2 : 1}</span>
    
  182.         </div>
    
  183.       );
    
  184.     }
    
  185.     await act(() => {
    
  186.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  187.         <App isClient={false} />,
    
  188.       );
    
  189.       pipe(writable);
    
  190.     });
    
  191.     expect(getVisibleChildren(container)).toEqual(
    
  192.       <div>
    
  193.         <span>Server Text</span>
    
  194.         <span>1</span>
    
  195.       </div>,
    
  196.     );
    
  197.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  198.       onRecoverableError(error) {
    
  199.         // Don't miss a hydration error. There should be none.
    
  200.         Scheduler.log(error.message);
    
  201.       },
    
  202.     });
    
  203.     await waitForAll([]);
    
  204.     // The text mismatch should be *silently* fixed. Even in production.
    
  205.     expect(getVisibleChildren(container)).toEqual(
    
  206.       <div>
    
  207.         <span>Client Text</span>
    
  208.         <span>2</span>
    
  209.       </div>,
    
  210.     );
    
  211.   });
    
  212. 
    
  213.   // @gate enableClientRenderFallbackOnTextMismatch
    
  214.   it('suppresses but does not fix multiple text node mismatches with suppressHydrationWarning', async () => {
    
  215.     function App({isClient}) {
    
  216.       return (
    
  217.         <div>
    
  218.           <span suppressHydrationWarning={true}>
    
  219.             {isClient ? 'Client1' : 'Server1'}
    
  220.             {isClient ? 'Client2' : 'Server2'}
    
  221.           </span>
    
  222.         </div>
    
  223.       );
    
  224.     }
    
  225.     await act(() => {
    
  226.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  227.         <App isClient={false} />,
    
  228.       );
    
  229.       pipe(writable);
    
  230.     });
    
  231.     expect(getVisibleChildren(container)).toEqual(
    
  232.       <div>
    
  233.         <span>
    
  234.           {'Server1'}
    
  235.           {'Server2'}
    
  236.         </span>
    
  237.       </div>,
    
  238.     );
    
  239.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  240.       onRecoverableError(error) {
    
  241.         Scheduler.log(error.message);
    
  242.       },
    
  243.     });
    
  244.     await waitForAll([]);
    
  245.     expect(getVisibleChildren(container)).toEqual(
    
  246.       <div>
    
  247.         <span>
    
  248.           {'Server1'}
    
  249.           {'Server2'}
    
  250.         </span>
    
  251.       </div>,
    
  252.     );
    
  253.   });
    
  254. 
    
  255.   // @gate !enableClientRenderFallbackOnTextMismatch
    
  256.   it('suppresses and fixes multiple text node mismatches with suppressHydrationWarning', async () => {
    
  257.     function App({isClient}) {
    
  258.       return (
    
  259.         <div>
    
  260.           <span suppressHydrationWarning={true}>
    
  261.             {isClient ? 'Client1' : 'Server1'}
    
  262.             {isClient ? 'Client2' : 'Server2'}
    
  263.           </span>
    
  264.         </div>
    
  265.       );
    
  266.     }
    
  267.     await act(() => {
    
  268.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  269.         <App isClient={false} />,
    
  270.       );
    
  271.       pipe(writable);
    
  272.     });
    
  273.     expect(getVisibleChildren(container)).toEqual(
    
  274.       <div>
    
  275.         <span>
    
  276.           {'Server1'}
    
  277.           {'Server2'}
    
  278.         </span>
    
  279.       </div>,
    
  280.     );
    
  281.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  282.       onRecoverableError(error) {
    
  283.         Scheduler.log(error.message);
    
  284.       },
    
  285.     });
    
  286.     await waitForAll([]);
    
  287.     expect(getVisibleChildren(container)).toEqual(
    
  288.       <div>
    
  289.         <span>
    
  290.           {'Client1'}
    
  291.           {'Client2'}
    
  292.         </span>
    
  293.       </div>,
    
  294.     );
    
  295.   });
    
  296. 
    
  297.   it('errors on text-to-element mismatches with suppressHydrationWarning', async () => {
    
  298.     function App({isClient}) {
    
  299.       return (
    
  300.         <div>
    
  301.           <span suppressHydrationWarning={true}>
    
  302.             Hello, {isClient ? <span>Client</span> : 'Server'}!
    
  303.           </span>
    
  304.         </div>
    
  305.       );
    
  306.     }
    
  307.     await act(() => {
    
  308.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  309.         <App isClient={false} />,
    
  310.       );
    
  311.       pipe(writable);
    
  312.     });
    
  313.     expect(getVisibleChildren(container)).toEqual(
    
  314.       <div>
    
  315.         <span>
    
  316.           {'Hello, '}
    
  317.           {'Server'}
    
  318.           {'!'}
    
  319.         </span>
    
  320.       </div>,
    
  321.     );
    
  322.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  323.       onRecoverableError(error) {
    
  324.         Scheduler.log(error.message);
    
  325.       },
    
  326.     });
    
  327.     await expect(async () => {
    
  328.       await waitForAll([
    
  329.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  330.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  331.       ]);
    
  332.     }).toErrorDev(
    
  333.       [
    
  334.         'Expected server HTML to contain a matching <span> in <span>',
    
  335.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  336.       ],
    
  337.       {withoutStack: 1},
    
  338.     );
    
  339.     expect(getVisibleChildren(container)).toEqual(
    
  340.       <div>
    
  341.         <span>
    
  342.           Hello, <span>Client</span>!
    
  343.         </span>
    
  344.       </div>,
    
  345.     );
    
  346.   });
    
  347. 
    
  348.   // @gate enableClientRenderFallbackOnTextMismatch
    
  349.   it('suppresses but does not fix client-only single text node mismatches with suppressHydrationWarning', async () => {
    
  350.     function App({text}) {
    
  351.       return (
    
  352.         <div>
    
  353.           <span suppressHydrationWarning={true}>{text}</span>
    
  354.         </div>
    
  355.       );
    
  356.     }
    
  357.     await act(() => {
    
  358.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  359.         <App text={null} />,
    
  360.       );
    
  361.       pipe(writable);
    
  362.     });
    
  363.     expect(getVisibleChildren(container)).toEqual(
    
  364.       <div>
    
  365.         <span />
    
  366.       </div>,
    
  367.     );
    
  368.     const root = ReactDOMClient.hydrateRoot(container, <App text="Client" />, {
    
  369.       onRecoverableError(error) {
    
  370.         Scheduler.log(error.message);
    
  371.       },
    
  372.     });
    
  373.     await waitForAll([]);
    
  374.     expect(getVisibleChildren(container)).toEqual(
    
  375.       <div>
    
  376.         <span />
    
  377.       </div>,
    
  378.     );
    
  379.     // An update fixes it though.
    
  380.     root.render(<App text="Client 2" />);
    
  381.     await waitForAll([]);
    
  382.     expect(getVisibleChildren(container)).toEqual(
    
  383.       <div>
    
  384.         <span>Client 2</span>
    
  385.       </div>,
    
  386.     );
    
  387.   });
    
  388. 
    
  389.   // @gate !enableClientRenderFallbackOnTextMismatch
    
  390.   it('suppresses and fixes client-only single text node mismatches with suppressHydrationWarning', async () => {
    
  391.     function App({isClient}) {
    
  392.       return (
    
  393.         <div>
    
  394.           <span suppressHydrationWarning={true}>
    
  395.             {isClient ? 'Client' : null}
    
  396.           </span>
    
  397.         </div>
    
  398.       );
    
  399.     }
    
  400.     await act(() => {
    
  401.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  402.         <App isClient={false} />,
    
  403.       );
    
  404.       pipe(writable);
    
  405.     });
    
  406.     expect(getVisibleChildren(container)).toEqual(
    
  407.       <div>
    
  408.         <span />
    
  409.       </div>,
    
  410.     );
    
  411.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  412.       onRecoverableError(error) {
    
  413.         Scheduler.log(error.message);
    
  414.       },
    
  415.     });
    
  416.     await waitForAll([]);
    
  417.     expect(getVisibleChildren(container)).toEqual(
    
  418.       <div>
    
  419.         <span>{'Client'}</span>
    
  420.       </div>,
    
  421.     );
    
  422.   });
    
  423. 
    
  424.   // TODO: This behavior is not consistent with client-only single text node.
    
  425. 
    
  426.   it('errors on server-only single text node mismatches with suppressHydrationWarning', async () => {
    
  427.     function App({isClient}) {
    
  428.       return (
    
  429.         <div>
    
  430.           <span suppressHydrationWarning={true}>
    
  431.             {isClient ? null : 'Server'}
    
  432.           </span>
    
  433.         </div>
    
  434.       );
    
  435.     }
    
  436.     await act(() => {
    
  437.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  438.         <App isClient={false} />,
    
  439.       );
    
  440.       pipe(writable);
    
  441.     });
    
  442.     expect(getVisibleChildren(container)).toEqual(
    
  443.       <div>
    
  444.         <span>Server</span>
    
  445.       </div>,
    
  446.     );
    
  447.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  448.       onRecoverableError(error) {
    
  449.         Scheduler.log(error.message);
    
  450.       },
    
  451.     });
    
  452.     await expect(async () => {
    
  453.       await waitForAll([
    
  454.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  455.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  456.       ]);
    
  457.     }).toErrorDev(
    
  458.       [
    
  459.         'Did not expect server HTML to contain the text node "Server" in <span>',
    
  460.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  461.       ],
    
  462.       {withoutStack: 1},
    
  463.     );
    
  464.     expect(getVisibleChildren(container)).toEqual(
    
  465.       <div>
    
  466.         <span />
    
  467.       </div>,
    
  468.     );
    
  469.   });
    
  470. 
    
  471.   it('errors on client-only extra text node mismatches with suppressHydrationWarning', async () => {
    
  472.     function App({isClient}) {
    
  473.       return (
    
  474.         <div>
    
  475.           <span suppressHydrationWarning={true}>
    
  476.             <span>Shared</span>
    
  477.             {isClient ? 'Client' : null}
    
  478.           </span>
    
  479.         </div>
    
  480.       );
    
  481.     }
    
  482.     await act(() => {
    
  483.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  484.         <App isClient={false} />,
    
  485.       );
    
  486.       pipe(writable);
    
  487.     });
    
  488.     expect(getVisibleChildren(container)).toEqual(
    
  489.       <div>
    
  490.         <span>
    
  491.           <span>Shared</span>
    
  492.         </span>
    
  493.       </div>,
    
  494.     );
    
  495.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  496.       onRecoverableError(error) {
    
  497.         Scheduler.log(error.message);
    
  498.       },
    
  499.     });
    
  500.     await expect(async () => {
    
  501.       await waitForAll([
    
  502.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  503.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  504.       ]);
    
  505.     }).toErrorDev(
    
  506.       [
    
  507.         'Expected server HTML to contain a matching text node for "Client" in <span>.',
    
  508.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  509.       ],
    
  510.       {withoutStack: 1},
    
  511.     );
    
  512.     expect(getVisibleChildren(container)).toEqual(
    
  513.       <div>
    
  514.         <span>
    
  515.           <span>Shared</span>
    
  516.           {'Client'}
    
  517.         </span>
    
  518.       </div>,
    
  519.     );
    
  520.   });
    
  521. 
    
  522.   it('errors on server-only extra text node mismatches with suppressHydrationWarning', async () => {
    
  523.     function App({isClient}) {
    
  524.       return (
    
  525.         <div>
    
  526.           <span suppressHydrationWarning={true}>
    
  527.             <span>Shared</span>
    
  528.             {isClient ? null : 'Server'}
    
  529.           </span>
    
  530.         </div>
    
  531.       );
    
  532.     }
    
  533.     await act(() => {
    
  534.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  535.         <App isClient={false} />,
    
  536.       );
    
  537.       pipe(writable);
    
  538.     });
    
  539.     expect(getVisibleChildren(container)).toEqual(
    
  540.       <div>
    
  541.         <span>
    
  542.           <span>Shared</span>Server
    
  543.         </span>
    
  544.       </div>,
    
  545.     );
    
  546.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  547.       onRecoverableError(error) {
    
  548.         Scheduler.log(error.message);
    
  549.       },
    
  550.     });
    
  551.     await expect(async () => {
    
  552.       await waitForAll([
    
  553.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  554.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  555.       ]);
    
  556.     }).toErrorDev(
    
  557.       [
    
  558.         'Did not expect server HTML to contain the text node "Server" in <span>.',
    
  559.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  560.       ],
    
  561.       {withoutStack: 1},
    
  562.     );
    
  563.     expect(getVisibleChildren(container)).toEqual(
    
  564.       <div>
    
  565.         <span>
    
  566.           <span>Shared</span>
    
  567.         </span>
    
  568.       </div>,
    
  569.     );
    
  570.   });
    
  571. 
    
  572.   it('errors on element-to-text mismatches with suppressHydrationWarning', async () => {
    
  573.     function App({isClient}) {
    
  574.       return (
    
  575.         <div>
    
  576.           <span suppressHydrationWarning={true}>
    
  577.             Hello, {isClient ? 'Client' : <span>Server</span>}!
    
  578.           </span>
    
  579.         </div>
    
  580.       );
    
  581.     }
    
  582.     await act(() => {
    
  583.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  584.         <App isClient={false} />,
    
  585.       );
    
  586.       pipe(writable);
    
  587.     });
    
  588.     expect(getVisibleChildren(container)).toEqual(
    
  589.       <div>
    
  590.         <span>
    
  591.           Hello, <span>Server</span>!
    
  592.         </span>
    
  593.       </div>,
    
  594.     );
    
  595.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  596.       onRecoverableError(error) {
    
  597.         Scheduler.log(error.message);
    
  598.       },
    
  599.     });
    
  600.     await expect(async () => {
    
  601.       await waitForAll([
    
  602.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  603.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  604.       ]);
    
  605.     }).toErrorDev(
    
  606.       [
    
  607.         'Expected server HTML to contain a matching text node for "Client" in <span>.',
    
  608.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  609.       ],
    
  610.       {withoutStack: 1},
    
  611.     );
    
  612.     expect(getVisibleChildren(container)).toEqual(
    
  613.       <div>
    
  614.         <span>
    
  615.           {'Hello, '}
    
  616.           {'Client'}
    
  617.           {'!'}
    
  618.         </span>
    
  619.       </div>,
    
  620.     );
    
  621.   });
    
  622. 
    
  623.   it('suppresses but does not fix attribute mismatches with suppressHydrationWarning', async () => {
    
  624.     function App({isClient}) {
    
  625.       return (
    
  626.         <div>
    
  627.           <span
    
  628.             suppressHydrationWarning={true}
    
  629.             className={isClient ? 'client' : 'server'}
    
  630.             style={{opacity: isClient ? 1 : 0}}
    
  631.             data-serveronly={isClient ? null : 'server-only'}
    
  632.             data-clientonly={isClient ? 'client-only' : null}
    
  633.           />
    
  634.         </div>
    
  635.       );
    
  636.     }
    
  637.     await act(() => {
    
  638.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  639.         <App isClient={false} />,
    
  640.       );
    
  641.       pipe(writable);
    
  642.     });
    
  643.     expect(getVisibleChildren(container)).toEqual(
    
  644.       <div>
    
  645.         <span class="server" style="opacity:0" data-serveronly="server-only" />
    
  646.       </div>,
    
  647.     );
    
  648.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  649.       onRecoverableError(error) {
    
  650.         Scheduler.log(error.message);
    
  651.       },
    
  652.     });
    
  653.     await waitForAll([]);
    
  654.     expect(getVisibleChildren(container)).toEqual(
    
  655.       <div>
    
  656.         <span class="server" style="opacity:0" data-serveronly="server-only" />
    
  657.       </div>,
    
  658.     );
    
  659.   });
    
  660. 
    
  661.   it('suppresses and does not fix html mismatches with suppressHydrationWarning', async () => {
    
  662.     function App({isClient}) {
    
  663.       return (
    
  664.         <div>
    
  665.           <p
    
  666.             suppressHydrationWarning={true}
    
  667.             dangerouslySetInnerHTML={{
    
  668.               __html: isClient ? 'Client HTML' : 'Server HTML',
    
  669.             }}
    
  670.           />
    
  671.         </div>
    
  672.       );
    
  673.     }
    
  674.     await act(() => {
    
  675.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  676.         <App isClient={false} />,
    
  677.       );
    
  678.       pipe(writable);
    
  679.     });
    
  680.     expect(getVisibleChildren(container)).toEqual(
    
  681.       <div>
    
  682.         <p>Server HTML</p>
    
  683.       </div>,
    
  684.     );
    
  685.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  686.       onRecoverableError(error) {
    
  687.         Scheduler.log(error.message);
    
  688.       },
    
  689.     });
    
  690.     await waitForAll([]);
    
  691.     expect(getVisibleChildren(container)).toEqual(
    
  692.       <div>
    
  693.         <p>Server HTML</p>
    
  694.       </div>,
    
  695.     );
    
  696.   });
    
  697. 
    
  698.   it('errors on insertions with suppressHydrationWarning', async () => {
    
  699.     function App({isClient}) {
    
  700.       return (
    
  701.         <div suppressHydrationWarning={true}>
    
  702.           <p>Client and server</p>
    
  703.           {isClient && <p>Client only</p>}
    
  704.         </div>
    
  705.       );
    
  706.     }
    
  707.     await act(() => {
    
  708.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  709.         <App isClient={false} />,
    
  710.       );
    
  711.       pipe(writable);
    
  712.     });
    
  713.     expect(getVisibleChildren(container)).toEqual(
    
  714.       <div>
    
  715.         <p>Client and server</p>
    
  716.       </div>,
    
  717.     );
    
  718.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  719.       onRecoverableError(error) {
    
  720.         Scheduler.log(error.message);
    
  721.       },
    
  722.     });
    
  723.     await expect(async () => {
    
  724.       await waitForAll([
    
  725.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  726.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  727.       ]);
    
  728.     }).toErrorDev(
    
  729.       [
    
  730.         'Expected server HTML to contain a matching <p> in <div>.',
    
  731.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  732.       ],
    
  733.       {withoutStack: 1},
    
  734.     );
    
  735.     expect(getVisibleChildren(container)).toEqual(
    
  736.       <div>
    
  737.         <p>Client and server</p>
    
  738.         <p>Client only</p>
    
  739.       </div>,
    
  740.     );
    
  741.   });
    
  742. 
    
  743.   it('errors on deletions with suppressHydrationWarning', async () => {
    
  744.     function App({isClient}) {
    
  745.       return (
    
  746.         <div suppressHydrationWarning={true}>
    
  747.           <p>Client and server</p>
    
  748.           {!isClient && <p>Server only</p>}
    
  749.         </div>
    
  750.       );
    
  751.     }
    
  752.     await act(() => {
    
  753.       const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
    
  754.         <App isClient={false} />,
    
  755.       );
    
  756.       pipe(writable);
    
  757.     });
    
  758.     expect(getVisibleChildren(container)).toEqual(
    
  759.       <div>
    
  760.         <p>Client and server</p>
    
  761.         <p>Server only</p>
    
  762.       </div>,
    
  763.     );
    
  764.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  765.       onRecoverableError(error) {
    
  766.         Scheduler.log(error.message);
    
  767.       },
    
  768.     });
    
  769.     await expect(async () => {
    
  770.       await waitForAll([
    
  771.         'Hydration failed because the initial UI does not match what was rendered on the server.',
    
  772.         'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
    
  773.       ]);
    
  774.     }).toErrorDev(
    
  775.       [
    
  776.         'Did not expect server HTML to contain a <p> in <div>.',
    
  777.         'An error occurred during hydration. The server HTML was replaced with client content in <div>.',
    
  778.       ],
    
  779.       {withoutStack: 1},
    
  780.     );
    
  781.     expect(getVisibleChildren(container)).toEqual(
    
  782.       <div>
    
  783.         <p>Client and server</p>
    
  784.       </div>,
    
  785.     );
    
  786.   });
    
  787. });