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 ReactTestUtils;
    
  15. 
    
  16. describe('ReactIdentity', () => {
    
  17.   beforeEach(() => {
    
  18.     jest.resetModules();
    
  19.     React = require('react');
    
  20.     ReactDOM = require('react-dom');
    
  21.     ReactTestUtils = require('react-dom/test-utils');
    
  22.   });
    
  23. 
    
  24.   it('should allow key property to express identity', () => {
    
  25.     let node;
    
  26.     const Component = props => (
    
  27.       <div ref={c => (node = c)}>
    
  28.         <div key={props.swap ? 'banana' : 'apple'} />
    
  29.         <div key={props.swap ? 'apple' : 'banana'} />
    
  30.       </div>
    
  31.     );
    
  32. 
    
  33.     const container = document.createElement('div');
    
  34.     ReactDOM.render(<Component />, container);
    
  35.     const origChildren = Array.from(node.childNodes);
    
  36.     ReactDOM.render(<Component swap={true} />, container);
    
  37.     const newChildren = Array.from(node.childNodes);
    
  38.     expect(origChildren[0]).toBe(newChildren[1]);
    
  39.     expect(origChildren[1]).toBe(newChildren[0]);
    
  40.   });
    
  41. 
    
  42.   it('should use composite identity', () => {
    
  43.     class Wrapper extends React.Component {
    
  44.       render() {
    
  45.         return <a>{this.props.children}</a>;
    
  46.       }
    
  47.     }
    
  48. 
    
  49.     const container = document.createElement('div');
    
  50.     let node1;
    
  51.     let node2;
    
  52.     ReactDOM.render(
    
  53.       <Wrapper key="wrap1">
    
  54.         <span ref={c => (node1 = c)} />
    
  55.       </Wrapper>,
    
  56.       container,
    
  57.     );
    
  58.     ReactDOM.render(
    
  59.       <Wrapper key="wrap2">
    
  60.         <span ref={c => (node2 = c)} />
    
  61.       </Wrapper>,
    
  62.       container,
    
  63.     );
    
  64. 
    
  65.     expect(node1).not.toBe(node2);
    
  66.   });
    
  67. 
    
  68.   function renderAComponentWithKeyIntoContainer(key, container) {
    
  69.     class Wrapper extends React.Component {
    
  70.       spanRef = React.createRef();
    
  71.       render() {
    
  72.         return (
    
  73.           <div>
    
  74.             <span ref={this.spanRef} key={key} />
    
  75.           </div>
    
  76.         );
    
  77.       }
    
  78.     }
    
  79. 
    
  80.     const instance = ReactDOM.render(<Wrapper />, container);
    
  81.     const span = instance.spanRef.current;
    
  82.     expect(span).not.toBe(null);
    
  83.   }
    
  84. 
    
  85.   it('should allow any character as a key, in a detached parent', () => {
    
  86.     const detachedContainer = document.createElement('div');
    
  87.     renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", detachedContainer);
    
  88.   });
    
  89. 
    
  90.   it('should allow any character as a key, in an attached parent', () => {
    
  91.     // This test exists to protect against implementation details that
    
  92.     // incorrectly query escaped IDs using DOM tools like getElementById.
    
  93.     const attachedContainer = document.createElement('div');
    
  94.     document.body.appendChild(attachedContainer);
    
  95. 
    
  96.     renderAComponentWithKeyIntoContainer("<'WEIRD/&\\key'>", attachedContainer);
    
  97. 
    
  98.     document.body.removeChild(attachedContainer);
    
  99.   });
    
  100. 
    
  101.   it('should not allow scripts in keys to execute', () => {
    
  102.     const h4x0rKey =
    
  103.       '"><script>window[\'YOUVEBEENH4X0RED\']=true;</script><div id="';
    
  104. 
    
  105.     const attachedContainer = document.createElement('div');
    
  106.     document.body.appendChild(attachedContainer);
    
  107. 
    
  108.     renderAComponentWithKeyIntoContainer(h4x0rKey, attachedContainer);
    
  109. 
    
  110.     document.body.removeChild(attachedContainer);
    
  111. 
    
  112.     // If we get this far, make sure we haven't executed the code
    
  113.     expect(window.YOUVEBEENH4X0RED).toBe(undefined);
    
  114.   });
    
  115. 
    
  116.   it('should let restructured components retain their uniqueness', () => {
    
  117.     const instance0 = <span />;
    
  118.     const instance1 = <span />;
    
  119.     const instance2 = <span />;
    
  120. 
    
  121.     class TestComponent extends React.Component {
    
  122.       render() {
    
  123.         return (
    
  124.           <div>
    
  125.             {instance2}
    
  126.             {this.props.children[0]}
    
  127.             {this.props.children[1]}
    
  128.           </div>
    
  129.         );
    
  130.       }
    
  131.     }
    
  132. 
    
  133.     class TestContainer extends React.Component {
    
  134.       render() {
    
  135.         return (
    
  136.           <TestComponent>
    
  137.             {instance0}
    
  138.             {instance1}
    
  139.           </TestComponent>
    
  140.         );
    
  141.       }
    
  142.     }
    
  143. 
    
  144.     expect(function () {
    
  145.       ReactTestUtils.renderIntoDocument(<TestContainer />);
    
  146.     }).not.toThrow();
    
  147.   });
    
  148. 
    
  149.   it('should let nested restructures retain their uniqueness', () => {
    
  150.     const instance0 = <span />;
    
  151.     const instance1 = <span />;
    
  152.     const instance2 = <span />;
    
  153. 
    
  154.     class TestComponent extends React.Component {
    
  155.       render() {
    
  156.         return (
    
  157.           <div>
    
  158.             {instance2}
    
  159.             {this.props.children[0]}
    
  160.             {this.props.children[1]}
    
  161.           </div>
    
  162.         );
    
  163.       }
    
  164.     }
    
  165. 
    
  166.     class TestContainer extends React.Component {
    
  167.       render() {
    
  168.         return (
    
  169.           <div>
    
  170.             <TestComponent>
    
  171.               {instance0}
    
  172.               {instance1}
    
  173.             </TestComponent>
    
  174.           </div>
    
  175.         );
    
  176.       }
    
  177.     }
    
  178. 
    
  179.     expect(function () {
    
  180.       ReactTestUtils.renderIntoDocument(<TestContainer />);
    
  181.     }).not.toThrow();
    
  182.   });
    
  183. 
    
  184.   it('should let text nodes retain their uniqueness', () => {
    
  185.     class TestComponent extends React.Component {
    
  186.       render() {
    
  187.         return (
    
  188.           <div>
    
  189.             {this.props.children}
    
  190.             <span />
    
  191.           </div>
    
  192.         );
    
  193.       }
    
  194.     }
    
  195. 
    
  196.     class TestContainer extends React.Component {
    
  197.       render() {
    
  198.         return (
    
  199.           <TestComponent>
    
  200.             <div />
    
  201.             {'second'}
    
  202.           </TestComponent>
    
  203.         );
    
  204.       }
    
  205.     }
    
  206. 
    
  207.     expect(function () {
    
  208.       ReactTestUtils.renderIntoDocument(<TestContainer />);
    
  209.     }).not.toThrow();
    
  210.   });
    
  211. 
    
  212.   it('should retain key during updates in composite components', () => {
    
  213.     class TestComponent extends React.Component {
    
  214.       render() {
    
  215.         return <div>{this.props.children}</div>;
    
  216.       }
    
  217.     }
    
  218. 
    
  219.     class TestContainer extends React.Component {
    
  220.       state = {swapped: false};
    
  221. 
    
  222.       swap = () => {
    
  223.         this.setState({swapped: true});
    
  224.       };
    
  225. 
    
  226.       render() {
    
  227.         return (
    
  228.           <TestComponent>
    
  229.             {this.state.swapped ? this.props.second : this.props.first}
    
  230.             {this.state.swapped ? this.props.first : this.props.second}
    
  231.           </TestComponent>
    
  232.         );
    
  233.       }
    
  234.     }
    
  235. 
    
  236.     const instance0 = <span key="A" />;
    
  237.     const instance1 = <span key="B" />;
    
  238. 
    
  239.     let wrapped = <TestContainer first={instance0} second={instance1} />;
    
  240. 
    
  241.     wrapped = ReactDOM.render(wrapped, document.createElement('div'));
    
  242.     const div = ReactDOM.findDOMNode(wrapped);
    
  243. 
    
  244.     const beforeA = div.childNodes[0];
    
  245.     const beforeB = div.childNodes[1];
    
  246.     wrapped.swap();
    
  247.     const afterA = div.childNodes[1];
    
  248.     const afterB = div.childNodes[0];
    
  249. 
    
  250.     expect(beforeA).toBe(afterA);
    
  251.     expect(beforeB).toBe(afterB);
    
  252.   });
    
  253. 
    
  254.   it('should not allow implicit and explicit keys to collide', () => {
    
  255.     const component = (
    
  256.       <div>
    
  257.         <span />
    
  258.         <span key="0" />
    
  259.       </div>
    
  260.     );
    
  261. 
    
  262.     expect(function () {
    
  263.       ReactTestUtils.renderIntoDocument(component);
    
  264.     }).not.toThrow();
    
  265.   });
    
  266. 
    
  267.   it('should throw if key is a Temporal-like object', () => {
    
  268.     class TemporalLike {
    
  269.       valueOf() {
    
  270.         // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
    
  271.         // See https://tc39.es/proposal-temporal/docs/plaindate.html#valueOf
    
  272.         throw new TypeError('prod message');
    
  273.       }
    
  274.       toString() {
    
  275.         return '2020-01-01';
    
  276.       }
    
  277.     }
    
  278. 
    
  279.     const el = document.createElement('div');
    
  280.     const test = () =>
    
  281.       ReactDOM.render(
    
  282.         <div>
    
  283.           <span key={new TemporalLike()} />
    
  284.         </div>,
    
  285.         el,
    
  286.       );
    
  287.     expect(() =>
    
  288.       expect(test).toThrowError(new TypeError('prod message')),
    
  289.     ).toErrorDev(
    
  290.       'The provided key is an unsupported type TemporalLike.' +
    
  291.         ' This value must be coerced to a string before using it here.',
    
  292.       {withoutStack: true},
    
  293.     );
    
  294.   });
    
  295. });