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. import * as React from 'react';
    
  13. import * as ReactDOM from 'react-dom';
    
  14. import * as ReactDOMServer from 'react-dom/server';
    
  15. import * as ReactTestUtils from 'react-dom/test-utils';
    
  16. 
    
  17. function getTestDocument(markup) {
    
  18.   const doc = document.implementation.createHTMLDocument('');
    
  19.   doc.open();
    
  20.   doc.write(
    
  21.     markup ||
    
  22.       '<!doctype html><html><meta charset=utf-8><title>test doc</title>',
    
  23.   );
    
  24.   doc.close();
    
  25.   return doc;
    
  26. }
    
  27. 
    
  28. describe('ReactTestUtils', () => {
    
  29.   it('Simulate should have locally attached media events', () => {
    
  30.     expect(Object.keys(ReactTestUtils.Simulate).sort()).toMatchSnapshot();
    
  31.   });
    
  32. 
    
  33.   it('gives Jest mocks a passthrough implementation with mockComponent()', () => {
    
  34.     class MockedComponent extends React.Component {
    
  35.       render() {
    
  36.         throw new Error('Should not get here.');
    
  37.       }
    
  38.     }
    
  39.     // This is close enough to what a Jest mock would give us.
    
  40.     MockedComponent.prototype.render = jest.fn();
    
  41. 
    
  42.     // Patch it up so it returns its children.
    
  43.     expect(() => ReactTestUtils.mockComponent(MockedComponent)).toWarnDev(
    
  44.       'ReactTestUtils.mockComponent() is deprecated. ' +
    
  45.         'Use shallow rendering or jest.mock() instead.\n\n' +
    
  46.         'See https://reactjs.org/link/test-utils-mock-component for more information.',
    
  47.       {withoutStack: true},
    
  48.     );
    
  49. 
    
  50.     // De-duplication check
    
  51.     ReactTestUtils.mockComponent(MockedComponent);
    
  52. 
    
  53.     const container = document.createElement('div');
    
  54.     ReactDOM.render(<MockedComponent>Hello</MockedComponent>, container);
    
  55.     expect(container.textContent).toBe('Hello');
    
  56.   });
    
  57. 
    
  58.   it('can scryRenderedComponentsWithType', () => {
    
  59.     class Child extends React.Component {
    
  60.       render() {
    
  61.         return null;
    
  62.       }
    
  63.     }
    
  64.     class Wrapper extends React.Component {
    
  65.       render() {
    
  66.         return (
    
  67.           <div>
    
  68.             <Child />
    
  69.           </div>
    
  70.         );
    
  71.       }
    
  72.     }
    
  73.     const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
    
  74.     const scryResults = ReactTestUtils.scryRenderedComponentsWithType(
    
  75.       renderedComponent,
    
  76.       Child,
    
  77.     );
    
  78.     expect(scryResults.length).toBe(1);
    
  79.   });
    
  80. 
    
  81.   it('can scryRenderedDOMComponentsWithClass with TextComponent', () => {
    
  82.     class Wrapper extends React.Component {
    
  83.       render() {
    
  84.         return (
    
  85.           <div>
    
  86.             Hello <span>Jim</span>
    
  87.           </div>
    
  88.         );
    
  89.       }
    
  90.     }
    
  91. 
    
  92.     const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
    
  93.     const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  94.       renderedComponent,
    
  95.       'NonExistentClass',
    
  96.     );
    
  97.     expect(scryResults.length).toBe(0);
    
  98.   });
    
  99. 
    
  100.   it('can scryRenderedDOMComponentsWithClass with className contains \\n', () => {
    
  101.     class Wrapper extends React.Component {
    
  102.       render() {
    
  103.         return (
    
  104.           <div>
    
  105.             Hello <span className={'x\ny'}>Jim</span>
    
  106.           </div>
    
  107.         );
    
  108.       }
    
  109.     }
    
  110. 
    
  111.     const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
    
  112.     const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  113.       renderedComponent,
    
  114.       'x',
    
  115.     );
    
  116.     expect(scryResults.length).toBe(1);
    
  117.   });
    
  118. 
    
  119.   it('can scryRenderedDOMComponentsWithClass with multiple classes', () => {
    
  120.     class Wrapper extends React.Component {
    
  121.       render() {
    
  122.         return (
    
  123.           <div>
    
  124.             Hello <span className={'x y z'}>Jim</span>
    
  125.           </div>
    
  126.         );
    
  127.       }
    
  128.     }
    
  129. 
    
  130.     const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
    
  131.     const scryResults1 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  132.       renderedComponent,
    
  133.       'x y',
    
  134.     );
    
  135.     expect(scryResults1.length).toBe(1);
    
  136. 
    
  137.     const scryResults2 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  138.       renderedComponent,
    
  139.       'x z',
    
  140.     );
    
  141.     expect(scryResults2.length).toBe(1);
    
  142. 
    
  143.     const scryResults3 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  144.       renderedComponent,
    
  145.       ['x', 'y'],
    
  146.     );
    
  147.     expect(scryResults3.length).toBe(1);
    
  148. 
    
  149.     expect(scryResults1[0]).toBe(scryResults2[0]);
    
  150.     expect(scryResults1[0]).toBe(scryResults3[0]);
    
  151. 
    
  152.     const scryResults4 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  153.       renderedComponent,
    
  154.       ['x', 'a'],
    
  155.     );
    
  156.     expect(scryResults4.length).toBe(0);
    
  157. 
    
  158.     const scryResults5 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  159.       renderedComponent,
    
  160.       ['x a'],
    
  161.     );
    
  162.     expect(scryResults5.length).toBe(0);
    
  163.   });
    
  164. 
    
  165.   it('traverses children in the correct order', () => {
    
  166.     class Wrapper extends React.Component {
    
  167.       render() {
    
  168.         return <div>{this.props.children}</div>;
    
  169.       }
    
  170.     }
    
  171. 
    
  172.     const container = document.createElement('div');
    
  173.     ReactDOM.render(
    
  174.       <Wrapper>
    
  175.         {null}
    
  176.         <div>purple</div>
    
  177.       </Wrapper>,
    
  178.       container,
    
  179.     );
    
  180.     const tree = ReactDOM.render(
    
  181.       <Wrapper>
    
  182.         <div>orange</div>
    
  183.         <div>purple</div>
    
  184.       </Wrapper>,
    
  185.       container,
    
  186.     );
    
  187. 
    
  188.     const log = [];
    
  189.     ReactTestUtils.findAllInRenderedTree(tree, function (child) {
    
  190.       if (ReactTestUtils.isDOMComponent(child)) {
    
  191.         log.push(ReactDOM.findDOMNode(child).textContent);
    
  192.       }
    
  193.     });
    
  194. 
    
  195.     // Should be document order, not mount order (which would be purple, orange)
    
  196.     expect(log).toEqual(['orangepurple', 'orange', 'purple']);
    
  197.   });
    
  198. 
    
  199.   it('should support injected wrapper components as DOM components', () => {
    
  200.     const injectedDOMComponents = [
    
  201.       'button',
    
  202.       'form',
    
  203.       'iframe',
    
  204.       'img',
    
  205.       'input',
    
  206.       'option',
    
  207.       'select',
    
  208.       'textarea',
    
  209.     ];
    
  210. 
    
  211.     injectedDOMComponents.forEach(function (type) {
    
  212.       const testComponent = ReactTestUtils.renderIntoDocument(
    
  213.         React.createElement(type),
    
  214.       );
    
  215.       expect(testComponent.tagName).toBe(type.toUpperCase());
    
  216.       expect(ReactTestUtils.isDOMComponent(testComponent)).toBe(true);
    
  217.     });
    
  218. 
    
  219.     // Full-page components (html, head, body) can't be rendered into a div
    
  220.     // directly...
    
  221.     class Root extends React.Component {
    
  222.       htmlRef = React.createRef();
    
  223.       headRef = React.createRef();
    
  224.       bodyRef = React.createRef();
    
  225. 
    
  226.       render() {
    
  227.         return (
    
  228.           <html ref={this.htmlRef}>
    
  229.             <head ref={this.headRef}>
    
  230.               <title>hello</title>
    
  231.             </head>
    
  232.             <body ref={this.bodyRef}>hello, world</body>
    
  233.           </html>
    
  234.         );
    
  235.       }
    
  236.     }
    
  237. 
    
  238.     const markup = ReactDOMServer.renderToString(<Root />);
    
  239.     const testDocument = getTestDocument(markup);
    
  240.     const component = ReactDOM.hydrate(<Root />, testDocument);
    
  241. 
    
  242.     expect(component.htmlRef.current.tagName).toBe('HTML');
    
  243.     expect(component.headRef.current.tagName).toBe('HEAD');
    
  244.     expect(component.bodyRef.current.tagName).toBe('BODY');
    
  245.     expect(ReactTestUtils.isDOMComponent(component.htmlRef.current)).toBe(true);
    
  246.     expect(ReactTestUtils.isDOMComponent(component.headRef.current)).toBe(true);
    
  247.     expect(ReactTestUtils.isDOMComponent(component.bodyRef.current)).toBe(true);
    
  248.   });
    
  249. 
    
  250.   it('can scry with stateless components involved', () => {
    
  251.     const Function = () => (
    
  252.       <div>
    
  253.         <hr />
    
  254.       </div>
    
  255.     );
    
  256. 
    
  257.     class SomeComponent extends React.Component {
    
  258.       render() {
    
  259.         return (
    
  260.           <div>
    
  261.             <Function />
    
  262.             <hr />
    
  263.           </div>
    
  264.         );
    
  265.       }
    
  266.     }
    
  267. 
    
  268.     const inst = ReactTestUtils.renderIntoDocument(<SomeComponent />);
    
  269.     const hrs = ReactTestUtils.scryRenderedDOMComponentsWithTag(inst, 'hr');
    
  270.     expect(hrs.length).toBe(2);
    
  271.   });
    
  272. 
    
  273.   it('provides a clear error when passing invalid objects to scry', () => {
    
  274.     // This is probably too relaxed but it's existing behavior.
    
  275.     ReactTestUtils.findAllInRenderedTree(null, 'span');
    
  276.     ReactTestUtils.findAllInRenderedTree(undefined, 'span');
    
  277.     ReactTestUtils.findAllInRenderedTree('', 'span');
    
  278.     ReactTestUtils.findAllInRenderedTree(0, 'span');
    
  279.     ReactTestUtils.findAllInRenderedTree(false, 'span');
    
  280. 
    
  281.     expect(() => {
    
  282.       ReactTestUtils.findAllInRenderedTree([], 'span');
    
  283.     }).toThrow(
    
  284.       'findAllInRenderedTree(...): the first argument must be a React class instance. ' +
    
  285.         'Instead received: an array.',
    
  286.     );
    
  287.     expect(() => {
    
  288.       ReactTestUtils.scryRenderedDOMComponentsWithClass(10, 'button');
    
  289.     }).toThrow(
    
  290.       'scryRenderedDOMComponentsWithClass(...): the first argument must be a React class instance. ' +
    
  291.         'Instead received: 10.',
    
  292.     );
    
  293.     expect(() => {
    
  294.       ReactTestUtils.findRenderedDOMComponentWithClass('hello', 'button');
    
  295.     }).toThrow(
    
  296.       'findRenderedDOMComponentWithClass(...): the first argument must be a React class instance. ' +
    
  297.         'Instead received: hello.',
    
  298.     );
    
  299.     expect(() => {
    
  300.       ReactTestUtils.scryRenderedDOMComponentsWithTag(
    
  301.         {x: true, y: false},
    
  302.         'span',
    
  303.       );
    
  304.     }).toThrow(
    
  305.       'scryRenderedDOMComponentsWithTag(...): the first argument must be a React class instance. ' +
    
  306.         'Instead received: object with keys {x, y}.',
    
  307.     );
    
  308.     const div = document.createElement('div');
    
  309.     expect(() => {
    
  310.       ReactTestUtils.findRenderedDOMComponentWithTag(div, 'span');
    
  311.     }).toThrow(
    
  312.       'findRenderedDOMComponentWithTag(...): the first argument must be a React class instance. ' +
    
  313.         'Instead received: a DOM node.',
    
  314.     );
    
  315.     expect(() => {
    
  316.       ReactTestUtils.scryRenderedComponentsWithType(true, 'span');
    
  317.     }).toThrow(
    
  318.       'scryRenderedComponentsWithType(...): the first argument must be a React class instance. ' +
    
  319.         'Instead received: true.',
    
  320.     );
    
  321.     expect(() => {
    
  322.       ReactTestUtils.findRenderedComponentWithType(true, 'span');
    
  323.     }).toThrow(
    
  324.       'findRenderedComponentWithType(...): the first argument must be a React class instance. ' +
    
  325.         'Instead received: true.',
    
  326.     );
    
  327.   });
    
  328. 
    
  329.   describe('Simulate', () => {
    
  330.     it('should change the value of an input field', () => {
    
  331.       const obj = {
    
  332.         handler: function (e) {
    
  333.           e.persist();
    
  334.         },
    
  335.       };
    
  336.       spyOnDevAndProd(obj, 'handler');
    
  337.       const container = document.createElement('div');
    
  338.       const node = ReactDOM.render(
    
  339.         <input type="text" onChange={obj.handler} />,
    
  340.         container,
    
  341.       );
    
  342. 
    
  343.       node.value = 'giraffe';
    
  344.       ReactTestUtils.Simulate.change(node);
    
  345. 
    
  346.       expect(obj.handler).toHaveBeenCalledWith(
    
  347.         expect.objectContaining({target: node}),
    
  348.       );
    
  349.     });
    
  350. 
    
  351.     it('should change the value of an input field in a component', () => {
    
  352.       class SomeComponent extends React.Component {
    
  353.         inputRef = React.createRef();
    
  354.         render() {
    
  355.           return (
    
  356.             <div>
    
  357.               <input
    
  358.                 type="text"
    
  359.                 ref={this.inputRef}
    
  360.                 onChange={this.props.handleChange}
    
  361.               />
    
  362.             </div>
    
  363.           );
    
  364.         }
    
  365.       }
    
  366. 
    
  367.       const obj = {
    
  368.         handler: function (e) {
    
  369.           e.persist();
    
  370.         },
    
  371.       };
    
  372.       spyOnDevAndProd(obj, 'handler');
    
  373.       const container = document.createElement('div');
    
  374.       const instance = ReactDOM.render(
    
  375.         <SomeComponent handleChange={obj.handler} />,
    
  376.         container,
    
  377.       );
    
  378. 
    
  379.       const node = instance.inputRef.current;
    
  380.       node.value = 'zebra';
    
  381.       ReactTestUtils.Simulate.change(node);
    
  382. 
    
  383.       expect(obj.handler).toHaveBeenCalledWith(
    
  384.         expect.objectContaining({target: node}),
    
  385.       );
    
  386.     });
    
  387. 
    
  388.     it('should not warn when used with extra properties', () => {
    
  389.       const CLIENT_X = 100;
    
  390. 
    
  391.       class Component extends React.Component {
    
  392.         handleClick = e => {
    
  393.           expect(e.clientX).toBe(CLIENT_X);
    
  394.         };
    
  395. 
    
  396.         render() {
    
  397.           return <div onClick={this.handleClick} />;
    
  398.         }
    
  399.       }
    
  400. 
    
  401.       const element = document.createElement('div');
    
  402.       const instance = ReactDOM.render(<Component />, element);
    
  403.       ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance), {
    
  404.         clientX: CLIENT_X,
    
  405.       });
    
  406.     });
    
  407. 
    
  408.     it('should set the type of the event', () => {
    
  409.       let event;
    
  410.       const stub = jest.fn().mockImplementation(e => {
    
  411.         e.persist();
    
  412.         event = e;
    
  413.       });
    
  414. 
    
  415.       const container = document.createElement('div');
    
  416.       const instance = ReactDOM.render(<div onKeyDown={stub} />, container);
    
  417.       const node = ReactDOM.findDOMNode(instance);
    
  418. 
    
  419.       ReactTestUtils.Simulate.keyDown(node);
    
  420. 
    
  421.       expect(event.type).toBe('keydown');
    
  422.       expect(event.nativeEvent.type).toBe('keydown');
    
  423.     });
    
  424. 
    
  425.     it('should work with renderIntoDocument', () => {
    
  426.       const onChange = jest.fn();
    
  427. 
    
  428.       class MyComponent extends React.Component {
    
  429.         render() {
    
  430.           return (
    
  431.             <div>
    
  432.               <input type="text" onChange={onChange} />
    
  433.             </div>
    
  434.           );
    
  435.         }
    
  436.       }
    
  437. 
    
  438.       const instance = ReactTestUtils.renderIntoDocument(<MyComponent />);
    
  439.       const input = ReactTestUtils.findRenderedDOMComponentWithTag(
    
  440.         instance,
    
  441.         'input',
    
  442.       );
    
  443.       input.value = 'giraffe';
    
  444.       ReactTestUtils.Simulate.change(input);
    
  445. 
    
  446.       expect(onChange).toHaveBeenCalledWith(
    
  447.         expect.objectContaining({target: input}),
    
  448.       );
    
  449.     });
    
  450.   });
    
  451. 
    
  452.   it('should call setState callback with no arguments', () => {
    
  453.     let mockArgs;
    
  454.     class Component extends React.Component {
    
  455.       componentDidMount() {
    
  456.         this.setState({}, (...args) => (mockArgs = args));
    
  457.       }
    
  458.       render() {
    
  459.         return false;
    
  460.       }
    
  461.     }
    
  462. 
    
  463.     ReactTestUtils.renderIntoDocument(<Component />);
    
  464.     expect(mockArgs.length).toEqual(0);
    
  465.   });
    
  466.   it('should find rendered component with type in document', () => {
    
  467.     class MyComponent extends React.Component {
    
  468.       render() {
    
  469.         return true;
    
  470.       }
    
  471.     }
    
  472. 
    
  473.     const instance = ReactTestUtils.renderIntoDocument(<MyComponent />);
    
  474.     const renderedComponentType = ReactTestUtils.findRenderedComponentWithType(
    
  475.       instance,
    
  476.       MyComponent,
    
  477.     );
    
  478. 
    
  479.     expect(renderedComponentType).toBe(instance);
    
  480.   });
    
  481. });