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 node
    
  9.  */
    
  10. 
    
  11. 'use strict';
    
  12. 
    
  13. let React;
    
  14. let ReactDOMServer;
    
  15. let PropTypes;
    
  16. let ReactCurrentDispatcher;
    
  17. 
    
  18. describe('ReactDOMServer', () => {
    
  19.   beforeEach(() => {
    
  20.     jest.resetModules();
    
  21.     React = require('react');
    
  22.     PropTypes = require('prop-types');
    
  23.     ReactDOMServer = require('react-dom/server');
    
  24.     ReactCurrentDispatcher =
    
  25.       React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
    
  26.         .ReactCurrentDispatcher;
    
  27.   });
    
  28. 
    
  29.   describe('renderToString', () => {
    
  30.     it('should generate simple markup', () => {
    
  31.       const response = ReactDOMServer.renderToString(<span>hello world</span>);
    
  32.       expect(response).toMatch(new RegExp('<span' + '>hello world</span>'));
    
  33.     });
    
  34. 
    
  35.     it('should generate simple markup for self-closing tags', () => {
    
  36.       const response = ReactDOMServer.renderToString(<img />);
    
  37.       expect(response).toMatch(new RegExp('<img' + '/>'));
    
  38.     });
    
  39. 
    
  40.     it('should generate comment markup for component returns null', () => {
    
  41.       class NullComponent extends React.Component {
    
  42.         render() {
    
  43.           return null;
    
  44.         }
    
  45.       }
    
  46. 
    
  47.       const response = ReactDOMServer.renderToString(<NullComponent />);
    
  48.       expect(response).toBe('');
    
  49.     });
    
  50. 
    
  51.     // TODO: Test that listeners are not registered onto any document/container.
    
  52. 
    
  53.     it('should render composite components', () => {
    
  54.       class Parent extends React.Component {
    
  55.         render() {
    
  56.           return (
    
  57.             <div>
    
  58.               <Child name="child" />
    
  59.             </div>
    
  60.           );
    
  61.         }
    
  62.       }
    
  63. 
    
  64.       class Child extends React.Component {
    
  65.         render() {
    
  66.           return <span>My name is {this.props.name}</span>;
    
  67.         }
    
  68.       }
    
  69. 
    
  70.       const response = ReactDOMServer.renderToString(<Parent />);
    
  71.       expect(response).toMatch(
    
  72.         new RegExp(
    
  73.           '<div>' +
    
  74.             '<span' +
    
  75.             '>' +
    
  76.             'My name is <!-- -->child' +
    
  77.             '</span>' +
    
  78.             '</div>',
    
  79.         ),
    
  80.       );
    
  81.     });
    
  82. 
    
  83.     it('should only execute certain lifecycle methods', () => {
    
  84.       function runTest() {
    
  85.         const lifecycle = [];
    
  86. 
    
  87.         class TestComponent extends React.Component {
    
  88.           constructor(props) {
    
  89.             super(props);
    
  90.             lifecycle.push('getInitialState');
    
  91.             this.state = {name: 'TestComponent'};
    
  92.           }
    
  93. 
    
  94.           UNSAFE_componentWillMount() {
    
  95.             lifecycle.push('componentWillMount');
    
  96.           }
    
  97. 
    
  98.           componentDidMount() {
    
  99.             lifecycle.push('componentDidMount');
    
  100.           }
    
  101. 
    
  102.           render() {
    
  103.             lifecycle.push('render');
    
  104.             return <span>Component name: {this.state.name}</span>;
    
  105.           }
    
  106. 
    
  107.           UNSAFE_componentWillUpdate() {
    
  108.             lifecycle.push('componentWillUpdate');
    
  109.           }
    
  110. 
    
  111.           componentDidUpdate() {
    
  112.             lifecycle.push('componentDidUpdate');
    
  113.           }
    
  114. 
    
  115.           shouldComponentUpdate() {
    
  116.             lifecycle.push('shouldComponentUpdate');
    
  117.           }
    
  118. 
    
  119.           UNSAFE_componentWillReceiveProps() {
    
  120.             lifecycle.push('componentWillReceiveProps');
    
  121.           }
    
  122. 
    
  123.           componentWillUnmount() {
    
  124.             lifecycle.push('componentWillUnmount');
    
  125.           }
    
  126.         }
    
  127. 
    
  128.         const response = ReactDOMServer.renderToString(<TestComponent />);
    
  129. 
    
  130.         expect(response).toMatch(
    
  131.           new RegExp(
    
  132.             '<span>' + 'Component name: <!-- -->TestComponent' + '</span>',
    
  133.           ),
    
  134.         );
    
  135.         expect(lifecycle).toEqual([
    
  136.           'getInitialState',
    
  137.           'componentWillMount',
    
  138.           'render',
    
  139.         ]);
    
  140.       }
    
  141. 
    
  142.       runTest();
    
  143.     });
    
  144. 
    
  145.     it('should throw with silly args', () => {
    
  146.       expect(
    
  147.         ReactDOMServer.renderToString.bind(ReactDOMServer, {x: 123}),
    
  148.       ).toThrowError(
    
  149.         'Objects are not valid as a React child (found: object with keys {x})',
    
  150.       );
    
  151.     });
    
  152. 
    
  153.     it('should throw prop mapping error for an <iframe /> with invalid props', () => {
    
  154.       expect(() => {
    
  155.         ReactDOMServer.renderToString(<iframe style="border:none;" />);
    
  156.       }).toThrowError(
    
  157.         'The `style` prop expects a mapping from style properties to values, not ' +
    
  158.           "a string. For example, style={{marginRight: spacing + 'em'}} when using JSX.",
    
  159.       );
    
  160.     });
    
  161. 
    
  162.     it('should not crash on poisoned hasOwnProperty', () => {
    
  163.       let html;
    
  164.       expect(
    
  165.         () =>
    
  166.           (html = ReactDOMServer.renderToString(
    
  167.             <div hasOwnProperty="poison">
    
  168.               <span unknown="test" />
    
  169.             </div>,
    
  170.           )),
    
  171.       ).toErrorDev(['React does not recognize the `hasOwnProperty` prop']);
    
  172.       expect(html).toContain('<span unknown="test">');
    
  173.     });
    
  174.   });
    
  175. 
    
  176.   describe('renderToStaticMarkup', () => {
    
  177.     it('should not put checksum and React ID on components', () => {
    
  178.       class NestedComponent extends React.Component {
    
  179.         render() {
    
  180.           return <div>inner text</div>;
    
  181.         }
    
  182.       }
    
  183. 
    
  184.       class TestComponent extends React.Component {
    
  185.         render() {
    
  186.           return (
    
  187.             <span>
    
  188.               <NestedComponent />
    
  189.             </span>
    
  190.           );
    
  191.         }
    
  192.       }
    
  193. 
    
  194.       const response = ReactDOMServer.renderToStaticMarkup(<TestComponent />);
    
  195. 
    
  196.       expect(response).toBe('<span><div>inner text</div></span>');
    
  197.     });
    
  198. 
    
  199.     it('should not put checksum and React ID on text components', () => {
    
  200.       class TestComponent extends React.Component {
    
  201.         render() {
    
  202.           return (
    
  203.             <span>
    
  204.               {'hello'} {'world'}
    
  205.             </span>
    
  206.           );
    
  207.         }
    
  208.       }
    
  209. 
    
  210.       const response = ReactDOMServer.renderToStaticMarkup(<TestComponent />);
    
  211. 
    
  212.       expect(response).toBe('<span>hello world</span>');
    
  213.     });
    
  214. 
    
  215.     it('should not use comments for empty nodes', () => {
    
  216.       class TestComponent extends React.Component {
    
  217.         render() {
    
  218.           return null;
    
  219.         }
    
  220.       }
    
  221. 
    
  222.       const response = ReactDOMServer.renderToStaticMarkup(<TestComponent />);
    
  223. 
    
  224.       expect(response).toBe('');
    
  225.     });
    
  226. 
    
  227.     it('should only execute certain lifecycle methods', () => {
    
  228.       function runTest() {
    
  229.         const lifecycle = [];
    
  230. 
    
  231.         class TestComponent extends React.Component {
    
  232.           constructor(props) {
    
  233.             super(props);
    
  234.             lifecycle.push('getInitialState');
    
  235.             this.state = {name: 'TestComponent'};
    
  236.           }
    
  237. 
    
  238.           UNSAFE_componentWillMount() {
    
  239.             lifecycle.push('componentWillMount');
    
  240.           }
    
  241. 
    
  242.           componentDidMount() {
    
  243.             lifecycle.push('componentDidMount');
    
  244.           }
    
  245. 
    
  246.           render() {
    
  247.             lifecycle.push('render');
    
  248.             return <span>Component name: {this.state.name}</span>;
    
  249.           }
    
  250. 
    
  251.           UNSAFE_componentWillUpdate() {
    
  252.             lifecycle.push('componentWillUpdate');
    
  253.           }
    
  254. 
    
  255.           componentDidUpdate() {
    
  256.             lifecycle.push('componentDidUpdate');
    
  257.           }
    
  258. 
    
  259.           shouldComponentUpdate() {
    
  260.             lifecycle.push('shouldComponentUpdate');
    
  261.           }
    
  262. 
    
  263.           UNSAFE_componentWillReceiveProps() {
    
  264.             lifecycle.push('componentWillReceiveProps');
    
  265.           }
    
  266. 
    
  267.           componentWillUnmount() {
    
  268.             lifecycle.push('componentWillUnmount');
    
  269.           }
    
  270.         }
    
  271. 
    
  272.         const response = ReactDOMServer.renderToStaticMarkup(<TestComponent />);
    
  273. 
    
  274.         expect(response).toBe('<span>Component name: TestComponent</span>');
    
  275.         expect(lifecycle).toEqual([
    
  276.           'getInitialState',
    
  277.           'componentWillMount',
    
  278.           'render',
    
  279.         ]);
    
  280.       }
    
  281. 
    
  282.       runTest();
    
  283.     });
    
  284. 
    
  285.     it('should throw with silly args', () => {
    
  286.       expect(
    
  287.         ReactDOMServer.renderToStaticMarkup.bind(ReactDOMServer, {x: 123}),
    
  288.       ).toThrowError(
    
  289.         'Objects are not valid as a React child (found: object with keys {x})',
    
  290.       );
    
  291.     });
    
  292. 
    
  293.     it('allows setState in componentWillMount without using DOM', () => {
    
  294.       class Component extends React.Component {
    
  295.         UNSAFE_componentWillMount() {
    
  296.           this.setState({text: 'hello, world'});
    
  297.         }
    
  298. 
    
  299.         render() {
    
  300.           return <div>{this.state.text}</div>;
    
  301.         }
    
  302.       }
    
  303.       const markup = ReactDOMServer.renderToStaticMarkup(<Component />);
    
  304.       expect(markup).toContain('hello, world');
    
  305.     });
    
  306. 
    
  307.     it('allows setState in componentWillMount with custom constructor', () => {
    
  308.       class Component extends React.Component {
    
  309.         constructor() {
    
  310.           super();
    
  311.           this.state = {text: 'default state'};
    
  312.         }
    
  313. 
    
  314.         UNSAFE_componentWillMount() {
    
  315.           this.setState({text: 'hello, world'});
    
  316.         }
    
  317. 
    
  318.         render() {
    
  319.           return <div>{this.state.text}</div>;
    
  320.         }
    
  321.       }
    
  322.       const markup = ReactDOMServer.renderToStaticMarkup(<Component />);
    
  323.       expect(markup).toContain('hello, world');
    
  324.     });
    
  325. 
    
  326.     it('renders with props when using custom constructor', () => {
    
  327.       class Component extends React.Component {
    
  328.         constructor() {
    
  329.           super();
    
  330.         }
    
  331. 
    
  332.         render() {
    
  333.           return <div>{this.props.text}</div>;
    
  334.         }
    
  335.       }
    
  336. 
    
  337.       const markup = ReactDOMServer.renderToStaticMarkup(
    
  338.         <Component text="hello, world" />,
    
  339.       );
    
  340.       expect(markup).toContain('hello, world');
    
  341.     });
    
  342. 
    
  343.     // @gate !disableLegacyContext
    
  344.     it('renders with context when using custom constructor', () => {
    
  345.       class Component extends React.Component {
    
  346.         constructor() {
    
  347.           super();
    
  348.         }
    
  349. 
    
  350.         render() {
    
  351.           return <div>{this.context.text}</div>;
    
  352.         }
    
  353.       }
    
  354. 
    
  355.       Component.contextTypes = {
    
  356.         text: PropTypes.string.isRequired,
    
  357.       };
    
  358. 
    
  359.       class ContextProvider extends React.Component {
    
  360.         getChildContext() {
    
  361.           return {
    
  362.             text: 'hello, world',
    
  363.           };
    
  364.         }
    
  365. 
    
  366.         render() {
    
  367.           return this.props.children;
    
  368.         }
    
  369.       }
    
  370. 
    
  371.       ContextProvider.childContextTypes = {
    
  372.         text: PropTypes.string,
    
  373.       };
    
  374. 
    
  375.       const markup = ReactDOMServer.renderToStaticMarkup(
    
  376.         <ContextProvider>
    
  377.           <Component />
    
  378.         </ContextProvider>,
    
  379.       );
    
  380.       expect(markup).toContain('hello, world');
    
  381.     });
    
  382. 
    
  383.     it('renders with new context API', () => {
    
  384.       const Context = React.createContext(0);
    
  385. 
    
  386.       function Consumer(props) {
    
  387.         return (
    
  388.           <Context.Consumer>{value => 'Result: ' + value}</Context.Consumer>
    
  389.         );
    
  390.       }
    
  391. 
    
  392.       const Indirection = React.Fragment;
    
  393. 
    
  394.       function App(props) {
    
  395.         return (
    
  396.           <Context.Provider value={props.value}>
    
  397.             <Context.Provider value={2}>
    
  398.               <Consumer />
    
  399.             </Context.Provider>
    
  400.             <Indirection>
    
  401.               <Indirection>
    
  402.                 <Consumer />
    
  403.                 <Context.Provider value={3}>
    
  404.                   <Consumer />
    
  405.                 </Context.Provider>
    
  406.               </Indirection>
    
  407.             </Indirection>
    
  408.             <Consumer />
    
  409.           </Context.Provider>
    
  410.         );
    
  411.       }
    
  412. 
    
  413.       const markup = ReactDOMServer.renderToStaticMarkup(<App value={1} />);
    
  414.       // Extract the numbers rendered by the consumers
    
  415.       const results = markup.match(/\d+/g).map(Number);
    
  416.       expect(results).toEqual([2, 1, 3, 1]);
    
  417.     });
    
  418. 
    
  419.     it('renders with dispatcher.readContext mechanism', () => {
    
  420.       const Context = React.createContext(0);
    
  421. 
    
  422.       function readContext(context) {
    
  423.         return ReactCurrentDispatcher.current.readContext(context);
    
  424.       }
    
  425. 
    
  426.       function Consumer(props) {
    
  427.         return 'Result: ' + readContext(Context);
    
  428.       }
    
  429. 
    
  430.       const Indirection = React.Fragment;
    
  431. 
    
  432.       function App(props) {
    
  433.         return (
    
  434.           <Context.Provider value={props.value}>
    
  435.             <Context.Provider value={2}>
    
  436.               <Consumer />
    
  437.             </Context.Provider>
    
  438.             <Indirection>
    
  439.               <Indirection>
    
  440.                 <Consumer />
    
  441.                 <Context.Provider value={3}>
    
  442.                   <Consumer />
    
  443.                 </Context.Provider>
    
  444.               </Indirection>
    
  445.             </Indirection>
    
  446.             <Consumer />
    
  447.           </Context.Provider>
    
  448.         );
    
  449.       }
    
  450. 
    
  451.       const markup = ReactDOMServer.renderToStaticMarkup(<App value={1} />);
    
  452.       // Extract the numbers rendered by the consumers
    
  453.       const results = markup.match(/\d+/g).map(Number);
    
  454.       expect(results).toEqual([2, 1, 3, 1]);
    
  455.     });
    
  456. 
    
  457.     it('renders context API, reentrancy', () => {
    
  458.       const Context = React.createContext(0);
    
  459. 
    
  460.       function Consumer(props) {
    
  461.         return (
    
  462.           <Context.Consumer>{value => 'Result: ' + value}</Context.Consumer>
    
  463.         );
    
  464.       }
    
  465. 
    
  466.       let reentrantMarkup;
    
  467.       function Reentrant() {
    
  468.         reentrantMarkup = ReactDOMServer.renderToStaticMarkup(
    
  469.           <App value={1} reentrant={false} />,
    
  470.         );
    
  471.         return null;
    
  472.       }
    
  473. 
    
  474.       const Indirection = React.Fragment;
    
  475. 
    
  476.       function App(props) {
    
  477.         return (
    
  478.           <Context.Provider value={props.value}>
    
  479.             {props.reentrant && <Reentrant />}
    
  480.             <Context.Provider value={2}>
    
  481.               <Consumer />
    
  482.             </Context.Provider>
    
  483.             <Indirection>
    
  484.               <Indirection>
    
  485.                 <Consumer />
    
  486.                 <Context.Provider value={3}>
    
  487.                   <Consumer />
    
  488.                 </Context.Provider>
    
  489.               </Indirection>
    
  490.             </Indirection>
    
  491.             <Consumer />
    
  492.           </Context.Provider>
    
  493.         );
    
  494.       }
    
  495. 
    
  496.       const markup = ReactDOMServer.renderToStaticMarkup(
    
  497.         <App value={1} reentrant={true} />,
    
  498.       );
    
  499.       // Extract the numbers rendered by the consumers
    
  500.       const results = markup.match(/\d+/g).map(Number);
    
  501.       const reentrantResults = reentrantMarkup.match(/\d+/g).map(Number);
    
  502.       expect(results).toEqual([2, 1, 3, 1]);
    
  503.       expect(reentrantResults).toEqual([2, 1, 3, 1]);
    
  504.     });
    
  505. 
    
  506.     it('renders components with different batching strategies', () => {
    
  507.       class StaticComponent extends React.Component {
    
  508.         render() {
    
  509.           const staticContent = ReactDOMServer.renderToStaticMarkup(
    
  510.             <div>
    
  511.               <img src="foo-bar.jpg" />
    
  512.             </div>,
    
  513.           );
    
  514.           return <div dangerouslySetInnerHTML={{__html: staticContent}} />;
    
  515.         }
    
  516.       }
    
  517. 
    
  518.       class Component extends React.Component {
    
  519.         UNSAFE_componentWillMount() {
    
  520.           this.setState({text: 'hello, world'});
    
  521.         }
    
  522. 
    
  523.         render() {
    
  524.           return <div>{this.state.text}</div>;
    
  525.         }
    
  526.       }
    
  527. 
    
  528.       expect(
    
  529.         ReactDOMServer.renderToString.bind(
    
  530.           ReactDOMServer,
    
  531.           <div>
    
  532.             <StaticComponent />
    
  533.             <Component />
    
  534.           </div>,
    
  535.         ),
    
  536.       ).not.toThrow();
    
  537.     });
    
  538. 
    
  539.     it('renders synchronously resolved lazy component', () => {
    
  540.       const LazyFoo = React.lazy(() => ({
    
  541.         then(resolve) {
    
  542.           resolve({
    
  543.             default: function Foo({id}) {
    
  544.               return <div id={id}>lazy</div>;
    
  545.             },
    
  546.           });
    
  547.         },
    
  548.       }));
    
  549. 
    
  550.       expect(ReactDOMServer.renderToStaticMarkup(<LazyFoo id="foo" />)).toEqual(
    
  551.         '<div id="foo">lazy</div>',
    
  552.       );
    
  553.     });
    
  554. 
    
  555.     it('throws error from synchronously rejected lazy component', () => {
    
  556.       const LazyFoo = React.lazy(() => ({
    
  557.         then(resolve, reject) {
    
  558.           reject(new Error('Bad lazy'));
    
  559.         },
    
  560.       }));
    
  561. 
    
  562.       expect(() => ReactDOMServer.renderToStaticMarkup(<LazyFoo />)).toThrow(
    
  563.         'Bad lazy',
    
  564.       );
    
  565.     });
    
  566. 
    
  567.     it('aborts synchronously any suspended tasks and renders their fallbacks', () => {
    
  568.       const promise = new Promise(res => {});
    
  569.       function Suspender() {
    
  570.         throw promise;
    
  571.       }
    
  572.       const response = ReactDOMServer.renderToStaticMarkup(
    
  573.         <React.Suspense fallback={'fallback'}>
    
  574.           <Suspender />
    
  575.         </React.Suspense>,
    
  576.       );
    
  577.       expect(response).toEqual('fallback');
    
  578.     });
    
  579.   });
    
  580. 
    
  581.   describe('renderToNodeStream', () => {
    
  582.     it('should generate simple markup', () => {
    
  583.       const SuccessfulElement = React.createElement(() => <img />);
    
  584.       let response;
    
  585.       expect(() => {
    
  586.         response = ReactDOMServer.renderToNodeStream(SuccessfulElement);
    
  587.       }).toErrorDev(
    
  588.         'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  589.         {withoutStack: true},
    
  590.       );
    
  591.       expect(response.read().toString()).toMatch(new RegExp('<img' + '/>'));
    
  592.     });
    
  593. 
    
  594.     it('should handle errors correctly', () => {
    
  595.       const FailingElement = React.createElement(() => {
    
  596.         throw new Error('An Error');
    
  597.       });
    
  598.       let response;
    
  599.       expect(() => {
    
  600.         response = ReactDOMServer.renderToNodeStream(FailingElement);
    
  601.       }).toErrorDev(
    
  602.         'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  603.         {withoutStack: true},
    
  604.       );
    
  605.       return new Promise(resolve => {
    
  606.         response.once('error', () => {
    
  607.           resolve();
    
  608.         });
    
  609.         expect(response.read()).toBeNull();
    
  610.       });
    
  611.     });
    
  612.   });
    
  613. 
    
  614.   describe('renderToStaticNodeStream', () => {
    
  615.     it('should generate simple markup', () => {
    
  616.       const SuccessfulElement = React.createElement(() => <img />);
    
  617.       const response =
    
  618.         ReactDOMServer.renderToStaticNodeStream(SuccessfulElement);
    
  619.       expect(response.read().toString()).toMatch(new RegExp('<img' + '/>'));
    
  620.     });
    
  621. 
    
  622.     it('should handle errors correctly', () => {
    
  623.       const FailingElement = React.createElement(() => {
    
  624.         throw new Error('An Error');
    
  625.       });
    
  626.       const response = ReactDOMServer.renderToStaticNodeStream(FailingElement);
    
  627.       return new Promise(resolve => {
    
  628.         response.once('error', () => {
    
  629.           resolve();
    
  630.         });
    
  631.         expect(response.read()).toBeNull();
    
  632.       });
    
  633.     });
    
  634. 
    
  635.     it('should refer users to new apis when using suspense', async () => {
    
  636.       let resolve = null;
    
  637.       const promise = new Promise(res => {
    
  638.         resolve = () => {
    
  639.           resolved = true;
    
  640.           res();
    
  641.         };
    
  642.       });
    
  643.       let resolved = false;
    
  644.       function Suspender() {
    
  645.         if (resolved) {
    
  646.           return 'resolved';
    
  647.         }
    
  648.         throw promise;
    
  649.       }
    
  650. 
    
  651.       let response;
    
  652.       expect(() => {
    
  653.         response = ReactDOMServer.renderToNodeStream(
    
  654.           <div>
    
  655.             <React.Suspense fallback={'fallback'}>
    
  656.               <Suspender />
    
  657.             </React.Suspense>
    
  658.           </div>,
    
  659.         );
    
  660.       }).toErrorDev(
    
  661.         'renderToNodeStream is deprecated. Use renderToPipeableStream instead.',
    
  662.         {withoutStack: true},
    
  663.       );
    
  664.       await resolve();
    
  665.       expect(response.read().toString()).toEqual(
    
  666.         '<div><!--$-->resolved<!-- --><!--/$--></div>',
    
  667.       );
    
  668.     });
    
  669.   });
    
  670. 
    
  671.   it('warns with a no-op when an async setState is triggered', () => {
    
  672.     class Foo extends React.Component {
    
  673.       UNSAFE_componentWillMount() {
    
  674.         this.setState({text: 'hello'});
    
  675.         setTimeout(() => {
    
  676.           this.setState({text: 'error'});
    
  677.         });
    
  678.       }
    
  679.       render() {
    
  680.         return <div onClick={() => {}}>{this.state.text}</div>;
    
  681.       }
    
  682.     }
    
  683. 
    
  684.     ReactDOMServer.renderToString(<Foo />);
    
  685.     expect(() => jest.runOnlyPendingTimers()).toErrorDev(
    
  686.       'Warning: setState(...): Can only update a mounting component.' +
    
  687.         ' This usually means you called setState() outside componentWillMount() on the server.' +
    
  688.         ' This is a no-op.\n\nPlease check the code for the Foo component.',
    
  689.       {withoutStack: true},
    
  690.     );
    
  691. 
    
  692.     const markup = ReactDOMServer.renderToStaticMarkup(<Foo />);
    
  693.     expect(markup).toBe('<div>hello</div>');
    
  694.     // No additional warnings are expected
    
  695.     jest.runOnlyPendingTimers();
    
  696.   });
    
  697. 
    
  698.   it('warns with a no-op when an async forceUpdate is triggered', () => {
    
  699.     class Baz extends React.Component {
    
  700.       UNSAFE_componentWillMount() {
    
  701.         this.forceUpdate();
    
  702.         setTimeout(() => {
    
  703.           this.forceUpdate();
    
  704.         });
    
  705.       }
    
  706. 
    
  707.       render() {
    
  708.         return <div onClick={() => {}} />;
    
  709.       }
    
  710.     }
    
  711. 
    
  712.     ReactDOMServer.renderToString(<Baz />);
    
  713.     expect(() => jest.runOnlyPendingTimers()).toErrorDev(
    
  714.       'Warning: forceUpdate(...): Can only update a mounting component. ' +
    
  715.         'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
    
  716.         'This is a no-op.\n\nPlease check the code for the Baz component.',
    
  717.       {withoutStack: true},
    
  718.     );
    
  719.     const markup = ReactDOMServer.renderToStaticMarkup(<Baz />);
    
  720.     expect(markup).toBe('<div></div>');
    
  721.   });
    
  722. 
    
  723.   it('does not get confused by throwing null', () => {
    
  724.     function Bad() {
    
  725.       // eslint-disable-next-line no-throw-literal
    
  726.       throw null;
    
  727.     }
    
  728. 
    
  729.     let didError;
    
  730.     let error;
    
  731.     try {
    
  732.       ReactDOMServer.renderToString(<Bad />);
    
  733.     } catch (err) {
    
  734.       didError = true;
    
  735.       error = err;
    
  736.     }
    
  737.     expect(didError).toBe(true);
    
  738.     expect(error).toBe(null);
    
  739.   });
    
  740. 
    
  741.   it('does not get confused by throwing undefined', () => {
    
  742.     function Bad() {
    
  743.       // eslint-disable-next-line no-throw-literal
    
  744.       throw undefined;
    
  745.     }
    
  746. 
    
  747.     let didError;
    
  748.     let error;
    
  749.     try {
    
  750.       ReactDOMServer.renderToString(<Bad />);
    
  751.     } catch (err) {
    
  752.       didError = true;
    
  753.       error = err;
    
  754.     }
    
  755.     expect(didError).toBe(true);
    
  756.     expect(error).toBe(undefined);
    
  757.   });
    
  758. 
    
  759.   it('does not get confused by throwing a primitive', () => {
    
  760.     function Bad() {
    
  761.       // eslint-disable-next-line no-throw-literal
    
  762.       throw 'foo';
    
  763.     }
    
  764. 
    
  765.     let didError;
    
  766.     let error;
    
  767.     try {
    
  768.       ReactDOMServer.renderToString(<Bad />);
    
  769.     } catch (err) {
    
  770.       didError = true;
    
  771.       error = err;
    
  772.     }
    
  773.     expect(didError).toBe(true);
    
  774.     expect(error).toBe('foo');
    
  775.   });
    
  776. 
    
  777.   it('should throw (in dev) when children are mutated during render', () => {
    
  778.     function Wrapper(props) {
    
  779.       props.children[1] = <p key={1} />; // Mutation is illegal
    
  780.       return <div>{props.children}</div>;
    
  781.     }
    
  782.     if (__DEV__) {
    
  783.       expect(() => {
    
  784.         ReactDOMServer.renderToStaticMarkup(
    
  785.           <Wrapper>
    
  786.             <span key={0} />
    
  787.             <span key={1} />
    
  788.             <span key={2} />
    
  789.           </Wrapper>,
    
  790.         );
    
  791.       }).toThrowError(/Cannot assign to read only property.*/);
    
  792.     } else {
    
  793.       expect(
    
  794.         ReactDOMServer.renderToStaticMarkup(
    
  795.           <Wrapper>
    
  796.             <span key={0} />
    
  797.             <span key={1} />
    
  798.             <span key={2} />
    
  799.           </Wrapper>,
    
  800.         ),
    
  801.       ).toContain('<p>');
    
  802.     }
    
  803.   });
    
  804. 
    
  805.   it('warns about lowercase html but not in svg tags', () => {
    
  806.     function CompositeG(props) {
    
  807.       // Make sure namespace passes through composites
    
  808.       return <g>{props.children}</g>;
    
  809.     }
    
  810.     expect(() =>
    
  811.       ReactDOMServer.renderToStaticMarkup(
    
  812.         <div>
    
  813.           <inPUT />
    
  814.           <svg>
    
  815.             <CompositeG>
    
  816.               <linearGradient />
    
  817.               <foreignObject>
    
  818.                 {/* back to HTML */}
    
  819.                 <iFrame />
    
  820.               </foreignObject>
    
  821.             </CompositeG>
    
  822.           </svg>
    
  823.         </div>,
    
  824.       ),
    
  825.     ).toErrorDev([
    
  826.       'Warning: <inPUT /> is using incorrect casing. ' +
    
  827.         'Use PascalCase for React components, ' +
    
  828.         'or lowercase for HTML elements.',
    
  829.       // linearGradient doesn't warn
    
  830.       'Warning: <iFrame /> is using incorrect casing. ' +
    
  831.         'Use PascalCase for React components, ' +
    
  832.         'or lowercase for HTML elements.',
    
  833.     ]);
    
  834.   });
    
  835. 
    
  836.   it('should warn about contentEditable and children', () => {
    
  837.     expect(() =>
    
  838.       ReactDOMServer.renderToString(<div contentEditable={true} children="" />),
    
  839.     ).toErrorDev(
    
  840.       'Warning: A component is `contentEditable` and contains `children` ' +
    
  841.         'managed by React. It is now your responsibility to guarantee that ' +
    
  842.         'none of those nodes are unexpectedly modified or duplicated. This ' +
    
  843.         'is probably not intentional.\n    in div (at **)',
    
  844.     );
    
  845.   });
    
  846. 
    
  847.   it('should warn when server rendering a class with a render method that does not extend React.Component', () => {
    
  848.     class ClassWithRenderNotExtended {
    
  849.       render() {
    
  850.         return <div />;
    
  851.       }
    
  852.     }
    
  853. 
    
  854.     expect(() => {
    
  855.       expect(() =>
    
  856.         ReactDOMServer.renderToString(<ClassWithRenderNotExtended />),
    
  857.       ).toThrow(TypeError);
    
  858.     }).toErrorDev(
    
  859.       'Warning: The <ClassWithRenderNotExtended /> component appears to have a render method, ' +
    
  860.         "but doesn't extend React.Component. This is likely to cause errors. " +
    
  861.         'Change ClassWithRenderNotExtended to extend React.Component instead.',
    
  862.     );
    
  863. 
    
  864.     // Test deduplication
    
  865.     expect(() => {
    
  866.       ReactDOMServer.renderToString(<ClassWithRenderNotExtended />);
    
  867.     }).toThrow(TypeError);
    
  868.   });
    
  869. 
    
  870.   // We're just testing importing, not using it.
    
  871.   // It is important because even isomorphic components may import it.
    
  872.   it('can import react-dom in Node environment', () => {
    
  873.     if (
    
  874.       typeof requestAnimationFrame !== 'undefined' ||
    
  875.       global.hasOwnProperty('requestAnimationFrame') ||
    
  876.       typeof requestIdleCallback !== 'undefined' ||
    
  877.       global.hasOwnProperty('requestIdleCallback') ||
    
  878.       typeof window !== 'undefined' ||
    
  879.       global.hasOwnProperty('window')
    
  880.     ) {
    
  881.       // Don't remove this. This test is specifically checking
    
  882.       // what happens when they *don't* exist. It's useless otherwise.
    
  883.       throw new Error('Expected this test to run in a Node environment.');
    
  884.     }
    
  885.     jest.resetModules();
    
  886.     expect(() => {
    
  887.       require('react-dom');
    
  888.     }).not.toThrow();
    
  889.   });
    
  890. 
    
  891.   it('includes a useful stack in warnings', () => {
    
  892.     function A() {
    
  893.       return null;
    
  894.     }
    
  895. 
    
  896.     function B() {
    
  897.       return (
    
  898.         <font>
    
  899.           <C>
    
  900.             <span ariaTypo="no" />
    
  901.           </C>
    
  902.         </font>
    
  903.       );
    
  904.     }
    
  905. 
    
  906.     class C extends React.Component {
    
  907.       render() {
    
  908.         return <b>{this.props.children}</b>;
    
  909.       }
    
  910.     }
    
  911. 
    
  912.     function Child() {
    
  913.       return [<A key="1" />, <B key="2" />, <span ariaTypo2="no" />];
    
  914.     }
    
  915. 
    
  916.     function App() {
    
  917.       return (
    
  918.         <div>
    
  919.           <section />
    
  920.           <span>
    
  921.             <Child />
    
  922.           </span>
    
  923.         </div>
    
  924.       );
    
  925.     }
    
  926. 
    
  927.     expect(() => ReactDOMServer.renderToString(<App />)).toErrorDev([
    
  928.       'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  929.         '    in span (at **)\n' +
    
  930.         '    in b (at **)\n' +
    
  931.         '    in C (at **)\n' +
    
  932.         '    in font (at **)\n' +
    
  933.         '    in B (at **)\n' +
    
  934.         '    in Child (at **)\n' +
    
  935.         '    in span (at **)\n' +
    
  936.         '    in div (at **)\n' +
    
  937.         '    in App (at **)',
    
  938.       'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  939.         '    in span (at **)\n' +
    
  940.         '    in Child (at **)\n' +
    
  941.         '    in span (at **)\n' +
    
  942.         '    in div (at **)\n' +
    
  943.         '    in App (at **)',
    
  944.     ]);
    
  945.   });
    
  946. 
    
  947.   it('reports stacks with re-entrant renderToString() calls', () => {
    
  948.     function Child2(props) {
    
  949.       return <span ariaTypo3="no">{props.children}</span>;
    
  950.     }
    
  951. 
    
  952.     function App2() {
    
  953.       return (
    
  954.         <Child2>
    
  955.           {ReactDOMServer.renderToString(<blink ariaTypo2="no" />)}
    
  956.         </Child2>
    
  957.       );
    
  958.     }
    
  959. 
    
  960.     function Child() {
    
  961.       return (
    
  962.         <span ariaTypo4="no">{ReactDOMServer.renderToString(<App2 />)}</span>
    
  963.       );
    
  964.     }
    
  965. 
    
  966.     function App() {
    
  967.       return (
    
  968.         <div>
    
  969.           <span ariaTypo="no" />
    
  970.           <Child />
    
  971.           <font ariaTypo5="no" />
    
  972.         </div>
    
  973.       );
    
  974.     }
    
  975. 
    
  976.     expect(() => ReactDOMServer.renderToString(<App />)).toErrorDev([
    
  977.       // ReactDOMServer(App > div > span)
    
  978.       'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  979.         '    in span (at **)\n' +
    
  980.         '    in div (at **)\n' +
    
  981.         '    in App (at **)',
    
  982.       // ReactDOMServer(App > div > Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink)
    
  983.       'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  984.         '    in blink (at **)',
    
  985.       // ReactDOMServer(App > div > Child) >>> ReactDOMServer(App2 > Child2 > span)
    
  986.       'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  987.         '    in span (at **)\n' +
    
  988.         '    in Child2 (at **)\n' +
    
  989.         '    in App2 (at **)',
    
  990.       // ReactDOMServer(App > div > Child > span)
    
  991.       'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  992.         '    in span (at **)\n' +
    
  993.         '    in Child (at **)\n' +
    
  994.         '    in div (at **)\n' +
    
  995.         '    in App (at **)',
    
  996.       // ReactDOMServer(App > div > font)
    
  997.       'Invalid ARIA attribute `ariaTypo5`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
    
  998.         '    in font (at **)\n' +
    
  999.         '    in div (at **)\n' +
    
  1000.         '    in App (at **)',
    
  1001.     ]);
    
  1002.   });
    
  1003. 
    
  1004.   it('should warn if an invalid contextType is defined', () => {
    
  1005.     const Context = React.createContext();
    
  1006. 
    
  1007.     class ComponentA extends React.Component {
    
  1008.       // It should warn for both Context.Consumer and Context.Provider
    
  1009.       static contextType = Context.Consumer;
    
  1010.       render() {
    
  1011.         return <div />;
    
  1012.       }
    
  1013.     }
    
  1014.     class ComponentB extends React.Component {
    
  1015.       static contextType = Context.Provider;
    
  1016.       render() {
    
  1017.         return <div />;
    
  1018.       }
    
  1019.     }
    
  1020. 
    
  1021.     expect(() => {
    
  1022.       ReactDOMServer.renderToString(<ComponentA />);
    
  1023.     }).toErrorDev(
    
  1024.       'Warning: ComponentA defines an invalid contextType. ' +
    
  1025.         'contextType should point to the Context object returned by React.createContext(). ' +
    
  1026.         'Did you accidentally pass the Context.Consumer instead?',
    
  1027.     );
    
  1028. 
    
  1029.     // Warnings should be deduped by component type
    
  1030.     ReactDOMServer.renderToString(<ComponentA />);
    
  1031. 
    
  1032.     expect(() => {
    
  1033.       ReactDOMServer.renderToString(<ComponentB />);
    
  1034.     }).toErrorDev(
    
  1035.       'Warning: ComponentB defines an invalid contextType. ' +
    
  1036.         'contextType should point to the Context object returned by React.createContext(). ' +
    
  1037.         'Did you accidentally pass the Context.Provider instead?',
    
  1038.     );
    
  1039.   });
    
  1040. 
    
  1041.   it('should not warn when class contextType is null', () => {
    
  1042.     class Foo extends React.Component {
    
  1043.       static contextType = null; // Handy for conditional declaration
    
  1044.       render() {
    
  1045.         return this.context.hello.world;
    
  1046.       }
    
  1047.     }
    
  1048. 
    
  1049.     expect(() => {
    
  1050.       ReactDOMServer.renderToString(<Foo />);
    
  1051.     }).toThrow("Cannot read property 'world' of undefined");
    
  1052.   });
    
  1053. 
    
  1054.   it('should warn when class contextType is undefined', () => {
    
  1055.     class Foo extends React.Component {
    
  1056.       // This commonly happens with circular deps
    
  1057.       // https://github.com/facebook/react/issues/13969
    
  1058.       static contextType = undefined;
    
  1059.       render() {
    
  1060.         return this.context.hello.world;
    
  1061.       }
    
  1062.     }
    
  1063. 
    
  1064.     expect(() => {
    
  1065.       expect(() => {
    
  1066.         ReactDOMServer.renderToString(<Foo />);
    
  1067.       }).toThrow("Cannot read property 'world' of undefined");
    
  1068.     }).toErrorDev(
    
  1069.       'Foo defines an invalid contextType. ' +
    
  1070.         'contextType should point to the Context object returned by React.createContext(). ' +
    
  1071.         'However, it is set to undefined. ' +
    
  1072.         'This can be caused by a typo or by mixing up named and default imports. ' +
    
  1073.         'This can also happen due to a circular dependency, ' +
    
  1074.         'so try moving the createContext() call to a separate file.',
    
  1075.     );
    
  1076.   });
    
  1077. 
    
  1078.   it('should warn when class contextType is an object', () => {
    
  1079.     class Foo extends React.Component {
    
  1080.       // Can happen due to a typo
    
  1081.       static contextType = {
    
  1082.         x: 42,
    
  1083.         y: 'hello',
    
  1084.       };
    
  1085.       render() {
    
  1086.         return this.context.hello.world;
    
  1087.       }
    
  1088.     }
    
  1089. 
    
  1090.     expect(() => {
    
  1091.       expect(() => {
    
  1092.         ReactDOMServer.renderToString(<Foo />);
    
  1093.       }).toThrow("Cannot read property 'hello' of undefined");
    
  1094.     }).toErrorDev(
    
  1095.       'Foo defines an invalid contextType. ' +
    
  1096.         'contextType should point to the Context object returned by React.createContext(). ' +
    
  1097.         'However, it is set to an object with keys {x, y}.',
    
  1098.     );
    
  1099.   });
    
  1100. 
    
  1101.   it('should warn when class contextType is a primitive', () => {
    
  1102.     class Foo extends React.Component {
    
  1103.       static contextType = 'foo';
    
  1104.       render() {
    
  1105.         return this.context.hello.world;
    
  1106.       }
    
  1107.     }
    
  1108. 
    
  1109.     expect(() => {
    
  1110.       expect(() => {
    
  1111.         ReactDOMServer.renderToString(<Foo />);
    
  1112.       }).toThrow("Cannot read property 'world' of undefined");
    
  1113.     }).toErrorDev(
    
  1114.       'Foo defines an invalid contextType. ' +
    
  1115.         'contextType should point to the Context object returned by React.createContext(). ' +
    
  1116.         'However, it is set to a string.',
    
  1117.     );
    
  1118.   });
    
  1119. 
    
  1120.   describe('custom element server rendering', () => {
    
  1121.     it('String properties should be server rendered for custom elements', () => {
    
  1122.       const output = ReactDOMServer.renderToString(
    
  1123.         <my-custom-element foo="bar" />,
    
  1124.       );
    
  1125.       expect(output).toBe(`<my-custom-element foo="bar"></my-custom-element>`);
    
  1126.     });
    
  1127. 
    
  1128.     it('Number properties should be server rendered for custom elements', () => {
    
  1129.       const output = ReactDOMServer.renderToString(
    
  1130.         <my-custom-element foo={5} />,
    
  1131.       );
    
  1132.       expect(output).toBe(`<my-custom-element foo="5"></my-custom-element>`);
    
  1133.     });
    
  1134. 
    
  1135.     // @gate enableCustomElementPropertySupport
    
  1136.     it('Object properties should not be server rendered for custom elements', () => {
    
  1137.       const output = ReactDOMServer.renderToString(
    
  1138.         <my-custom-element foo={{foo: 'bar'}} />,
    
  1139.       );
    
  1140.       expect(output).toBe(`<my-custom-element></my-custom-element>`);
    
  1141.     });
    
  1142. 
    
  1143.     // @gate enableCustomElementPropertySupport
    
  1144.     it('Array properties should not be server rendered for custom elements', () => {
    
  1145.       const output = ReactDOMServer.renderToString(
    
  1146.         <my-custom-element foo={['foo', 'bar']} />,
    
  1147.       );
    
  1148.       expect(output).toBe(`<my-custom-element></my-custom-element>`);
    
  1149.     });
    
  1150. 
    
  1151.     it('Function properties should not be server rendered for custom elements', () => {
    
  1152.       const output = ReactDOMServer.renderToString(
    
  1153.         <my-custom-element foo={() => console.log('bar')} />,
    
  1154.       );
    
  1155.       expect(output).toBe(`<my-custom-element></my-custom-element>`);
    
  1156.     });
    
  1157.   });
    
  1158. });