diff --git a/packages/react-events/src/dom/__tests__/Focus-test.internal.js b/packages/react-events/src/dom/__tests__/Focus-test.internal.js index 1c685a14e42e..3fca82bb7052 100644 --- a/packages/react-events/src/dom/__tests__/Focus-test.internal.js +++ b/packages/react-events/src/dom/__tests__/Focus-test.internal.js @@ -13,9 +13,7 @@ let React; let ReactFeatureFlags; let ReactDOM; let FocusResponder; -let FocusWithinResponder; let useFocusResponder; -let useFocusWithinResponder; const createEvent = (type, data) => { const event = document.createEvent('CustomEvent'); @@ -42,10 +40,7 @@ const modulesInit = () => { React = require('react'); ReactDOM = require('react-dom'); FocusResponder = require('react-events/focus').FocusResponder; - FocusWithinResponder = require('react-events/focus').FocusWithinResponder; useFocusResponder = require('react-events/focus').useFocusResponder; - useFocusWithinResponder = require('react-events/focus') - .useFocusWithinResponder; }; describe('Focus event responder', () => { @@ -406,246 +401,3 @@ describe('Focus event responder', () => { expect(FocusResponder.displayName).toBe('Focus'); }); }); - -describe('FocusWithin event responder', () => { - let container; - - beforeEach(() => { - jest.resetModules(); - modulesInit(); - - container = document.createElement('div'); - document.body.appendChild(container); - }); - - afterEach(() => { - ReactDOM.render(null, container); - document.body.removeChild(container); - container = null; - }); - - describe('disabled', () => { - let onFocusWithinChange, onFocusWithinVisibleChange, ref; - - beforeEach(() => { - onFocusWithinChange = jest.fn(); - onFocusWithinVisibleChange = jest.fn(); - ref = React.createRef(); - const Component = () => { - const listener = useFocusWithinResponder({ - disabled: true, - onFocusWithinChange, - onFocusWithinVisibleChange, - }); - return
; - }; - ReactDOM.render(, container); - }); - - it('prevents custom events being dispatched', () => { - ref.current.dispatchEvent(createEvent('focus')); - ref.current.dispatchEvent(createEvent('blur')); - expect(onFocusWithinChange).not.toBeCalled(); - expect(onFocusWithinVisibleChange).not.toBeCalled(); - }); - }); - - describe('onFocusWithinChange', () => { - let onFocusWithinChange, ref, innerRef, innerRef2; - - beforeEach(() => { - onFocusWithinChange = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - innerRef2 = React.createRef(); - const Component = () => { - const listener = useFocusWithinResponder({ - onFocusWithinChange, - }); - return ( -
-
-
-
- ); - }; - ReactDOM.render(, container); - }); - - it('is called after "blur" and "focus" events on focus target', () => { - ref.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinChange).toHaveBeenCalledWith(true); - ref.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinChange).toHaveBeenCalledWith(false); - }); - - it('is called after "blur" and "focus" events on descendants', () => { - innerRef.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinChange).toHaveBeenCalledWith(true); - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinChange).toHaveBeenCalledWith(false); - }); - - it('is only called once when focus moves within and outside the subtree', () => { - // focus shifts into subtree - innerRef.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinChange).toHaveBeenCalledWith(true); - // focus moves around subtree - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef2.current}), - ); - innerRef2.current.dispatchEvent(createEvent('focus')); - innerRef2.current.dispatchEvent( - createEvent('blur', {relatedTarget: ref.current}), - ); - ref.current.dispatchEvent(createEvent('focus')); - ref.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef.current}), - ); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - // focus shifts outside subtree - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinChange).toHaveBeenCalledWith(false); - }); - }); - - describe('onFocusWithinVisibleChange', () => { - let onFocusWithinVisibleChange, ref, innerRef, innerRef2; - - beforeEach(() => { - onFocusWithinVisibleChange = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - innerRef2 = React.createRef(); - const Component = () => { - const listener = useFocusWithinResponder({ - onFocusWithinVisibleChange, - }); - return ( -
-
-
-
- ); - }; - ReactDOM.render(, container); - }); - - it('is called after "focus" and "blur" on focus target if keyboard was used', () => { - // use keyboard first - container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'})); - ref.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - ref.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - }); - - it('is called after "focus" and "blur" on descendants if keyboard was used', () => { - // use keyboard first - container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'})); - innerRef.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - }); - - it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => { - // use keyboard first - ref.current.dispatchEvent(createEvent('focus')); - ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'})); - ref.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef.current}), - ); - innerRef.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - // then use pointer on the next target, focus should no longer be visible - innerRef2.current.dispatchEvent(createEvent('pointerdown')); - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef2.current}), - ); - innerRef2.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - // then use keyboard again - innerRef2.current.dispatchEvent( - createKeyboardEvent('keydown', {key: 'Tab', shiftKey: true}), - ); - innerRef2.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef.current}), - ); - innerRef.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - // then use pointer on the target, focus should no longer be visible - innerRef.current.dispatchEvent(createEvent('pointerdown')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - // onFocusVisibleChange should not be called again - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4); - }); - - it('is not called after "focus" and "blur" events without keyboard', () => { - innerRef.current.dispatchEvent(createEvent('pointerdown')); - innerRef.current.dispatchEvent(createEvent('focus')); - container.dispatchEvent(createEvent('pointerdown')); - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0); - }); - - it('is only called once when focus moves within and outside the subtree', () => { - // focus shifts into subtree - innerRef.current.dispatchEvent(createEvent('focus')); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - // focus moves around subtree - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef2.current}), - ); - innerRef2.current.dispatchEvent(createEvent('focus')); - innerRef2.current.dispatchEvent( - createEvent('blur', {relatedTarget: ref.current}), - ); - ref.current.dispatchEvent(createEvent('focus')); - ref.current.dispatchEvent( - createEvent('blur', {relatedTarget: innerRef.current}), - ); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - // focus shifts outside subtree - innerRef.current.dispatchEvent( - createEvent('blur', {relatedTarget: container}), - ); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - }); - }); - - it('expect displayName to show up for event component', () => { - expect(FocusWithinResponder.displayName).toBe('FocusWithin'); - }); -}); diff --git a/packages/react-events/src/dom/__tests__/FocusWithin-test.internal.js b/packages/react-events/src/dom/__tests__/FocusWithin-test.internal.js new file mode 100644 index 000000000000..5de66686fd65 --- /dev/null +++ b/packages/react-events/src/dom/__tests__/FocusWithin-test.internal.js @@ -0,0 +1,288 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactFeatureFlags; +let ReactDOM; +let FocusWithinResponder; +let useFocusWithinResponder; + +const createEvent = (type, data) => { + const event = document.createEvent('CustomEvent'); + event.initCustomEvent(type, true, true); + if (data != null) { + Object.entries(data).forEach(([key, value]) => { + event[key] = value; + }); + } + return event; +}; + +const createKeyboardEvent = (type, data) => { + return new KeyboardEvent(type, { + bubbles: true, + cancelable: true, + ...data, + }); +}; + +const modulesInit = () => { + ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableFlareAPI = true; + React = require('react'); + ReactDOM = require('react-dom'); + FocusWithinResponder = require('react-events/focus').FocusWithinResponder; + useFocusWithinResponder = require('react-events/focus') + .useFocusWithinResponder; +}; + +describe('FocusWithin event responder', () => { + let container; + + beforeEach(() => { + jest.resetModules(); + modulesInit(); + + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + ReactDOM.render(null, container); + document.body.removeChild(container); + container = null; + }); + + describe('disabled', () => { + let onFocusWithinChange, onFocusWithinVisibleChange, ref; + + beforeEach(() => { + onFocusWithinChange = jest.fn(); + onFocusWithinVisibleChange = jest.fn(); + ref = React.createRef(); + const Component = () => { + const listener = useFocusWithinResponder({ + disabled: true, + onFocusWithinChange, + onFocusWithinVisibleChange, + }); + return
; + }; + ReactDOM.render(, container); + }); + + it('prevents custom events being dispatched', () => { + ref.current.dispatchEvent(createEvent('focus')); + ref.current.dispatchEvent(createEvent('blur')); + expect(onFocusWithinChange).not.toBeCalled(); + expect(onFocusWithinVisibleChange).not.toBeCalled(); + }); + }); + + describe('onFocusWithinChange', () => { + let onFocusWithinChange, ref, innerRef, innerRef2; + + beforeEach(() => { + onFocusWithinChange = jest.fn(); + ref = React.createRef(); + innerRef = React.createRef(); + innerRef2 = React.createRef(); + const Component = () => { + const listener = useFocusWithinResponder({ + onFocusWithinChange, + }); + return ( +
+
+
+
+ ); + }; + ReactDOM.render(, container); + }); + + it('is called after "blur" and "focus" events on focus target', () => { + ref.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinChange).toHaveBeenCalledWith(true); + ref.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinChange).toHaveBeenCalledWith(false); + }); + + it('is called after "blur" and "focus" events on descendants', () => { + innerRef.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinChange).toHaveBeenCalledWith(true); + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinChange).toHaveBeenCalledWith(false); + }); + + it('is only called once when focus moves within and outside the subtree', () => { + // focus shifts into subtree + innerRef.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinChange).toHaveBeenCalledWith(true); + // focus moves around subtree + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef2.current}), + ); + innerRef2.current.dispatchEvent(createEvent('focus')); + innerRef2.current.dispatchEvent( + createEvent('blur', {relatedTarget: ref.current}), + ); + ref.current.dispatchEvent(createEvent('focus')); + ref.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef.current}), + ); + expect(onFocusWithinChange).toHaveBeenCalledTimes(1); + // focus shifts outside subtree + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinChange).toHaveBeenCalledWith(false); + }); + }); + + describe('onFocusWithinVisibleChange', () => { + let onFocusWithinVisibleChange, ref, innerRef, innerRef2; + + beforeEach(() => { + onFocusWithinVisibleChange = jest.fn(); + ref = React.createRef(); + innerRef = React.createRef(); + innerRef2 = React.createRef(); + const Component = () => { + const listener = useFocusWithinResponder({ + onFocusWithinVisibleChange, + }); + return ( +
+
+
+
+ ); + }; + ReactDOM.render(, container); + }); + + it('is called after "focus" and "blur" on focus target if keyboard was used', () => { + // use keyboard first + container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'})); + ref.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); + ref.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); + }); + + it('is called after "focus" and "blur" on descendants if keyboard was used', () => { + // use keyboard first + container.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'})); + innerRef.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); + }); + + it('is called if non-keyboard event is dispatched on target previously focused with keyboard', () => { + // use keyboard first + ref.current.dispatchEvent(createEvent('focus')); + ref.current.dispatchEvent(createKeyboardEvent('keydown', {key: 'Tab'})); + ref.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef.current}), + ); + innerRef.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); + // then use pointer on the next target, focus should no longer be visible + innerRef2.current.dispatchEvent(createEvent('pointerdown')); + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef2.current}), + ); + innerRef2.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); + // then use keyboard again + innerRef2.current.dispatchEvent( + createKeyboardEvent('keydown', {key: 'Tab', shiftKey: true}), + ); + innerRef2.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef.current}), + ); + innerRef.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); + // then use pointer on the target, focus should no longer be visible + innerRef.current.dispatchEvent(createEvent('pointerdown')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); + // onFocusVisibleChange should not be called again + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4); + }); + + it('is not called after "focus" and "blur" events without keyboard', () => { + innerRef.current.dispatchEvent(createEvent('pointerdown')); + innerRef.current.dispatchEvent(createEvent('focus')); + container.dispatchEvent(createEvent('pointerdown')); + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0); + }); + + it('is only called once when focus moves within and outside the subtree', () => { + // focus shifts into subtree + innerRef.current.dispatchEvent(createEvent('focus')); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); + // focus moves around subtree + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef2.current}), + ); + innerRef2.current.dispatchEvent(createEvent('focus')); + innerRef2.current.dispatchEvent( + createEvent('blur', {relatedTarget: ref.current}), + ); + ref.current.dispatchEvent(createEvent('focus')); + ref.current.dispatchEvent( + createEvent('blur', {relatedTarget: innerRef.current}), + ); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); + // focus shifts outside subtree + innerRef.current.dispatchEvent( + createEvent('blur', {relatedTarget: container}), + ); + expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); + expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); + }); + }); + + it('expect displayName to show up for event component', () => { + expect(FocusWithinResponder.displayName).toBe('FocusWithin'); + }); +});