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. 
    
  15. describe('EnterLeaveEventPlugin', () => {
    
  16.   let container;
    
  17. 
    
  18.   beforeEach(() => {
    
  19.     jest.resetModules();
    
  20. 
    
  21.     React = require('react');
    
  22.     ReactDOM = require('react-dom');
    
  23. 
    
  24.     // The container has to be attached for events to fire.
    
  25.     container = document.createElement('div');
    
  26.     document.body.appendChild(container);
    
  27.   });
    
  28. 
    
  29.   afterEach(() => {
    
  30.     document.body.removeChild(container);
    
  31.     container = null;
    
  32.   });
    
  33. 
    
  34.   it('should set onMouseLeave relatedTarget properly in iframe', () => {
    
  35.     const iframe = document.createElement('iframe');
    
  36.     container.appendChild(iframe);
    
  37.     const iframeDocument = iframe.contentDocument;
    
  38.     iframeDocument.write(
    
  39.       '<!DOCTYPE html><html><head></head><body><div></div></body></html>',
    
  40.     );
    
  41.     iframeDocument.close();
    
  42. 
    
  43.     const leaveEvents = [];
    
  44.     const node = ReactDOM.render(
    
  45.       <div
    
  46.         onMouseLeave={e => {
    
  47.           e.persist();
    
  48.           leaveEvents.push(e);
    
  49.         }}
    
  50.       />,
    
  51.       iframeDocument.body.getElementsByTagName('div')[0],
    
  52.     );
    
  53. 
    
  54.     node.dispatchEvent(
    
  55.       new MouseEvent('mouseout', {
    
  56.         bubbles: true,
    
  57.         cancelable: true,
    
  58.         relatedTarget: iframe.contentWindow,
    
  59.       }),
    
  60.     );
    
  61. 
    
  62.     expect(leaveEvents.length).toBe(1);
    
  63.     expect(leaveEvents[0].target).toBe(node);
    
  64.     expect(leaveEvents[0].relatedTarget).toBe(iframe.contentWindow);
    
  65.   });
    
  66. 
    
  67.   it('should set onMouseEnter relatedTarget properly in iframe', () => {
    
  68.     const iframe = document.createElement('iframe');
    
  69.     container.appendChild(iframe);
    
  70.     const iframeDocument = iframe.contentDocument;
    
  71.     iframeDocument.write(
    
  72.       '<!DOCTYPE html><html><head></head><body><div></div></body></html>',
    
  73.     );
    
  74.     iframeDocument.close();
    
  75. 
    
  76.     const enterEvents = [];
    
  77.     const node = ReactDOM.render(
    
  78.       <div
    
  79.         onMouseEnter={e => {
    
  80.           e.persist();
    
  81.           enterEvents.push(e);
    
  82.         }}
    
  83.       />,
    
  84.       iframeDocument.body.getElementsByTagName('div')[0],
    
  85.     );
    
  86. 
    
  87.     node.dispatchEvent(
    
  88.       new MouseEvent('mouseover', {
    
  89.         bubbles: true,
    
  90.         cancelable: true,
    
  91.         relatedTarget: null,
    
  92.       }),
    
  93.     );
    
  94. 
    
  95.     expect(enterEvents.length).toBe(1);
    
  96.     expect(enterEvents[0].target).toBe(node);
    
  97.     expect(enterEvents[0].relatedTarget).toBe(iframe.contentWindow);
    
  98.   });
    
  99. 
    
  100.   // Regression test for https://github.com/facebook/react/issues/10906.
    
  101.   it('should find the common parent after updates', () => {
    
  102.     let parentEnterCalls = 0;
    
  103.     let childEnterCalls = 0;
    
  104.     let parent = null;
    
  105. 
    
  106.     class Parent extends React.Component {
    
  107.       render() {
    
  108.         return (
    
  109.           <div
    
  110.             onMouseEnter={() => parentEnterCalls++}
    
  111.             ref={node => (parent = node)}>
    
  112.             {this.props.showChild && (
    
  113.               <div onMouseEnter={() => childEnterCalls++} />
    
  114.             )}
    
  115.           </div>
    
  116.         );
    
  117.       }
    
  118.     }
    
  119. 
    
  120.     ReactDOM.render(<Parent />, container);
    
  121.     // The issue only reproduced on insertion during the first update.
    
  122.     ReactDOM.render(<Parent showChild={true} />, container);
    
  123. 
    
  124.     // Enter from parent into the child.
    
  125.     parent.dispatchEvent(
    
  126.       new MouseEvent('mouseout', {
    
  127.         bubbles: true,
    
  128.         cancelable: true,
    
  129.         relatedTarget: parent.firstChild,
    
  130.       }),
    
  131.     );
    
  132. 
    
  133.     // Entering a child should fire on the child, not on the parent.
    
  134.     expect(childEnterCalls).toBe(1);
    
  135.     expect(parentEnterCalls).toBe(0);
    
  136.   });
    
  137. 
    
  138.   // Test for https://github.com/facebook/react/issues/16763.
    
  139.   it('should call mouseEnter once from sibling rendered inside a rendered component', done => {
    
  140.     const mockFn = jest.fn();
    
  141. 
    
  142.     class Parent extends React.Component {
    
  143.       constructor(props) {
    
  144.         super(props);
    
  145.         this.parentEl = React.createRef();
    
  146.       }
    
  147. 
    
  148.       componentDidMount() {
    
  149.         ReactDOM.render(<MouseEnterDetect />, this.parentEl.current);
    
  150.       }
    
  151. 
    
  152.       render() {
    
  153.         return <div ref={this.parentEl} />;
    
  154.       }
    
  155.     }
    
  156. 
    
  157.     class MouseEnterDetect extends React.Component {
    
  158.       constructor(props) {
    
  159.         super(props);
    
  160.         this.firstEl = React.createRef();
    
  161.         this.siblingEl = React.createRef();
    
  162.       }
    
  163. 
    
  164.       componentDidMount() {
    
  165.         this.siblingEl.current.dispatchEvent(
    
  166.           new MouseEvent('mouseout', {
    
  167.             bubbles: true,
    
  168.             cancelable: true,
    
  169.             relatedTarget: this.firstEl.current,
    
  170.           }),
    
  171.         );
    
  172.         expect(mockFn.mock.calls.length).toBe(1);
    
  173.         done();
    
  174.       }
    
  175. 
    
  176.       render() {
    
  177.         return (
    
  178.           <React.Fragment>
    
  179.             <div ref={this.firstEl} onMouseEnter={mockFn} />
    
  180.             <div ref={this.siblingEl} />
    
  181.           </React.Fragment>
    
  182.         );
    
  183.       }
    
  184.     }
    
  185. 
    
  186.     ReactDOM.render(<Parent />, container);
    
  187.   });
    
  188. 
    
  189.   it('should call mouseEnter when pressing a non tracked React node', done => {
    
  190.     const mockFn = jest.fn();
    
  191. 
    
  192.     class Parent extends React.Component {
    
  193.       constructor(props) {
    
  194.         super(props);
    
  195.         this.parentEl = React.createRef();
    
  196.       }
    
  197. 
    
  198.       componentDidMount() {
    
  199.         ReactDOM.render(<MouseEnterDetect />, this.parentEl.current);
    
  200.       }
    
  201. 
    
  202.       render() {
    
  203.         return <div ref={this.parentEl} />;
    
  204.       }
    
  205.     }
    
  206. 
    
  207.     class MouseEnterDetect extends React.Component {
    
  208.       constructor(props) {
    
  209.         super(props);
    
  210.         this.divRef = React.createRef();
    
  211.         this.siblingEl = React.createRef();
    
  212.       }
    
  213. 
    
  214.       componentDidMount() {
    
  215.         const attachedNode = document.createElement('div');
    
  216.         this.divRef.current.appendChild(attachedNode);
    
  217.         attachedNode.dispatchEvent(
    
  218.           new MouseEvent('mouseout', {
    
  219.             bubbles: true,
    
  220.             cancelable: true,
    
  221.             relatedTarget: this.siblingEl.current,
    
  222.           }),
    
  223.         );
    
  224.         expect(mockFn.mock.calls.length).toBe(1);
    
  225.         done();
    
  226.       }
    
  227. 
    
  228.       render() {
    
  229.         return (
    
  230.           <div ref={this.divRef}>
    
  231.             <div ref={this.siblingEl} onMouseEnter={mockFn} />
    
  232.           </div>
    
  233.         );
    
  234.       }
    
  235.     }
    
  236. 
    
  237.     ReactDOM.render(<Parent />, container);
    
  238.   });
    
  239. 
    
  240.   it('should work with portals outside of the root that has onMouseLeave', () => {
    
  241.     const divRef = React.createRef();
    
  242.     const onMouseLeave = jest.fn();
    
  243. 
    
  244.     function Component() {
    
  245.       return (
    
  246.         <div onMouseLeave={onMouseLeave}>
    
  247.           {ReactDOM.createPortal(<div ref={divRef} />, document.body)}
    
  248.         </div>
    
  249.       );
    
  250.     }
    
  251. 
    
  252.     ReactDOM.render(<Component />, container);
    
  253. 
    
  254.     // Leave from the portal div
    
  255.     divRef.current.dispatchEvent(
    
  256.       new MouseEvent('mouseout', {
    
  257.         bubbles: true,
    
  258.         cancelable: true,
    
  259.         relatedTarget: document.body,
    
  260.       }),
    
  261.     );
    
  262. 
    
  263.     expect(onMouseLeave).toHaveBeenCalledTimes(1);
    
  264.   });
    
  265. 
    
  266.   it('should work with portals that have onMouseEnter outside of the root ', () => {
    
  267.     const divRef = React.createRef();
    
  268.     const otherDivRef = React.createRef();
    
  269.     const onMouseEnter = jest.fn();
    
  270. 
    
  271.     function Component() {
    
  272.       return (
    
  273.         <div ref={divRef}>
    
  274.           {ReactDOM.createPortal(
    
  275.             <div ref={otherDivRef} onMouseEnter={onMouseEnter} />,
    
  276.             document.body,
    
  277.           )}
    
  278.         </div>
    
  279.       );
    
  280.     }
    
  281. 
    
  282.     ReactDOM.render(<Component />, container);
    
  283. 
    
  284.     // Leave from the portal div
    
  285.     divRef.current.dispatchEvent(
    
  286.       new MouseEvent('mouseout', {
    
  287.         bubbles: true,
    
  288.         cancelable: true,
    
  289.         relatedTarget: otherDivRef.current,
    
  290.       }),
    
  291.     );
    
  292. 
    
  293.     expect(onMouseEnter).toHaveBeenCalledTimes(1);
    
  294.   });
    
  295. });