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. let idCallOrder;
    
  17. const recordID = function (id) {
    
  18.   idCallOrder.push(id);
    
  19. };
    
  20. const recordIDAndStopPropagation = function (id, event) {
    
  21.   recordID(id);
    
  22.   event.stopPropagation();
    
  23. };
    
  24. const recordIDAndReturnFalse = function (id, event) {
    
  25.   recordID(id);
    
  26.   return false;
    
  27. };
    
  28. const LISTENER = jest.fn();
    
  29. const ON_CLICK_KEY = 'onClick';
    
  30. const ON_MOUSE_ENTER_KEY = 'onMouseEnter';
    
  31. 
    
  32. let GRANDPARENT;
    
  33. let PARENT;
    
  34. let CHILD;
    
  35. let BUTTON;
    
  36. 
    
  37. let putListener;
    
  38. let deleteAllListeners;
    
  39. 
    
  40. let container;
    
  41. 
    
  42. // This test is written in a bizarre way because it was previously using internals.
    
  43. // It should probably be rewritten but we're keeping it for some extra coverage.
    
  44. describe('ReactBrowserEventEmitter', () => {
    
  45.   beforeEach(() => {
    
  46.     jest.resetModules();
    
  47.     LISTENER.mockClear();
    
  48. 
    
  49.     React = require('react');
    
  50.     ReactDOM = require('react-dom');
    
  51.     ReactTestUtils = require('react-dom/test-utils');
    
  52. 
    
  53.     container = document.createElement('div');
    
  54.     document.body.appendChild(container);
    
  55. 
    
  56.     let GRANDPARENT_PROPS = {};
    
  57.     let PARENT_PROPS = {};
    
  58.     let CHILD_PROPS = {};
    
  59.     let BUTTON_PROPS = {};
    
  60. 
    
  61.     function Child(props) {
    
  62.       return <div ref={c => (CHILD = c)} {...props} />;
    
  63.     }
    
  64. 
    
  65.     class ChildWrapper extends React.PureComponent {
    
  66.       render() {
    
  67.         return <Child {...this.props} />;
    
  68.       }
    
  69.     }
    
  70. 
    
  71.     function renderTree() {
    
  72.       ReactDOM.render(
    
  73.         <div ref={c => (GRANDPARENT = c)} {...GRANDPARENT_PROPS}>
    
  74.           <div ref={c => (PARENT = c)} {...PARENT_PROPS}>
    
  75.             <ChildWrapper {...CHILD_PROPS} />
    
  76.             <button disabled={true} ref={c => (BUTTON = c)} {...BUTTON_PROPS} />
    
  77.           </div>
    
  78.         </div>,
    
  79.         container,
    
  80.       );
    
  81.     }
    
  82. 
    
  83.     renderTree();
    
  84. 
    
  85.     putListener = function (node, eventName, listener) {
    
  86.       switch (node) {
    
  87.         case CHILD:
    
  88.           CHILD_PROPS[eventName] = listener;
    
  89.           break;
    
  90.         case PARENT:
    
  91.           PARENT_PROPS[eventName] = listener;
    
  92.           break;
    
  93.         case GRANDPARENT:
    
  94.           GRANDPARENT_PROPS[eventName] = listener;
    
  95.           break;
    
  96.         case BUTTON:
    
  97.           BUTTON_PROPS[eventName] = listener;
    
  98.           break;
    
  99.       }
    
  100.       // Rerender with new event listeners
    
  101.       renderTree();
    
  102.     };
    
  103.     deleteAllListeners = function (node) {
    
  104.       switch (node) {
    
  105.         case CHILD:
    
  106.           CHILD_PROPS = {};
    
  107.           break;
    
  108.         case PARENT:
    
  109.           PARENT_PROPS = {};
    
  110.           break;
    
  111.         case GRANDPARENT:
    
  112.           GRANDPARENT_PROPS = {};
    
  113.           break;
    
  114.         case BUTTON:
    
  115.           BUTTON_PROPS = {};
    
  116.           break;
    
  117.       }
    
  118.       renderTree();
    
  119.     };
    
  120. 
    
  121.     idCallOrder = [];
    
  122.   });
    
  123. 
    
  124.   afterEach(() => {
    
  125.     document.body.removeChild(container);
    
  126.     container = null;
    
  127.   });
    
  128. 
    
  129.   it('should bubble simply', () => {
    
  130.     putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
    
  131.     putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
    
  132.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
    
  133.     CHILD.click();
    
  134.     expect(idCallOrder.length).toBe(3);
    
  135.     expect(idCallOrder[0]).toBe(CHILD);
    
  136.     expect(idCallOrder[1]).toBe(PARENT);
    
  137.     expect(idCallOrder[2]).toBe(GRANDPARENT);
    
  138.   });
    
  139. 
    
  140.   it('should bubble to the right handler after an update', () => {
    
  141.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, 'GRANDPARENT'));
    
  142.     putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
    
  143.     putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
    
  144.     CHILD.click();
    
  145.     expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);
    
  146. 
    
  147.     idCallOrder = [];
    
  148. 
    
  149.     // Update just the grand parent without updating the child.
    
  150.     putListener(
    
  151.       GRANDPARENT,
    
  152.       ON_CLICK_KEY,
    
  153.       recordID.bind(null, 'UPDATED_GRANDPARENT'),
    
  154.     );
    
  155. 
    
  156.     CHILD.click();
    
  157.     expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
    
  158.   });
    
  159. 
    
  160.   it('should continue bubbling if an error is thrown', () => {
    
  161.     putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
    
  162.     putListener(PARENT, ON_CLICK_KEY, function () {
    
  163.       recordID(PARENT);
    
  164.       throw new Error('Handler interrupted');
    
  165.     });
    
  166.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
    
  167.     expect(function () {
    
  168.       ReactTestUtils.Simulate.click(CHILD);
    
  169.     }).toThrow();
    
  170.     expect(idCallOrder.length).toBe(3);
    
  171.     expect(idCallOrder[0]).toBe(CHILD);
    
  172.     expect(idCallOrder[1]).toBe(PARENT);
    
  173.     expect(idCallOrder[2]).toBe(GRANDPARENT);
    
  174.   });
    
  175. 
    
  176.   it('should set currentTarget', () => {
    
  177.     putListener(CHILD, ON_CLICK_KEY, function (event) {
    
  178.       recordID(CHILD);
    
  179.       expect(event.currentTarget).toBe(CHILD);
    
  180.     });
    
  181.     putListener(PARENT, ON_CLICK_KEY, function (event) {
    
  182.       recordID(PARENT);
    
  183.       expect(event.currentTarget).toBe(PARENT);
    
  184.     });
    
  185.     putListener(GRANDPARENT, ON_CLICK_KEY, function (event) {
    
  186.       recordID(GRANDPARENT);
    
  187.       expect(event.currentTarget).toBe(GRANDPARENT);
    
  188.     });
    
  189.     CHILD.click();
    
  190.     expect(idCallOrder.length).toBe(3);
    
  191.     expect(idCallOrder[0]).toBe(CHILD);
    
  192.     expect(idCallOrder[1]).toBe(PARENT);
    
  193.     expect(idCallOrder[2]).toBe(GRANDPARENT);
    
  194.   });
    
  195. 
    
  196.   it('should support stopPropagation()', () => {
    
  197.     putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
    
  198.     putListener(
    
  199.       PARENT,
    
  200.       ON_CLICK_KEY,
    
  201.       recordIDAndStopPropagation.bind(null, PARENT),
    
  202.     );
    
  203.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
    
  204.     CHILD.click();
    
  205.     expect(idCallOrder.length).toBe(2);
    
  206.     expect(idCallOrder[0]).toBe(CHILD);
    
  207.     expect(idCallOrder[1]).toBe(PARENT);
    
  208.   });
    
  209. 
    
  210.   it('should support overriding .isPropagationStopped()', () => {
    
  211.     // Ew. See D4504876.
    
  212.     putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
    
  213.     putListener(PARENT, ON_CLICK_KEY, function (e) {
    
  214.       recordID(PARENT, e);
    
  215.       // This stops React bubbling but avoids touching the native event
    
  216.       e.isPropagationStopped = () => true;
    
  217.     });
    
  218.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
    
  219.     CHILD.click();
    
  220.     expect(idCallOrder.length).toBe(2);
    
  221.     expect(idCallOrder[0]).toBe(CHILD);
    
  222.     expect(idCallOrder[1]).toBe(PARENT);
    
  223.   });
    
  224. 
    
  225.   it('should stop after first dispatch if stopPropagation', () => {
    
  226.     putListener(
    
  227.       CHILD,
    
  228.       ON_CLICK_KEY,
    
  229.       recordIDAndStopPropagation.bind(null, CHILD),
    
  230.     );
    
  231.     putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
    
  232.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
    
  233.     CHILD.click();
    
  234.     expect(idCallOrder.length).toBe(1);
    
  235.     expect(idCallOrder[0]).toBe(CHILD);
    
  236.   });
    
  237. 
    
  238.   it('should not stopPropagation if false is returned', () => {
    
  239.     putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
    
  240.     putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
    
  241.     putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
    
  242.     CHILD.click();
    
  243.     expect(idCallOrder.length).toBe(3);
    
  244.     expect(idCallOrder[0]).toBe(CHILD);
    
  245.     expect(idCallOrder[1]).toBe(PARENT);
    
  246.     expect(idCallOrder[2]).toBe(GRANDPARENT);
    
  247.   });
    
  248. 
    
  249.   /**
    
  250.    * The entire event registration state of the world should be "locked-in" at
    
  251.    * the time the event occurs. This is to resolve many edge cases that come
    
  252.    * about from a listener on a lower-in-DOM node causing structural changes at
    
  253.    * places higher in the DOM. If this lower-in-DOM node causes new content to
    
  254.    * be rendered at a place higher-in-DOM, we need to be careful not to invoke
    
  255.    * these new listeners.
    
  256.    */
    
  257. 
    
  258.   it('should invoke handlers that were removed while bubbling', () => {
    
  259.     const handleParentClick = jest.fn();
    
  260.     const handleChildClick = function (event) {
    
  261.       deleteAllListeners(PARENT);
    
  262.     };
    
  263.     putListener(CHILD, ON_CLICK_KEY, handleChildClick);
    
  264.     putListener(PARENT, ON_CLICK_KEY, handleParentClick);
    
  265.     CHILD.click();
    
  266.     expect(handleParentClick).toHaveBeenCalledTimes(1);
    
  267.   });
    
  268. 
    
  269.   it('should not invoke newly inserted handlers while bubbling', () => {
    
  270.     const handleParentClick = jest.fn();
    
  271.     const handleChildClick = function (event) {
    
  272.       putListener(PARENT, ON_CLICK_KEY, handleParentClick);
    
  273.     };
    
  274.     putListener(CHILD, ON_CLICK_KEY, handleChildClick);
    
  275.     CHILD.click();
    
  276.     expect(handleParentClick).toHaveBeenCalledTimes(0);
    
  277.   });
    
  278. 
    
  279.   it('should have mouse enter simulated by test utils', () => {
    
  280.     putListener(CHILD, ON_MOUSE_ENTER_KEY, recordID.bind(null, CHILD));
    
  281.     ReactTestUtils.Simulate.mouseEnter(CHILD);
    
  282.     expect(idCallOrder.length).toBe(1);
    
  283.     expect(idCallOrder[0]).toBe(CHILD);
    
  284.   });
    
  285. });