import React, { useState } from 'react'; import { resetWarned } from 'rc-util/lib/warning'; import scrollIntoView from 'scroll-into-view-if-needed'; import Anchor from '..'; import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils'; import Button from '../../button'; import type { AnchorDirection } from '../Anchor'; const { Link } = Anchor; function createDiv() { const root = document.createElement('div'); document.body.appendChild(root); return root; } let idCounter = 0; const getHashUrl = () => `Anchor-API-${idCounter++}`; jest.mock('scroll-into-view-if-needed', () => jest.fn()); Object.defineProperty(window, 'location', { value: { replace: jest.fn(), }, }); describe('Anchor Render', () => { const getBoundingClientRectMock = jest.spyOn( HTMLHeadingElement.prototype, 'getBoundingClientRect', ); const getClientRectsMock = jest.spyOn(HTMLHeadingElement.prototype, 'getClientRects'); const scrollIntoViewMock = jest.createMockFromModule<any>('scroll-into-view-if-needed'); beforeAll(() => { jest.useFakeTimers(); getBoundingClientRectMock.mockReturnValue({ width: 100, height: 100, top: 1000, } as DOMRect); getClientRectsMock.mockReturnValue([1] as unknown as DOMRectList); }); beforeEach(() => { jest.useFakeTimers(); scrollIntoViewMock.mockReset(); }); afterEach(() => { jest.clearAllTimers(); jest.useRealTimers(); }); afterAll(() => { jest.clearAllTimers(); jest.useRealTimers(); getBoundingClientRectMock.mockRestore(); getClientRectsMock.mockRestore(); }); it('renders items correctly', () => { const { container, asFragment } = render( <Anchor items={[ { key: '1', href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', href: '#anchor-demo-static', title: 'Static demo', }, { key: '3', href: '#api', title: 'API', children: [ { key: '4', href: '#anchor-props', title: 'Anchor Props', children: [ { key: '5', href: '#link-props', title: 'Link Props', }, ], }, ], }, ]} />, ); expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(5); const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!).map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title'), ); expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#anchor-demo-basic'); expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#anchor-demo-static'); expect((linkTitles[3] as HTMLAnchorElement).href).toContain('#api'); expect( ( container.querySelector( '.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link-title', ) as HTMLAnchorElement )?.href, ).toContain('#anchor-props'); expect( ( container.querySelector( '.ant-anchor .ant-anchor-link .ant-anchor-link .ant-anchor-link .ant-anchor-link-title', ) as HTMLAnchorElement )?.href, ).toContain('#link-props'); expect(asFragment().firstChild).toMatchSnapshot(); }); it('renders items correctly#horizontal', () => { const { container, asFragment } = render( <Anchor items={[ { key: '1', href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', href: '#anchor-demo-static', title: 'Static demo', }, { key: '3', href: '#api', title: 'API', }, ]} />, ); expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(3); const linkTitles = Array.from(container.querySelector('.ant-anchor')?.childNodes!).map((n) => (n as HTMLElement).querySelector('.ant-anchor-link-title'), ); expect((linkTitles[1] as HTMLAnchorElement).href).toContain('#anchor-demo-basic'); expect((linkTitles[2] as HTMLAnchorElement).href).toContain('#anchor-demo-static'); expect((linkTitles[3] as HTMLAnchorElement).href).toContain('#api'); expect(asFragment().firstChild).toMatchSnapshot(); }); it('render items and ignore jsx children', () => { const { container, asFragment } = render( <Anchor items={[ { key: '1', href: '#anchor-demo-basic', title: 'Item Basic Demo', }, ]} > <Link href="#api" title="API" /> </Anchor>, ); expect(container.querySelectorAll('.ant-anchor .ant-anchor-link').length).toBe(1); expect( (container.querySelector('.ant-anchor .ant-anchor-link-title') as HTMLAnchorElement).href, ).toContain('#anchor-demo-basic'); expect(asFragment().firstChild).toMatchSnapshot(); }); it('actives the target when clicking a link', async () => { const hash = getHashUrl(); const { container } = render( <Anchor prefixCls="ant-anchor" direction="horizontal" items={[ { key: hash, title: hash, href: `http://www.example.com/#${hash}`, }, ]} />, ); const link = container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!; fireEvent.click(link); await waitFakeTimer(); expect(link.classList).toContain('ant-anchor-link-title-active'); }); it('scrolls the page when clicking a link', async () => { const root = createDiv(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); render(<div id="/faq?locale=en#Q1">Q1</div>, { container: root }); const { container } = render( <Anchor items={[{ key: 'Q1', title: 'Q1', href: '/#/faq?locale=en#Q1' }]} />, ); const link = container.querySelector(`a[href="/#/faq?locale=en#Q1"]`)!; fireEvent.click(link); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenCalled(); }); it('handleScroll should not be triggered when scrolling caused by clicking a link', async () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const root = createDiv(); const onChange = jest.fn(); render( <div> <div id={hash1}>Hello</div> <div id={hash2}>World</div> </div>, { container: root }, ); const { container } = render( <Anchor onChange={onChange} items={[ { key: hash1, href: `#${hash1}`, title: hash1 }, { key: hash2, href: `#${hash2}`, title: hash2 }, ]} />, ); onChange.mockClear(); const link = container.querySelector(`a[href="#${hash2}"]`)!; // this will trigger 1 onChange fireEvent.click(link); // smooth scroll caused by clicking needs time to finish. // we scroll the window before it finish, the scroll listener should not be triggered, fireEvent.scroll(window); await waitFakeTimer(); // if the scroll listener is triggered, we will get 2 onChange, now we expect only 1. expect(onChange).toHaveBeenCalledTimes(1); }); it('should update DOM when children are unmounted', () => { const hash = getHashUrl(); const { container, rerender } = render( <Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} />, ); expect(container.querySelectorAll('.ant-anchor-link-title')).toHaveLength(1); expect(container.querySelector('.ant-anchor-link-title')).toHaveAttribute('href', `#${hash}`); rerender(<Anchor />); expect(container.querySelector('.ant-anchor-link-title')).toBeFalsy(); }); it('should update DOM when link href is changed', async () => { const hash = getHashUrl(); function AnchorUpdate({ href }: { href: string }) { return <Anchor items={[{ key: hash, href, title: hash }]} />; } const { container, rerender } = render(<AnchorUpdate href={`#${hash}`} />); expect(container.querySelector(`a[href="#${hash}"]`)).toBeTruthy(); rerender(<AnchorUpdate href={`#${hash}_1`} />); expect(container.querySelector(`a[href="#${hash}_1"]`)).toBeTruthy(); }); it('targetOffset prop', async () => { const hash = getHashUrl(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); render(<h1 id={hash}>Hello</h1>, { container: root }); const { container, rerender } = render( <Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} />, ); const setProps = (props: Record<string, any>) => rerender(<Anchor {...props} items={[{ key: hash, href: `#${hash}`, title: hash }]} />); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); setProps({ offsetTop: 100 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); setProps({ targetOffset: 200 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); }); // https://github.com/ant-design/ant-design/issues/31941 it('targetOffset prop when contain spaces', async () => { const hash = `${getHashUrl()} s p a c e s`; const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); render(<h1 id={hash}>Hello</h1>, { container: root }); const { container, rerender } = render( <Anchor items={[{ key: hash, href: `#${hash}`, title: hash }]} />, ); const setProps = (props: Record<string, any>) => rerender(<Anchor {...props} items={[{ key: hash, href: `#${hash}`, title: hash }]} />); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); setProps({ offsetTop: 100 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); setProps({ targetOffset: 200 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); }); it('onClick event', () => { const hash = getHashUrl(); let event; let link; const handleClick = ( e: React.MouseEvent<HTMLElement>, _link: { title: React.ReactNode; href: string }, ) => { event = e; link = _link; }; const href = `#${hash}`; const title = hash; const { container } = render( <Anchor onClick={handleClick} items={[{ key: hash, href, title }]} />, ); fireEvent.click(container.querySelector(`a[href="${href}"]`)!); expect(event).not.toBe(undefined); expect(link).toEqual({ href, title }); }); it('replaces item href in browser history', () => { const hash = getHashUrl(); const href = `#${hash}`; const title = hash; const { container } = render(<Anchor replace items={[{ key: hash, href, title }]} />); fireEvent.click(container.querySelector(`a[href="${href}"]`)!); expect(window.location.replace).toHaveBeenCalledWith(href); }); it('onChange event', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const onChange = jest.fn(); const { container } = render( <Anchor onChange={onChange} items={[ { key: hash1, href: `#${hash1}`, title: hash1, }, { key: hash2, href: `#${hash2}`, title: hash2, }, ]} />, // https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0 { legacyRoot: true }, ); expect(onChange).toHaveBeenCalledTimes(1); fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!); expect(onChange).toHaveBeenCalledTimes(2); expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`); }); it('should be used the latest onChange method', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const beforeFn = jest.fn(); const afterFn = jest.fn(); const Demo: React.FC = () => { const [trigger, setTrigger] = useState(false); const onChange = trigger ? afterFn : beforeFn; return ( <> <Button className="test-button" onClick={() => setTrigger(true)} /> <Anchor onChange={onChange} items={[ { key: hash1, href: `#${hash1}`, title: hash1, }, { key: hash2, href: `#${hash2}`, title: hash2, }, ]} /> </> ); }; const { container } = render(<Demo />); expect(beforeFn).toHaveBeenCalled(); expect(afterFn).not.toHaveBeenCalled(); beforeFn.mockClear(); afterFn.mockClear(); fireEvent.click(container.querySelector('.test-button')!); fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!); expect(beforeFn).not.toHaveBeenCalled(); expect(afterFn).toHaveBeenCalled(); }); it('handles invalid hash correctly', () => { const { container } = render( <Anchor items={[{ key: 'title', href: 'nonexistent', title: 'title' }]} />, ); const link = container.querySelector(`a[href="nonexistent"]`)!; fireEvent.click(link); expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe('title'); }); it('test edge case when getBoundingClientRect return zero size', async () => { getBoundingClientRectMock.mockReturnValue({ width: 0, height: 0, top: 1000 } as DOMRect); const hash = getHashUrl(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); render(<h1 id={hash}>Hello</h1>, { container: root }); const { container, rerender } = render( <Anchor> <Link href={`#${hash}`} title={hash} /> </Anchor>, ); const setProps = (props: Record<string, any>) => rerender( <Anchor {...props}> <Link href={`#${hash}`} title={hash} /> </Anchor>, ); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); setProps({ offsetTop: 100 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); setProps({ targetOffset: 200 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); getBoundingClientRectMock.mockReturnValue({ width: 100, height: 100, top: 1000, } as DOMRect); }); it('test edge case when container is not windows', async () => { const hash = getHashUrl(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); render(<h1 id={hash}>Hello</h1>, { container: root }); const { container, rerender } = render( <Anchor getContainer={() => document.body}> <Link href={`#${hash}`} title={hash} /> </Anchor>, ); const setProps = (props: Record<string, any>) => rerender( <Anchor getContainer={() => document.body} {...props}> <Link href={`#${hash}`} title={hash} /> </Anchor>, ); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); setProps({ offsetTop: 100 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); setProps({ targetOffset: 200 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); }); describe('getCurrentAnchor', () => { it('getCurrentAnchor prop', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const getCurrentAnchor = () => `#${hash2}`; const { container } = render( <Anchor getCurrentAnchor={getCurrentAnchor} items={[ { key: hash1, href: `#${hash1}`, title: hash1 }, { key: hash2, href: `#${hash2}`, title: hash2 }, ]} />, ); expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash2); }); // https://github.com/ant-design/ant-design/issues/30584 it('should trigger onChange when have getCurrentAnchor', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const onChange = jest.fn(); const { container } = render( <Anchor onChange={onChange} getCurrentAnchor={() => hash1} items={[ { key: hash1, href: `#${hash1}`, title: hash1 }, { key: hash2, href: `#${hash2}`, title: hash2 }, ]} />, // https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0 { legacyRoot: true }, ); // Should be 2 times: // 1. '' // 2. hash1 (Since `getCurrentAnchor` still return same hash) expect(onChange).toHaveBeenCalledTimes(2); fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!); expect(onChange).toHaveBeenCalledTimes(3); expect(onChange).toHaveBeenLastCalledWith(`#${hash2}`); }); // https://github.com/ant-design/ant-design/issues/34784 it('getCurrentAnchor have default link as argument', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const getCurrentAnchor = jest.fn(); const { container } = render( <Anchor getCurrentAnchor={getCurrentAnchor} items={[ { key: hash1, href: `#${hash1}`, title: hash1 }, { key: hash2, href: `#${hash2}`, title: hash2 }, ]} />, ); fireEvent.click(container.querySelector(`a[href="#${hash1}"]`)!); expect(getCurrentAnchor).toHaveBeenCalledWith(`#${hash1}`); fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!); expect(getCurrentAnchor).toHaveBeenCalledWith(`#${hash2}`); }); // https://github.com/ant-design/ant-design/issues/37627 it('should update active link when getCurrentAnchor changes', async () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const Demo: React.FC<{ current: string }> = ({ current }) => ( <Anchor getCurrentAnchor={() => `#${current}`} items={[ { key: hash1, href: `#${hash1}`, title: hash1 }, { key: hash2, href: `#${hash2}`, title: hash2 }, ]} /> ); const { container, rerender } = render(<Demo current={hash1} />); expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash1); rerender(<Demo current={hash2} />); expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe(hash2); }); it('should render correctly when href is null', () => { expect(() => { render( <Anchor items={[{ key: 'test', href: null as unknown as string, title: 'test' }]} />, ); fireEvent.scroll(window || document); }).not.toThrow(); }); it('should repeat trigger when scrolling', () => { const getCurrentAnchor = jest.fn(); render( <Anchor getCurrentAnchor={getCurrentAnchor} items={[{ key: 'test', href: null as unknown as string, title: 'test' }]} />, ); for (let i = 0; i < 100; i += 1) { getCurrentAnchor.mockReset(); fireEvent.scroll(window || document); expect(getCurrentAnchor).toHaveBeenCalled(); } }); }); describe('horizontal anchor', () => { describe('scroll x', () => { it('targetOffset horizontal', async () => { const hash = getHashUrl(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); render(<h1 id={hash}>Hello</h1>, { container: root }); const { container, rerender } = render( <Anchor direction="horizontal" items={[ { key: hash, href: `#${hash}`, title: hash, }, ]} />, ); const setProps = (props: Record<string, any>) => rerender( <Anchor {...props} direction="horizontal" items={[ { key: hash, href: `#${hash}`, title: hash, }, ]} />, ); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollIntoView).toHaveBeenCalled(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); setProps({ offsetTop: 100 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); setProps({ targetOffset: 200 }); fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); }); }); it('test direction prop', () => { const { container } = render( <Anchor direction="horizontal" items={[ { key: '1', href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', href: '#anchor-demo-static', title: 'Static demo', }, { key: '3', href: '#api', title: 'API', }, ]} />, ); expect(container.querySelectorAll('.ant-anchor-ink').length).toBe(1); expect( container .querySelector('.ant-anchor-wrapper') ?.classList.contains('ant-anchor-wrapper-horizontal'), ).toBeTruthy(); }); it('nested children via items should be filtered out when direction is horizontal', () => { const { container } = render( <Anchor direction="horizontal" items={[ { key: '1', href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', href: '#anchor-demo-static', title: 'Static demo', }, { key: '3', href: '#api', title: 'API', children: [ { key: '4', href: '#anchor-props', title: 'Anchor Props', }, { key: '5', href: '#link-props', title: 'Link Props', }, ], }, ]} />, ); expect(container.querySelectorAll('.ant-anchor-link').length).toBe(3); }); it('nested children via jsx should be filtered out when direction is horizontal', () => { const { container } = render( <Anchor direction="horizontal"> <Link href="#anchor-demo-basic" title="Basic demo" /> <Link href="#anchor-demo-static" title="Static demo" /> <Link href="#api" title="API"> <Link href="#anchor-props" title="Anchor Props" /> <Link href="#link-props" title="Link Props" /> </Link> </Anchor>, ); expect(container.querySelectorAll('.ant-anchor-link').length).toBe(3); }); }); describe('deprecated/legacy jsx syntax', () => { it('renders jsx correctly', () => { const hash = getHashUrl(); const { container } = render( <Anchor> <Link href={`#${hash}`} title={hash} /> </Anchor>, ); expect(container.querySelector(`a[href="#${hash}"]`)).not.toBe(null); }); it('actives the target when clicking a link', async () => { const hash = getHashUrl(); const { container } = render( <Anchor prefixCls="ant-anchor"> <Link href={`http://www.example.com/#${hash}`} title={hash} /> </Anchor>, ); const link = container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!; fireEvent.click(link); await waitFakeTimer(); expect(link.classList).toContain('ant-anchor-link-title-active'); }); it('scrolls the page when clicking a link', async () => { const root = createDiv(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); render(<div id="/faq?locale=en#Q1">Q1</div>, { container: root }); const { container } = render( <Anchor> <Link href="/#/faq?locale=en#Q1" title="Q1" /> </Anchor>, ); const link = container.querySelector(`a[href="/#/faq?locale=en#Q1"]`)!; fireEvent.click(link); await waitFakeTimer(); expect(scrollToSpy).toHaveBeenCalled(); }); it('handleScroll should not be triggered when scrolling caused by clicking a link', async () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const root = createDiv(); const onChange = jest.fn(); render( <div> <div id={hash1}>Hello</div> <div id={hash2}>World</div> </div>, { container: root }, ); const { container } = render( <Anchor onChange={onChange}> <Link href={`#${hash1}`} title={hash1} /> <Link href={`#${hash2}`} title={hash2} /> </Anchor>, ); onChange.mockClear(); const link = container.querySelector(`a[href="#${hash2}"]`)!; // this will trigger 1 onChange fireEvent.click(link); // smooth scroll caused by clicking needs time to finish. // we scroll the window before it finish, the scroll listener should not be triggered, fireEvent.scroll(window); await waitFakeTimer(); // if the scroll listener is triggered, we will get 2 onChange, now we expect only 1. expect(onChange).toHaveBeenCalledTimes(1); }); it('should update DOM when children are unmounted', () => { const hash = getHashUrl(); const { container, rerender } = render( <Anchor> <Link href={`#${hash}`} title={hash} /> </Anchor>, ); expect(container.querySelectorAll('.ant-anchor-link-title')).toHaveLength(1); expect(container.querySelector('.ant-anchor-link-title')).toHaveAttribute('href', `#${hash}`); rerender(<Anchor />); expect(container.querySelector('.ant-anchor-link-title')).toBeFalsy(); }); it('should update DOM when link href is changed', async () => { const hash = getHashUrl(); function AnchorUpdate({ href }: { href: string }) { return ( <Anchor> <Link href={href} title={hash} /> </Anchor> ); } const { container, rerender } = render(<AnchorUpdate href={`#${hash}`} />); expect(container.querySelector(`a[href="#${hash}"]`)).toBeTruthy(); rerender(<AnchorUpdate href={`#${hash}_1`} />); expect(container.querySelector(`a[href="#${hash}_1"]`)).toBeTruthy(); }); it('handles invalid hash correctly', () => { const { container } = render( <Anchor> <Link href="nonexistent" title="title" /> </Anchor>, ); const link = container.querySelector(`a[href="nonexistent"]`)!; fireEvent.click(link); expect(container.querySelector(`.ant-anchor-link-title-active`)?.textContent).toBe('title'); }); }); describe('warning', () => { let errSpy: jest.SpyInstance; beforeEach(() => { resetWarned(); errSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); }); afterEach(() => { errSpy.mockRestore(); }); it('warning nested children when direction is horizontal ', () => { render( <Anchor direction="horizontal" items={[ { key: '1', href: '#anchor-demo-basic', title: 'Item Basic Demo', }, { key: '2', href: '#anchor-demo-static', title: 'Static demo', }, { key: '3', href: '#api', title: 'API', children: [ { key: '4', href: '#anchor-props', title: 'Anchor Props', }, ], }, ]} />, ); expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: Anchor] `Anchor items#children` is not supported when `Anchor` direction is horizontal.', ); }); it('deprecated jsx style', () => { render( <Anchor direction="horizontal"> <Link href="#anchor-demo-basic" title="Basic demo" /> <Link href="#anchor-demo-static" title="Static demo" /> </Anchor>, ); expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: Anchor] `Anchor children` is deprecated. Please use `items` instead.', ); }); it('deprecated jsx style for direction#vertical', () => { render( <Anchor> <Link href="#anchor-demo-basic" title="Basic demo" /> <Link href="#anchor-demo-static" title="Static demo" /> </Anchor>, ); expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: Anchor] `Anchor children` is deprecated. Please use `items` instead.', ); }); it('deprecated jsx style for direction#vertical 1: with nested children', () => { render( <Anchor direction="horizontal"> <Link href="#api" title="API"> <Link href="#anchor-props" title="Anchor Props" /> </Link> </Anchor>, ); expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: Anchor] `Anchor children` is deprecated. Please use `items` instead.', ); expect(errSpy).toHaveBeenCalledWith( 'Warning: [antd: Anchor.Link] `Anchor.Link children` is not supported when `Anchor` direction is horizontal', ); }); it('switch direction', async () => { const Foo: React.FC = () => { const [direction, setDirection] = useState<AnchorDirection>('vertical'); const toggle = () => { setDirection(direction === 'vertical' ? 'horizontal' : 'vertical'); }; return ( <div> <button onClick={toggle} type="button"> toggle </button> <Anchor direction={direction} items={[ { title: 'part-1', href: 'part-1', key: 'part-1', }, { title: 'part-2', href: 'part-2', key: 'part-2', }, ]} /> </div> ); }; const wrapper = await render(<Foo />); (await wrapper.findByText('part-1')).click(); await waitFakeTimer(); const ink = wrapper.container.querySelector<HTMLSpanElement>('.ant-anchor-ink')!; const toggleButton = wrapper.container.querySelector('button')!; fireEvent.click(toggleButton); act(() => jest.runAllTimers()); expect(!!ink.style.left).toBe(true); expect(!!ink.style.width).toBe(true); expect(ink.style.top).toBe(''); expect(ink.style.height).toBe(''); fireEvent.click(toggleButton); act(() => jest.runAllTimers()); expect(!!ink.style.top).toBe(true); expect(!!ink.style.height).toBe(true); expect(ink.style.left).toBe(''); expect(ink.style.width).toBe(''); }); }); });