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 = require('react');
    
  13. let ReactDOM = require('react-dom');
    
  14. let ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  15. let ReactTestUtils = require('react-dom/test-utils');
    
  16. 
    
  17. // This is testing if string refs are deleted from `instance.refs`
    
  18. // Once support for string refs is removed, this test can be removed.
    
  19. // Detaching is already tested in refs-detruction-test.js
    
  20. describe('reactiverefs', () => {
    
  21.   let container;
    
  22. 
    
  23.   beforeEach(() => {
    
  24.     jest.resetModules();
    
  25.     React = require('react');
    
  26.     ReactDOM = require('react-dom');
    
  27.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  28.     ReactTestUtils = require('react-dom/test-utils');
    
  29.   });
    
  30. 
    
  31.   afterEach(() => {
    
  32.     if (container) {
    
  33.       document.body.removeChild(container);
    
  34.       container = null;
    
  35.     }
    
  36.   });
    
  37. 
    
  38.   /**
    
  39.    * Counts clicks and has a renders an item for each click. Each item rendered
    
  40.    * has a ref of the form "clickLogN".
    
  41.    */
    
  42.   class ClickCounter extends React.Component {
    
  43.     state = {count: this.props.initialCount};
    
  44. 
    
  45.     triggerReset = () => {
    
  46.       this.setState({count: this.props.initialCount});
    
  47.     };
    
  48. 
    
  49.     handleClick = () => {
    
  50.       this.setState({count: this.state.count + 1});
    
  51.     };
    
  52. 
    
  53.     render() {
    
  54.       const children = [];
    
  55.       let i;
    
  56.       for (i = 0; i < this.state.count; i++) {
    
  57.         children.push(
    
  58.           <div
    
  59.             className="clickLogDiv"
    
  60.             key={'clickLog' + i}
    
  61.             ref={'clickLog' + i}
    
  62.           />,
    
  63.         );
    
  64.       }
    
  65.       return (
    
  66.         <span className="clickIncrementer" onClick={this.handleClick}>
    
  67.           {children}
    
  68.         </span>
    
  69.       );
    
  70.     }
    
  71.   }
    
  72. 
    
  73.   const expectClickLogsLengthToBe = function (instance, length) {
    
  74.     const clickLogs = ReactTestUtils.scryRenderedDOMComponentsWithClass(
    
  75.       instance,
    
  76.       'clickLogDiv',
    
  77.     );
    
  78.     expect(clickLogs.length).toBe(length);
    
  79.     expect(Object.keys(instance.refs.myCounter.refs).length).toBe(length);
    
  80.   };
    
  81. 
    
  82.   /**
    
  83.    * Render a TestRefsComponent and ensure that the main refs are wired up.
    
  84.    */
    
  85.   const renderTestRefsComponent = function () {
    
  86.     /**
    
  87.      * Only purpose is to test that refs are tracked even when applied to a
    
  88.      * component that is injected down several layers. Ref systems are difficult to
    
  89.      * build in such a way that ownership is maintained in an airtight manner.
    
  90.      */
    
  91.     class GeneralContainerComponent extends React.Component {
    
  92.       render() {
    
  93.         return <div>{this.props.children}</div>;
    
  94.       }
    
  95.     }
    
  96. 
    
  97.     /**
    
  98.      * Notice how refs ownership is maintained even when injecting a component
    
  99.      * into a different parent.
    
  100.      */
    
  101.     class TestRefsComponent extends React.Component {
    
  102.       doReset = () => {
    
  103.         this.refs.myCounter.triggerReset();
    
  104.       };
    
  105. 
    
  106.       render() {
    
  107.         return (
    
  108.           <div>
    
  109.             <div ref="resetDiv" onClick={this.doReset}>
    
  110.               Reset Me By Clicking This.
    
  111.             </div>
    
  112.             <GeneralContainerComponent ref="myContainer">
    
  113.               <ClickCounter ref="myCounter" initialCount={1} />
    
  114.             </GeneralContainerComponent>
    
  115.           </div>
    
  116.         );
    
  117.       }
    
  118.     }
    
  119. 
    
  120.     container = document.createElement('div');
    
  121.     document.body.appendChild(container);
    
  122. 
    
  123.     let testRefsComponent;
    
  124.     expect(() => {
    
  125.       testRefsComponent = ReactDOM.render(<TestRefsComponent />, container);
    
  126.     }).toErrorDev([
    
  127.       'Warning: Component "div" contains the string ref "resetDiv". ' +
    
  128.         'Support for string refs will be removed in a future major release. ' +
    
  129.         'We recommend using useRef() or createRef() instead. ' +
    
  130.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  131.         '    in div (at **)\n' +
    
  132.         '    in TestRefsComponent (at **)',
    
  133.       'Warning: Component "span" contains the string ref "clickLog0". ' +
    
  134.         'Support for string refs will be removed in a future major release. ' +
    
  135.         'We recommend using useRef() or createRef() instead. ' +
    
  136.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  137.         '    in span (at **)\n' +
    
  138.         '    in ClickCounter (at **)\n' +
    
  139.         '    in div (at **)\n' +
    
  140.         '    in GeneralContainerComponent (at **)\n' +
    
  141.         '    in div (at **)\n' +
    
  142.         '    in TestRefsComponent (at **)',
    
  143.     ]);
    
  144. 
    
  145.     expect(testRefsComponent instanceof TestRefsComponent).toBe(true);
    
  146. 
    
  147.     const generalContainer = testRefsComponent.refs.myContainer;
    
  148.     expect(generalContainer instanceof GeneralContainerComponent).toBe(true);
    
  149. 
    
  150.     const counter = testRefsComponent.refs.myCounter;
    
  151.     expect(counter instanceof ClickCounter).toBe(true);
    
  152. 
    
  153.     return testRefsComponent;
    
  154.   };
    
  155. 
    
  156.   /**
    
  157.    * Ensure that for every click log there is a corresponding ref (from the
    
  158.    * perspective of the injected ClickCounter component.
    
  159.    */
    
  160.   it('Should increase refs with an increase in divs', () => {
    
  161.     const testRefsComponent = renderTestRefsComponent();
    
  162.     const clickIncrementer = ReactTestUtils.findRenderedDOMComponentWithClass(
    
  163.       testRefsComponent,
    
  164.       'clickIncrementer',
    
  165.     );
    
  166. 
    
  167.     expectClickLogsLengthToBe(testRefsComponent, 1);
    
  168. 
    
  169.     // After clicking the reset, there should still only be one click log ref.
    
  170.     testRefsComponent.refs.resetDiv.click();
    
  171.     expectClickLogsLengthToBe(testRefsComponent, 1);
    
  172. 
    
  173.     // Begin incrementing clicks (and therefore refs).
    
  174.     clickIncrementer.click();
    
  175.     expectClickLogsLengthToBe(testRefsComponent, 2);
    
  176. 
    
  177.     clickIncrementer.click();
    
  178.     expectClickLogsLengthToBe(testRefsComponent, 3);
    
  179. 
    
  180.     // Now reset again
    
  181.     testRefsComponent.refs.resetDiv.click();
    
  182.     expectClickLogsLengthToBe(testRefsComponent, 1);
    
  183.   });
    
  184. });
    
  185. 
    
  186. if (!ReactFeatureFlags.disableModulePatternComponents) {
    
  187.   describe('factory components', () => {
    
  188.     it('Should correctly get the ref', () => {
    
  189.       function Comp() {
    
  190.         return {
    
  191.           elemRef: React.createRef(),
    
  192.           render() {
    
  193.             return <div ref={this.elemRef} />;
    
  194.           },
    
  195.         };
    
  196.       }
    
  197. 
    
  198.       let inst;
    
  199.       expect(
    
  200.         () => (inst = ReactTestUtils.renderIntoDocument(<Comp />)),
    
  201.       ).toErrorDev(
    
  202.         'Warning: The <Comp /> component appears to be a function component that returns a class instance. ' +
    
  203.           'Change Comp to a class that extends React.Component instead. ' +
    
  204.           "If you can't use a class try assigning the prototype on the function as a workaround. " +
    
  205.           '`Comp.prototype = React.Component.prototype`. ' +
    
  206.           "Don't use an arrow function since it cannot be called with `new` by React.",
    
  207.       );
    
  208.       expect(inst.elemRef.current.tagName).toBe('DIV');
    
  209.     });
    
  210.   });
    
  211. }
    
  212. 
    
  213. /**
    
  214.  * Tests that when a ref hops around children, we can track that correctly.
    
  215.  */
    
  216. describe('ref swapping', () => {
    
  217.   let RefHopsAround;
    
  218.   beforeEach(() => {
    
  219.     jest.resetModules();
    
  220.     React = require('react');
    
  221.     ReactDOM = require('react-dom');
    
  222.     ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  223.     ReactTestUtils = require('react-dom/test-utils');
    
  224. 
    
  225.     RefHopsAround = class extends React.Component {
    
  226.       state = {count: 0};
    
  227.       hopRef = React.createRef();
    
  228.       divOneRef = React.createRef();
    
  229.       divTwoRef = React.createRef();
    
  230.       divThreeRef = React.createRef();
    
  231. 
    
  232.       moveRef = () => {
    
  233.         this.setState({count: this.state.count + 1});
    
  234.       };
    
  235. 
    
  236.       render() {
    
  237.         const count = this.state.count;
    
  238.         /**
    
  239.          * What we have here, is three divs with refs (div1/2/3), but a single
    
  240.          * moving cursor ref `hopRef` that "hops" around the three. We'll call the
    
  241.          * `moveRef()` function several times and make sure that the hop ref
    
  242.          * points to the correct divs.
    
  243.          */
    
  244.         return (
    
  245.           <div>
    
  246.             <div
    
  247.               className="first"
    
  248.               ref={count % 3 === 0 ? this.hopRef : this.divOneRef}
    
  249.             />
    
  250.             <div
    
  251.               className="second"
    
  252.               ref={count % 3 === 1 ? this.hopRef : this.divTwoRef}
    
  253.             />
    
  254.             <div
    
  255.               className="third"
    
  256.               ref={count % 3 === 2 ? this.hopRef : this.divThreeRef}
    
  257.             />
    
  258.           </div>
    
  259.         );
    
  260.       }
    
  261.     };
    
  262.   });
    
  263. 
    
  264.   it('Allow refs to hop around children correctly', () => {
    
  265.     const refHopsAround = ReactTestUtils.renderIntoDocument(<RefHopsAround />);
    
  266. 
    
  267.     const firstDiv = ReactTestUtils.findRenderedDOMComponentWithClass(
    
  268.       refHopsAround,
    
  269.       'first',
    
  270.     );
    
  271.     const secondDiv = ReactTestUtils.findRenderedDOMComponentWithClass(
    
  272.       refHopsAround,
    
  273.       'second',
    
  274.     );
    
  275.     const thirdDiv = ReactTestUtils.findRenderedDOMComponentWithClass(
    
  276.       refHopsAround,
    
  277.       'third',
    
  278.     );
    
  279. 
    
  280.     expect(refHopsAround.hopRef.current).toEqual(firstDiv);
    
  281.     expect(refHopsAround.divTwoRef.current).toEqual(secondDiv);
    
  282.     expect(refHopsAround.divThreeRef.current).toEqual(thirdDiv);
    
  283. 
    
  284.     refHopsAround.moveRef();
    
  285.     expect(refHopsAround.divOneRef.current).toEqual(firstDiv);
    
  286.     expect(refHopsAround.hopRef.current).toEqual(secondDiv);
    
  287.     expect(refHopsAround.divThreeRef.current).toEqual(thirdDiv);
    
  288. 
    
  289.     refHopsAround.moveRef();
    
  290.     expect(refHopsAround.divOneRef.current).toEqual(firstDiv);
    
  291.     expect(refHopsAround.divTwoRef.current).toEqual(secondDiv);
    
  292.     expect(refHopsAround.hopRef.current).toEqual(thirdDiv);
    
  293. 
    
  294.     /**
    
  295.      * Make sure that after the third, we're back to where we started and the
    
  296.      * refs are completely restored.
    
  297.      */
    
  298.     refHopsAround.moveRef();
    
  299.     expect(refHopsAround.hopRef.current).toEqual(firstDiv);
    
  300.     expect(refHopsAround.divTwoRef.current).toEqual(secondDiv);
    
  301.     expect(refHopsAround.divThreeRef.current).toEqual(thirdDiv);
    
  302.   });
    
  303. 
    
  304.   it('always has a value for this.refs', () => {
    
  305.     class Component extends React.Component {
    
  306.       render() {
    
  307.         return <div />;
    
  308.       }
    
  309.     }
    
  310. 
    
  311.     const instance = ReactTestUtils.renderIntoDocument(<Component />);
    
  312.     expect(!!instance.refs).toBe(true);
    
  313.   });
    
  314. 
    
  315.   it('ref called correctly for stateless component', () => {
    
  316.     let refCalled = 0;
    
  317.     function Inner(props) {
    
  318.       return <a ref={props.saveA} />;
    
  319.     }
    
  320. 
    
  321.     class Outer extends React.Component {
    
  322.       saveA = () => {
    
  323.         refCalled++;
    
  324.       };
    
  325. 
    
  326.       componentDidMount() {
    
  327.         this.setState({});
    
  328.       }
    
  329. 
    
  330.       render() {
    
  331.         return <Inner saveA={this.saveA} />;
    
  332.       }
    
  333.     }
    
  334. 
    
  335.     ReactTestUtils.renderIntoDocument(<Outer />);
    
  336.     expect(refCalled).toBe(1);
    
  337.   });
    
  338. 
    
  339.   it('coerces numbers to strings', () => {
    
  340.     class A extends React.Component {
    
  341.       render() {
    
  342.         return <div ref={1} />;
    
  343.       }
    
  344.     }
    
  345.     let a;
    
  346.     expect(() => {
    
  347.       a = ReactTestUtils.renderIntoDocument(<A />);
    
  348.     }).toErrorDev([
    
  349.       'Warning: Component "A" contains the string ref "1". ' +
    
  350.         'Support for string refs will be removed in a future major release. ' +
    
  351.         'We recommend using useRef() or createRef() instead. ' +
    
  352.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  353.         '    in A (at **)',
    
  354.     ]);
    
  355.     expect(a.refs[1].nodeName).toBe('DIV');
    
  356.   });
    
  357. 
    
  358.   it('provides an error for invalid refs', () => {
    
  359.     expect(() => {
    
  360.       ReactTestUtils.renderIntoDocument(<div ref={10} />);
    
  361.     }).toThrow(
    
  362.       'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
    
  363.     );
    
  364.     expect(() => {
    
  365.       ReactTestUtils.renderIntoDocument(<div ref={true} />);
    
  366.     }).toThrow(
    
  367.       'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
    
  368.     );
    
  369.     expect(() => {
    
  370.       ReactTestUtils.renderIntoDocument(<div ref={Symbol('foo')} />);
    
  371.     }).toThrow(
    
  372.       'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
    
  373.     );
    
  374.     // This works
    
  375.     ReactTestUtils.renderIntoDocument(<div ref={undefined} />);
    
  376.     ReactTestUtils.renderIntoDocument({
    
  377.       $$typeof: Symbol.for('react.element'),
    
  378.       type: 'div',
    
  379.       props: {},
    
  380.       key: null,
    
  381.       ref: null,
    
  382.     });
    
  383.     // But this doesn't
    
  384.     expect(() => {
    
  385.       ReactTestUtils.renderIntoDocument({
    
  386.         $$typeof: Symbol.for('react.element'),
    
  387.         type: 'div',
    
  388.         props: {},
    
  389.         key: null,
    
  390.         ref: undefined,
    
  391.       });
    
  392.     }).toThrow(
    
  393.       'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
    
  394.     );
    
  395.   });
    
  396. });
    
  397. 
    
  398. describe('root level refs', () => {
    
  399.   it('attaches and detaches root refs', () => {
    
  400.     let inst = null;
    
  401. 
    
  402.     // host node
    
  403.     let ref = jest.fn(value => (inst = value));
    
  404.     const container = document.createElement('div');
    
  405.     let result = ReactDOM.render(<div ref={ref} />, container);
    
  406.     expect(ref).toHaveBeenCalledTimes(1);
    
  407.     expect(ref.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement);
    
  408.     expect(result).toBe(ref.mock.calls[0][0]);
    
  409.     ReactDOM.unmountComponentAtNode(container);
    
  410.     expect(ref).toHaveBeenCalledTimes(2);
    
  411.     expect(ref.mock.calls[1][0]).toBe(null);
    
  412. 
    
  413.     // composite
    
  414.     class Comp extends React.Component {
    
  415.       method() {
    
  416.         return true;
    
  417.       }
    
  418.       render() {
    
  419.         return <div>Comp</div>;
    
  420.       }
    
  421.     }
    
  422. 
    
  423.     inst = null;
    
  424.     ref = jest.fn(value => (inst = value));
    
  425.     result = ReactDOM.render(<Comp ref={ref} />, container);
    
  426. 
    
  427.     expect(ref).toHaveBeenCalledTimes(1);
    
  428.     expect(inst).toBeInstanceOf(Comp);
    
  429.     expect(result).toBe(inst);
    
  430. 
    
  431.     // ensure we have the correct instance
    
  432.     expect(result.method()).toBe(true);
    
  433.     expect(inst.method()).toBe(true);
    
  434. 
    
  435.     ReactDOM.unmountComponentAtNode(container);
    
  436.     expect(ref).toHaveBeenCalledTimes(2);
    
  437.     expect(ref.mock.calls[1][0]).toBe(null);
    
  438. 
    
  439.     // fragment
    
  440.     inst = null;
    
  441.     ref = jest.fn(value => (inst = value));
    
  442.     let divInst = null;
    
  443.     const ref2 = jest.fn(value => (divInst = value));
    
  444.     result = ReactDOM.render(
    
  445.       [
    
  446.         <Comp ref={ref} key="a" />,
    
  447.         5,
    
  448.         <div ref={ref2} key="b">
    
  449.           Hello
    
  450.         </div>,
    
  451.       ],
    
  452.       container,
    
  453.     );
    
  454. 
    
  455.     // first call should be `Comp`
    
  456.     expect(ref).toHaveBeenCalledTimes(1);
    
  457.     expect(ref.mock.calls[0][0]).toBeInstanceOf(Comp);
    
  458.     expect(result).toBe(ref.mock.calls[0][0]);
    
  459. 
    
  460.     expect(ref2).toHaveBeenCalledTimes(1);
    
  461.     expect(divInst).toBeInstanceOf(HTMLDivElement);
    
  462.     expect(result).not.toBe(divInst);
    
  463. 
    
  464.     ReactDOM.unmountComponentAtNode(container);
    
  465.     expect(ref).toHaveBeenCalledTimes(2);
    
  466.     expect(ref.mock.calls[1][0]).toBe(null);
    
  467.     expect(ref2).toHaveBeenCalledTimes(2);
    
  468.     expect(ref2.mock.calls[1][0]).toBe(null);
    
  469. 
    
  470.     // null
    
  471.     result = ReactDOM.render(null, container);
    
  472.     expect(result).toBe(null);
    
  473. 
    
  474.     // primitives
    
  475.     result = ReactDOM.render(5, container);
    
  476.     expect(result).toBeInstanceOf(Text);
    
  477.   });
    
  478. });
    
  479. 
    
  480. describe('creating element with string ref in constructor', () => {
    
  481.   class RefTest extends React.Component {
    
  482.     constructor(props) {
    
  483.       super(props);
    
  484.       this.p = <p ref="p">Hello!</p>;
    
  485.     }
    
  486. 
    
  487.     render() {
    
  488.       return <div>{this.p}</div>;
    
  489.     }
    
  490.   }
    
  491. 
    
  492.   it('throws an error', () => {
    
  493.     ReactTestUtils = require('react-dom/test-utils');
    
  494. 
    
  495.     expect(function () {
    
  496.       ReactTestUtils.renderIntoDocument(<RefTest />);
    
  497.     }).toThrowError(
    
  498.       'Element ref was specified as a string (p) but no owner was set. This could happen for one of' +
    
  499.         ' the following reasons:\n' +
    
  500.         '1. You may be adding a ref to a function component\n' +
    
  501.         "2. You may be adding a ref to a component that was not created inside a component's render method\n" +
    
  502.         '3. You have multiple copies of React loaded\n' +
    
  503.         'See https://reactjs.org/link/refs-must-have-owner for more information.',
    
  504.     );
    
  505.   });
    
  506. });
    
  507. 
    
  508. describe('strings refs across renderers', () => {
    
  509.   it('does not break', () => {
    
  510.     class Parent extends React.Component {
    
  511.       render() {
    
  512.         // This component owns both refs.
    
  513.         return (
    
  514.           <Indirection
    
  515.             child1={<div ref="child1" />}
    
  516.             child2={<div ref="child2" />}
    
  517.           />
    
  518.         );
    
  519.       }
    
  520.     }
    
  521. 
    
  522.     class Indirection extends React.Component {
    
  523.       componentDidUpdate() {
    
  524.         // One ref is being rendered later using another renderer copy.
    
  525.         jest.resetModules();
    
  526.         const AnotherCopyOfReactDOM = require('react-dom');
    
  527.         AnotherCopyOfReactDOM.render(this.props.child2, div2);
    
  528.       }
    
  529.       render() {
    
  530.         // The other one is being rendered directly.
    
  531.         return this.props.child1;
    
  532.       }
    
  533.     }
    
  534. 
    
  535.     const div1 = document.createElement('div');
    
  536.     const div2 = document.createElement('div');
    
  537. 
    
  538.     let inst;
    
  539.     expect(() => {
    
  540.       inst = ReactDOM.render(<Parent />, div1);
    
  541.     }).toErrorDev([
    
  542.       'Warning: Component "Indirection" contains the string ref "child1". ' +
    
  543.         'Support for string refs will be removed in a future major release. ' +
    
  544.         'We recommend using useRef() or createRef() instead. ' +
    
  545.         'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref\n' +
    
  546.         '    in Indirection (at **)\n' +
    
  547.         '    in Parent (at **)',
    
  548.     ]);
    
  549. 
    
  550.     // Only the first ref has rendered yet.
    
  551.     expect(inst.refs.child1.tagName).toBe('DIV');
    
  552.     expect(inst.refs.child1).toBe(div1.firstChild);
    
  553. 
    
  554.     expect(() => {
    
  555.       // Now both refs should be rendered.
    
  556.       ReactDOM.render(<Parent />, div1);
    
  557.     }).toErrorDev(
    
  558.       [
    
  559.         'Warning: Component "Root" contains the string ref "child2". ' +
    
  560.           'Support for string refs will be removed in a future major release. ' +
    
  561.           'We recommend using useRef() or createRef() instead. ' +
    
  562.           'Learn more about using refs safely here: https://reactjs.org/link/strict-mode-string-ref',
    
  563.       ],
    
  564.       {withoutStack: true},
    
  565.     );
    
  566.     expect(inst.refs.child1.tagName).toBe('DIV');
    
  567.     expect(inst.refs.child1).toBe(div1.firstChild);
    
  568.     expect(inst.refs.child2.tagName).toBe('DIV');
    
  569.     expect(inst.refs.child2).toBe(div2.firstChild);
    
  570.   });
    
  571. });
    
  572. 
    
  573. describe('refs return clean up function', () => {
    
  574.   it('calls clean up function if it exists', () => {
    
  575.     const container = document.createElement('div');
    
  576.     let cleanUp = jest.fn();
    
  577.     let setup = jest.fn();
    
  578. 
    
  579.     ReactDOM.render(
    
  580.       <div
    
  581.         ref={_ref => {
    
  582.           setup(_ref);
    
  583.           return cleanUp;
    
  584.         }}
    
  585.       />,
    
  586.       container,
    
  587.     );
    
  588. 
    
  589.     ReactDOM.render(
    
  590.       <div
    
  591.         ref={_ref => {
    
  592.           setup(_ref);
    
  593.         }}
    
  594.       />,
    
  595.       container,
    
  596.     );
    
  597. 
    
  598.     expect(setup).toHaveBeenCalledTimes(2);
    
  599.     expect(cleanUp).toHaveBeenCalledTimes(1);
    
  600.     expect(cleanUp.mock.calls[0][0]).toBe(undefined);
    
  601. 
    
  602.     ReactDOM.render(<div ref={_ref => {}} />, container);
    
  603. 
    
  604.     expect(cleanUp).toHaveBeenCalledTimes(1);
    
  605.     expect(setup).toHaveBeenCalledTimes(3);
    
  606.     expect(setup.mock.calls[2][0]).toBe(null);
    
  607. 
    
  608.     cleanUp = jest.fn();
    
  609.     setup = jest.fn();
    
  610. 
    
  611.     ReactDOM.render(
    
  612.       <div
    
  613.         ref={_ref => {
    
  614.           setup(_ref);
    
  615.           return cleanUp;
    
  616.         }}
    
  617.       />,
    
  618.       container,
    
  619.     );
    
  620. 
    
  621.     expect(setup).toHaveBeenCalledTimes(1);
    
  622.     expect(cleanUp).toHaveBeenCalledTimes(0);
    
  623. 
    
  624.     ReactDOM.render(
    
  625.       <div
    
  626.         ref={_ref => {
    
  627.           setup(_ref);
    
  628.           return cleanUp;
    
  629.         }}
    
  630.       />,
    
  631.       container,
    
  632.     );
    
  633. 
    
  634.     expect(setup).toHaveBeenCalledTimes(2);
    
  635.     expect(cleanUp).toHaveBeenCalledTimes(1);
    
  636.   });
    
  637. 
    
  638.   it('handles ref functions with stable identity', () => {
    
  639.     const container = document.createElement('div');
    
  640.     const cleanUp = jest.fn();
    
  641.     const setup = jest.fn();
    
  642. 
    
  643.     function _onRefChange(_ref) {
    
  644.       setup(_ref);
    
  645.       return cleanUp;
    
  646.     }
    
  647. 
    
  648.     ReactDOM.render(<div ref={_onRefChange} />, container);
    
  649. 
    
  650.     expect(setup).toHaveBeenCalledTimes(1);
    
  651.     expect(cleanUp).toHaveBeenCalledTimes(0);
    
  652. 
    
  653.     ReactDOM.render(
    
  654.       <div className="niceClassName" ref={_onRefChange} />,
    
  655.       container,
    
  656.     );
    
  657. 
    
  658.     expect(setup).toHaveBeenCalledTimes(1);
    
  659.     expect(cleanUp).toHaveBeenCalledTimes(0);
    
  660. 
    
  661.     ReactDOM.render(<div />, container);
    
  662. 
    
  663.     expect(setup).toHaveBeenCalledTimes(1);
    
  664.     expect(cleanUp).toHaveBeenCalledTimes(1);
    
  665.   });
    
  666. 
    
  667.   it('warns if clean up function is returned when called with null', () => {
    
  668.     const container = document.createElement('div');
    
  669.     const cleanUp = jest.fn();
    
  670.     const setup = jest.fn();
    
  671.     let returnCleanUp = false;
    
  672. 
    
  673.     ReactDOM.render(
    
  674.       <div
    
  675.         ref={_ref => {
    
  676.           setup(_ref);
    
  677.           if (returnCleanUp) {
    
  678.             return cleanUp;
    
  679.           }
    
  680.         }}
    
  681.       />,
    
  682.       container,
    
  683.     );
    
  684. 
    
  685.     expect(setup).toHaveBeenCalledTimes(1);
    
  686.     expect(cleanUp).toHaveBeenCalledTimes(0);
    
  687. 
    
  688.     returnCleanUp = true;
    
  689. 
    
  690.     expect(() => {
    
  691.       ReactDOM.render(
    
  692.         <div
    
  693.           ref={_ref => {
    
  694.             setup(_ref);
    
  695.             if (returnCleanUp) {
    
  696.               return cleanUp;
    
  697.             }
    
  698.           }}
    
  699.         />,
    
  700.         container,
    
  701.       );
    
  702.     }).toErrorDev('Unexpected return value from a callback ref in div');
    
  703.   });
    
  704. });