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. let React;
    
  13. let ReactDOM;
    
  14. let ReactDOMServer;
    
  15. 
    
  16. // In standard React, TextComponent keeps track of different Text templates
    
  17. // using comments. However, in React Fiber, those comments are not outputted due
    
  18. // to the way Fiber keeps track of the templates.
    
  19. // This function "Normalizes" childNodes lists to avoid the presence of comments
    
  20. // and make the child list identical in standard React and Fiber
    
  21. function filterOutComments(nodeList) {
    
  22.   return [].slice.call(nodeList).filter(node => !(node instanceof Comment));
    
  23. }
    
  24. 
    
  25. describe('ReactDOMTextComponent', () => {
    
  26.   beforeEach(() => {
    
  27.     React = require('react');
    
  28.     ReactDOM = require('react-dom');
    
  29.     ReactDOMServer = require('react-dom/server');
    
  30.   });
    
  31. 
    
  32.   it('updates a mounted text component in place', () => {
    
  33.     const el = document.createElement('div');
    
  34.     let inst = ReactDOM.render(
    
  35.       <div>
    
  36.         <span />
    
  37.         {'foo'}
    
  38.         {'bar'}
    
  39.       </div>,
    
  40.       el,
    
  41.     );
    
  42.     let nodes = filterOutComments(inst.childNodes);
    
  43. 
    
  44.     const foo = nodes[1];
    
  45.     const bar = nodes[2];
    
  46.     expect(foo.data).toBe('foo');
    
  47.     expect(bar.data).toBe('bar');
    
  48. 
    
  49.     inst = ReactDOM.render(
    
  50.       <div>
    
  51.         <span />
    
  52.         {'baz'}
    
  53.         {'qux'}
    
  54.       </div>,
    
  55.       el,
    
  56.     );
    
  57.     // After the update, the text nodes should have stayed in place (as opposed
    
  58.     // to getting unmounted and remounted)
    
  59.     nodes = filterOutComments(inst.childNodes);
    
  60.     expect(nodes[1]).toBe(foo);
    
  61.     expect(nodes[2]).toBe(bar);
    
  62.     expect(foo.data).toBe('baz');
    
  63.     expect(bar.data).toBe('qux');
    
  64.   });
    
  65. 
    
  66.   it('can be toggled in and out of the markup', () => {
    
  67.     const el = document.createElement('div');
    
  68.     let inst = ReactDOM.render(
    
  69.       <div>
    
  70.         {'foo'}
    
  71.         <div />
    
  72.         {'bar'}
    
  73.       </div>,
    
  74.       el,
    
  75.     );
    
  76. 
    
  77.     let childNodes = filterOutComments(inst.childNodes);
    
  78.     const childDiv = childNodes[1];
    
  79. 
    
  80.     inst = ReactDOM.render(
    
  81.       <div>
    
  82.         {null}
    
  83.         <div />
    
  84.         {null}
    
  85.       </div>,
    
  86.       el,
    
  87.     );
    
  88.     childNodes = filterOutComments(inst.childNodes);
    
  89.     expect(childNodes.length).toBe(1);
    
  90.     expect(childNodes[0]).toBe(childDiv);
    
  91. 
    
  92.     inst = ReactDOM.render(
    
  93.       <div>
    
  94.         {'foo'}
    
  95.         <div />
    
  96.         {'bar'}
    
  97.       </div>,
    
  98.       el,
    
  99.     );
    
  100.     childNodes = filterOutComments(inst.childNodes);
    
  101.     expect(childNodes.length).toBe(3);
    
  102.     expect(childNodes[0].data).toBe('foo');
    
  103.     expect(childNodes[1]).toBe(childDiv);
    
  104.     expect(childNodes[2].data).toBe('bar');
    
  105.   });
    
  106. 
    
  107.   /**
    
  108.    * The following Node.normalize() tests are intentionally failing.
    
  109.    * See #9836 tracking whether we'll need to fix this or if it's unnecessary.
    
  110.    */
    
  111. 
    
  112.   xit('can reconcile text merged by Node.normalize() alongside other elements', () => {
    
  113.     const el = document.createElement('div');
    
  114.     let inst = ReactDOM.render(
    
  115.       <div>
    
  116.         {'foo'}
    
  117.         {'bar'}
    
  118.         {'baz'}
    
  119.         <span />
    
  120.         {'qux'}
    
  121.       </div>,
    
  122.       el,
    
  123.     );
    
  124. 
    
  125.     inst.normalize();
    
  126. 
    
  127.     inst = ReactDOM.render(
    
  128.       <div>
    
  129.         {'bar'}
    
  130.         {'baz'}
    
  131.         {'qux'}
    
  132.         <span />
    
  133.         {'foo'}
    
  134.       </div>,
    
  135.       el,
    
  136.     );
    
  137.     expect(inst.textContent).toBe('barbazquxfoo');
    
  138.   });
    
  139. 
    
  140.   xit('can reconcile text merged by Node.normalize()', () => {
    
  141.     const el = document.createElement('div');
    
  142.     let inst = ReactDOM.render(
    
  143.       <div>
    
  144.         {'foo'}
    
  145.         {'bar'}
    
  146.         {'baz'}
    
  147.       </div>,
    
  148.       el,
    
  149.     );
    
  150. 
    
  151.     inst.normalize();
    
  152. 
    
  153.     inst = ReactDOM.render(
    
  154.       <div>
    
  155.         {'bar'}
    
  156.         {'baz'}
    
  157.         {'qux'}
    
  158.       </div>,
    
  159.       el,
    
  160.     );
    
  161.     expect(inst.textContent).toBe('barbazqux');
    
  162.   });
    
  163. 
    
  164.   it('can reconcile text from pre-rendered markup', () => {
    
  165.     const el = document.createElement('div');
    
  166.     let reactEl = (
    
  167.       <div>
    
  168.         {'foo'}
    
  169.         {'bar'}
    
  170.         {'baz'}
    
  171.       </div>
    
  172.     );
    
  173.     el.innerHTML = ReactDOMServer.renderToString(reactEl);
    
  174. 
    
  175.     ReactDOM.hydrate(reactEl, el);
    
  176.     expect(el.textContent).toBe('foobarbaz');
    
  177. 
    
  178.     ReactDOM.unmountComponentAtNode(el);
    
  179. 
    
  180.     reactEl = (
    
  181.       <div>
    
  182.         {''}
    
  183.         {''}
    
  184.         {''}
    
  185.       </div>
    
  186.     );
    
  187.     el.innerHTML = ReactDOMServer.renderToString(reactEl);
    
  188. 
    
  189.     ReactDOM.hydrate(reactEl, el);
    
  190.     expect(el.textContent).toBe('');
    
  191.   });
    
  192. 
    
  193.   xit('can reconcile text arbitrarily split into multiple nodes', () => {
    
  194.     const el = document.createElement('div');
    
  195.     let inst = ReactDOM.render(
    
  196.       <div>
    
  197.         <span />
    
  198.         {'foobarbaz'}
    
  199.       </div>,
    
  200.       el,
    
  201.     );
    
  202. 
    
  203.     const childNodes = filterOutComments(inst.childNodes);
    
  204.     const textNode = childNodes[1];
    
  205.     textNode.textContent = 'foo';
    
  206.     inst.insertBefore(
    
  207.       document.createTextNode('bar'),
    
  208.       childNodes[1].nextSibling,
    
  209.     );
    
  210.     inst.insertBefore(
    
  211.       document.createTextNode('baz'),
    
  212.       childNodes[1].nextSibling,
    
  213.     );
    
  214. 
    
  215.     inst = ReactDOM.render(
    
  216.       <div>
    
  217.         <span />
    
  218.         {'barbazqux'}
    
  219.       </div>,
    
  220.       el,
    
  221.     );
    
  222.     expect(inst.textContent).toBe('barbazqux');
    
  223.   });
    
  224. 
    
  225.   xit('can reconcile text arbitrarily split into multiple nodes on some substitutions only', () => {
    
  226.     const el = document.createElement('div');
    
  227.     let inst = ReactDOM.render(
    
  228.       <div>
    
  229.         <span />
    
  230.         {'bar'}
    
  231.         <span />
    
  232.         {'foobarbaz'}
    
  233.         {'foo'}
    
  234.         {'barfoo'}
    
  235.         <span />
    
  236.       </div>,
    
  237.       el,
    
  238.     );
    
  239. 
    
  240.     const childNodes = filterOutComments(inst.childNodes);
    
  241.     const textNode = childNodes[3];
    
  242.     textNode.textContent = 'foo';
    
  243.     inst.insertBefore(
    
  244.       document.createTextNode('bar'),
    
  245.       childNodes[3].nextSibling,
    
  246.     );
    
  247.     inst.insertBefore(
    
  248.       document.createTextNode('baz'),
    
  249.       childNodes[3].nextSibling,
    
  250.     );
    
  251.     const secondTextNode = childNodes[5];
    
  252.     secondTextNode.textContent = 'bar';
    
  253.     inst.insertBefore(
    
  254.       document.createTextNode('foo'),
    
  255.       childNodes[5].nextSibling,
    
  256.     );
    
  257. 
    
  258.     inst = ReactDOM.render(
    
  259.       <div>
    
  260.         <span />
    
  261.         {'baz'}
    
  262.         <span />
    
  263.         {'barbazqux'}
    
  264.         {'bar'}
    
  265.         {'bazbar'}
    
  266.         <span />
    
  267.       </div>,
    
  268.       el,
    
  269.     );
    
  270.     expect(inst.textContent).toBe('bazbarbazquxbarbazbar');
    
  271.   });
    
  272. 
    
  273.   xit('can unmount normalized text nodes', () => {
    
  274.     const el = document.createElement('div');
    
  275.     ReactDOM.render(
    
  276.       <div>
    
  277.         {''}
    
  278.         {'foo'}
    
  279.         {'bar'}
    
  280.       </div>,
    
  281.       el,
    
  282.     );
    
  283.     el.normalize();
    
  284.     ReactDOM.render(<div />, el);
    
  285.     expect(el.innerHTML).toBe('<div></div>');
    
  286.   });
    
  287. 
    
  288.   it('throws for Temporal-like text nodes', () => {
    
  289.     const el = document.createElement('div');
    
  290.     class TemporalLike {
    
  291.       valueOf() {
    
  292.         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
    
  293.         // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
    
  294.         throw new TypeError('prod message');
    
  295.       }
    
  296.       toString() {
    
  297.         return '2020-01-01';
    
  298.       }
    
  299.     }
    
  300.     expect(() =>
    
  301.       ReactDOM.render(<div>{new TemporalLike()}</div>, el),
    
  302.     ).toThrowError(
    
  303.       new Error(
    
  304.         'Objects are not valid as a React child (found: object with keys {}).' +
    
  305.           ' If you meant to render a collection of children, use an array instead.',
    
  306.       ),
    
  307.     );
    
  308.   });
    
  309. });