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. const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
    
  14. 
    
  15. const TEXT_NODE_TYPE = 3;
    
  16. 
    
  17. let React;
    
  18. let ReactDOM;
    
  19. let ReactDOMServer;
    
  20. let ReactTestUtils;
    
  21. 
    
  22. function initModules() {
    
  23.   jest.resetModules();
    
  24.   React = require('react');
    
  25.   ReactDOM = require('react-dom');
    
  26.   ReactDOMServer = require('react-dom/server');
    
  27.   ReactTestUtils = require('react-dom/test-utils');
    
  28. 
    
  29.   // Make them available to the helpers.
    
  30.   return {
    
  31.     ReactDOM,
    
  32.     ReactDOMServer,
    
  33.     ReactTestUtils,
    
  34.   };
    
  35. }
    
  36. 
    
  37. const {
    
  38.   resetModules,
    
  39.   itRenders,
    
  40.   itThrowsWhenRendering,
    
  41.   serverRender,
    
  42.   streamRender,
    
  43.   clientCleanRender,
    
  44.   clientRenderOnServerString,
    
  45. } = ReactDOMServerIntegrationUtils(initModules);
    
  46. 
    
  47. describe('ReactDOMServerIntegration', () => {
    
  48.   beforeEach(() => {
    
  49.     resetModules();
    
  50.   });
    
  51. 
    
  52.   describe('elements and children', function () {
    
  53.     function expectNode(node, type, value) {
    
  54.       expect(node).not.toBe(null);
    
  55.       expect(node.nodeType).toBe(type);
    
  56.       expect(node.nodeValue).toMatch(value);
    
  57.     }
    
  58. 
    
  59.     function expectTextNode(node, text) {
    
  60.       expectNode(node, TEXT_NODE_TYPE, text);
    
  61.     }
    
  62. 
    
  63.     describe('text children', function () {
    
  64.       itRenders('a div with text', async render => {
    
  65.         const e = await render(<div>Text</div>);
    
  66.         expect(e.tagName).toBe('DIV');
    
  67.         expect(e.childNodes.length).toBe(1);
    
  68.         expectNode(e.firstChild, TEXT_NODE_TYPE, 'Text');
    
  69.       });
    
  70. 
    
  71.       itRenders('a div with text with flanking whitespace', async render => {
    
  72.         // prettier-ignore
    
  73.         const e = await render(<div>  Text </div>);
    
  74.         expect(e.childNodes.length).toBe(1);
    
  75.         expectNode(e.childNodes[0], TEXT_NODE_TYPE, '  Text ');
    
  76.       });
    
  77. 
    
  78.       itRenders('a div with an empty text child', async render => {
    
  79.         const e = await render(<div>{''}</div>);
    
  80.         expect(e.childNodes.length).toBe(0);
    
  81.       });
    
  82. 
    
  83.       itRenders('a div with multiple empty text children', async render => {
    
  84.         const e = await render(
    
  85.           <div>
    
  86.             {''}
    
  87.             {''}
    
  88.             {''}
    
  89.           </div>,
    
  90.         );
    
  91.         expect(e.childNodes.length).toBe(0);
    
  92.         expect(e.textContent).toBe('');
    
  93.       });
    
  94. 
    
  95.       itRenders('a div with multiple whitespace children', async render => {
    
  96.         // prettier-ignore
    
  97.         const e = await render(<div>{' '}{' '}{' '}</div>);
    
  98.         if (
    
  99.           render === serverRender ||
    
  100.           render === clientRenderOnServerString ||
    
  101.           render === streamRender
    
  102.         ) {
    
  103.           // For plain server markup result we have comments between.
    
  104.           // If we're able to hydrate, they remain.
    
  105.           expect(e.childNodes.length).toBe(5);
    
  106.           expectTextNode(e.childNodes[0], ' ');
    
  107.           expectTextNode(e.childNodes[2], ' ');
    
  108.           expectTextNode(e.childNodes[4], ' ');
    
  109.         } else {
    
  110.           expect(e.childNodes.length).toBe(3);
    
  111.           expectTextNode(e.childNodes[0], ' ');
    
  112.           expectTextNode(e.childNodes[1], ' ');
    
  113.           expectTextNode(e.childNodes[2], ' ');
    
  114.         }
    
  115.       });
    
  116. 
    
  117.       itRenders('a div with text sibling to a node', async render => {
    
  118.         const e = await render(
    
  119.           <div>
    
  120.             Text<span>More Text</span>
    
  121.           </div>,
    
  122.         );
    
  123.         expect(e.childNodes.length).toBe(2);
    
  124.         const spanNode = e.childNodes[1];
    
  125.         expectTextNode(e.childNodes[0], 'Text');
    
  126.         expect(spanNode.tagName).toBe('SPAN');
    
  127.         expect(spanNode.childNodes.length).toBe(1);
    
  128.         expectNode(spanNode.firstChild, TEXT_NODE_TYPE, 'More Text');
    
  129.       });
    
  130. 
    
  131.       itRenders('a non-standard element with text', async render => {
    
  132.         // This test suite generally assumes that we get exactly
    
  133.         // the same warnings (or none) for all scenarios including
    
  134.         // SSR + innerHTML, hydration, and client-side rendering.
    
  135.         // However this particular warning fires only when creating
    
  136.         // DOM nodes on the client side. We force it to fire early
    
  137.         // so that it gets deduplicated later, and doesn't fail the test.
    
  138.         expect(() => {
    
  139.           ReactDOM.render(<nonstandard />, document.createElement('div'));
    
  140.         }).toErrorDev('The tag <nonstandard> is unrecognized in this browser.');
    
  141. 
    
  142.         const e = await render(<nonstandard>Text</nonstandard>);
    
  143.         expect(e.tagName).toBe('NONSTANDARD');
    
  144.         expect(e.childNodes.length).toBe(1);
    
  145.         expectNode(e.firstChild, TEXT_NODE_TYPE, 'Text');
    
  146.       });
    
  147. 
    
  148.       itRenders('a custom element with text', async render => {
    
  149.         const e = await render(<custom-element>Text</custom-element>);
    
  150.         expect(e.tagName).toBe('CUSTOM-ELEMENT');
    
  151.         expect(e.childNodes.length).toBe(1);
    
  152.         expectNode(e.firstChild, TEXT_NODE_TYPE, 'Text');
    
  153.       });
    
  154. 
    
  155.       itRenders('a leading blank child with a text sibling', async render => {
    
  156.         const e = await render(<div>{''}foo</div>);
    
  157.         expect(e.childNodes.length).toBe(1);
    
  158.         expectTextNode(e.childNodes[0], 'foo');
    
  159.       });
    
  160. 
    
  161.       itRenders('a trailing blank child with a text sibling', async render => {
    
  162.         const e = await render(<div>foo{''}</div>);
    
  163.         expect(e.childNodes.length).toBe(1);
    
  164.         expectTextNode(e.childNodes[0], 'foo');
    
  165.       });
    
  166. 
    
  167.       itRenders('an element with two text children', async render => {
    
  168.         const e = await render(
    
  169.           <div>
    
  170.             {'foo'}
    
  171.             {'bar'}
    
  172.           </div>,
    
  173.         );
    
  174.         if (
    
  175.           render === serverRender ||
    
  176.           render === clientRenderOnServerString ||
    
  177.           render === streamRender
    
  178.         ) {
    
  179.           // In the server render output there's a comment between them.
    
  180.           expect(e.childNodes.length).toBe(3);
    
  181.           expectTextNode(e.childNodes[0], 'foo');
    
  182.           expectTextNode(e.childNodes[2], 'bar');
    
  183.         } else {
    
  184.           expect(e.childNodes.length).toBe(2);
    
  185.           expectTextNode(e.childNodes[0], 'foo');
    
  186.           expectTextNode(e.childNodes[1], 'bar');
    
  187.         }
    
  188.       });
    
  189. 
    
  190.       itRenders(
    
  191.         'a component returning text node between two text nodes',
    
  192.         async render => {
    
  193.           const B = () => 'b';
    
  194.           const e = await render(
    
  195.             <div>
    
  196.               {'a'}
    
  197.               <B />
    
  198.               {'c'}
    
  199.             </div>,
    
  200.           );
    
  201.           if (
    
  202.             render === serverRender ||
    
  203.             render === clientRenderOnServerString ||
    
  204.             render === streamRender
    
  205.           ) {
    
  206.             // In the server render output there's a comment between them.
    
  207.             expect(e.childNodes.length).toBe(5);
    
  208.             expectTextNode(e.childNodes[0], 'a');
    
  209.             expectTextNode(e.childNodes[2], 'b');
    
  210.             expectTextNode(e.childNodes[4], 'c');
    
  211.           } else {
    
  212.             expect(e.childNodes.length).toBe(3);
    
  213.             expectTextNode(e.childNodes[0], 'a');
    
  214.             expectTextNode(e.childNodes[1], 'b');
    
  215.             expectTextNode(e.childNodes[2], 'c');
    
  216.           }
    
  217.         },
    
  218.       );
    
  219. 
    
  220.       itRenders('a tree with sibling host and text nodes', async render => {
    
  221.         class X extends React.Component {
    
  222.           render() {
    
  223.             return [null, [<Y key="1" />], false];
    
  224.           }
    
  225.         }
    
  226. 
    
  227.         function Y() {
    
  228.           return [<Z key="1" />, ['c']];
    
  229.         }
    
  230. 
    
  231.         function Z() {
    
  232.           return null;
    
  233.         }
    
  234. 
    
  235.         const e = await render(
    
  236.           <div>
    
  237.             {[['a'], 'b']}
    
  238.             <div>
    
  239.               <X key="1" />d
    
  240.             </div>
    
  241.             e
    
  242.           </div>,
    
  243.         );
    
  244.         if (
    
  245.           render === serverRender ||
    
  246.           render === streamRender ||
    
  247.           render === clientRenderOnServerString
    
  248.         ) {
    
  249.           // In the server render output there's comments between text nodes.
    
  250.           expect(e.childNodes.length).toBe(5);
    
  251.           expectTextNode(e.childNodes[0], 'a');
    
  252.           expectTextNode(e.childNodes[2], 'b');
    
  253.           expect(e.childNodes[3].childNodes.length).toBe(3);
    
  254.           expectTextNode(e.childNodes[3].childNodes[0], 'c');
    
  255.           expectTextNode(e.childNodes[3].childNodes[2], 'd');
    
  256.           expectTextNode(e.childNodes[4], 'e');
    
  257.         } else {
    
  258.           expect(e.childNodes.length).toBe(4);
    
  259.           expectTextNode(e.childNodes[0], 'a');
    
  260.           expectTextNode(e.childNodes[1], 'b');
    
  261.           expect(e.childNodes[2].childNodes.length).toBe(2);
    
  262.           expectTextNode(e.childNodes[2].childNodes[0], 'c');
    
  263.           expectTextNode(e.childNodes[2].childNodes[1], 'd');
    
  264.           expectTextNode(e.childNodes[3], 'e');
    
  265.         }
    
  266.       });
    
  267.     });
    
  268. 
    
  269.     describe('number children', function () {
    
  270.       itRenders('a number as single child', async render => {
    
  271.         const e = await render(<div>{3}</div>);
    
  272.         expect(e.textContent).toBe('3');
    
  273.       });
    
  274. 
    
  275.       // zero is falsey, so it could look like no children if the code isn't careful.
    
  276.       itRenders('zero as single child', async render => {
    
  277.         const e = await render(<div>{0}</div>);
    
  278.         expect(e.textContent).toBe('0');
    
  279.       });
    
  280. 
    
  281.       itRenders('an element with number and text children', async render => {
    
  282.         const e = await render(
    
  283.           <div>
    
  284.             {'foo'}
    
  285.             {40}
    
  286.           </div>,
    
  287.         );
    
  288.         // with Fiber, there are just two text nodes.
    
  289.         if (
    
  290.           render === serverRender ||
    
  291.           render === clientRenderOnServerString ||
    
  292.           render === streamRender
    
  293.         ) {
    
  294.           // In the server markup there's a comment between.
    
  295.           expect(e.childNodes.length).toBe(3);
    
  296.           expectTextNode(e.childNodes[0], 'foo');
    
  297.           expectTextNode(e.childNodes[2], '40');
    
  298.         } else {
    
  299.           expect(e.childNodes.length).toBe(2);
    
  300.           expectTextNode(e.childNodes[0], 'foo');
    
  301.           expectTextNode(e.childNodes[1], '40');
    
  302.         }
    
  303.       });
    
  304.     });
    
  305. 
    
  306.     describe('null, false, and undefined children', function () {
    
  307.       itRenders('null single child as blank', async render => {
    
  308.         const e = await render(<div>{null}</div>);
    
  309.         expect(e.childNodes.length).toBe(0);
    
  310.       });
    
  311. 
    
  312.       itRenders('false single child as blank', async render => {
    
  313.         const e = await render(<div>{false}</div>);
    
  314.         expect(e.childNodes.length).toBe(0);
    
  315.       });
    
  316. 
    
  317.       itRenders('undefined single child as blank', async render => {
    
  318.         const e = await render(<div>{undefined}</div>);
    
  319.         expect(e.childNodes.length).toBe(0);
    
  320.       });
    
  321. 
    
  322.       itRenders('a null component children as empty', async render => {
    
  323.         const NullComponent = () => null;
    
  324.         const e = await render(
    
  325.           <div>
    
  326.             <NullComponent />
    
  327.           </div>,
    
  328.         );
    
  329.         expect(e.childNodes.length).toBe(0);
    
  330.       });
    
  331. 
    
  332.       itRenders('null children as blank', async render => {
    
  333.         const e = await render(<div>{null}foo</div>);
    
  334.         expect(e.childNodes.length).toBe(1);
    
  335.         expectTextNode(e.childNodes[0], 'foo');
    
  336.       });
    
  337. 
    
  338.       itRenders('false children as blank', async render => {
    
  339.         const e = await render(<div>{false}foo</div>);
    
  340.         expect(e.childNodes.length).toBe(1);
    
  341.         expectTextNode(e.childNodes[0], 'foo');
    
  342.       });
    
  343. 
    
  344.       itRenders('null and false children together as blank', async render => {
    
  345.         const e = await render(
    
  346.           <div>
    
  347.             {false}
    
  348.             {null}foo{null}
    
  349.             {false}
    
  350.           </div>,
    
  351.         );
    
  352.         expect(e.childNodes.length).toBe(1);
    
  353.         expectTextNode(e.childNodes[0], 'foo');
    
  354.       });
    
  355. 
    
  356.       itRenders('only null and false children as blank', async render => {
    
  357.         const e = await render(
    
  358.           <div>
    
  359.             {false}
    
  360.             {null}
    
  361.             {null}
    
  362.             {false}
    
  363.           </div>,
    
  364.         );
    
  365.         expect(e.childNodes.length).toBe(0);
    
  366.       });
    
  367.     });
    
  368. 
    
  369.     describe('elements with implicit namespaces', function () {
    
  370.       itRenders('an svg element', async render => {
    
  371.         const e = await render(<svg />);
    
  372.         expect(e.childNodes.length).toBe(0);
    
  373.         expect(e.tagName).toBe('svg');
    
  374.         expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
    
  375.       });
    
  376. 
    
  377.       itRenders('svg child element with an attribute', async render => {
    
  378.         const e = await render(<svg viewBox="0 0 0 0" />);
    
  379.         expect(e.childNodes.length).toBe(0);
    
  380.         expect(e.tagName).toBe('svg');
    
  381.         expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
    
  382.         expect(e.getAttribute('viewBox')).toBe('0 0 0 0');
    
  383.       });
    
  384. 
    
  385.       itRenders(
    
  386.         'svg child element with a namespace attribute',
    
  387.         async render => {
    
  388.           let e = await render(
    
  389.             <svg>
    
  390.               <image xlinkHref="http://i.imgur.com/w7GCRPb.png" />
    
  391.             </svg>,
    
  392.           );
    
  393.           e = e.firstChild;
    
  394.           expect(e.childNodes.length).toBe(0);
    
  395.           expect(e.tagName).toBe('image');
    
  396.           expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
    
  397.           expect(e.getAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe(
    
  398.             'http://i.imgur.com/w7GCRPb.png',
    
  399.           );
    
  400.         },
    
  401.       );
    
  402. 
    
  403.       itRenders('svg child element with a badly cased alias', async render => {
    
  404.         let e = await render(
    
  405.           <svg>
    
  406.             <image xlinkhref="http://i.imgur.com/w7GCRPb.png" />
    
  407.           </svg>,
    
  408.           1,
    
  409.         );
    
  410.         e = e.firstChild;
    
  411.         expect(e.hasAttributeNS('http://www.w3.org/1999/xlink', 'href')).toBe(
    
  412.           false,
    
  413.         );
    
  414.         expect(e.getAttribute('xlinkhref')).toBe(
    
  415.           'http://i.imgur.com/w7GCRPb.png',
    
  416.         );
    
  417.       });
    
  418. 
    
  419.       itRenders('svg element with a tabIndex attribute', async render => {
    
  420.         const e = await render(<svg tabIndex="1" />);
    
  421.         expect(e.tabIndex).toBe(1);
    
  422.       });
    
  423. 
    
  424.       itRenders(
    
  425.         'svg element with a badly cased tabIndex attribute',
    
  426.         async render => {
    
  427.           const e = await render(<svg tabindex="1" />, 1);
    
  428.           expect(e.tabIndex).toBe(1);
    
  429.         },
    
  430.       );
    
  431. 
    
  432.       itRenders('svg element with a mixed case name', async render => {
    
  433.         let e = await render(
    
  434.           <svg>
    
  435.             <filter>
    
  436.               <feMorphology />
    
  437.             </filter>
    
  438.           </svg>,
    
  439.         );
    
  440.         e = e.firstChild.firstChild;
    
  441.         expect(e.childNodes.length).toBe(0);
    
  442.         expect(e.tagName).toBe('feMorphology');
    
  443.         expect(e.namespaceURI).toBe('http://www.w3.org/2000/svg');
    
  444.       });
    
  445. 
    
  446.       itRenders('a math element', async render => {
    
  447.         const e = await render(<math />);
    
  448.         expect(e.childNodes.length).toBe(0);
    
  449.         expect(e.tagName).toBe('math');
    
  450.         expect(e.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML');
    
  451.       });
    
  452.     });
    
  453.     // specially wrapped components
    
  454.     // (see the big switch near the beginning ofReactDOMComponent.mountComponent)
    
  455.     itRenders('an img', async render => {
    
  456.       const e = await render(<img />);
    
  457.       expect(e.childNodes.length).toBe(0);
    
  458.       expect(e.nextSibling).toBe(null);
    
  459.       expect(e.tagName).toBe('IMG');
    
  460.     });
    
  461. 
    
  462.     itRenders('a button', async render => {
    
  463.       const e = await render(<button />);
    
  464.       expect(e.childNodes.length).toBe(0);
    
  465.       expect(e.nextSibling).toBe(null);
    
  466.       expect(e.tagName).toBe('BUTTON');
    
  467.     });
    
  468. 
    
  469.     itRenders('a div with dangerouslySetInnerHTML number', async render => {
    
  470.       // Put dangerouslySetInnerHTML one level deeper because otherwise
    
  471.       // hydrating from a bad markup would cause a mismatch (since we don't
    
  472.       // patch dangerouslySetInnerHTML as text content).
    
  473.       const e = (
    
  474.         await render(
    
  475.           <div>
    
  476.             <span dangerouslySetInnerHTML={{__html: 0}} />
    
  477.           </div>,
    
  478.         )
    
  479.       ).firstChild;
    
  480.       expect(e.childNodes.length).toBe(1);
    
  481.       expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
    
  482.       expect(e.textContent).toBe('0');
    
  483.     });
    
  484. 
    
  485.     itRenders('a div with dangerouslySetInnerHTML boolean', async render => {
    
  486.       // Put dangerouslySetInnerHTML one level deeper because otherwise
    
  487.       // hydrating from a bad markup would cause a mismatch (since we don't
    
  488.       // patch dangerouslySetInnerHTML as text content).
    
  489.       const e = (
    
  490.         await render(
    
  491.           <div>
    
  492.             <span dangerouslySetInnerHTML={{__html: false}} />
    
  493.           </div>,
    
  494.         )
    
  495.       ).firstChild;
    
  496.       expect(e.childNodes.length).toBe(1);
    
  497.       expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
    
  498.       expect(e.firstChild.data).toBe('false');
    
  499.     });
    
  500. 
    
  501.     itRenders(
    
  502.       'a div with dangerouslySetInnerHTML text string',
    
  503.       async render => {
    
  504.         // Put dangerouslySetInnerHTML one level deeper because otherwise
    
  505.         // hydrating from a bad markup would cause a mismatch (since we don't
    
  506.         // patch dangerouslySetInnerHTML as text content).
    
  507.         const e = (
    
  508.           await render(
    
  509.             <div>
    
  510.               <span dangerouslySetInnerHTML={{__html: 'hello'}} />
    
  511.             </div>,
    
  512.           )
    
  513.         ).firstChild;
    
  514.         expect(e.childNodes.length).toBe(1);
    
  515.         expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
    
  516.         expect(e.textContent).toBe('hello');
    
  517.       },
    
  518.     );
    
  519. 
    
  520.     itRenders(
    
  521.       'a div with dangerouslySetInnerHTML element string',
    
  522.       async render => {
    
  523.         const e = await render(
    
  524.           <div dangerouslySetInnerHTML={{__html: "<span id='child'/>"}} />,
    
  525.         );
    
  526.         expect(e.childNodes.length).toBe(1);
    
  527.         expect(e.firstChild.tagName).toBe('SPAN');
    
  528.         expect(e.firstChild.getAttribute('id')).toBe('child');
    
  529.         expect(e.firstChild.childNodes.length).toBe(0);
    
  530.       },
    
  531.     );
    
  532. 
    
  533.     itRenders('a div with dangerouslySetInnerHTML object', async render => {
    
  534.       const obj = {
    
  535.         toString() {
    
  536.           return "<span id='child'/>";
    
  537.         },
    
  538.       };
    
  539.       const e = await render(<div dangerouslySetInnerHTML={{__html: obj}} />);
    
  540.       expect(e.childNodes.length).toBe(1);
    
  541.       expect(e.firstChild.tagName).toBe('SPAN');
    
  542.       expect(e.firstChild.getAttribute('id')).toBe('child');
    
  543.       expect(e.firstChild.childNodes.length).toBe(0);
    
  544.     });
    
  545. 
    
  546.     itRenders(
    
  547.       'a div with dangerouslySetInnerHTML set to null',
    
  548.       async render => {
    
  549.         const e = await render(
    
  550.           <div dangerouslySetInnerHTML={{__html: null}} />,
    
  551.         );
    
  552.         expect(e.childNodes.length).toBe(0);
    
  553.       },
    
  554.     );
    
  555. 
    
  556.     itRenders(
    
  557.       'a div with dangerouslySetInnerHTML set to undefined',
    
  558.       async render => {
    
  559.         const e = await render(
    
  560.           <div dangerouslySetInnerHTML={{__html: undefined}} />,
    
  561.         );
    
  562.         expect(e.childNodes.length).toBe(0);
    
  563.       },
    
  564.     );
    
  565. 
    
  566.     itRenders('a noscript with children', async render => {
    
  567.       const e = await render(
    
  568.         <noscript>
    
  569.           <div>Enable JavaScript to run this app.</div>
    
  570.         </noscript>,
    
  571.       );
    
  572.       if (render === clientCleanRender) {
    
  573.         // On the client we ignore the contents of a noscript
    
  574.         expect(e.childNodes.length).toBe(0);
    
  575.       } else {
    
  576.         // On the server or when hydrating the content should be correct
    
  577.         expect(e.childNodes.length).toBe(1);
    
  578.         expect(e.firstChild.textContent).toBe(
    
  579.           '<div>Enable JavaScript to run this app.</div>',
    
  580.         );
    
  581.       }
    
  582.     });
    
  583. 
    
  584.     describe('newline-eating elements', function () {
    
  585.       itRenders(
    
  586.         'a newline-eating tag with content not starting with \\n',
    
  587.         async render => {
    
  588.           const e = await render(<pre>Hello</pre>);
    
  589.           expect(e.textContent).toBe('Hello');
    
  590.         },
    
  591.       );
    
  592.       itRenders(
    
  593.         'a newline-eating tag with content starting with \\n',
    
  594.         async render => {
    
  595.           const e = await render(<pre>{'\nHello'}</pre>);
    
  596.           expect(e.textContent).toBe('\nHello');
    
  597.         },
    
  598.       );
    
  599.       itRenders('a normal tag with content starting with \\n', async render => {
    
  600.         const e = await render(<div>{'\nHello'}</div>);
    
  601.         expect(e.textContent).toBe('\nHello');
    
  602.       });
    
  603.     });
    
  604. 
    
  605.     describe('different component implementations', function () {
    
  606.       function checkFooDiv(e) {
    
  607.         expect(e.childNodes.length).toBe(1);
    
  608.         expectNode(e.firstChild, TEXT_NODE_TYPE, 'foo');
    
  609.       }
    
  610. 
    
  611.       itRenders('stateless components', async render => {
    
  612.         const FunctionComponent = () => <div>foo</div>;
    
  613.         checkFooDiv(await render(<FunctionComponent />));
    
  614.       });
    
  615. 
    
  616.       itRenders('ES6 class components', async render => {
    
  617.         class ClassComponent extends React.Component {
    
  618.           render() {
    
  619.             return <div>foo</div>;
    
  620.           }
    
  621.         }
    
  622.         checkFooDiv(await render(<ClassComponent />));
    
  623.       });
    
  624. 
    
  625.       if (require('shared/ReactFeatureFlags').disableModulePatternComponents) {
    
  626.         itThrowsWhenRendering(
    
  627.           'factory components',
    
  628.           async render => {
    
  629.             const FactoryComponent = () => {
    
  630.               return {
    
  631.                 render: function () {
    
  632.                   return <div>foo</div>;
    
  633.                 },
    
  634.               };
    
  635.             };
    
  636.             await render(<FactoryComponent />, 1);
    
  637.           },
    
  638.           'Objects are not valid as a React child (found: object with keys {render})',
    
  639.         );
    
  640.       } else {
    
  641.         itRenders('factory components', async render => {
    
  642.           const FactoryComponent = () => {
    
  643.             return {
    
  644.               render: function () {
    
  645.                 return <div>foo</div>;
    
  646.               },
    
  647.             };
    
  648.           };
    
  649.           checkFooDiv(await render(<FactoryComponent />, 1));
    
  650.         });
    
  651.       }
    
  652.     });
    
  653. 
    
  654.     describe('component hierarchies', function () {
    
  655.       itRenders('single child hierarchies of components', async render => {
    
  656.         const Component = props => <div>{props.children}</div>;
    
  657.         let e = await render(
    
  658.           <Component>
    
  659.             <Component>
    
  660.               <Component>
    
  661.                 <Component />
    
  662.               </Component>
    
  663.             </Component>
    
  664.           </Component>,
    
  665.         );
    
  666.         for (let i = 0; i < 3; i++) {
    
  667.           expect(e.tagName).toBe('DIV');
    
  668.           expect(e.childNodes.length).toBe(1);
    
  669.           e = e.firstChild;
    
  670.         }
    
  671.         expect(e.tagName).toBe('DIV');
    
  672.         expect(e.childNodes.length).toBe(0);
    
  673.       });
    
  674. 
    
  675.       itRenders('multi-child hierarchies of components', async render => {
    
  676.         const Component = props => <div>{props.children}</div>;
    
  677.         const e = await render(
    
  678.           <Component>
    
  679.             <Component>
    
  680.               <Component />
    
  681.               <Component />
    
  682.             </Component>
    
  683.             <Component>
    
  684.               <Component />
    
  685.               <Component />
    
  686.             </Component>
    
  687.           </Component>,
    
  688.         );
    
  689.         expect(e.tagName).toBe('DIV');
    
  690.         expect(e.childNodes.length).toBe(2);
    
  691.         for (let i = 0; i < 2; i++) {
    
  692.           const child = e.childNodes[i];
    
  693.           expect(child.tagName).toBe('DIV');
    
  694.           expect(child.childNodes.length).toBe(2);
    
  695.           for (let j = 0; j < 2; j++) {
    
  696.             const grandchild = child.childNodes[j];
    
  697.             expect(grandchild.tagName).toBe('DIV');
    
  698.             expect(grandchild.childNodes.length).toBe(0);
    
  699.           }
    
  700.         }
    
  701.       });
    
  702. 
    
  703.       itRenders('a div with a child', async render => {
    
  704.         const e = await render(
    
  705.           <div id="parent">
    
  706.             <div id="child" />
    
  707.           </div>,
    
  708.         );
    
  709.         expect(e.id).toBe('parent');
    
  710.         expect(e.childNodes.length).toBe(1);
    
  711.         expect(e.childNodes[0].id).toBe('child');
    
  712.         expect(e.childNodes[0].childNodes.length).toBe(0);
    
  713.       });
    
  714. 
    
  715.       itRenders('a div with multiple children', async render => {
    
  716.         const e = await render(
    
  717.           <div id="parent">
    
  718.             <div id="child1" />
    
  719.             <div id="child2" />
    
  720.           </div>,
    
  721.         );
    
  722.         expect(e.id).toBe('parent');
    
  723.         expect(e.childNodes.length).toBe(2);
    
  724.         expect(e.childNodes[0].id).toBe('child1');
    
  725.         expect(e.childNodes[0].childNodes.length).toBe(0);
    
  726.         expect(e.childNodes[1].id).toBe('child2');
    
  727.         expect(e.childNodes[1].childNodes.length).toBe(0);
    
  728.       });
    
  729. 
    
  730.       itRenders(
    
  731.         'a div with multiple children separated by whitespace',
    
  732.         async render => {
    
  733.           const e = await render(
    
  734.             <div id="parent">
    
  735.               <div id="child1" /> <div id="child2" />
    
  736.             </div>,
    
  737.           );
    
  738.           expect(e.id).toBe('parent');
    
  739.           expect(e.childNodes.length).toBe(3);
    
  740.           const child1 = e.childNodes[0];
    
  741.           const textNode = e.childNodes[1];
    
  742.           const child2 = e.childNodes[2];
    
  743.           expect(child1.id).toBe('child1');
    
  744.           expect(child1.childNodes.length).toBe(0);
    
  745.           expectTextNode(textNode, ' ');
    
  746.           expect(child2.id).toBe('child2');
    
  747.           expect(child2.childNodes.length).toBe(0);
    
  748.         },
    
  749.       );
    
  750. 
    
  751.       itRenders(
    
  752.         'a div with a single child surrounded by whitespace',
    
  753.         async render => {
    
  754.           // prettier-ignore
    
  755.           const e = await render(<div id="parent">  <div id="child" />   </div>); // eslint-disable-line no-multi-spaces
    
  756.           expect(e.childNodes.length).toBe(3);
    
  757.           const textNode1 = e.childNodes[0];
    
  758.           const child = e.childNodes[1];
    
  759.           const textNode2 = e.childNodes[2];
    
  760.           expect(e.id).toBe('parent');
    
  761.           expectTextNode(textNode1, '  ');
    
  762.           expect(child.id).toBe('child');
    
  763.           expect(child.childNodes.length).toBe(0);
    
  764.           expectTextNode(textNode2, '   ');
    
  765.         },
    
  766.       );
    
  767. 
    
  768.       itRenders('a composite with multiple children', async render => {
    
  769.         const Component = props => props.children;
    
  770.         const e = await render(
    
  771.           <Component>{['a', 'b', [undefined], [[false, 'c']]]}</Component>,
    
  772.         );
    
  773. 
    
  774.         const parent = e.parentNode;
    
  775.         if (
    
  776.           render === serverRender ||
    
  777.           render === clientRenderOnServerString ||
    
  778.           render === streamRender
    
  779.         ) {
    
  780.           // For plain server markup result we have comments between.
    
  781.           // If we're able to hydrate, they remain.
    
  782.           expect(parent.childNodes.length).toBe(5);
    
  783.           expectTextNode(parent.childNodes[0], 'a');
    
  784.           expectTextNode(parent.childNodes[2], 'b');
    
  785.           expectTextNode(parent.childNodes[4], 'c');
    
  786.         } else {
    
  787.           expect(parent.childNodes.length).toBe(3);
    
  788.           expectTextNode(parent.childNodes[0], 'a');
    
  789.           expectTextNode(parent.childNodes[1], 'b');
    
  790.           expectTextNode(parent.childNodes[2], 'c');
    
  791.         }
    
  792.       });
    
  793.     });
    
  794. 
    
  795.     describe('escaping >, <, and &', function () {
    
  796.       itRenders('>,<, and & as single child', async render => {
    
  797.         const e = await render(<div>{'<span>Text&quot;</span>'}</div>);
    
  798.         expect(e.childNodes.length).toBe(1);
    
  799.         expectNode(e.firstChild, TEXT_NODE_TYPE, '<span>Text&quot;</span>');
    
  800.       });
    
  801. 
    
  802.       itRenders('>,<, and & as multiple children', async render => {
    
  803.         const e = await render(
    
  804.           <div>
    
  805.             {'<span>Text1&quot;</span>'}
    
  806.             {'<span>Text2&quot;</span>'}
    
  807.           </div>,
    
  808.         );
    
  809.         if (
    
  810.           render === serverRender ||
    
  811.           render === clientRenderOnServerString ||
    
  812.           render === streamRender
    
  813.         ) {
    
  814.           expect(e.childNodes.length).toBe(3);
    
  815.           expectTextNode(e.childNodes[0], '<span>Text1&quot;</span>');
    
  816.           expectTextNode(e.childNodes[2], '<span>Text2&quot;</span>');
    
  817.         } else {
    
  818.           expect(e.childNodes.length).toBe(2);
    
  819.           expectTextNode(e.childNodes[0], '<span>Text1&quot;</span>');
    
  820.           expectTextNode(e.childNodes[1], '<span>Text2&quot;</span>');
    
  821.         }
    
  822.       });
    
  823.     });
    
  824. 
    
  825.     describe('carriage return and null character', () => {
    
  826.       // HTML parsing normalizes CR and CRLF to LF.
    
  827.       // It also ignores null character.
    
  828.       // https://www.w3.org/TR/html5/single-page.html#preprocessing-the-input-stream
    
  829.       // If we have a mismatch, it might be caused by that (and should not be reported).
    
  830.       // We won't be patching up in this case as that matches our past behavior.
    
  831. 
    
  832.       itRenders(
    
  833.         'an element with one text child with special characters',
    
  834.         async render => {
    
  835.           const e = await render(<div>{'foo\rbar\r\nbaz\nqux\u0000'}</div>);
    
  836.           if (render === serverRender || render === streamRender) {
    
  837.             expect(e.childNodes.length).toBe(1);
    
  838.             // Everything becomes LF when parsed from server HTML.
    
  839.             // Null character is ignored.
    
  840.             expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\nbar\nbaz\nqux');
    
  841.           } else {
    
  842.             expect(e.childNodes.length).toBe(1);
    
  843.             // Client rendering (or hydration) uses JS value with CR.
    
  844.             // Null character stays.
    
  845.             expectNode(
    
  846.               e.childNodes[0],
    
  847.               TEXT_NODE_TYPE,
    
  848.               'foo\rbar\r\nbaz\nqux\u0000',
    
  849.             );
    
  850.           }
    
  851.         },
    
  852.       );
    
  853. 
    
  854.       itRenders(
    
  855.         'an element with two text children with special characters',
    
  856.         async render => {
    
  857.           const e = await render(
    
  858.             <div>
    
  859.               {'foo\rbar'}
    
  860.               {'\r\nbaz\nqux\u0000'}
    
  861.             </div>,
    
  862.           );
    
  863.           if (render === serverRender || render === streamRender) {
    
  864.             // We have three nodes because there is a comment between them.
    
  865.             expect(e.childNodes.length).toBe(3);
    
  866.             // Everything becomes LF when parsed from server HTML.
    
  867.             // Null character is ignored.
    
  868.             expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\nbar');
    
  869.             expectNode(e.childNodes[2], TEXT_NODE_TYPE, '\nbaz\nqux');
    
  870.           } else if (render === clientRenderOnServerString) {
    
  871.             // We have three nodes because there is a comment between them.
    
  872.             expect(e.childNodes.length).toBe(3);
    
  873.             // Hydration uses JS value with CR and null character.
    
  874.             expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\rbar');
    
  875.             expectNode(e.childNodes[2], TEXT_NODE_TYPE, '\r\nbaz\nqux\u0000');
    
  876.           } else {
    
  877.             expect(e.childNodes.length).toBe(2);
    
  878.             // Client rendering uses JS value with CR and null character.
    
  879.             expectNode(e.childNodes[0], TEXT_NODE_TYPE, 'foo\rbar');
    
  880.             expectNode(e.childNodes[1], TEXT_NODE_TYPE, '\r\nbaz\nqux\u0000');
    
  881.           }
    
  882.         },
    
  883.       );
    
  884. 
    
  885.       itRenders(
    
  886.         'an element with an attribute value with special characters',
    
  887.         async render => {
    
  888.           const e = await render(<a title={'foo\rbar\r\nbaz\nqux\u0000'} />);
    
  889.           if (
    
  890.             render === serverRender ||
    
  891.             render === streamRender ||
    
  892.             render === clientRenderOnServerString
    
  893.           ) {
    
  894.             // Everything becomes LF when parsed from server HTML.
    
  895.             // Null character in an attribute becomes the replacement character.
    
  896.             // Hydration also ends up with LF because we don't patch up attributes.
    
  897.             expect(e.title).toBe('foo\nbar\nbaz\nqux\uFFFD');
    
  898.           } else {
    
  899.             // Client rendering uses JS value with CR and null character.
    
  900.             expect(e.title).toBe('foo\rbar\r\nbaz\nqux\u0000');
    
  901.           }
    
  902.         },
    
  903.       );
    
  904.     });
    
  905. 
    
  906.     describe('components that render nullish', function () {
    
  907.       itRenders('a function returning null', async render => {
    
  908.         const NullComponent = () => null;
    
  909.         await render(<NullComponent />);
    
  910.       });
    
  911. 
    
  912.       itRenders('a class returning null', async render => {
    
  913.         class NullComponent extends React.Component {
    
  914.           render() {
    
  915.             return null;
    
  916.           }
    
  917.         }
    
  918.         await render(<NullComponent />);
    
  919.       });
    
  920. 
    
  921.       itRenders('a function returning undefined', async render => {
    
  922.         const UndefinedComponent = () => undefined;
    
  923.         await render(<UndefinedComponent />);
    
  924.       });
    
  925. 
    
  926.       itRenders('a class returning undefined', async render => {
    
  927.         class UndefinedComponent extends React.Component {
    
  928.           render() {
    
  929.             return undefined;
    
  930.           }
    
  931.         }
    
  932.         await render(<UndefinedComponent />);
    
  933.       });
    
  934.     });
    
  935. 
    
  936.     describe('components that throw errors', function () {
    
  937.       itThrowsWhenRendering(
    
  938.         'a function returning an object',
    
  939.         async render => {
    
  940.           const ObjectComponent = () => ({x: 123});
    
  941.           await render(<ObjectComponent />, 1);
    
  942.         },
    
  943.         'Objects are not valid as a React child (found: object with keys {x}).' +
    
  944.           (__DEV__
    
  945.             ? ' If you meant to render a collection of children, use ' +
    
  946.               'an array instead.'
    
  947.             : ''),
    
  948.       );
    
  949. 
    
  950.       itThrowsWhenRendering(
    
  951.         'a class returning an object',
    
  952.         async render => {
    
  953.           class ObjectComponent extends React.Component {
    
  954.             render() {
    
  955.               return {x: 123};
    
  956.             }
    
  957.           }
    
  958.           await render(<ObjectComponent />, 1);
    
  959.         },
    
  960.         'Objects are not valid as a React child (found: object with keys {x}).' +
    
  961.           (__DEV__
    
  962.             ? ' If you meant to render a collection of children, use ' +
    
  963.               'an array instead.'
    
  964.             : ''),
    
  965.       );
    
  966. 
    
  967.       itThrowsWhenRendering(
    
  968.         'top-level object',
    
  969.         async render => {
    
  970.           await render({x: 123});
    
  971.         },
    
  972.         'Objects are not valid as a React child (found: object with keys {x}).' +
    
  973.           (__DEV__
    
  974.             ? ' If you meant to render a collection of children, use ' +
    
  975.               'an array instead.'
    
  976.             : ''),
    
  977.       );
    
  978.     });
    
  979. 
    
  980.     describe('badly-typed elements', function () {
    
  981.       itThrowsWhenRendering(
    
  982.         'object',
    
  983.         async render => {
    
  984.           let EmptyComponent = {};
    
  985.           expect(() => {
    
  986.             EmptyComponent = <EmptyComponent />;
    
  987.           }).toErrorDev(
    
  988.             'Warning: React.createElement: type is invalid -- expected a string ' +
    
  989.               '(for built-in components) or a class/function (for composite ' +
    
  990.               'components) but got: object. You likely forgot to export your ' +
    
  991.               "component from the file it's defined in, or you might have mixed up " +
    
  992.               'default and named imports.',
    
  993.             {withoutStack: true},
    
  994.           );
    
  995.           await render(EmptyComponent);
    
  996.         },
    
  997.         'Element type is invalid: expected a string (for built-in components) or a class/function ' +
    
  998.           '(for composite components) but got: object.' +
    
  999.           (__DEV__
    
  1000.             ? " You likely forgot to export your component from the file it's defined in, " +
    
  1001.               'or you might have mixed up default and named imports.'
    
  1002.             : ''),
    
  1003.       );
    
  1004. 
    
  1005.       itThrowsWhenRendering(
    
  1006.         'null',
    
  1007.         async render => {
    
  1008.           let NullComponent = null;
    
  1009.           expect(() => {
    
  1010.             NullComponent = <NullComponent />;
    
  1011.           }).toErrorDev(
    
  1012.             'Warning: React.createElement: type is invalid -- expected a string ' +
    
  1013.               '(for built-in components) or a class/function (for composite ' +
    
  1014.               'components) but got: null.',
    
  1015.             {withoutStack: true},
    
  1016.           );
    
  1017.           await render(NullComponent);
    
  1018.         },
    
  1019.         'Element type is invalid: expected a string (for built-in components) or a class/function ' +
    
  1020.           '(for composite components) but got: null',
    
  1021.       );
    
  1022. 
    
  1023.       itThrowsWhenRendering(
    
  1024.         'undefined',
    
  1025.         async render => {
    
  1026.           let UndefinedComponent = undefined;
    
  1027.           expect(() => {
    
  1028.             UndefinedComponent = <UndefinedComponent />;
    
  1029.           }).toErrorDev(
    
  1030.             'Warning: React.createElement: type is invalid -- expected a string ' +
    
  1031.               '(for built-in components) or a class/function (for composite ' +
    
  1032.               'components) but got: undefined. You likely forgot to export your ' +
    
  1033.               "component from the file it's defined in, or you might have mixed up " +
    
  1034.               'default and named imports.',
    
  1035.             {withoutStack: true},
    
  1036.           );
    
  1037. 
    
  1038.           await render(UndefinedComponent);
    
  1039.         },
    
  1040.         'Element type is invalid: expected a string (for built-in components) or a class/function ' +
    
  1041.           '(for composite components) but got: undefined.' +
    
  1042.           (__DEV__
    
  1043.             ? " You likely forgot to export your component from the file it's defined in, " +
    
  1044.               'or you might have mixed up default and named imports.'
    
  1045.             : ''),
    
  1046.       );
    
  1047.     });
    
  1048.   });
    
  1049. });