/**
* 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)
})
})
})