diff --git a/components/anchor/Anchor.tsx b/components/anchor/Anchor.tsx index 6a612096a3..5c45f41a91 100644 --- a/components/anchor/Anchor.tsx +++ b/components/anchor/Anchor.tsx @@ -3,7 +3,6 @@ import classNames from 'classnames'; import memoizeOne from 'memoize-one'; import addEventListener from 'rc-util/lib/Dom/addEventListener'; import Affix from '../affix'; -import AnchorLink from './AnchorLink'; import { ConfigContext, ConfigConsumerProps } from '../config-provider'; import scrollTo from '../_util/scrollTo'; import getScroll from '../_util/getScroll'; @@ -62,6 +61,10 @@ export interface AnchorProps { onChange?: (currentActiveLink: string) => void; } +interface InternalAnchorProps extends AnchorProps { + anchorPrefixCls: string; +} + export interface AnchorState { activeLink: null | string; } @@ -84,9 +87,7 @@ export interface AntAnchor { ) => void; } -export default class Anchor extends React.Component { - static Link: typeof AnchorLink; - +class Anchor extends React.Component { static defaultProps = { affix: true, showInkInFixed: false, @@ -268,9 +269,9 @@ export default class Anchor extends React.Component((props, ref) => { + const { prefixCls: customizePrefixCls } = props; + const { getPrefixCls } = React.useContext(ConfigContext); + + const anchorPrefixCls = getPrefixCls('anchor', customizePrefixCls); + + const anchorProps: InternalAnchorProps = { + ...props, + + anchorPrefixCls, + }; + + return ; +}); + +export default AnchorFC; diff --git a/components/anchor/__tests__/Anchor.test.tsx b/components/anchor/__tests__/Anchor.test.tsx index 207b3b05de..1a87f8dd57 100644 --- a/components/anchor/__tests__/Anchor.test.tsx +++ b/components/anchor/__tests__/Anchor.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { mount } from 'enzyme'; import Anchor from '..'; +import type { InternalAnchorClass } from '../Anchor'; import { sleep, render } from '../../../tests/utils'; const { Link } = Anchor; @@ -47,43 +48,42 @@ describe('Anchor Render', () => { it('Anchor render perfectly', () => { const hash = getHashUrl(); - const wrapper = mount( + const wrapper = mount( , ); wrapper.find(`a[href="#${hash}"]`).simulate('click'); - - wrapper.find(Anchor).instance().handleScroll(); - expect(wrapper.find(Anchor).instance().state).not.toBe(null); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + anchorInstance.handleScroll(); + expect(anchorInstance.state).not.toBe(null); }); it('Anchor render perfectly for complete href - click', () => { const hash = getHashUrl(); - const wrapper = mount( + const wrapper = mount( , ); wrapper.find(`a[href="http://www.example.com/#${hash}"]`).simulate('click'); - expect(wrapper.find(Anchor).instance().state.activeLink).toBe( - `http://www.example.com/#${hash}`, - ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + expect(anchorInstance.state.activeLink).toBe(`http://www.example.com/#${hash}`); }); it('Anchor render perfectly for complete href - hash router', async () => { const root = createDiv(); const scrollToSpy = jest.spyOn(window, 'scrollTo'); mount(
Q1
, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( , ); - - wrapper.find(Anchor).instance().handleScrollTo('/#/faq?locale=en#Q1'); - expect(wrapper.find(Anchor).instance().state.activeLink).toBe('/#/faq?locale=en#Q1'); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + anchorInstance.handleScrollTo('/#/faq?locale=en#Q1'); + expect(anchorInstance.state.activeLink).toBe('/#/faq?locale=en#Q1'); expect(scrollToSpy).not.toHaveBeenCalled(); await sleep(1000); expect(scrollToSpy).toHaveBeenCalled(); @@ -93,15 +93,15 @@ describe('Anchor Render', () => { const hash = getHashUrl(); const root = createDiv(); mount(
Hello
, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( , ); - wrapper.find(Anchor).instance().handleScroll(); - expect(wrapper.find(Anchor).instance().state.activeLink).toBe( - `http://www.example.com/#${hash}`, - ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScroll(); + expect(anchorInstance.state.activeLink).toBe(`http://www.example.com/#${hash}`); }); it('Anchor render perfectly for complete href - scrollTo', async () => { @@ -109,13 +109,15 @@ describe('Anchor Render', () => { const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); mount(
Hello
, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( , ); - wrapper.find(Anchor).instance().handleScrollTo(`##${hash}`); - expect(wrapper.find(Anchor).instance().state.activeLink).toBe(`##${hash}`); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScrollTo(`##${hash}`); + expect(anchorInstance.state.activeLink).toBe(`##${hash}`); const calls = scrollToSpy.mock.calls.length; await sleep(1000); expect(scrollToSpy.mock.calls.length).toBeGreaterThan(calls); @@ -123,15 +125,14 @@ describe('Anchor Render', () => { it('should remove listener when unmount', async () => { const hash = getHashUrl(); - const wrapper = mount( + const wrapper = mount( , ); - const removeListenerSpy = jest.spyOn( - (wrapper.find(Anchor).instance() as any).scrollEvent, - 'remove', - ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove'); wrapper.unmount(); expect(removeListenerSpy).toHaveBeenCalled(); }); @@ -153,23 +154,20 @@ describe('Anchor Render', () => { it('should update links when link href update', async () => { const hash = getHashUrl(); - let anchorInstance: Anchor | null = null; function AnchorUpdate({ href }: { href: string }) { return ( - { - anchorInstance = c; - }} - > + ); } const wrapper = mount(); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; if (anchorInstance == null) { throw new Error('anchorInstance should not be null'); } + expect((anchorInstance as any).links).toEqual([`#${hash}`]); wrapper.setProps({ href: `#${hash}_1` }); expect((anchorInstance as any).links).toEqual([`#${hash}_1`]); @@ -190,7 +188,7 @@ describe('Anchor Render', () => { const href = `#${hash}`; const title = hash; - const wrapper = mount( + const wrapper = mount( , @@ -198,7 +196,9 @@ describe('Anchor Render', () => { wrapper.find(`a[href="${href}"]`).simulate('click'); - wrapper.find(Anchor).instance().handleScroll(); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScroll(); expect(event).not.toBe(undefined); expect(link).toEqual({ href, title }); }); @@ -210,15 +210,14 @@ describe('Anchor Render', () => { const getContainerA = createGetContainer(hash); const getContainerB = createGetContainer(hash); - const wrapper = mount( + const wrapper = mount( , ); - const removeListenerSpy = jest.spyOn( - (wrapper.find(Anchor).instance() as any).scrollEvent, - 'remove', - ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove'); await sleep(1000); wrapper.setProps({ getContainer: getContainerB }); expect(removeListenerSpy).not.toHaveBeenCalled(); @@ -237,16 +236,15 @@ describe('Anchor Render', () => { ); const getContainerA = createGetContainer(hash1); const getContainerB = createGetContainer(hash2); - const wrapper = mount( + const wrapper = mount( , ); - const removeListenerSpy = jest.spyOn( - (wrapper.find(Anchor).instance() as any).scrollEvent, - 'remove', - ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove'); expect(removeListenerSpy).not.toHaveBeenCalled(); await sleep(1000); wrapper.setProps({ getContainer: getContainerB }); @@ -264,8 +262,10 @@ describe('Anchor Render', () => { , ); wrapper.find(`a[href="#${hash}"]`).simulate('click'); - (wrapper.find(Anchor).instance() as any).handleScroll(); - expect(wrapper.find(Anchor).instance().state).not.toBe(null); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + (anchorInstance as any).handleScroll(); + expect(anchorInstance.state).not.toBe(null); }); it('Same function returns different DOM', async () => { @@ -294,10 +294,9 @@ describe('Anchor Render', () => { , ); - const removeListenerSpy = jest.spyOn( - (wrapper.find(Anchor).instance() as any).scrollEvent, - 'remove', - ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove'); expect(removeListenerSpy).not.toHaveBeenCalled(); await sleep(1000); holdContainer.container = document.getElementById(hash2); @@ -325,24 +324,26 @@ describe('Anchor Render', () => { const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); mount(

Hello

, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( , ); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); dateNowMock = dataNowMockFn(); wrapper.setProps({ offsetTop: 100 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); dateNowMock = dataNowMockFn(); wrapper.setProps({ targetOffset: 200 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); @@ -370,24 +371,26 @@ describe('Anchor Render', () => { const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); mount(

Hello

, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( , ); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); dateNowMock = dataNowMockFn(); wrapper.setProps({ offsetTop: 100 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); dateNowMock = dataNowMockFn(); wrapper.setProps({ targetOffset: 200 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); @@ -398,20 +401,22 @@ describe('Anchor Render', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const onChange = jest.fn(); - const wrapper = mount( + const wrapper = mount( , ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + expect(onChange).toHaveBeenCalledTimes(1); - wrapper.find(Anchor).instance().handleScrollTo(hash2); + anchorInstance.handleScrollTo(hash2); expect(onChange).toHaveBeenCalledTimes(2); expect(onChange).toHaveBeenCalledWith(hash2); }); it('invalid hash', async () => { - const wrapper = mount( + const wrapper = mount( , @@ -419,8 +424,10 @@ describe('Anchor Render', () => { wrapper.find(`a[href="notexsited"]`).simulate('click'); - wrapper.find(Anchor).instance().handleScrollTo('notexsited'); - expect(wrapper.find(Anchor).instance().state).not.toBe(null); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScrollTo('notexsited'); + expect(anchorInstance.state).not.toBe(null); }); it('test edge case when getBoundingClientRect return zero size', async () => { @@ -448,24 +455,26 @@ describe('Anchor Render', () => { const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); mount(

Hello

, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( , ); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000); dateNowMock = dataNowMockFn(); wrapper.setProps({ offsetTop: 100 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900); dateNowMock = dataNowMockFn(); wrapper.setProps({ targetOffset: 200 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); @@ -497,24 +506,26 @@ describe('Anchor Render', () => { const scrollToSpy = jest.spyOn(window, 'scrollTo'); const root = createDiv(); mount(

Hello

, { attachTo: root }); - const wrapper = mount( + const wrapper = mount( document.body}> , ); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); dateNowMock = dataNowMockFn(); wrapper.setProps({ offsetTop: 100 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); dateNowMock = dataNowMockFn(); wrapper.setProps({ targetOffset: 200 }); - wrapper.find(Anchor).instance().handleScrollTo(`#${hash}`); + anchorInstance.handleScrollTo(`#${hash}`); await sleep(30); expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800); @@ -526,13 +537,15 @@ describe('Anchor Render', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const getCurrentAnchor = () => `#${hash2}`; - const wrapper = mount( + const wrapper = mount( , ); - expect(wrapper.find(Anchor).instance().state.activeLink).toBe(`#${hash2}`); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + + expect(anchorInstance.state.activeLink).toBe(`#${hash2}`); }); // https://github.com/ant-design/ant-design/issues/30584 @@ -540,14 +553,16 @@ describe('Anchor Render', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const onChange = jest.fn(); - const wrapper = mount( + const wrapper = mount( hash1}> , ); + const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass; + expect(onChange).toHaveBeenCalledTimes(1); - wrapper.find(Anchor).instance().handleScrollTo(hash2); + anchorInstance.handleScrollTo(hash2); expect(onChange).toHaveBeenCalledTimes(2); expect(onChange).toHaveBeenCalledWith(hash2); }); @@ -557,7 +572,7 @@ describe('Anchor Render', () => { const hash1 = getHashUrl(); const hash2 = getHashUrl(); const getCurrentAnchor = jest.fn(); - const wrapper = mount( + const wrapper = mount( diff --git a/components/anchor/index.tsx b/components/anchor/index.tsx index 7a0e2f8649..c260028a5e 100644 --- a/components/anchor/index.tsx +++ b/components/anchor/index.tsx @@ -1,8 +1,16 @@ -import Anchor from './Anchor'; +import InternalAnchor from './Anchor'; import AnchorLink from './AnchorLink'; export { AnchorProps } from './Anchor'; export { AnchorLinkProps } from './AnchorLink'; +type InternalAnchorType = typeof InternalAnchor; + +interface AnchorInterface extends InternalAnchorType { + Link: typeof AnchorLink; +} + +const Anchor = InternalAnchor as AnchorInterface; + Anchor.Link = AnchorLink; export default Anchor;