/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import { clearPointers, createEventTarget, describeWithPointerEvent, setPointerEvent, } from 'dom-event-testing-library' import * as React from 'react' import * as ReactDOM from 'react-dom' import { act } from 'react-dom/test-utils' import { testOnly_resetActiveModality } from '../../modality/index' import useHover from '..' function createRoot(rootNode) { return { render(element) { ReactDOM.render(element, rootNode) }, } } describeWithPointerEvent('useHover', (hasPointerEvents) => { let root let rootNode beforeEach(() => { setPointerEvent(hasPointerEvents) rootNode = document.createElement('div') document.body.appendChild(rootNode) root = createRoot(rootNode) }) afterEach(() => { root.render(null) document.body.removeChild(rootNode) rootNode = null root = null testOnly_resetActiveModality() // make sure all tests reset state machine tracking pointers on the mock surface clearPointers() }) describe('contain', () => { let onHoverChange, onHoverStart, onHoverUpdate, onHoverEnd, ref, childRef const componentInit = () => { onHoverChange = jest.fn() onHoverStart = jest.fn() onHoverUpdate = jest.fn() onHoverEnd = jest.fn() ref = React.createRef() childRef = React.createRef() const Component = () => { useHover(ref, { onHoverChange, onHoverStart, onHoverUpdate, onHoverEnd, }) useHover(childRef, { contain: true }) return (
) } act(() => { root.render() }) } test('contains the hover gesture', () => { componentInit() const target = createEventTarget(ref.current) const child = createEventTarget(childRef.current) act(() => { target.pointerover() target.pointerout() child.pointerover() }) expect(onHoverEnd).toBeCalled() act(() => { child.pointerout() }) expect(onHoverStart).toBeCalled() }) }) describe('disabled', () => { let onHoverChange, onHoverStart, onHoverUpdate, onHoverEnd, ref const componentInit = () => { onHoverChange = jest.fn() onHoverStart = jest.fn() onHoverUpdate = jest.fn() onHoverEnd = jest.fn() ref = React.createRef() const Component = () => { useHover(ref, { disabled: true, onHoverChange, onHoverStart, onHoverUpdate, onHoverEnd, }) return
} act(() => { root.render() }) } test('does not call callbacks', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerover() target.pointerout() }) expect(onHoverChange).not.toBeCalled() expect(onHoverStart).not.toBeCalled() expect(onHoverUpdate).not.toBeCalled() expect(onHoverEnd).not.toBeCalled() }) }) describe('onHoverStart', () => { let onHoverStart, ref const componentInit = () => { onHoverStart = jest.fn() ref = React.createRef() const Component = () => { useHover(ref, { onHoverStart }) return
} act(() => { root.render() }) } test('is called for mouse pointers', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerover({ pointerType: 'mouse' }) }) expect(onHoverStart).toBeCalledTimes(1) }) test('is not called for touch pointers', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerdown({ pointerType: 'touch' }) target.pointerup({ pointerType: 'touch' }) }) expect(onHoverStart).not.toBeCalled() }) test('is called if a mouse pointer is used after a touch pointer', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerdown({ pointerType: 'touch' }) target.pointerup({ pointerType: 'touch' }) target.pointerover({ pointerType: 'mouse' }) }) expect(onHoverStart).toBeCalledTimes(1) }) }) describe('onHoverChange', () => { let onHoverChange, ref const componentInit = () => { onHoverChange = jest.fn() ref = React.createRef() const Component = () => { useHover(ref, { onHoverChange }) return
} act(() => { root.render() }) } test('is called for mouse pointers', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerover() }) expect(onHoverChange).toBeCalledTimes(1) expect(onHoverChange).toBeCalledWith(true) act(() => { target.pointerout() }) expect(onHoverChange).toBeCalledTimes(2) expect(onHoverChange).toBeCalledWith(false) }) test('is not called for touch pointers', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerdown({ pointerType: 'touch' }) target.pointerup({ pointerType: 'touch' }) }) expect(onHoverChange).not.toBeCalled() }) }) describe('onHoverEnd', () => { let onHoverEnd, ref, childRef const componentInit = () => { onHoverEnd = jest.fn() ref = React.createRef() childRef = React.createRef() const Component = () => { useHover(ref, { onHoverEnd }) return (
) } act(() => { root.render() }) } test('is called for mouse pointers', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerover() target.pointerout() }) expect(onHoverEnd).toBeCalledTimes(1) }) test('is not called for touch pointers', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerdown({ pointerType: 'touch' }) target.pointerup({ pointerType: 'touch' }) }) expect(onHoverEnd).not.toBeCalled() }) test('is not called when entering children of the target', () => { componentInit() const target = createEventTarget(ref.current) const child = createEventTarget(childRef.current) act(() => { target.pointerover() target.pointerout({ relatedTarget: childRef.current }) child.pointerover({ relatedTarget: target.node }) }) expect(onHoverEnd).not.toBeCalled() }) }) describe('onHoverUpdate', () => { test('is called after the active pointer moves"', () => { const onHoverUpdate = jest.fn() const ref = React.createRef() const Component = () => { useHover(ref, { onHoverUpdate }) return
} act(() => { root.render() }) const target = createEventTarget(ref.current) act(() => { target.pointerover() target.pointerhover({ x: 0, y: 0 }) target.pointerhover({ x: 1, y: 1 }) }) expect(onHoverUpdate).toBeCalledTimes(2) }) }) describe('repeat use', () => { let onHoverChange, onHoverStart, onHoverUpdate, onHoverEnd, ref const componentInit = () => { onHoverChange = jest.fn() onHoverStart = jest.fn() onHoverUpdate = jest.fn() onHoverEnd = jest.fn() ref = React.createRef() const Component = () => { useHover(ref, { onHoverChange, onHoverStart, onHoverUpdate, onHoverEnd, }) return
} act(() => { root.render() }) } test('callbacks are called each time', () => { componentInit() const target = createEventTarget(ref.current) act(() => { target.pointerover() target.pointerhover({ x: 1, y: 1 }) target.pointerout() }) expect(onHoverStart).toBeCalledTimes(1) expect(onHoverUpdate).toBeCalledTimes(1) expect(onHoverEnd).toBeCalledTimes(1) expect(onHoverChange).toBeCalledTimes(2) act(() => { target.pointerover() target.pointerhover({ x: 1, y: 1 }) target.pointerout() }) expect(onHoverStart).toBeCalledTimes(2) expect(onHoverUpdate).toBeCalledTimes(2) expect(onHoverEnd).toBeCalledTimes(2) expect(onHoverChange).toBeCalledTimes(4) }) }) })