import React from 'react'; import { spyElementPrototype } from '@rc-component/util/lib/test/domHook'; import type { TooltipPlacement } from '..'; import Tooltip from '..'; import getPlacements from '../../_util/placements'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import Button from '../../button'; import ConfigProvider from '../../config-provider'; import DatePicker from '../../date-picker'; import Input from '../../input'; import Group from '../../input/Group'; import Radio from '../../radio'; import Switch from '../../switch'; import { isTooltipOpen } from './util'; import { resetWarned } from '@rc-component/util/lib/warning'; describe('Tooltip', () => { mountTest(Tooltip); rtlTest(Tooltip); beforeEach(() => { jest.useFakeTimers(); }); afterEach(() => { jest.useRealTimers(); jest.clearAllTimers(); }); beforeAll(() => { spyElementPrototype(HTMLElement, 'offsetParent', { get: () => ({}), }); }); it('check `onOpenChange` arguments', async () => { const onOpenChange = jest.fn(); const ref = React.createRef(); const { container, rerender } = render(
Hello world!
, ); // `title` is empty. const divElement = container.querySelector('#hello'); fireEvent.mouseEnter(divElement!); await waitFakeTimer(); expect(onOpenChange).not.toHaveBeenCalled(); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); fireEvent.mouseLeave(divElement!); await waitFakeTimer(); expect(onOpenChange).not.toHaveBeenCalled(); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); // update `title` value. rerender(
Hello world!
, ); fireEvent.mouseEnter(divElement!); await waitFakeTimer(); expect(onOpenChange).toHaveBeenLastCalledWith(true); expect(isTooltipOpen()).toBeTruthy(); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); fireEvent.mouseLeave(divElement!); await waitFakeTimer(); expect(onOpenChange).toHaveBeenLastCalledWith(false); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); // add `open` props. rerender(
Hello world!
, ); fireEvent.mouseEnter(divElement!); await waitFakeTimer(); expect(onOpenChange).toHaveBeenLastCalledWith(true); const lastCount = onOpenChange.mock.calls.length; expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); // always trigger onOpenChange fireEvent.mouseLeave(divElement!); await waitFakeTimer(); expect(onOpenChange.mock.calls.length).toBe(lastCount); // no change with lastCount expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); }); it('should hide when mouse leave native disabled button', async () => { const onOpenChange = jest.fn(); const ref = React.createRef(); const { container } = render( , ); const button = container.getElementsByTagName('button')[0]; fireEvent.pointerEnter(button); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(true); expect(isTooltipOpen()).toBeTruthy(); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); fireEvent.pointerLeave(button); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(false); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); }); describe('should hide when mouse leave antd disabled component', () => { function testComponent(name: string, Component: typeof Button | typeof Switch) { it(name, async () => { const onOpenChange = jest.fn(); const ref = React.createRef(); const { container } = render( , ); expect(container.children[0]).toMatchSnapshot(); const button = container.getElementsByTagName('button')[0]; fireEvent.pointerEnter(button); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(true); expect(isTooltipOpen()).toBeTruthy(); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); fireEvent.pointerLeave(button); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(false); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); }); } testComponent('Button', Button); testComponent('Switch', Switch); }); it('should render disabled Button style properly', () => { const { container: containerInline } = render( , ); const { container: containerBlock } = render( , ); expect(getComputedStyle(containerInline.querySelector('button')!)?.display).toBe('inline-flex'); expect(getComputedStyle(containerBlock.querySelector('button')!)?.display).toBe('block'); }); it('should works for date picker', async () => { const onOpenChange = jest.fn(); const ref = React.createRef(); const { container } = render( , ); expect(container.getElementsByClassName('ant-picker')).toHaveLength(1); const picker = container.getElementsByClassName('ant-picker')[0]; fireEvent.mouseEnter(picker); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(true); expect(isTooltipOpen()).toBeTruthy(); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); fireEvent.mouseLeave(picker); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(false); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); }); it('should works for input group', async () => { const onOpenChange = jest.fn(); const ref = React.createRef(); const { container } = render( , ); expect(container.getElementsByClassName('ant-input-group')).toHaveLength(1); const inputGroup = container.getElementsByClassName('ant-input-group')[0]; fireEvent.mouseEnter(inputGroup); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(true); expect(isTooltipOpen()).toBeTruthy(); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); fireEvent.mouseLeave(inputGroup); await waitFakeTimer(); expect(onOpenChange).toHaveBeenCalledWith(false); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); }); // https://github.com/ant-design/ant-design/issues/20891 it('should display zero', () => { const { container } = render(
, ); expect(container.querySelector('.ant-tooltip-inner')?.innerHTML).toBe('0'); }); it('autoAdjustOverflow should be object or undefined', () => { expect(() => { render(
, ); }).not.toThrow(); expect(() => { render(
, ); }).not.toThrow(); }); describe('support other placement when mouse enter', () => { const placementList = [ 'top', 'left', 'right', 'bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom', ] as const; const testPlacement = (name: string, placement: TooltipPlacement) => { it(name, async () => { const { container } = render( Hello world! , ); expect(container.getElementsByTagName('span')).toHaveLength(1); const element = container.getElementsByTagName('span')[0]; fireEvent.mouseEnter(element); await waitFakeTimer(); expect(document.querySelector(`.ant-tooltip-placement-${placement}`)).toBeTruthy(); }); it(`${name} with arrowPointAtCenter`, async () => { const placementInfo: Record = getPlacements({ arrowPointAtCenter: true, autoAdjustOverflow: false, arrowWidth: 0, borderRadius: 10, offset: 0, }); // Safe to rewrite follow all check const { offset } = placementInfo[placement]; const existO = offset[0] !== 0 || offset[1] !== 0; if (['left', 'right', 'top', 'bottom'].includes(placement)) { expect(existO).toBeFalsy(); } else { expect(existO).toBeTruthy(); } }); }; placementList.forEach((placement) => testPlacement(`Placement ${placement}`, placement)); }); it('should works for mismatch placement', async () => { const { container } = render( Hello world! , ); const button = container.getElementsByTagName('span')[0]; fireEvent.mouseEnter(button); await waitFakeTimer(); expect(document.querySelector('.ant-tooltip')).not.toBeNull(); }); it('should pass styles.body through to the inner component', () => { const { container } = render(
, ); expect(container.querySelector('.ant-tooltip-inner')?.style?.color).toBe('red'); }); it('should work with loading switch', () => { const onOpenChange = jest.fn(); const { container } = render( , ); fireEvent.pointerEnter(container.getElementsByTagName('button')[0]); expect(onOpenChange).toHaveBeenLastCalledWith(true); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); }); it('should work with disabled Radio', () => { const onOpenChange = jest.fn(); const { container } = render( , ); fireEvent.pointerEnter(container.getElementsByTagName('input')[0]); expect(onOpenChange).toHaveBeenLastCalledWith(true); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); }); it('should work with Fragment children', async () => { const onOpenChange = jest.fn(); const ref = React.createRef(); const { container } = render( <>
Hello world!
Hello world!
, ); const divElement = container.querySelector('.hello'); fireEvent.mouseEnter(divElement!); expect(onOpenChange).toHaveBeenLastCalledWith(true); await waitFakeTimer(); expect(isTooltipOpen()).toBeTruthy(); expect(container.querySelector('.ant-tooltip-open')).not.toBeNull(); fireEvent.mouseLeave(divElement!); expect(onOpenChange).toHaveBeenLastCalledWith(false); await waitFakeTimer(); expect(isTooltipOpen()).toBeFalsy(); expect(container.querySelector('.ant-tooltip-open')).toBeNull(); }); it('deprecated warning', async () => { resetWarned(); const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const { rerender } = render( , ); await waitFakeTimer(); rerender( test , ); expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: Tooltip] `destroyTooltipOnHide` is deprecated. Please use `destroyOnHidden` instead.', ); errSpy.mockRestore(); }); it('not inject className when children className is not string type', () => { const HOC = ({ className }: { className: () => string }) => ; const { container } = render( 'bamboo'} /> , ); expect(container.querySelector('.bamboo')).toBeTruthy(); expect(container.querySelector('.ant-tooltip')).toBeTruthy(); }); it('support arrow props pass false to hide arrow', () => { const { container } = render(
target
, ); expect(container).toMatchSnapshot(); }); it('support arrow props by default', () => { const { container } = render(
target
, ); expect(container).toMatchSnapshot(); }); it('should apply custom styles to Tooltip', () => { const customClassNames = { body: 'custom-body', root: 'custom-root', }; const customStyles = { body: { color: 'red' }, root: { backgroundColor: 'blue' }, }; const { container } = render( } styles={customStyles} open> , ); const tooltipElement = container.querySelector('.ant-tooltip') as HTMLElement; const tooltipBodyElement = container.querySelector('.ant-tooltip-inner') as HTMLElement; // 验证 classNames expect(tooltipElement.classList).toContain('custom-root'); expect(tooltipBodyElement.classList).toContain('custom-body'); // 验证 styles expect(tooltipElement.style.backgroundColor).toBe('blue'); expect(tooltipBodyElement.style.color).toBe('red'); }); it('ConfigProvider support arrow props', () => { const TooltipTestComponent = () => { const [configArrow, setConfigArrow] = React.useState(true); return (
target
); }; const { container } = render(); const getTooltipArrow = () => container.querySelector('.ant-tooltip-arrow'); const configbtn = container.querySelector('.configArrow'); expect(getTooltipArrow()).not.toBeNull(); fireEvent.click(configbtn!); expect(getTooltipArrow()).toBeNull(); }); it('ConfigProvider with arrow set to false, Tooltip arrow controlled by prop', () => { const TooltipTestComponent = () => { const [arrow, setArrow] = React.useState(true); return (
target
); }; const { container } = render(); const getTooltipArrow = () => container.querySelector('.ant-tooltip-arrow'); const toggleArrowBtn = container.querySelector('.toggleArrow'); // Initial render, arrow should be visible because Tooltip's arrow prop is true expect(getTooltipArrow()).not.toBeNull(); // Click the toggleArrow button to hide the arrow fireEvent.click(toggleArrowBtn!); expect(getTooltipArrow()).toBeNull(); // Click the toggleArrow button again to show the arrow fireEvent.click(toggleArrowBtn!); expect(getTooltipArrow()).not.toBeNull(); }); });