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 {createEventTarget, setPointerEvent} from 'dom-event-testing-library';
    
  13. 
    
  14. let React;
    
  15. let ReactFeatureFlags;
    
  16. let ReactDOM;
    
  17. let useFocus;
    
  18. let act;
    
  19. 
    
  20. function initializeModules(hasPointerEvents) {
    
  21.   setPointerEvent(hasPointerEvents);
    
  22.   jest.resetModules();
    
  23.   ReactFeatureFlags = require('shared/ReactFeatureFlags');
    
  24.   ReactFeatureFlags.enableCreateEventHandleAPI = true;
    
  25.   React = require('react');
    
  26.   ReactDOM = require('react-dom');
    
  27.   act = require('internal-test-utils').act;
    
  28.   // TODO: This import throws outside of experimental mode. Figure out better
    
  29.   // strategy for gated imports.
    
  30.   if (__EXPERIMENTAL__ || global.__WWW__) {
    
  31.     useFocus = require('react-interactions/events/focus').useFocus;
    
  32.   }
    
  33. }
    
  34. 
    
  35. const forcePointerEvents = true;
    
  36. const table = [[forcePointerEvents], [!forcePointerEvents]];
    
  37. 
    
  38. describe.each(table)(`useFocus hasPointerEvents=%s`, hasPointerEvents => {
    
  39.   let container;
    
  40. 
    
  41.   beforeEach(() => {
    
  42.     initializeModules(hasPointerEvents);
    
  43.     container = document.createElement('div');
    
  44.     document.body.appendChild(container);
    
  45.   });
    
  46. 
    
  47.   afterEach(() => {
    
  48.     ReactDOM.render(null, container);
    
  49.     document.body.removeChild(container);
    
  50.     container = null;
    
  51.   });
    
  52. 
    
  53.   describe('disabled', () => {
    
  54.     let onBlur, onFocus, ref;
    
  55. 
    
  56.     const componentInit = async () => {
    
  57.       onBlur = jest.fn();
    
  58.       onFocus = jest.fn();
    
  59.       ref = React.createRef();
    
  60.       const Component = () => {
    
  61.         useFocus(ref, {
    
  62.           disabled: true,
    
  63.           onBlur,
    
  64.           onFocus,
    
  65.         });
    
  66.         return <div ref={ref} />;
    
  67.       };
    
  68.       await act(() => {
    
  69.         ReactDOM.render(<Component />, container);
    
  70.       });
    
  71.     };
    
  72. 
    
  73.     // @gate www
    
  74.     it('does not call callbacks', async () => {
    
  75.       await componentInit();
    
  76.       const target = createEventTarget(ref.current);
    
  77.       target.focus();
    
  78.       target.blur();
    
  79.       expect(onFocus).not.toBeCalled();
    
  80.       expect(onBlur).not.toBeCalled();
    
  81.     });
    
  82.   });
    
  83. 
    
  84.   describe('onBlur', () => {
    
  85.     let onBlur, ref;
    
  86. 
    
  87.     const componentInit = async () => {
    
  88.       onBlur = jest.fn();
    
  89.       ref = React.createRef();
    
  90.       const Component = () => {
    
  91.         useFocus(ref, {
    
  92.           onBlur,
    
  93.         });
    
  94.         return <div ref={ref} />;
    
  95.       };
    
  96.       await act(() => {
    
  97.         ReactDOM.render(<Component />, container);
    
  98.       });
    
  99.     };
    
  100. 
    
  101.     // @gate www
    
  102.     it('is called after "blur" event', async () => {
    
  103.       await componentInit();
    
  104.       const target = createEventTarget(ref.current);
    
  105.       target.focus();
    
  106.       target.blur();
    
  107.       expect(onBlur).toHaveBeenCalledTimes(1);
    
  108.     });
    
  109.   });
    
  110. 
    
  111.   describe('onFocus', () => {
    
  112.     let onFocus, ref, innerRef;
    
  113. 
    
  114.     const componentInit = async () => {
    
  115.       onFocus = jest.fn();
    
  116.       ref = React.createRef();
    
  117.       innerRef = React.createRef();
    
  118.       const Component = () => {
    
  119.         useFocus(ref, {
    
  120.           onFocus,
    
  121.         });
    
  122.         return (
    
  123.           <div ref={ref}>
    
  124.             <a ref={innerRef} />
    
  125.           </div>
    
  126.         );
    
  127.       };
    
  128.       await act(() => {
    
  129.         ReactDOM.render(<Component />, container);
    
  130.       });
    
  131.     };
    
  132. 
    
  133.     // @gate www
    
  134.     it('is called after "focus" event', async () => {
    
  135.       await componentInit();
    
  136.       const target = createEventTarget(ref.current);
    
  137.       target.focus();
    
  138.       expect(onFocus).toHaveBeenCalledTimes(1);
    
  139.     });
    
  140. 
    
  141.     // @gate www
    
  142.     it('is not called if descendants of target receive focus', async () => {
    
  143.       await componentInit();
    
  144.       const target = createEventTarget(innerRef.current);
    
  145.       target.focus();
    
  146.       expect(onFocus).not.toBeCalled();
    
  147.     });
    
  148.   });
    
  149. 
    
  150.   describe('onFocusChange', () => {
    
  151.     let onFocusChange, ref, innerRef;
    
  152. 
    
  153.     const componentInit = async () => {
    
  154.       onFocusChange = jest.fn();
    
  155.       ref = React.createRef();
    
  156.       innerRef = React.createRef();
    
  157.       const Component = () => {
    
  158.         useFocus(ref, {
    
  159.           onFocusChange,
    
  160.         });
    
  161.         return (
    
  162.           <div ref={ref}>
    
  163.             <div ref={innerRef} />
    
  164.           </div>
    
  165.         );
    
  166.       };
    
  167.       await act(() => {
    
  168.         ReactDOM.render(<Component />, container);
    
  169.       });
    
  170.     };
    
  171. 
    
  172.     // @gate www
    
  173.     it('is called after "blur" and "focus" events', async () => {
    
  174.       await componentInit();
    
  175.       const target = createEventTarget(ref.current);
    
  176.       target.focus();
    
  177.       expect(onFocusChange).toHaveBeenCalledTimes(1);
    
  178.       expect(onFocusChange).toHaveBeenCalledWith(true);
    
  179.       target.blur();
    
  180.       expect(onFocusChange).toHaveBeenCalledTimes(2);
    
  181.       expect(onFocusChange).toHaveBeenCalledWith(false);
    
  182.     });
    
  183. 
    
  184.     // @gate www
    
  185.     it('is not called after "blur" and "focus" events on descendants', async () => {
    
  186.       await componentInit();
    
  187.       const target = createEventTarget(innerRef.current);
    
  188.       target.focus();
    
  189.       expect(onFocusChange).toHaveBeenCalledTimes(0);
    
  190.       target.blur();
    
  191.       expect(onFocusChange).toHaveBeenCalledTimes(0);
    
  192.     });
    
  193.   });
    
  194. 
    
  195.   describe('onFocusVisibleChange', () => {
    
  196.     let onFocusVisibleChange, ref, innerRef;
    
  197. 
    
  198.     const componentInit = async () => {
    
  199.       onFocusVisibleChange = jest.fn();
    
  200.       ref = React.createRef();
    
  201.       innerRef = React.createRef();
    
  202.       const Component = () => {
    
  203.         useFocus(ref, {
    
  204.           onFocusVisibleChange,
    
  205.         });
    
  206.         return (
    
  207.           <div ref={ref}>
    
  208.             <div ref={innerRef} />
    
  209.           </div>
    
  210.         );
    
  211.       };
    
  212.       await act(() => {
    
  213.         ReactDOM.render(<Component />, container);
    
  214.       });
    
  215.     };
    
  216. 
    
  217.     // @gate www
    
  218.     it('is called after "focus" and "blur" if keyboard navigation is active', async () => {
    
  219.       await componentInit();
    
  220.       const target = createEventTarget(ref.current);
    
  221.       const containerTarget = createEventTarget(container);
    
  222.       // use keyboard first
    
  223.       containerTarget.keydown({key: 'Tab'});
    
  224.       target.focus();
    
  225.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
    
  226.       expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
    
  227.       target.blur({relatedTarget: container});
    
  228.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
    
  229.       expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
    
  230.     });
    
  231. 
    
  232.     // @gate www
    
  233.     it('is called if non-keyboard event is dispatched on target previously focused with keyboard', async () => {
    
  234.       await componentInit();
    
  235.       const target = createEventTarget(ref.current);
    
  236.       const containerTarget = createEventTarget(container);
    
  237.       // use keyboard first
    
  238.       containerTarget.keydown({key: 'Tab'});
    
  239.       target.focus();
    
  240.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(1);
    
  241.       expect(onFocusVisibleChange).toHaveBeenCalledWith(true);
    
  242.       // then use pointer on the target, focus should no longer be visible
    
  243.       target.pointerdown();
    
  244.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
    
  245.       expect(onFocusVisibleChange).toHaveBeenCalledWith(false);
    
  246.       // onFocusVisibleChange should not be called again
    
  247.       target.blur({relatedTarget: container});
    
  248.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(2);
    
  249.     });
    
  250. 
    
  251.     // @gate www
    
  252.     it('is not called after "focus" and "blur" events without keyboard', async () => {
    
  253.       await componentInit();
    
  254.       const target = createEventTarget(ref.current);
    
  255.       const containerTarget = createEventTarget(container);
    
  256.       target.pointerdown();
    
  257.       target.pointerup();
    
  258.       containerTarget.pointerdown();
    
  259.       target.blur({relatedTarget: container});
    
  260.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
    
  261.     });
    
  262. 
    
  263.     // @gate www
    
  264.     it('is not called after "blur" and "focus" events on descendants', async () => {
    
  265.       await componentInit();
    
  266.       const innerTarget = createEventTarget(innerRef.current);
    
  267.       const containerTarget = createEventTarget(container);
    
  268.       containerTarget.keydown({key: 'Tab'});
    
  269.       innerTarget.focus();
    
  270.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
    
  271.       innerTarget.blur({relatedTarget: container});
    
  272.       expect(onFocusVisibleChange).toHaveBeenCalledTimes(0);
    
  273.     });
    
  274.   });
    
  275. 
    
  276.   describe('nested Focus components', () => {
    
  277.     // @gate www
    
  278.     it('propagates events in the correct order', async () => {
    
  279.       const events = [];
    
  280.       const innerRef = React.createRef();
    
  281.       const outerRef = React.createRef();
    
  282.       const createEventHandler = msg => () => {
    
  283.         events.push(msg);
    
  284.       };
    
  285. 
    
  286.       const Inner = () => {
    
  287.         useFocus(innerRef, {
    
  288.           onBlur: createEventHandler('inner: onBlur'),
    
  289.           onFocus: createEventHandler('inner: onFocus'),
    
  290.           onFocusChange: createEventHandler('inner: onFocusChange'),
    
  291.         });
    
  292.         return <div ref={innerRef} />;
    
  293.       };
    
  294. 
    
  295.       const Outer = () => {
    
  296.         useFocus(outerRef, {
    
  297.           onBlur: createEventHandler('outer: onBlur'),
    
  298.           onFocus: createEventHandler('outer: onFocus'),
    
  299.           onFocusChange: createEventHandler('outer: onFocusChange'),
    
  300.         });
    
  301.         return (
    
  302.           <div ref={outerRef}>
    
  303.             <Inner />
    
  304.           </div>
    
  305.         );
    
  306.       };
    
  307. 
    
  308.       await act(() => {
    
  309.         ReactDOM.render(<Outer />, container);
    
  310.       });
    
  311.       const innerTarget = createEventTarget(innerRef.current);
    
  312.       const outerTarget = createEventTarget(outerRef.current);
    
  313. 
    
  314.       outerTarget.focus();
    
  315.       outerTarget.blur();
    
  316.       innerTarget.focus();
    
  317.       innerTarget.blur();
    
  318.       expect(events).toEqual([
    
  319.         'outer: onFocus',
    
  320.         'outer: onFocusChange',
    
  321.         'outer: onBlur',
    
  322.         'outer: onFocusChange',
    
  323.         'inner: onFocus',
    
  324.         'inner: onFocusChange',
    
  325.         'inner: onBlur',
    
  326.         'inner: onFocusChange',
    
  327.       ]);
    
  328.     });
    
  329.   });
    
  330. });