1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @emails react-core
    
  8.  */
    
  9. 
    
  10. 'use strict';
    
  11. 
    
  12. const React = require('react');
    
  13. const ReactDOMClient = require('react-dom/client');
    
  14. const ReactTestUtils = require('react-dom/test-utils');
    
  15. const act = require('internal-test-utils').act;
    
  16. 
    
  17. // Helpers
    
  18. const testAllPermutations = async function (testCases) {
    
  19.   for (let i = 0; i < testCases.length; i += 2) {
    
  20.     const renderWithChildren = testCases[i];
    
  21.     const expectedResultAfterRender = testCases[i + 1];
    
  22. 
    
  23.     for (let j = 0; j < testCases.length; j += 2) {
    
  24.       const updateWithChildren = testCases[j];
    
  25.       const expectedResultAfterUpdate = testCases[j + 1];
    
  26. 
    
  27.       const container = document.createElement('div');
    
  28.       const root = ReactDOMClient.createRoot(container);
    
  29.       await act(() => root.render(<div>{renderWithChildren}</div>));
    
  30.       expectChildren(container, expectedResultAfterRender);
    
  31. 
    
  32.       await act(() => root.render(<div>{updateWithChildren}</div>));
    
  33.       expectChildren(container, expectedResultAfterUpdate);
    
  34.     }
    
  35.   }
    
  36. };
    
  37. 
    
  38. const expectChildren = function (container, children) {
    
  39.   const outerNode = container.firstChild;
    
  40.   let textNode;
    
  41.   if (typeof children === 'string') {
    
  42.     textNode = outerNode.firstChild;
    
  43. 
    
  44.     if (children === '') {
    
  45.       expect(textNode != null).toBe(false);
    
  46.     } else {
    
  47.       expect(textNode != null).toBe(true);
    
  48.       expect(textNode.nodeType).toBe(3);
    
  49.       expect(textNode.data).toBe(String(children));
    
  50.     }
    
  51.   } else {
    
  52.     let mountIndex = 0;
    
  53. 
    
  54.     for (let i = 0; i < children.length; i++) {
    
  55.       const child = children[i];
    
  56. 
    
  57.       if (typeof child === 'string') {
    
  58.         if (child === '') {
    
  59.           continue;
    
  60.         }
    
  61.         textNode = outerNode.childNodes[mountIndex];
    
  62.         expect(textNode.nodeType).toBe(3);
    
  63.         expect(textNode.data).toBe(child);
    
  64.         mountIndex++;
    
  65.       } else {
    
  66.         const elementDOMNode = outerNode.childNodes[mountIndex];
    
  67.         expect(elementDOMNode.tagName).toBe('DIV');
    
  68.         mountIndex++;
    
  69.       }
    
  70.     }
    
  71.   }
    
  72. };
    
  73. 
    
  74. /**
    
  75.  * ReactMultiChild DOM integration test. In ReactDOM components, we make sure
    
  76.  * that single children that are strings are treated as "content" which is much
    
  77.  * faster to render and update.
    
  78.  */
    
  79. describe('ReactMultiChildText', () => {
    
  80.   jest.setTimeout(20000);
    
  81. 
    
  82.   it('should correctly handle all possible children for render and update', async () => {
    
  83.     await expect(async () => {
    
  84.       // prettier-ignore
    
  85.       await testAllPermutations([
    
  86.         // basic values
    
  87.         undefined, [],
    
  88.         null, [],
    
  89.         false, [],
    
  90.         true, [],
    
  91.         0, '0',
    
  92.         1.2, '1.2',
    
  93.         '', [],
    
  94.         'foo', 'foo',
    
  95. 
    
  96.         [], [],
    
  97.         [undefined], [],
    
  98.         [null], [],
    
  99.         [false], [],
    
  100.         [true], [],
    
  101.         [0], ['0'],
    
  102.         [1.2], ['1.2'],
    
  103.         [''], [],
    
  104.         ['foo'], ['foo'],
    
  105.         [<div />], [<div />],
    
  106. 
    
  107.         // two adjacent values
    
  108.         [true, 0], ['0'],
    
  109.         [0, 0], ['0', '0'],
    
  110.         [1.2, 0], ['1.2', '0'],
    
  111.         [0, ''], ['0', ''],
    
  112.         ['foo', 0], ['foo', '0'],
    
  113.         [0, <div />], ['0', <div />],
    
  114. 
    
  115.         [true, 1.2], ['1.2'],
    
  116.         [1.2, 0], ['1.2', '0'],
    
  117.         [1.2, 1.2], ['1.2', '1.2'],
    
  118.         [1.2, ''], ['1.2', ''],
    
  119.         ['foo', 1.2], ['foo', '1.2'],
    
  120.         [1.2, <div />], ['1.2', <div />],
    
  121. 
    
  122.         [true, ''], [''],
    
  123.         ['', 0], ['', '0'],
    
  124.         [1.2, ''], ['1.2', ''],
    
  125.         ['', ''], ['', ''],
    
  126.         ['foo', ''], ['foo', ''],
    
  127.         ['', <div />], ['', <div />],
    
  128. 
    
  129.         [true, 'foo'], ['foo'],
    
  130.         ['foo', 0], ['foo', '0'],
    
  131.         [1.2, 'foo'], ['1.2', 'foo'],
    
  132.         ['foo', ''], ['foo', ''],
    
  133.         ['foo', 'foo'], ['foo', 'foo'],
    
  134.         ['foo', <div />], ['foo', <div />],
    
  135. 
    
  136.         // values separated by an element
    
  137.         [true, <div />, true], [<div />],
    
  138.         [1.2, <div />, 1.2], ['1.2', <div />, '1.2'],
    
  139.         ['', <div />, ''], ['', <div />, ''],
    
  140.         ['foo', <div />, 'foo'], ['foo', <div />, 'foo'],
    
  141. 
    
  142.         [true, 1.2, <div />, '', 'foo'], ['1.2', <div />, '', 'foo'],
    
  143.         [1.2, '', <div />, 'foo', true], ['1.2', '', <div />, 'foo'],
    
  144.         ['', 'foo', <div />, true, 1.2], ['', 'foo', <div />, '1.2'],
    
  145. 
    
  146.         [true, 1.2, '', <div />, 'foo', true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
    
  147.         ['', 'foo', true, <div />, 1.2, '', 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
    
  148. 
    
  149.         // values inside arrays
    
  150.         [[true], [true]], [],
    
  151.         [[1.2], [1.2]], ['1.2', '1.2'],
    
  152.         [[''], ['']], ['', ''],
    
  153.         [['foo'], ['foo']], ['foo', 'foo'],
    
  154.         [[<div />], [<div />]], [<div />, <div />],
    
  155. 
    
  156.         [[true, 1.2, <div />], '', 'foo'], ['1.2', <div />, '', 'foo'],
    
  157.         [1.2, '', [<div />, 'foo', true]], ['1.2', '', <div />, 'foo'],
    
  158.         ['', ['foo', <div />, true], 1.2], ['', 'foo', <div />, '1.2'],
    
  159. 
    
  160.         [true, [1.2, '', <div />, 'foo'], true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
    
  161.         ['', 'foo', [true, <div />, 1.2, ''], 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
    
  162. 
    
  163.         // values inside elements
    
  164.         [<div>{true}{1.2}{<div />}</div>, '', 'foo'], [<div />, '', 'foo'],
    
  165.         [1.2, '', <div>{<div />}{'foo'}{true}</div>], ['1.2', '', <div />],
    
  166.         ['', <div>{'foo'}{<div />}{true}</div>, 1.2], ['', <div />, '1.2'],
    
  167. 
    
  168.         [true, <div>{1.2}{''}{<div />}{'foo'}</div>, true, 1.2], [<div />, '1.2'],
    
  169.         ['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
    
  170.       ]);
    
  171.     }).toErrorDev([
    
  172.       'Warning: Each child in a list should have a unique "key" prop.',
    
  173.       'Warning: Each child in a list should have a unique "key" prop.',
    
  174.     ]);
    
  175.   });
    
  176. 
    
  177.   it('should throw if rendering both HTML and children', () => {
    
  178.     expect(function () {
    
  179.       ReactTestUtils.renderIntoDocument(
    
  180.         <div dangerouslySetInnerHTML={{__html: 'abcdef'}}>ghjkl</div>,
    
  181.       );
    
  182.     }).toThrow();
    
  183.   });
    
  184. 
    
  185.   it('should render between nested components and inline children', () => {
    
  186.     ReactTestUtils.renderIntoDocument(
    
  187.       <div>
    
  188.         <h1>
    
  189.           <span />
    
  190.           <span />
    
  191.         </h1>
    
  192.       </div>,
    
  193.     );
    
  194. 
    
  195.     expect(function () {
    
  196.       ReactTestUtils.renderIntoDocument(
    
  197.         <div>
    
  198.           <h1>A</h1>
    
  199.         </div>,
    
  200.       );
    
  201.     }).not.toThrow();
    
  202. 
    
  203.     expect(function () {
    
  204.       ReactTestUtils.renderIntoDocument(
    
  205.         <div>
    
  206.           <h1>{['A']}</h1>
    
  207.         </div>,
    
  208.       );
    
  209.     }).not.toThrow();
    
  210. 
    
  211.     expect(function () {
    
  212.       ReactTestUtils.renderIntoDocument(
    
  213.         <div>
    
  214.           <h1>{['A', 'B']}</h1>
    
  215.         </div>,
    
  216.       );
    
  217.     }).not.toThrow();
    
  218.   });
    
  219. });