import React, { useEffect, useRef } from 'react'; import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; import Tour from '..'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { fireEvent, render, screen } from '../../../tests/utils'; import type { TourProps } from '../interface'; const mockBtnRect = ( rect: { x: number; y: number; width: number; height: number }, scrollIntoViewCb?: () => void, ) => { spyElementPrototypes(HTMLButtonElement, { getBoundingClientRect: { get(): any { return () => ({ ...rect, left: rect.x, top: rect.y }); }, }, scrollIntoView: { get(): any { scrollIntoViewCb?.(); return (val: boolean | ScrollIntoViewOptions) => val; }, }, }); }; describe('Tour', () => { mountTest(Tour); rtlTest(Tour); it('single', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); return ( <> coverBtnRef.current!, }, ]} /> ); }; const { getByText, baseElement } = render(); expect(getByText('cover title')).toBeTruthy(); expect(getByText('cover description.')).toBeTruthy(); expect(baseElement).toMatchSnapshot(); }); it('steps is empty', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); return ( <> ); }; const { baseElement } = render(); expect(baseElement).toMatchSnapshot(); }); it('steps props indicatorsRender', () => { const onClickMock = jest.fn(); const indicatorsRenderMock = jest.fn(); const App: React.FC = () => { const coverBtnRef = useRef(null); return ( <> ); }; const { baseElement } = render(); fireEvent.click(screen.getByRole('button', { name: 'Next' })); fireEvent.click(screen.getByRole('button', { name: 'Previous' })); fireEvent.click(screen.getByRole('button', { name: 'Next' })); fireEvent.click(screen.getByRole('button', { name: 'Next' })); fireEvent.click(screen.getByRole('button', { name: 'Finish' })); expect(onClickMock).toHaveBeenCalledTimes(5); expect(baseElement).toMatchSnapshot(); }); it('button props onClick', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); const [btnName, steBtnName] = React.useState('defaultBtn'); return ( <> {btnName} coverBtnRef.current!, nextButtonProps: { onClick: () => steBtnName('nextButton'), }, }, { title: '', target: () => coverBtnRef.current!, prevButtonProps: { onClick: () => steBtnName('prevButton'), }, nextButtonProps: { onClick: () => steBtnName('finishButton'), }, }, ]} /> ); }; const { baseElement } = render(); expect(baseElement.querySelector('#btnName')).toHaveTextContent('defaultBtn'); fireEvent.click(screen.getByRole('button', { name: 'Next' })); expect(baseElement.querySelector('#btnName')).toHaveTextContent('nextButton'); fireEvent.click(screen.getByRole('button', { name: 'Previous' })); expect(baseElement.querySelector('#btnName')).toHaveTextContent('prevButton'); fireEvent.click(screen.getByRole('button', { name: 'Next' })); fireEvent.click(screen.getByRole('button', { name: 'Finish' })); expect(baseElement.querySelector('#btnName')).toHaveTextContent('finishButton'); expect(baseElement).toMatchSnapshot(); }); it('Primary', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); return ( <> coverBtnRef.current!, }, ]} /> ); }; const { getByText, baseElement } = render(); expect(getByText('primary description.')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-content')?.parentElement).toHaveClass( 'ant-tour-primary', ); expect(baseElement).toMatchSnapshot(); }); it('step support Primary', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); return ( <> coverBtnRef.current!, }, { title: 'primary title', description: 'primary description.', target: () => coverBtnRef.current!, type: 'primary', }, ]} /> ); }; const { getByText, container, baseElement } = render(); expect(getByText('cover description.')).toBeTruthy(); expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeFalsy(); fireEvent.click(screen.getByRole('button', { name: 'Next' })); expect(getByText('primary description.')).toBeTruthy(); expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeTruthy(); expect(baseElement).toMatchSnapshot(); }); it('basic', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); const placementBtnRef = useRef(null); const [show, setShow] = React.useState(); useEffect(() => { if (show === false) { setShow(true); } }, [show]); return ( <>
{show && ( coverBtnRef.current!, cover: ( tour.png ), }, { title: 'Adjust Placement', description: 'Here is the content of Tour which show on the right.', placement: 'right', target: () => placementBtnRef.current!, }, ]} /> )} ); }; const { getByText, container, baseElement } = render(); fireEvent.click(screen.getByRole('button', { name: 'Show' })); expect(getByText('Show in Center')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: 'Next' })); expect(getByText('Here is the content of Tour.')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: 'Next' })); expect(getByText('Adjust Placement')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: 'Finish' })); expect(container.querySelector('.ant-tour')).toBeFalsy(); expect(baseElement).toMatchSnapshot(); }); it('panelRender should correct render when total is undefined or null', () => { [undefined, null].forEach((total: any) => { const { container } = render(test, total }]} />); expect( container.querySelector('.ant-tour-content .ant-tour-indicators'), ).toBeFalsy(); }); }); it('panelRender should correct render when title is undefined or null', () => { [undefined, null].forEach((title) => { const { container } = render(); expect( container.querySelector('.ant-tour-content .ant-tour-header'), ).toBeFalsy(); }); }); it('custom step pre btn & next btn className & style', () => { const App: React.FC = () => ( ), }, ]} /> ); const { container } = render(); // className expect(screen.getByRole('button', { name: 'Next' }).className.includes('customClassName')).toBe( true, ); // style expect(screen.getByRole('button', { name: 'Next' }).style.backgroundColor).toEqual( 'rgb(69, 69, 255)', ); expect(container.firstChild).toMatchSnapshot(); }); it('custom indicator', () => { const steps: TourProps['steps'] = [ { title: 'Upload File', description: 'Put your files here.', }, { title: 'Save', description: 'Save your changes.', }, { title: 'Other Actions', description: 'Click to see other actions.', }, ]; const App: React.FC = () => ( ( {current + 1} / {total} )} /> ); const { container } = render(); expect(container.querySelector('.custom-indicator')).toBeTruthy(); }); it('controlled current', () => { const App: React.FC = () => { const [current, setCurrent] = React.useState(0); return ( <>
); }; const { getByText, container, baseElement } = render(); fireEvent.click(screen.getByRole('button', { name: 'SetCurrent' })); expect(getByText('Primary description.')).toBeTruthy(); expect(container.querySelector('.ant-tour-primary .ant-tour-content')).toBeTruthy(); expect(baseElement).toMatchSnapshot(); }); it('support closeIcon', () => { const Demo = ({ closeIcon = false }: { closeIcon?: React.ReactNode }) => { const createBtnRef = useRef(null); const updateBtnRef = useRef(null); const deleteBtnRef = useRef(null); return (
createBtnRef.current!, mask: true, }, { title: '更新', closeIcon: !closeIcon, description: (
更新一条数据
), target: () => updateBtnRef.current!, }, { title: '删除', closeIcon: Close, description: (
危险操作:删除一条数据
), target: () => deleteBtnRef.current!, }, ]} />
); }; const { baseElement, rerender } = render(); const resetIndex = () => { // reset fireEvent.click(baseElement.querySelector('.ant-tour-prev-btn')!); fireEvent.click(baseElement.querySelector('.ant-tour-prev-btn')!); }; expect(baseElement.querySelector('.ant-tour-close')).toBeFalsy(); fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!); expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeTruthy(); fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!); expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy(); expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy(); resetIndex(); rerender(); expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeTruthy(); fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!); expect(baseElement.querySelector('.ant-tour-close')).toBeFalsy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy(); fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!); expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy(); expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy(); resetIndex(); rerender(X} />); expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.custom-global-close-icon')).toBeTruthy(); fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!); expect(baseElement.querySelector('.ant-tour-close')).toBeFalsy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy(); expect(baseElement.querySelector('.custom-global-close-icon')).toBeFalsy(); fireEvent.click(baseElement.querySelector('.ant-tour-next-btn')!); expect(baseElement.querySelector('.ant-tour-close')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-close-icon')).toBeFalsy(); expect(baseElement.querySelector('.custom-del-close-icon')).toBeTruthy(); resetIndex(); }); it('first step should be primary', () => { const App: React.FC = () => { const coverBtnRef = useRef(null); return ( <> coverBtnRef.current!, type: 'primary', className: 'should-be-primary', }, { title: '', target: () => coverBtnRef.current!, }, ]} /> ); }; render(); fireEvent.click(screen.getByRole('button', { name: 'target' })); expect(document.querySelector('.should-be-primary')).toBeTruthy(); expect(document.querySelector('.should-be-primary')).toHaveClass('ant-tour-primary'); }); // https://github.com/ant-design/ant-design/issues/49117 it('onClose current is correct', () => { const onClose = jest.fn(); const { container } = render( , ); fireEvent.click(container.querySelector('.ant-tour-next-btn')!); fireEvent.click(container.querySelector('.ant-tour-close-icon')!); expect(onClose).toHaveBeenLastCalledWith(1); }); it('should support gap.radius', () => { const App: React.FC<{ gap: TourProps['gap'] }> = ({ gap }) => { const ref = useRef(null); const [show, setShow] = React.useState(); const steps: TourProps['steps'] = [ { title: 'Show in Center', description: 'Here is the content of Tour.', target: () => ref.current!, }, ]; return ( <> ); }; const { rerender, baseElement } = render(); fireEvent.click(screen.getByRole('button', { name: 'Show' })); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute('rx', '4'); rerender(); fireEvent.click(screen.getByRole('button', { name: 'Show' })); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toBeTruthy(); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute('rx', '0'); }); it('should support gap.offset', () => { const gap = { offset: 10 }; const pos = { x: 100, y: 200, width: 230, height: 180 }; mockBtnRect(pos); const App: React.FC = () => { const ref = useRef(null); const [show, setShow] = React.useState(); const steps: TourProps['steps'] = [ { title: 'Show in Center', description: 'Here is the content of Tour.', target: () => ref.current!, }, ]; return ( <> ); }; const { baseElement } = render(); const targetBtn = screen.getByRole('button', { name: 'Show' }); fireEvent.click(targetBtn); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute( 'width', String(pos.width + gap.offset * 2), ); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute( 'height', String(pos.height + gap.offset * 2), ); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute( 'x', String(pos.x - gap.offset), ); expect(baseElement.querySelector('.ant-tour-placeholder-animated')).toHaveAttribute( 'y', String(pos.y - gap.offset), ); expect(baseElement).toMatchSnapshot(); }); // This test is for PurePanel which means safe to remove. describe('PurePanel', () => { const PurePanel = Tour._InternalPanelDoNotUseOrYouWillBeFired; it('closeIcon', () => { const { container } = render( , , ]} title="a" />, ); expect(container.querySelector('.bamboo')).toBeTruthy(); expect(container.querySelector('.little')).toBeTruthy(); }); }); });