'Testing hover state hooks
I created this useHover hook:
import { useRef, useState, useEffect } from 'react';
export const useHover = () => {
const [value, setValue] = useState(false);
const ref = useRef(null);
const handleMouseOver = () => setValue(true);
const handleMouseOut = () => setValue(false);
useEffect(
// eslint-disable-next-line consistent-return
() => {
const node = ref.current;
if (node) {
node.addEventListener('mouseover', handleMouseOver);
node.addEventListener('mouseout', handleMouseOut);
return () => {
node.removeEventListener('mouseover', handleMouseOver);
node.removeEventListener('mouseout', handleMouseOut);
};
}
},
[]
);
return [ref, value];
};
I wrote this test:
import React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import { fireEvent, render } from '@testing-library/react';
import { Box } from 'gestalt';
import userEvent from '@testing-library/user-event';
import { useHover } from './useHover';
describe('useHover', () => {
it('toogles', async () => {
const { result } = renderHook(() => useHover());
// eslint-disable-next-line no-unused-vars
const [ref, value] = result.current;
expect(value).toBeFalsy();
const Div = () => <Box ref={ref}>Test</Box>;
render(<Div />);
await userEvent.hover(ref.current);
expect(value).toBeTruthy();
fireEvent.mouseOut(ref.current);
expect(value).toBeFalsy();
});
});
But it fails:
21 | expect(value).toBeTruthy();
Solution 1:[1]
At first glance I see some possible issues
- The dependency array of the useEffect is empty, this will only fire the first time the useEffect gets called in a component.
- The node will always be falsy the first time the useEffect mounts, thus the dom event
listeners wont be added.
perhaps adding [ref.current] will solve thissee point 3 - Its generally not a good idea to use ref.current as a dependency in a dependency array.
- the value of "value" wont change anyway, since it was destructured into a constant value type, it will be the initial snapshot of that value type.
- the dom event listeners are different events to the events "fired" in the test
- Suggest using a useCallback hook instead and watch the callback functions in the dependency array for your custom hook
- Suggest Testing the hook in an actual component, since it relies on dom events linked to a component
Please see this codeSandBox
Solution 2:[2]
import React from 'react';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useHover } from '../../src/hooks/useHover';
describe('useHover', () => {
it('toogles', () => {
const Div = () => {
const [hoverRef, isHovering] = useHover();
return (
<div>
<div data-testid="trigger" ref={hoverRef}>
Trigger
</div>
{isHovering && <div data-testid="expected">Expected</div>}
</div>
);
};
const { queryByTestId } = render(<Div />);
expect(queryByTestId('expected')).not.toBeInTheDocument();
userEvent.hover(queryByTestId('trigger'));
expect(queryByTestId('expected')).toBeInTheDocument();
userEvent.unhover(queryByTestId('trigger'));
expect(queryByTestId('expected')).not.toBeInTheDocument();
});
});
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Anthony Cifuentes |