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 React;
    
  14. let ReactDOM;
    
  15. let ReactDOMClient;
    
  16. let ReactDOMServer;
    
  17. let ReactDOMServerBrowser;
    
  18. let waitForAll;
    
  19. let act;
    
  20. 
    
  21. // These tests rely both on ReactDOMServer and ReactDOM.
    
  22. // If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
    
  23. describe('ReactDOMServerHydration', () => {
    
  24.   beforeEach(() => {
    
  25.     jest.resetModules();
    
  26.     React = require('react');
    
  27.     ReactDOM = require('react-dom');
    
  28.     ReactDOMClient = require('react-dom/client');
    
  29.     ReactDOMServer = require('react-dom/server');
    
  30.     ReactDOMServerBrowser = require('react-dom/server.browser');
    
  31. 
    
  32.     const InternalTestUtils = require('internal-test-utils');
    
  33.     waitForAll = InternalTestUtils.waitForAll;
    
  34.     act = InternalTestUtils.act;
    
  35.   });
    
  36. 
    
  37.   it('should have the correct mounting behavior (new hydrate API)', () => {
    
  38.     let mountCount = 0;
    
  39.     let numClicks = 0;
    
  40. 
    
  41.     class TestComponent extends React.Component {
    
  42.       spanRef = React.createRef();
    
  43. 
    
  44.       componentDidMount() {
    
  45.         mountCount++;
    
  46.       }
    
  47. 
    
  48.       click = () => {
    
  49.         numClicks++;
    
  50.       };
    
  51. 
    
  52.       render() {
    
  53.         return (
    
  54.           <span ref={this.spanRef} onClick={this.click}>
    
  55.             Name: {this.props.name}
    
  56.           </span>
    
  57.         );
    
  58.       }
    
  59.     }
    
  60. 
    
  61.     const element = document.createElement('div');
    
  62.     document.body.appendChild(element);
    
  63.     try {
    
  64.       ReactDOM.render(<TestComponent />, element);
    
  65. 
    
  66.       let lastMarkup = element.innerHTML;
    
  67. 
    
  68.       // Exercise the update path. Markup should not change,
    
  69.       // but some lifecycle methods should be run again.
    
  70.       ReactDOM.render(<TestComponent name="x" />, element);
    
  71.       expect(mountCount).toEqual(1);
    
  72. 
    
  73.       // Unmount and remount. We should get another mount event and
    
  74.       // we should get different markup, as the IDs are unique each time.
    
  75.       ReactDOM.unmountComponentAtNode(element);
    
  76.       expect(element.innerHTML).toEqual('');
    
  77.       ReactDOM.render(<TestComponent name="x" />, element);
    
  78.       expect(mountCount).toEqual(2);
    
  79.       expect(element.innerHTML).not.toEqual(lastMarkup);
    
  80. 
    
  81.       // Now kill the node and render it on top of server-rendered markup, as if
    
  82.       // we used server rendering. We should mount again, but the markup should
    
  83.       // be unchanged. We will append a sentinel at the end of innerHTML to be
    
  84.       // sure that innerHTML was not changed.
    
  85.       ReactDOM.unmountComponentAtNode(element);
    
  86.       expect(element.innerHTML).toEqual('');
    
  87. 
    
  88.       lastMarkup = ReactDOMServer.renderToString(<TestComponent name="x" />);
    
  89.       element.innerHTML = lastMarkup;
    
  90. 
    
  91.       let instance = ReactDOM.hydrate(<TestComponent name="x" />, element);
    
  92.       expect(mountCount).toEqual(3);
    
  93.       expect(element.innerHTML).toBe(lastMarkup);
    
  94. 
    
  95.       // Ensure the events system works after mount into server markup
    
  96.       expect(numClicks).toEqual(0);
    
  97.       instance.spanRef.current.click();
    
  98.       expect(numClicks).toEqual(1);
    
  99. 
    
  100.       ReactDOM.unmountComponentAtNode(element);
    
  101.       expect(element.innerHTML).toEqual('');
    
  102. 
    
  103.       // Now simulate a situation where the app is not idempotent. React should
    
  104.       // warn but do the right thing.
    
  105.       element.innerHTML = lastMarkup;
    
  106.       expect(() => {
    
  107.         instance = ReactDOM.hydrate(<TestComponent name="y" />, element);
    
  108.       }).toErrorDev('Text content did not match. Server: "x" Client: "y"');
    
  109.       expect(mountCount).toEqual(4);
    
  110.       expect(element.innerHTML.length > 0).toBe(true);
    
  111.       expect(element.innerHTML).not.toEqual(lastMarkup);
    
  112. 
    
  113.       // Ensure the events system works after markup mismatch.
    
  114.       expect(numClicks).toEqual(1);
    
  115.       instance.spanRef.current.click();
    
  116.       expect(numClicks).toEqual(2);
    
  117.     } finally {
    
  118.       document.body.removeChild(element);
    
  119.     }
    
  120.   });
    
  121. 
    
  122.   // We have a polyfill for autoFocus on the client, but we intentionally don't
    
  123.   // want it to call focus() when hydrating because this can mess up existing
    
  124.   // focus before the JS has loaded.
    
  125.   it('should emit autofocus on the server but not focus() when hydrating', () => {
    
  126.     const element = document.createElement('div');
    
  127.     element.innerHTML = ReactDOMServer.renderToString(
    
  128.       <input autoFocus={true} />,
    
  129.     );
    
  130.     expect(element.firstChild.autofocus).toBe(true);
    
  131. 
    
  132.     // It should not be called on mount.
    
  133.     element.firstChild.focus = jest.fn();
    
  134.     ReactDOM.hydrate(<input autoFocus={true} />, element);
    
  135.     expect(element.firstChild.focus).not.toHaveBeenCalled();
    
  136. 
    
  137.     // Or during an update.
    
  138.     ReactDOM.render(<input autoFocus={true} />, element);
    
  139.     expect(element.firstChild.focus).not.toHaveBeenCalled();
    
  140.   });
    
  141. 
    
  142.   it('should not focus on either server or client with autofocus={false}', () => {
    
  143.     const element = document.createElement('div');
    
  144.     element.innerHTML = ReactDOMServer.renderToString(
    
  145.       <input autoFocus={false} />,
    
  146.     );
    
  147.     expect(element.firstChild.autofocus).toBe(false);
    
  148. 
    
  149.     element.firstChild.focus = jest.fn();
    
  150.     ReactDOM.hydrate(<input autoFocus={false} />, element);
    
  151.     expect(element.firstChild.focus).not.toHaveBeenCalled();
    
  152. 
    
  153.     ReactDOM.render(<input autoFocus={false} />, element);
    
  154.     expect(element.firstChild.focus).not.toHaveBeenCalled();
    
  155.   });
    
  156. 
    
  157.   // Regression test for https://github.com/facebook/react/issues/11726
    
  158.   it('should not focus on either server or client with autofocus={false} even if there is a markup mismatch', () => {
    
  159.     const element = document.createElement('div');
    
  160.     element.innerHTML = ReactDOMServer.renderToString(
    
  161.       <button autoFocus={false}>server</button>,
    
  162.     );
    
  163.     expect(element.firstChild.autofocus).toBe(false);
    
  164. 
    
  165.     element.firstChild.focus = jest.fn();
    
  166. 
    
  167.     expect(() =>
    
  168.       ReactDOM.hydrate(<button autoFocus={false}>client</button>, element),
    
  169.     ).toErrorDev(
    
  170.       'Warning: Text content did not match. Server: "server" Client: "client"',
    
  171.     );
    
  172. 
    
  173.     expect(element.firstChild.focus).not.toHaveBeenCalled();
    
  174.   });
    
  175. 
    
  176.   it('should warn when the style property differs', () => {
    
  177.     const element = document.createElement('div');
    
  178.     element.innerHTML = ReactDOMServer.renderToString(
    
  179.       <div style={{textDecoration: 'none', color: 'black', height: '10px'}} />,
    
  180.     );
    
  181.     expect(element.firstChild.style.textDecoration).toBe('none');
    
  182.     expect(element.firstChild.style.color).toBe('black');
    
  183. 
    
  184.     expect(() =>
    
  185.       ReactDOM.hydrate(
    
  186.         <div
    
  187.           style={{textDecoration: 'none', color: 'white', height: '10px'}}
    
  188.         />,
    
  189.         element,
    
  190.       ),
    
  191.     ).toErrorDev(
    
  192.       'Warning: Prop `style` did not match. Server: ' +
    
  193.         '"text-decoration:none;color:black;height:10px" Client: ' +
    
  194.         '"text-decoration:none;color:white;height:10px"',
    
  195.     );
    
  196.   });
    
  197. 
    
  198.   // @gate !disableIEWorkarounds || !__DEV__
    
  199.   it('should not warn when the style property differs on whitespace or order in IE', () => {
    
  200.     document.documentMode = 11;
    
  201.     jest.resetModules();
    
  202.     React = require('react');
    
  203.     ReactDOM = require('react-dom');
    
  204.     ReactDOMServer = require('react-dom/server');
    
  205.     try {
    
  206.       const element = document.createElement('div');
    
  207. 
    
  208.       // Simulate IE normalizing the style attribute. IE makes it equal to
    
  209.       // what's available under `node.style.cssText`.
    
  210.       element.innerHTML =
    
  211.         '<div style="height: 10px; color: black; text-decoration: none;"></div>';
    
  212. 
    
  213.       // We don't expect to see false positive warnings.
    
  214.       // https://github.com/facebook/react/issues/11807
    
  215.       ReactDOM.hydrate(
    
  216.         <div
    
  217.           style={{textDecoration: 'none', color: 'black', height: '10px'}}
    
  218.         />,
    
  219.         element,
    
  220.       );
    
  221.     } finally {
    
  222.       delete document.documentMode;
    
  223.     }
    
  224.   });
    
  225. 
    
  226.   it('should warn when the style property differs on whitespace in non-IE browsers', () => {
    
  227.     const element = document.createElement('div');
    
  228. 
    
  229.     element.innerHTML =
    
  230.       '<div style="text-decoration: none; color: black; height: 10px;"></div>';
    
  231. 
    
  232.     expect(() =>
    
  233.       ReactDOM.hydrate(
    
  234.         <div
    
  235.           style={{textDecoration: 'none', color: 'black', height: '10px'}}
    
  236.         />,
    
  237.         element,
    
  238.       ),
    
  239.     ).toErrorDev(
    
  240.       'Warning: Prop `style` did not match. Server: ' +
    
  241.         '"text-decoration: none; color: black; height: 10px;" Client: ' +
    
  242.         '"text-decoration:none;color:black;height:10px"',
    
  243.     );
    
  244.   });
    
  245. 
    
  246.   it('should throw rendering portals on the server', () => {
    
  247.     const div = document.createElement('div');
    
  248.     expect(() => {
    
  249.       ReactDOMServer.renderToString(
    
  250.         <div>{ReactDOM.createPortal(<div />, div)}</div>,
    
  251.       );
    
  252.     }).toThrow(
    
  253.       'Portals are not currently supported by the server renderer. ' +
    
  254.         'Render them conditionally so that they only appear on the client render.',
    
  255.     );
    
  256.   });
    
  257. 
    
  258.   it('should be able to render and hydrate Mode components', () => {
    
  259.     class ComponentWithWarning extends React.Component {
    
  260.       componentWillMount() {
    
  261.         // Expected warning
    
  262.       }
    
  263.       render() {
    
  264.         return 'Hi';
    
  265.       }
    
  266.     }
    
  267. 
    
  268.     const markup = (
    
  269.       <React.StrictMode>
    
  270.         <ComponentWithWarning />
    
  271.       </React.StrictMode>
    
  272.     );
    
  273. 
    
  274.     const element = document.createElement('div');
    
  275.     expect(() => {
    
  276.       element.innerHTML = ReactDOMServer.renderToString(markup);
    
  277.     }).toWarnDev('componentWillMount has been renamed');
    
  278.     expect(element.textContent).toBe('Hi');
    
  279. 
    
  280.     expect(() => {
    
  281.       ReactDOM.hydrate(markup, element);
    
  282.     }).toWarnDev('componentWillMount has been renamed', {
    
  283.       withoutStack: true,
    
  284.     });
    
  285.     expect(element.textContent).toBe('Hi');
    
  286.   });
    
  287. 
    
  288.   it('should be able to render and hydrate forwardRef components', () => {
    
  289.     const FunctionComponent = ({label, forwardedRef}) => (
    
  290.       <div ref={forwardedRef}>{label}</div>
    
  291.     );
    
  292.     const WrappedFunctionComponent = React.forwardRef((props, ref) => (
    
  293.       <FunctionComponent {...props} forwardedRef={ref} />
    
  294.     ));
    
  295. 
    
  296.     const ref = React.createRef();
    
  297.     const markup = <WrappedFunctionComponent ref={ref} label="Hi" />;
    
  298. 
    
  299.     const element = document.createElement('div');
    
  300.     element.innerHTML = ReactDOMServer.renderToString(markup);
    
  301.     expect(element.textContent).toBe('Hi');
    
  302.     expect(ref.current).toBe(null);
    
  303. 
    
  304.     ReactDOM.hydrate(markup, element);
    
  305.     expect(element.textContent).toBe('Hi');
    
  306.     expect(ref.current.tagName).toBe('DIV');
    
  307.   });
    
  308. 
    
  309.   it('should be able to render and hydrate Profiler components', () => {
    
  310.     const callback = jest.fn();
    
  311.     const markup = (
    
  312.       <React.Profiler id="profiler" onRender={callback}>
    
  313.         <div>Hi</div>
    
  314.       </React.Profiler>
    
  315.     );
    
  316. 
    
  317.     const element = document.createElement('div');
    
  318.     element.innerHTML = ReactDOMServer.renderToString(markup);
    
  319.     expect(element.textContent).toBe('Hi');
    
  320.     expect(callback).not.toHaveBeenCalled();
    
  321. 
    
  322.     ReactDOM.hydrate(markup, element);
    
  323.     expect(element.textContent).toBe('Hi');
    
  324.     if (__DEV__) {
    
  325.       expect(callback).toHaveBeenCalledTimes(1);
    
  326.       const [id, phase] = callback.mock.calls[0];
    
  327.       expect(id).toBe('profiler');
    
  328.       expect(phase).toBe('mount');
    
  329.     } else {
    
  330.       expect(callback).toHaveBeenCalledTimes(0);
    
  331.     }
    
  332.   });
    
  333. 
    
  334.   // Regression test for https://github.com/facebook/react/issues/11423
    
  335.   it('should ignore noscript content on the client and not warn about mismatches', () => {
    
  336.     const callback = jest.fn();
    
  337.     const TestComponent = ({onRender}) => {
    
  338.       onRender();
    
  339.       return <div>Enable JavaScript to run this app.</div>;
    
  340.     };
    
  341.     const markup = (
    
  342.       <noscript>
    
  343.         <TestComponent onRender={callback} />
    
  344.       </noscript>
    
  345.     );
    
  346. 
    
  347.     const element = document.createElement('div');
    
  348.     element.innerHTML = ReactDOMServer.renderToString(markup);
    
  349.     expect(callback).toHaveBeenCalledTimes(1);
    
  350.     expect(element.textContent).toBe(
    
  351.       '<div>Enable JavaScript to run this app.</div>',
    
  352.     );
    
  353. 
    
  354.     // On the client we want to keep the existing markup, but not render the
    
  355.     // actual elements for performance reasons and to avoid for example
    
  356.     // downloading images. This should also not warn for hydration mismatches.
    
  357.     ReactDOM.hydrate(markup, element);
    
  358.     expect(callback).toHaveBeenCalledTimes(1);
    
  359.     expect(element.textContent).toBe(
    
  360.       '<div>Enable JavaScript to run this app.</div>',
    
  361.     );
    
  362.   });
    
  363. 
    
  364.   it('should be able to use lazy components after hydrating', async () => {
    
  365.     const Lazy = React.lazy(
    
  366.       () =>
    
  367.         new Promise(resolve => {
    
  368.           setTimeout(
    
  369.             () =>
    
  370.               resolve({
    
  371.                 default: function World() {
    
  372.                   return 'world';
    
  373.                 },
    
  374.               }),
    
  375.             1000,
    
  376.           );
    
  377.         }),
    
  378.     );
    
  379.     class HelloWorld extends React.Component {
    
  380.       state = {isClient: false};
    
  381.       componentDidMount() {
    
  382.         this.setState({
    
  383.           isClient: true,
    
  384.         });
    
  385.       }
    
  386.       render() {
    
  387.         return (
    
  388.           <div>
    
  389.             Hello{' '}
    
  390.             {this.state.isClient && (
    
  391.               <React.Suspense fallback="loading">
    
  392.                 <Lazy />
    
  393.               </React.Suspense>
    
  394.             )}
    
  395.           </div>
    
  396.         );
    
  397.       }
    
  398.     }
    
  399. 
    
  400.     const element = document.createElement('div');
    
  401.     element.innerHTML = ReactDOMServer.renderToString(<HelloWorld />);
    
  402.     expect(element.textContent).toBe('Hello ');
    
  403. 
    
  404.     ReactDOM.hydrate(<HelloWorld />, element);
    
  405.     expect(element.textContent).toBe('Hello loading');
    
  406. 
    
  407.     // Resolve Lazy component
    
  408.     await act(() => jest.runAllTimers());
    
  409.     expect(element.textContent).toBe('Hello world');
    
  410.   });
    
  411. 
    
  412.   it('does not re-enter hydration after committing the first one', async () => {
    
  413.     const finalHTML = ReactDOMServer.renderToString(<div />);
    
  414.     const container = document.createElement('div');
    
  415.     container.innerHTML = finalHTML;
    
  416.     const root = await act(() =>
    
  417.       ReactDOMClient.hydrateRoot(container, <div />),
    
  418.     );
    
  419.     await act(() => root.render(null));
    
  420.     // This should not reenter hydration state and therefore not trigger hydration
    
  421.     // warnings.
    
  422.     await act(() => root.render(<div />));
    
  423.   });
    
  424. 
    
  425.   it('Suspense + hydration in legacy mode', () => {
    
  426.     const element = document.createElement('div');
    
  427.     element.innerHTML = '<div><div>Hello World</div></div>';
    
  428.     const div = element.firstChild.firstChild;
    
  429.     const ref = React.createRef();
    
  430.     expect(() =>
    
  431.       ReactDOM.hydrate(
    
  432.         <div>
    
  433.           <React.Suspense fallback={null}>
    
  434.             <div ref={ref}>Hello World</div>
    
  435.           </React.Suspense>
    
  436.         </div>,
    
  437.         element,
    
  438.       ),
    
  439.     ).toErrorDev(
    
  440.       'Warning: Did not expect server HTML to contain a <div> in <div>.',
    
  441.     );
    
  442. 
    
  443.     // The content should've been client rendered and replaced the
    
  444.     // existing div.
    
  445.     expect(ref.current).not.toBe(div);
    
  446.     // The HTML should be the same though.
    
  447.     expect(element.innerHTML).toBe('<div><div>Hello World</div></div>');
    
  448.   });
    
  449. 
    
  450.   it('Suspense + hydration in legacy mode (at root)', () => {
    
  451.     const element = document.createElement('div');
    
  452.     element.innerHTML = '<div>Hello World</div>';
    
  453.     const div = element.firstChild;
    
  454.     const ref = React.createRef();
    
  455.     ReactDOM.hydrate(
    
  456.       <React.Suspense fallback={null}>
    
  457.         <div ref={ref}>Hello World</div>
    
  458.       </React.Suspense>,
    
  459.       element,
    
  460.     );
    
  461. 
    
  462.     // The content should've been client rendered.
    
  463.     expect(ref.current).not.toBe(div);
    
  464.     // Unfortunately, since we don't delete the tail at the root, a duplicate will remain.
    
  465.     expect(element.innerHTML).toBe(
    
  466.       '<div>Hello World</div><div>Hello World</div>',
    
  467.     );
    
  468.   });
    
  469. 
    
  470.   it('Suspense + hydration in legacy mode with no fallback', () => {
    
  471.     const element = document.createElement('div');
    
  472.     element.innerHTML = '<div>Hello World</div>';
    
  473.     const div = element.firstChild;
    
  474.     const ref = React.createRef();
    
  475.     ReactDOM.hydrate(
    
  476.       <React.Suspense>
    
  477.         <div ref={ref}>Hello World</div>
    
  478.       </React.Suspense>,
    
  479.       element,
    
  480.     );
    
  481. 
    
  482.     // The content should've been client rendered.
    
  483.     expect(ref.current).not.toBe(div);
    
  484.     // Unfortunately, since we don't delete the tail at the root, a duplicate will remain.
    
  485.     expect(element.innerHTML).toBe(
    
  486.       '<div>Hello World</div><div>Hello World</div>',
    
  487.     );
    
  488.   });
    
  489. 
    
  490.   // regression test for https://github.com/facebook/react/issues/17170
    
  491.   it('should not warn if dangerouslySetInnerHtml=undefined', () => {
    
  492.     const domElement = document.createElement('div');
    
  493.     const reactElement = (
    
  494.       <div dangerouslySetInnerHTML={undefined}>
    
  495.         <p>Hello, World!</p>
    
  496.       </div>
    
  497.     );
    
  498.     const markup = ReactDOMServer.renderToStaticMarkup(reactElement);
    
  499.     domElement.innerHTML = markup;
    
  500. 
    
  501.     ReactDOM.hydrate(reactElement, domElement);
    
  502. 
    
  503.     expect(domElement.innerHTML).toEqual(markup);
    
  504.   });
    
  505. 
    
  506.   it('should warn if innerHTML mismatches with dangerouslySetInnerHTML=undefined and children on the client', () => {
    
  507.     const domElement = document.createElement('div');
    
  508.     const markup = ReactDOMServer.renderToStaticMarkup(
    
  509.       <div dangerouslySetInnerHTML={{__html: '<p>server</p>'}} />,
    
  510.     );
    
  511.     domElement.innerHTML = markup;
    
  512. 
    
  513.     expect(() => {
    
  514.       ReactDOM.hydrate(
    
  515.         <div dangerouslySetInnerHTML={undefined}>
    
  516.           <p>client</p>
    
  517.         </div>,
    
  518.         domElement,
    
  519.       );
    
  520. 
    
  521.       expect(domElement.innerHTML).not.toEqual(markup);
    
  522.     }).toErrorDev(
    
  523.       'Warning: Text content did not match. Server: "server" Client: "client"',
    
  524.     );
    
  525.   });
    
  526. 
    
  527.   it('should warn if innerHTML mismatches with dangerouslySetInnerHTML=undefined on the client', () => {
    
  528.     const domElement = document.createElement('div');
    
  529.     const markup = ReactDOMServer.renderToStaticMarkup(
    
  530.       <div dangerouslySetInnerHTML={{__html: '<p>server</p>'}} />,
    
  531.     );
    
  532.     domElement.innerHTML = markup;
    
  533. 
    
  534.     expect(() => {
    
  535.       ReactDOM.hydrate(<div dangerouslySetInnerHTML={undefined} />, domElement);
    
  536. 
    
  537.       expect(domElement.innerHTML).not.toEqual(markup);
    
  538.     }).toErrorDev(
    
  539.       'Warning: Did not expect server HTML to contain a <p> in <div>',
    
  540.     );
    
  541.   });
    
  542. 
    
  543.   it('should warn when hydrating read-only properties', () => {
    
  544.     const readOnlyProperties = [
    
  545.       'offsetParent',
    
  546.       'offsetTop',
    
  547.       'offsetLeft',
    
  548.       'offsetWidth',
    
  549.       'offsetHeight',
    
  550.       'isContentEditable',
    
  551.       'outerText',
    
  552.       'outerHTML',
    
  553.     ];
    
  554.     readOnlyProperties.forEach(readOnlyProperty => {
    
  555.       const props = {};
    
  556.       props[readOnlyProperty] = 'hello';
    
  557.       const jsx = React.createElement('my-custom-element', props);
    
  558.       const element = document.createElement('div');
    
  559.       element.innerHTML = ReactDOMServer.renderToString(jsx);
    
  560.       if (gate(flags => flags.enableCustomElementPropertySupport)) {
    
  561.         expect(() => ReactDOM.hydrate(jsx, element)).toErrorDev(
    
  562.           `Warning: Assignment to read-only property will result in a no-op: \`${readOnlyProperty}\``,
    
  563.         );
    
  564.       } else {
    
  565.         ReactDOM.hydrate(jsx, element);
    
  566.       }
    
  567.     });
    
  568.   });
    
  569. 
    
  570.   // @gate enableCustomElementPropertySupport
    
  571.   it('should not re-assign properties on hydration', () => {
    
  572.     const container = document.createElement('div');
    
  573.     document.body.appendChild(container);
    
  574. 
    
  575.     const jsx = React.createElement('my-custom-element', {
    
  576.       str: 'string',
    
  577.       obj: {foo: 'bar'},
    
  578.     });
    
  579. 
    
  580.     container.innerHTML = ReactDOMServer.renderToString(jsx);
    
  581.     const customElement = container.querySelector('my-custom-element');
    
  582. 
    
  583.     // Install setters to activate `in` check
    
  584.     Object.defineProperty(customElement, 'str', {
    
  585.       set: function (x) {
    
  586.         this._str = x;
    
  587.       },
    
  588.       get: function () {
    
  589.         return this._str;
    
  590.       },
    
  591.     });
    
  592.     Object.defineProperty(customElement, 'obj', {
    
  593.       set: function (x) {
    
  594.         this._obj = x;
    
  595.       },
    
  596.       get: function () {
    
  597.         return this._obj;
    
  598.       },
    
  599.     });
    
  600. 
    
  601.     ReactDOM.hydrate(jsx, container);
    
  602. 
    
  603.     expect(customElement.getAttribute('str')).toBe('string');
    
  604.     expect(customElement.getAttribute('obj')).toBe(null);
    
  605.     expect(customElement.str).toBe(undefined);
    
  606.     expect(customElement.obj).toBe(undefined);
    
  607.   });
    
  608. 
    
  609.   it('refers users to apis that support Suspense when something suspends', async () => {
    
  610.     const theInfinitePromise = new Promise(() => {});
    
  611.     function InfiniteSuspend() {
    
  612.       throw theInfinitePromise;
    
  613.     }
    
  614. 
    
  615.     function App({isClient}) {
    
  616.       return (
    
  617.         <div>
    
  618.           <React.Suspense fallback={'fallback'}>
    
  619.             {isClient ? 'resolved' : <InfiniteSuspend />}
    
  620.           </React.Suspense>
    
  621.         </div>
    
  622.       );
    
  623.     }
    
  624.     const container = document.createElement('div');
    
  625.     container.innerHTML = ReactDOMServer.renderToString(
    
  626.       <App isClient={false} />,
    
  627.     );
    
  628. 
    
  629.     const errors = [];
    
  630.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  631.       onRecoverableError(error, errorInfo) {
    
  632.         errors.push(error.message);
    
  633.       },
    
  634.     });
    
  635. 
    
  636.     await waitForAll([]);
    
  637.     expect(errors.length).toBe(1);
    
  638.     if (__DEV__) {
    
  639.       expect(errors[0]).toBe(
    
  640.         'The server did not finish this Suspense boundary: The server used "renderToString" ' +
    
  641.           'which does not support Suspense. If you intended for this Suspense boundary to render ' +
    
  642.           'the fallback content on the server consider throwing an Error somewhere within the ' +
    
  643.           'Suspense boundary. If you intended to have the server wait for the suspended component ' +
    
  644.           'please switch to "renderToPipeableStream" which supports Suspense on the server',
    
  645.       );
    
  646.     } else {
    
  647.       expect(errors[0]).toBe(
    
  648.         'The server could not finish this Suspense boundary, likely due to ' +
    
  649.           'an error during server rendering. Switched to client rendering.',
    
  650.       );
    
  651.     }
    
  652.   });
    
  653. 
    
  654.   it('refers users to apis that support Suspense when something suspends (browser)', async () => {
    
  655.     const theInfinitePromise = new Promise(() => {});
    
  656.     function InfiniteSuspend() {
    
  657.       throw theInfinitePromise;
    
  658.     }
    
  659. 
    
  660.     function App({isClient}) {
    
  661.       return (
    
  662.         <div>
    
  663.           <React.Suspense fallback={'fallback'}>
    
  664.             {isClient ? 'resolved' : <InfiniteSuspend />}
    
  665.           </React.Suspense>
    
  666.         </div>
    
  667.       );
    
  668.     }
    
  669.     const container = document.createElement('div');
    
  670.     container.innerHTML = ReactDOMServerBrowser.renderToString(
    
  671.       <App isClient={false} />,
    
  672.     );
    
  673. 
    
  674.     const errors = [];
    
  675.     ReactDOMClient.hydrateRoot(container, <App isClient={true} />, {
    
  676.       onRecoverableError(error, errorInfo) {
    
  677.         errors.push(error.message);
    
  678.       },
    
  679.     });
    
  680. 
    
  681.     await waitForAll([]);
    
  682.     expect(errors.length).toBe(1);
    
  683.     if (__DEV__) {
    
  684.       expect(errors[0]).toBe(
    
  685.         'The server did not finish this Suspense boundary: The server used "renderToString" ' +
    
  686.           'which does not support Suspense. If you intended for this Suspense boundary to render ' +
    
  687.           'the fallback content on the server consider throwing an Error somewhere within the ' +
    
  688.           'Suspense boundary. If you intended to have the server wait for the suspended component ' +
    
  689.           'please switch to "renderToReadableStream" which supports Suspense on the server',
    
  690.       );
    
  691.     } else {
    
  692.       expect(errors[0]).toBe(
    
  693.         'The server could not finish this Suspense boundary, likely due to ' +
    
  694.           'an error during server rendering. Switched to client rendering.',
    
  695.       );
    
  696.     }
    
  697.   });
    
  698. 
    
  699.   // @gate enableFormActions
    
  700.   it('allows rendering extra hidden inputs in a form', async () => {
    
  701.     const element = document.createElement('div');
    
  702.     element.innerHTML =
    
  703.       '<form>' +
    
  704.       '<input type="hidden" /><input type="hidden" name="a" value="A" />' +
    
  705.       '<input type="hidden" /><input type="submit" name="b" value="B" />' +
    
  706.       '<input type="hidden" /><button name="c" value="C"></button>' +
    
  707.       '<input type="hidden" />' +
    
  708.       '</form>';
    
  709.     const form = element.firstChild;
    
  710.     const ref = React.createRef();
    
  711.     const a = React.createRef();
    
  712.     const b = React.createRef();
    
  713.     const c = React.createRef();
    
  714.     await act(async () => {
    
  715.       ReactDOMClient.hydrateRoot(
    
  716.         element,
    
  717.         <form ref={ref}>
    
  718.           <input type="hidden" name="a" value="A" ref={a} />
    
  719.           <input type="submit" name="b" value="B" ref={b} />
    
  720.           <button name="c" value="C" ref={c} />
    
  721.         </form>,
    
  722.       );
    
  723.     });
    
  724. 
    
  725.     // The content should not have been client rendered.
    
  726.     expect(ref.current).toBe(form);
    
  727. 
    
  728.     expect(a.current.name).toBe('a');
    
  729.     expect(a.current.value).toBe('A');
    
  730.     expect(b.current.name).toBe('b');
    
  731.     expect(b.current.value).toBe('B');
    
  732.     expect(c.current.name).toBe('c');
    
  733.     expect(c.current.value).toBe('C');
    
  734.   });
    
  735. 
    
  736.   // @gate enableFormActions
    
  737.   it('allows rendering extra hidden inputs immediately before a text instance', async () => {
    
  738.     const element = document.createElement('div');
    
  739.     element.innerHTML =
    
  740.       '<button><input name="a" value="A" type="hidden" />Click <!-- -->me</button>';
    
  741.     const button = element.firstChild;
    
  742.     const ref = React.createRef();
    
  743.     const extraText = 'me';
    
  744. 
    
  745.     await act(() => {
    
  746.       ReactDOMClient.hydrateRoot(
    
  747.         element,
    
  748.         <button ref={ref}>Click {extraText}</button>,
    
  749.       );
    
  750.     });
    
  751. 
    
  752.     expect(ref.current).toBe(button);
    
  753.   });
    
  754. });