chore: merge master

This commit is contained in:
zombiej 2022-05-06 12:03:15 +08:00
commit 7460c95b65
16 changed files with 525 additions and 295 deletions

View File

@ -1,8 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import Anchor from '..';
import type { InternalAnchorClass } from '../Anchor';
import { sleep, render } from '../../../tests/utils';
import { sleep, render, fireEvent } from '../../../tests/utils';
const { Link } = Anchor;
@ -48,42 +47,54 @@ describe('Anchor Render', () => {
it('Anchor render perfectly', () => {
const hash = getHashUrl();
const wrapper = mount(
<Anchor>
let anchorInstance: InternalAnchorClass;
const { container } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
wrapper.find(`a[href="#${hash}"]`).simulate('click');
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScroll();
expect(anchorInstance.state).not.toBe(null);
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
anchorInstance!.handleScroll();
expect(anchorInstance!.state).not.toBe(null);
});
it('Anchor render perfectly for complete href - click', () => {
const hash = getHashUrl();
const wrapper = mount(
<Anchor>
let anchorInstance: InternalAnchorClass;
const { container } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`http://www.example.com/#${hash}`} title={hash} />
</Anchor>,
);
wrapper.find(`a[href="http://www.example.com/#${hash}"]`).simulate('click');
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
expect(anchorInstance.state.activeLink).toBe(`http://www.example.com/#${hash}`);
fireEvent.click(container.querySelector(`a[href="http://www.example.com/#${hash}"]`)!);
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(<div id="/faq?locale=en#Q1">Q1</div>, { attachTo: root });
const wrapper = mount(
<Anchor>
render(<div id="/faq?locale=en#Q1">Q1</div>, { container: root });
let anchorInstance: InternalAnchorClass;
render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href="/#/faq?locale=en#Q1" title="Q1" />
</Anchor>,
);
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');
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();
@ -92,32 +103,39 @@ describe('Anchor Render', () => {
it('Anchor render perfectly for complete href - scroll', () => {
const hash = getHashUrl();
const root = createDiv();
mount(<div id={hash}>Hello</div>, { attachTo: root });
const wrapper = mount(
<Anchor>
render(<div id={hash}>Hello</div>, { container: root });
let anchorInstance: InternalAnchorClass;
render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`http://www.example.com/#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScroll();
expect(anchorInstance.state.activeLink).toBe(`http://www.example.com/#${hash}`);
anchorInstance!.handleScroll();
expect(anchorInstance!.state!.activeLink).toBe(`http://www.example.com/#${hash}`);
});
it('Anchor render perfectly for complete href - scrollTo', async () => {
const hash = getHashUrl();
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<div id={`#${hash}`}>Hello</div>, { attachTo: root });
const wrapper = mount(
<Anchor>
render(<div id={`#${hash}`}>Hello</div>, { container: root });
let anchorInstance: InternalAnchorClass;
render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`##${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScrollTo(`##${hash}`);
expect(anchorInstance.state.activeLink).toBe(`##${hash}`);
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);
@ -125,15 +143,19 @@ describe('Anchor Render', () => {
it('should remove listener when unmount', async () => {
const hash = getHashUrl();
const wrapper = mount(
<Anchor>
let anchorInstance: InternalAnchorClass;
const { unmount } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove');
wrapper.unmount();
const removeListenerSpy = jest.spyOn((anchorInstance! as any).scrollEvent, 'remove');
unmount();
expect(removeListenerSpy).toHaveBeenCalled();
});
@ -154,23 +176,27 @@ describe('Anchor Render', () => {
it('should update links when link href update', async () => {
const hash = getHashUrl();
let anchorInstance: InternalAnchorClass;
function AnchorUpdate({ href }: { href: string }) {
return (
<Anchor>
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={href} title={hash} />
</Anchor>
);
}
const wrapper = mount(<AnchorUpdate href={`#${hash}`} />);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
const { rerender } = render(<AnchorUpdate href={`#${hash}`} />);
if (anchorInstance == null) {
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`]);
expect((anchorInstance as any)!.links).toEqual([`#${hash}`]);
rerender(<AnchorUpdate href={`#${hash}_1`} />);
expect((anchorInstance as any)!.links).toEqual([`#${hash}_1`]);
});
it('Anchor onClick event', () => {
@ -187,18 +213,20 @@ describe('Anchor Render', () => {
const href = `#${hash}`;
const title = hash;
const wrapper = mount(
<Anchor onClick={handleClick}>
let anchorInstance: InternalAnchorClass;
const { container } = render(
<Anchor
onClick={handleClick}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={href} title={title} />
</Anchor>,
);
wrapper.find(`a[href="${href}"]`).simulate('click');
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScroll();
fireEvent.click(container.querySelector(`a[href="${href}"]`)!);
anchorInstance!.handleScroll();
expect(event).not.toBe(undefined);
expect(link).toEqual({ href, title });
});
@ -206,20 +234,28 @@ describe('Anchor Render', () => {
it('Different function returns the same DOM', async () => {
const hash = getHashUrl();
const root = createDiv();
mount(<div id={hash}>Hello</div>, { attachTo: root });
render(<div id={hash}>Hello</div>, { container: root });
const getContainerA = createGetContainer(hash);
const getContainerB = createGetContainer(hash);
const wrapper = mount(
<Anchor getContainer={getContainerA}>
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
getContainer={getContainerA}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove');
const removeListenerSpy = jest.spyOn((anchorInstance! as any).scrollEvent, 'remove');
await sleep(1000);
wrapper.setProps({ getContainer: getContainerB });
rerender(
<Anchor getContainer={getContainerB}>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
expect(removeListenerSpy).not.toHaveBeenCalled();
});
@ -227,57 +263,73 @@ describe('Anchor Render', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const root = createDiv();
mount(
render(
<div>
<div id={hash1}>Hello</div>
<div id={hash2}>World</div>
</div>,
{ attachTo: root },
{ container: root },
);
const getContainerA = createGetContainer(hash1);
const getContainerB = createGetContainer(hash2);
const wrapper = mount(
<Anchor getContainer={getContainerA}>
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
getContainer={getContainerA}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
const removeListenerSpy = jest.spyOn((anchorInstance as any).scrollEvent, 'remove');
const removeListenerSpy = jest.spyOn((anchorInstance! as any).scrollEvent, 'remove');
expect(removeListenerSpy).not.toHaveBeenCalled();
await sleep(1000);
wrapper.setProps({ getContainer: getContainerB });
rerender(
<Anchor getContainer={getContainerB}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
expect(removeListenerSpy).toHaveBeenCalled();
});
it('Same function returns the same DOM', () => {
const hash = getHashUrl();
const root = createDiv();
mount(<div id={hash}>Hello</div>, { attachTo: root });
render(<div id={hash}>Hello</div>, { container: root });
const getContainer = createGetContainer(hash);
const wrapper = mount(
<Anchor getContainer={getContainer}>
let anchorInstance: InternalAnchorClass;
const { container } = render(
<Anchor
getContainer={getContainer}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
wrapper.find(`a[href="#${hash}"]`).simulate('click');
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
(anchorInstance as any).handleScroll();
expect(anchorInstance.state).not.toBe(null);
fireEvent.click(container.querySelector(`a[href="#${hash}"]`)!);
anchorInstance!.handleScroll();
expect(anchorInstance!.state).not.toBe(null);
});
it('Same function returns different DOM', async () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const root = createDiv();
mount(
render(
<div>
<div id={hash1}>Hello</div>
<div id={hash2}>World</div>
</div>,
{ attachTo: root },
{ container: root },
);
const holdContainer = {
container: document.getElementById(hash1),
@ -288,19 +340,28 @@ describe('Anchor Render', () => {
}
return holdContainer.container;
};
const wrapper = mount(
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
getContainer={getContainer}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
const removeListenerSpy = jest.spyOn((anchorInstance! as any).scrollEvent, 'remove');
expect(removeListenerSpy).not.toHaveBeenCalled();
await sleep(1000);
holdContainer.container = document.getElementById(hash2);
rerender(
<Anchor getContainer={getContainer}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
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);
wrapper.setProps({ 'data-only-trigger-re-render': true });
expect(removeListenerSpy).toHaveBeenCalled();
});
@ -323,27 +384,45 @@ describe('Anchor Render', () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
const wrapper = mount(
<Anchor>
render(<h1 id={hash}>Hello</h1>, { container: root });
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScrollTo(`#${hash}`);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
{...props}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
dateNowMock = dataNowMockFn();
wrapper.setProps({ offsetTop: 100 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ offsetTop: 100 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
dateNowMock = dataNowMockFn();
wrapper.setProps({ targetOffset: 200 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ targetOffset: 200 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
@ -370,27 +449,43 @@ describe('Anchor Render', () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
const wrapper = mount(
<Anchor>
render(<h1 id={hash}>Hello</h1>, { container: root });
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScrollTo(`#${hash}`);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
{...props}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
dateNowMock = dataNowMockFn();
wrapper.setProps({ offsetTop: 100 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ offsetTop: 100 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
dateNowMock = dataNowMockFn();
wrapper.setProps({ targetOffset: 200 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ targetOffset: 200 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
@ -401,33 +496,44 @@ describe('Anchor Render', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const onChange = jest.fn();
const wrapper = mount(
<Anchor onChange={onChange}>
let anchorInstance: InternalAnchorClass;
render(
<Anchor
onChange={onChange}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
// @ts-ignore
{ legacyRoot: true },
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
expect(onChange).toHaveBeenCalledTimes(1);
anchorInstance.handleScrollTo(hash2);
anchorInstance!.handleScrollTo(hash2);
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenCalledWith(hash2);
});
it('invalid hash', async () => {
const wrapper = mount(
<Anchor>
let anchorInstance: InternalAnchorClass;
const { container } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href="notexsited" title="title" />
</Anchor>,
);
wrapper.find(`a[href="notexsited"]`).simulate('click');
fireEvent.click(container.querySelector(`a[href="notexsited"]`)!);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScrollTo('notexsited');
expect(anchorInstance.state).not.toBe(null);
anchorInstance!.handleScrollTo('notexsited');
expect(anchorInstance!.state).not.toBe(null);
});
it('test edge case when getBoundingClientRect return zero size', async () => {
@ -454,27 +560,42 @@ describe('Anchor Render', () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
const wrapper = mount(
<Anchor>
render(<h1 id={hash}>Hello</h1>, { container: root });
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScrollTo(`#${hash}`);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
{...props}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 1000);
dateNowMock = dataNowMockFn();
wrapper.setProps({ offsetTop: 100 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ offsetTop: 100 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 900);
dateNowMock = dataNowMockFn();
wrapper.setProps({ targetOffset: 200 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ targetOffset: 200 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
@ -505,27 +626,44 @@ describe('Anchor Render', () => {
const scrollToSpy = jest.spyOn(window, 'scrollTo');
const root = createDiv();
mount(<h1 id={hash}>Hello</h1>, { attachTo: root });
const wrapper = mount(
<Anchor getContainer={() => document.body}>
render(<h1 id={hash}>Hello</h1>, { container: root });
let anchorInstance: InternalAnchorClass;
const { rerender } = render(
<Anchor
getContainer={() => document.body}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
anchorInstance.handleScrollTo(`#${hash}`);
const setProps = (props: Record<string, any>) =>
rerender(
<Anchor
getContainer={() => document.body}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
{...props}
>
<Link href={`#${hash}`} title={hash} />
</Anchor>,
);
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
dateNowMock = dataNowMockFn();
wrapper.setProps({ offsetTop: 100 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ offsetTop: 100 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
dateNowMock = dataNowMockFn();
wrapper.setProps({ targetOffset: 200 });
anchorInstance.handleScrollTo(`#${hash}`);
setProps({ targetOffset: 200 });
anchorInstance!.handleScrollTo(`#${hash}`);
await sleep(30);
expect(scrollToSpy).toHaveBeenLastCalledWith(0, 800);
@ -537,15 +675,20 @@ describe('Anchor Render', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const getCurrentAnchor = () => `#${hash2}`;
const wrapper = mount(
<Anchor getCurrentAnchor={getCurrentAnchor}>
let anchorInstance: InternalAnchorClass;
render(
<Anchor
getCurrentAnchor={getCurrentAnchor}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
expect(anchorInstance.state.activeLink).toBe(`#${hash2}`);
expect(anchorInstance!.state.activeLink).toBe(`#${hash2}`);
});
// https://github.com/ant-design/ant-design/issues/30584
@ -553,16 +696,25 @@ describe('Anchor Render', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const onChange = jest.fn();
const wrapper = mount(
<Anchor onChange={onChange} getCurrentAnchor={() => hash1}>
let anchorInstance: InternalAnchorClass;
render(
<Anchor
onChange={onChange}
getCurrentAnchor={() => hash1}
ref={node => {
anchorInstance = node as InternalAnchorClass;
}}
>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
// https://github.com/testing-library/react-testing-library/releases/tag/v13.0.0
// @ts-ignore
{ legacyRoot: true },
);
const anchorInstance = wrapper.find('Anchor').last().instance() as any as InternalAnchorClass;
expect(onChange).toHaveBeenCalledTimes(1);
anchorInstance.handleScrollTo(hash2);
anchorInstance!.handleScrollTo(hash2);
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenCalledWith(hash2);
});
@ -572,16 +724,16 @@ describe('Anchor Render', () => {
const hash1 = getHashUrl();
const hash2 = getHashUrl();
const getCurrentAnchor = jest.fn();
const wrapper = mount(
const { container } = render(
<Anchor getCurrentAnchor={getCurrentAnchor}>
<Link href={`#${hash1}`} title={hash1} />
<Link href={`#${hash2}`} title={hash2} />
</Anchor>,
);
wrapper.find(`a[href="#${hash1}"]`).simulate('click');
fireEvent.click(container.querySelector(`a[href="#${hash1}"]`)!);
expect(getCurrentAnchor).toHaveBeenCalledWith(`#${hash1}`);
wrapper.find(`a[href="#${hash2}"]`).simulate('click');
fireEvent.click(container.querySelector(`a[href="#${hash2}"]`)!);
expect(getCurrentAnchor).toHaveBeenCalledWith(`#${hash2}`);
});
});

View File

@ -1,7 +1,7 @@
import React, { memo, useState, useRef, useContext } from 'react';
import { mount } from 'enzyme';
import Anchor from '../Anchor';
import AnchorContext from '../context';
import { getNodeText, render, fireEvent } from '../../../tests/utils';
// we use'memo' here in order to only render inner component while context changed.
const CacheInner = memo(() => {
@ -38,14 +38,16 @@ const CacheOuter = () => {
};
it("Rendering on Anchor without changed AnchorContext won't trigger rendering on child component.", () => {
const wrapper = mount(<CacheOuter />);
const childCount = wrapper.find('#child_count').text();
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('2');
const { container } = render(<CacheOuter />);
const childCount = getNodeText(container.querySelector('#child_count')!);
fireEvent.click(container.querySelector('#parent_btn')!);
expect(getNodeText(container.querySelector('#parent_count')!)).toBe('2');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe(childCount);
wrapper.find('#parent_btn').at(0).simulate('click');
expect(wrapper.find('#parent_count').text()).toBe('3');
expect(getNodeText(container.querySelector('#child_count')!)).toBe(childCount);
fireEvent.click(container.querySelector('#parent_btn')!);
expect(getNodeText(container.querySelector('#parent_count')!)).toBe('3');
// child component won't rerender
expect(wrapper.find('#child_count').text()).toBe(childCount);
expect(getNodeText(container.querySelector('#child_count')!)).toBe(childCount);
});

View File

@ -31,7 +31,17 @@ export interface CarouselRef {
}
const Carousel = React.forwardRef<CarouselRef, CarouselProps>(
({ dots = true, arrows = false, draggable = false, dotPosition = 'bottom', ...props }, ref) => {
(
{
dots = true,
arrows = false,
draggable = false,
dotPosition = 'bottom',
vertical = dotPosition === 'left' || dotPosition === 'right',
...props
},
ref,
) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const slickRef = React.useRef<any>();
@ -61,6 +71,7 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>(
}, [props.children]);
const newProps = {
vertical,
...props,
};
@ -70,7 +81,6 @@ const Carousel = React.forwardRef<CarouselRef, CarouselProps>(
const prefixCls = getPrefixCls('carousel', newProps.prefixCls);
const dotsClass = 'slick-dots';
newProps.vertical = dotPosition === 'left' || dotPosition === 'right';
const enableDots = !!dots;
const dsClass = classNames(

View File

@ -1,5 +1,5 @@
import React from 'react';
import { mount } from 'enzyme';
import { render, fireEvent } from '../../../tests/utils';
import Checkbox from '..';
import focusTest from '../../../tests/shared/focusTest';
import { resetWarned } from '../../_util/devWarning';
@ -15,12 +15,14 @@ describe('Checkbox', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = mount(<Checkbox onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />);
const { container } = render(
<Checkbox onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />,
);
wrapper.find('label').simulate('mouseenter');
fireEvent.mouseEnter(container.querySelector('label'));
expect(onMouseEnter).toHaveBeenCalled();
wrapper.find('label').simulate('mouseleave');
fireEvent.mouseLeave(container.querySelector('label'));
expect(onMouseLeave).toHaveBeenCalled();
});
@ -28,7 +30,7 @@ describe('Checkbox', () => {
resetWarned();
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Checkbox value />);
render(<Checkbox value />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Checkbox] `value` is not a valid prop, do you mean `checked`?',
);

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import { mount } from 'enzyme';
import Collapse from '../../collapse';
import Table from '../../table';
import Checkbox from '../index';
@ -14,16 +13,16 @@ describe('CheckboxGroup', () => {
it('should work basically', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Checkbox.Group options={['Apple', 'Pear', 'Orange']} onChange={onChange} />,
);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChange).toHaveBeenCalledWith(['Apple']);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChange).toHaveBeenCalledWith(['Apple', 'Pear']);
wrapper.find('.ant-checkbox-input').at(2).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[2]);
expect(onChange).toHaveBeenCalledWith(['Apple', 'Pear', 'Orange']);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChange).toHaveBeenCalledWith(['Apple', 'Orange']);
});
@ -35,12 +34,12 @@ describe('CheckboxGroup', () => {
{ label: 'Pear', value: 'Pear' },
];
const groupWrapper = mount(
const { container } = render(
<Checkbox.Group options={options} onChange={onChangeGroup} disabled />,
);
groupWrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChangeGroup).not.toHaveBeenCalled();
groupWrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChangeGroup).not.toHaveBeenCalled();
});
@ -52,17 +51,17 @@ describe('CheckboxGroup', () => {
{ label: 'Orange', value: 'Orange', disabled: true },
];
const groupWrapper = mount(<Checkbox.Group options={options} onChange={onChangeGroup} />);
groupWrapper.find('.ant-checkbox-input').at(0).simulate('change');
const { container } = render(<Checkbox.Group options={options} onChange={onChangeGroup} />);
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChangeGroup).toHaveBeenCalledWith(['Apple']);
groupWrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChangeGroup).toHaveBeenCalledWith(['Apple']);
});
it('all children should have a name property', () => {
const wrapper = mount(<Checkbox.Group name="checkboxgroup" options={['Yes', 'No']} />);
wrapper.find('input[type="checkbox"]').forEach(el => {
expect(el.props().name).toEqual('checkboxgroup');
const { container } = render(<Checkbox.Group name="checkboxgroup" options={['Yes', 'No']} />);
[...container.querySelectorAll('input[type="checkbox"]')].forEach(el => {
expect(el.getAttribute('name')).toEqual('checkboxgroup');
});
});
@ -72,9 +71,9 @@ describe('CheckboxGroup', () => {
{ label: 'Orange', value: 'Orange', style: { fontSize: 12 } },
];
const wrapper = mount(<Checkbox.Group prefixCls="my-checkbox" options={options} />);
const { container } = render(<Checkbox.Group prefixCls="my-checkbox" options={options} />);
expect(wrapper.render()).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
it('should be controlled by value', () => {
@ -82,23 +81,22 @@ describe('CheckboxGroup', () => {
{ label: 'Apple', value: 'Apple' },
{ label: 'Orange', value: 'Orange' },
];
const wrapper = mount(<Checkbox.Group options={options} />);
expect(wrapper.find('.ant-checkbox-checked').length).toBe(0);
wrapper.setProps({ value: ['Apple'] });
wrapper.update();
expect(wrapper.find('.ant-checkbox-checked').length).toBe(1);
const renderCheckbox = props => <Checkbox.Group {...props} />;
const { container, rerender } = render(renderCheckbox({ options }));
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(0);
rerender(renderCheckbox({ options, value: 'Apple' }));
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(1);
});
// https://github.com/ant-design/ant-design/issues/12642
it('should trigger onChange in sub Checkbox', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Checkbox.Group>
<Checkbox value="my" onChange={onChange} />
</Checkbox.Group>,
);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChange).toHaveBeenCalled();
expect(onChange.mock.calls[0][0].target.value).toEqual('my');
});
@ -143,7 +141,7 @@ describe('CheckboxGroup', () => {
// https://github.com/ant-design/ant-design/issues/17297
it('onChange should keep the order of the original values', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Checkbox.Group onChange={onChange}>
<Checkbox key={1} value={1} />
<Checkbox key={2} value={2} />
@ -151,20 +149,19 @@ describe('CheckboxGroup', () => {
<Checkbox key={4} value={4} />
</Checkbox.Group>,
);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChange).toHaveBeenCalledWith([1]);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChange).toHaveBeenCalledWith([1, 2]);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChange).toHaveBeenCalledWith([2]);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[0]);
expect(onChange).toHaveBeenCalledWith([1, 2]);
});
// https://github.com/ant-design/ant-design/issues/21134
it('should work when checkbox is wrapped by other components', () => {
const wrapper = mount(
const { container } = render(
<Checkbox.Group>
<Collapse bordered={false}>
<Collapse.Panel header="test panel">
@ -175,28 +172,31 @@ describe('CheckboxGroup', () => {
</Collapse>
</Checkbox.Group>,
);
wrapper.find('.ant-collapse-item').at(0).find('.ant-collapse-header').simulate('click');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(wrapper.find('.ant-checkbox-checked').length).toBe(1);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(wrapper.find('.ant-checkbox-checked').length).toBe(0);
fireEvent.click(
container.querySelector('.ant-collapse-item').querySelector('.ant-collapse-header'),
);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(1);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(0);
});
it('skipGroup', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Checkbox.Group onChange={onChange}>
<Checkbox value={1} />
<Checkbox value={2} skipGroup />
</Checkbox.Group>,
);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChange).not.toHaveBeenCalled();
});
it('Table rowSelection', () => {
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Checkbox.Group onChange={onChange}>
<Table
dataSource={[{ key: 1, value: '1' }]}
@ -205,27 +205,32 @@ describe('CheckboxGroup', () => {
/>
</Checkbox.Group>,
);
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
fireEvent.click(container.querySelectorAll('.ant-checkbox-input')[1]);
expect(onChange).not.toHaveBeenCalled();
});
it('should get div ref', () => {
mount(
const refCalls = [];
render(
<Checkbox.Group
options={['Apple', 'Pear', 'Orange']}
ref={node => {
expect(node.nodeName).toBe('DIV');
refCalls.push(node);
}}
/>,
);
const [mountCall] = refCalls;
expect(mountCall.nodeName).toBe('DIV');
});
it('should support number option', () => {
const onChange = jest.fn();
const wrapper = mount(<Checkbox.Group options={[1, 'Pear', 'Orange']} onChange={onChange} />);
const { container } = render(
<Checkbox.Group options={[1, 'Pear', 'Orange']} onChange={onChange} />,
);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
fireEvent.click(container.querySelector('.ant-checkbox-input'));
expect(onChange).toHaveBeenCalledWith([1]);
});
@ -251,17 +256,13 @@ describe('CheckboxGroup', () => {
);
};
const wrapper = mount(<Demo />);
wrapper.find('.ant-checkbox-input').first().simulate('change');
const { container } = render(<Demo />);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
expect(onChange).toHaveBeenCalledWith([]);
wrapper.find('.ant-checkbox-input').first().simulate('change');
fireEvent.click(container.querySelector('.ant-checkbox-input'));
expect(onChange).toHaveBeenCalledWith(['length1']);
wrapper
.find('.ant-input')
.first()
.simulate('change', { target: { value: '' } });
wrapper.find('.ant-checkbox-input').first().simulate('change');
fireEvent.change(container.querySelector('.ant-input'), { target: { value: '' } });
fireEvent.click(container.querySelector('.ant-checkbox-input'));
expect(onChange).toHaveBeenCalledWith(['A']);
});
});

View File

@ -1,5 +1,6 @@
import React, { useState } from 'react';
import { mount } from 'enzyme';
import { createPortal } from 'react-dom';
import { render, fireEvent } from '../../../tests/utils';
// eslint-disable-next-line import/no-unresolved
import Form from '../../form';
@ -75,6 +76,40 @@ describe('Input', () => {
});
});
describe('click focus', () => {
it('click outside should also get focus', () => {
const { container } = render(<Input suffix={<span className="test-suffix" />} />);
const onFocus = jest.spyOn(container.querySelector('input')!, 'focus');
fireEvent.mouseDown(container.querySelector('.test-suffix')!);
fireEvent.mouseUp(container.querySelector('.test-suffix')!);
expect(onFocus).toHaveBeenCalled();
});
it('not get focus if out of component', () => {
const holder = document.createElement('span');
document.body.appendChild(holder);
const Popup = () => createPortal(<span className="popup" />, holder);
const { container } = render(
<Input
suffix={
<span className="test-suffix">
<Popup />
</span>
}
/>,
);
const onFocus = jest.spyOn(container.querySelector('input')!, 'focus');
fireEvent.mouseDown(document.querySelector('.popup')!);
fireEvent.mouseUp(document.querySelector('.popup')!);
expect(onFocus).not.toHaveBeenCalled();
document.body.removeChild(holder);
});
});
it('set mouse cursor position', () => {
const defaultValue = '11111';
const valLength = defaultValue.length;

View File

@ -404,27 +404,6 @@ describe('TextArea allowClear', () => {
expect(wrapper.find('input').props().value).toEqual('Light');
});
describe('click focus', () => {
it('click outside should also get focus', () => {
const wrapper = mount(<Input suffix={<span className="test-suffix" />} />);
const onFocus = jest.spyOn(wrapper.find('input').instance(), 'focus');
wrapper.find('.test-suffix').simulate('mouseUp');
expect(onFocus).toHaveBeenCalled();
});
it('not get focus if out of component', () => {
const wrapper = mount(<Input suffix={<span className="test-suffix" />} />);
const onFocus = jest.spyOn(wrapper.find('input').instance(), 'focus');
const ele = document.createElement('span');
document.body.appendChild(ele);
wrapper.find('.test-suffix').simulate('mouseUp', {
target: ele,
});
expect(onFocus).not.toHaveBeenCalled();
document.body.removeChild(ele);
});
});
it('scroll to bottom when autoSize', async () => {
const wrapper = mount(<Input.TextArea autoSize />, { attachTo: document.body });
wrapper.find('textarea').simulate('focus');

View File

@ -206,6 +206,7 @@ export interface ArgsProps {
duration?: number | null;
icon?: React.ReactNode;
placement?: NotificationPlacement;
maxCount?: number;
style?: React.CSSProperties;
prefixCls?: string;
className?: string;

View File

@ -12,7 +12,7 @@
padding: @segmented-container-padding;
color: @segmented-label-color;
background-color: @segmented-bg;
border-radius: 2px;
border-radius: @border-radius-base;
transition: all 0.3s @ease-in-out;
&-group {
@ -67,7 +67,7 @@
// syntactic sugar to add `icon` for Segmented Item
&-icon + * {
margin-left: 6px;
margin-left: @margin-sm / 2;
}
&-input {

View File

@ -29,7 +29,7 @@ exports[`Space should render correct with children 1`] = `
`;
exports[`Space should render width ConfigProvider 1`] = `
Array [
HTMLCollection [
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
@ -91,7 +91,7 @@ Array [
`;
exports[`Space should render width rtl 1`] = `
Array [
HTMLCollection [
<div
class="ant-space ant-space-horizontal ant-space-rtl ant-space-align-center"
>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { mount } from 'enzyme';
import { render } from '../../../tests/utils';
import Space from '..';
// eslint-disable-next-line no-unused-vars
import * as styleChecker from '../../_util/styleChecker';
@ -12,13 +12,14 @@ jest.mock('../../_util/styleChecker', () => ({
describe('flex gap', () => {
it('should render width empty children', () => {
const wrapper = mount(
const { container } = render(
<Space>
<span />
<span />
</Space>,
);
expect(wrapper.getDOMNode()[1].style['column-gap']).toBe('8px');
expect(wrapper.getDOMNode()[1].style['row-gap']).toBe('8px');
expect(container.querySelector('div.ant-space').style['column-gap']).toBe('8px');
expect(container.querySelector('div.ant-space').style['row-gap']).toBe('8px');
});
});

View File

@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { render, fireEvent } from '../../../tests/utils';
import Space from '..';
import ConfigProvider from '../../config-provider';
import mountTest from '../../../tests/shared/mountTest';
@ -11,13 +10,13 @@ describe('Space', () => {
rtlTest(Space);
it('should render width empty children', () => {
const wrapper = mount(<Space />);
const { container } = render(<Space />);
expect(wrapper.instance()).toBe(null);
expect(container.children.length).toBe(0);
});
it('should render width ConfigProvider', () => {
const wrapper = mount(
const { container } = render(
<ConfigProvider space={{ size: 'large' }}>
<Space>
<span>1</span>
@ -34,11 +33,11 @@ describe('Space', () => {
</ConfigProvider>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.children).toMatchSnapshot();
});
it('should render width rtl', () => {
const wrapper = mount(
const { container } = render(
<ConfigProvider direction="rtl">
<Space>
<span>1</span>
@ -55,46 +54,46 @@ describe('Space', () => {
</ConfigProvider>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.children).toMatchSnapshot();
});
it('should render width customize size', () => {
const wrapper = mount(
const { container } = render(
<Space size={10}>
<span>1</span>
<span>2</span>
</Space>,
);
expect(wrapper.find('div.ant-space-item').at(0).prop('style').marginRight).toBe(10);
expect(wrapper.find('div.ant-space-item').at(1).prop('style').marginRight).toBeUndefined();
expect(container.querySelector('div.ant-space-item').style.marginRight).toBe('10px');
expect(container.querySelectorAll('div.ant-space-item')[1].style.marginRight).toBe('');
});
it('should render width size 0', () => {
const wrapper = mount(
const { container } = render(
<Space size={NaN}>
<span>1</span>
<span>2</span>
</Space>,
);
expect(wrapper.find('div.ant-space-item').at(0).prop('style').marginRight).toBe(0);
expect(container.querySelector('div.ant-space-item').style.marginRight).toBe('0px');
});
it('should render vertical space width customize size', () => {
const wrapper = mount(
const { container } = render(
<Space size={10} direction="vertical">
<span>1</span>
<span>2</span>
</Space>,
);
expect(wrapper.find('div.ant-space-item').at(0).prop('style').marginBottom).toBe(10);
expect(wrapper.find('div.ant-space-item').at(1).prop('style').marginBottom).toBeUndefined();
expect(container.querySelector('div.ant-space-item').style.marginBottom).toBe('10px');
expect(container.querySelectorAll('div.ant-space-item')[1].style.marginBottom).toBe('');
});
it('should render correct with children', () => {
const wrapper = mount(
const { container } = render(
<Space>
text1<span>text1</span>
{/* eslint-disable-next-line react/jsx-no-useless-fragment */}
@ -102,18 +101,18 @@ describe('Space', () => {
</Space>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.children[0]).toMatchSnapshot();
});
it('should render with invalidElement', () => {
const wrapper = mount(
const { container } = render(
<Space>
text1<span>text1</span>
text1
</Space>,
);
expect(wrapper.find('div.ant-space-item').length).toBe(3);
expect(container.querySelectorAll('div.ant-space-item').length).toBe(3);
});
it('should be keep store', () => {
@ -144,25 +143,21 @@ describe('Space', () => {
</Space>
);
}
const wrapper = mount(<SpaceDemo />);
const { container } = render(<SpaceDemo />);
expect(wrapper.find('#demo').text()).toBe('1');
expect(container.querySelector('#demo')).toHaveTextContent('1');
act(() => {
wrapper.find('#demo').simulate('click');
});
fireEvent.click(container.querySelector('#demo'));
expect(wrapper.find('#demo').text()).toBe('2');
expect(container.querySelector('#demo')).toHaveTextContent('2');
act(() => {
wrapper.find('p').simulate('click');
});
fireEvent.click(container.querySelector('p'));
expect(wrapper.find('#demo').text()).toBe('2');
expect(container.querySelector('#demo')).toHaveTextContent('2');
});
it('split', () => {
const wrapper = mount(
const { container } = render(
<Space split="-">
text1<span>text1</span>
{/* eslint-disable-next-line react/jsx-no-useless-fragment */}
@ -170,13 +165,13 @@ describe('Space', () => {
</Space>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(container.children[0]).toMatchSnapshot();
});
// https://github.com/ant-design/ant-design/issues/35305
it('should not throw duplicated key warning', () => {
jest.spyOn(console, 'error').mockImplementation(() => undefined);
mount(
render(
<Space>
<div key="1" />
<div />

View File

@ -92,15 +92,12 @@ const Space: React.FC<SpaceProps> = props => {
latestIndex = i;
}
const keyOfChild = child && child.key;
// Add `-space-item` as suffix in case simple key string trigger duplicated key warning
// https://github.com/ant-design/ant-design/issues/35305
const defaultKey = `${i}-space-item`;
const key = (child && child.key) || `${itemClassName}-${i}`;
return (
<Item
className={itemClassName}
key={`${itemClassName}-${keyOfChild || defaultKey}`}
key={key}
direction={direction}
index={i}
marginDirection={marginDirection}

View File

@ -2305,4 +2305,53 @@ describe('Table.filter', () => {
);
expect(errorSpy).not.toBeCalled();
});
it('can reset if filterResetToDefaultFilteredValue and filter is changing', () => {
const wrapper = mount(
createTable({
columns: [
{
...column,
filters: [
{ text: 'Jack', value: 'Jack' },
{ text: 'Lucy', value: 'Lucy' },
],
defaultFilteredValue: ['Jack'],
filterResetToDefaultFilteredValue: true,
},
],
}),
);
expect(wrapper.find('tbody tr').length).toBe(1);
expect(wrapper.find('tbody tr').text()).toBe('Jack');
// open filter
wrapper.find('span.ant-dropdown-trigger').first().simulate('click');
expect(
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').props().disabled,
).toBeTruthy();
expect(wrapper.find('li.ant-dropdown-menu-item').at(0).text()).toBe('Jack');
expect(wrapper.find('li.ant-dropdown-menu-item').at(1).text()).toBe('Lucy');
// deselect default
wrapper.find('li.ant-dropdown-menu-item').at(0).simulate('click');
expect(
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').props().disabled,
).toBeFalsy();
// select other one
wrapper.find('li.ant-dropdown-menu-item').at(1).simulate('click');
expect(
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').props().disabled,
).toBeFalsy();
// deselect other one
wrapper.find('li.ant-dropdown-menu-item').at(1).simulate('click');
expect(
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').props().disabled,
).toBeFalsy();
// select default
wrapper.find('li.ant-dropdown-menu-item').at(0).simulate('click');
expect(
wrapper.find('.ant-table-filter-dropdown-btns .ant-btn-link').props().disabled,
).toBeTruthy();
});
});

View File

@ -407,16 +407,22 @@ function FilterDropdown<RecordType>(props: FilterDropdownProps<RecordType>) {
);
};
const getResetDisabled = () => {
if (filterResetToDefaultFilteredValue) {
return isEqual(
(defaultFilteredValue || []).map(key => String(key)),
selectedKeys,
);
}
return selectedKeys.length === 0;
};
dropdownContent = (
<>
{getFilterComponent()}
<div className={`${prefixCls}-dropdown-btns`}>
<Button
type="link"
size="small"
disabled={selectedKeys.length === 0}
onClick={() => onReset()}
>
<Button type="link" size="small" disabled={getResetDisabled()} onClick={() => onReset()}>
{locale.filterReset}
</Button>
<Button type="primary" size="small" onClick={onConfirm}>

View File

@ -25,7 +25,7 @@ class ComponentDoc extends React.Component {
expandAll: false,
visibleAll: process.env.NODE_ENV !== 'production',
showRiddleButton: false,
react18Demo: true,
react17Demo: false,
};
componentDidMount() {
@ -49,12 +49,12 @@ class ComponentDoc extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
const { location, theme } = this.props;
const { location: nextLocation, theme: nextTheme } = nextProps;
const { expandAll, visibleAll, showRiddleButton, react18Demo } = this.state;
const { expandAll, visibleAll, showRiddleButton, react17Demo } = this.state;
const {
expandAll: nextExpandAll,
visibleAll: nextVisibleAll,
showRiddleButton: nextShowRiddleButton,
react18Demo: nextReact18Demo,
react17Demo: nextReact17Demo,
} = nextState;
if (
@ -64,7 +64,7 @@ class ComponentDoc extends React.Component {
theme === nextTheme &&
visibleAll === nextVisibleAll &&
showRiddleButton === nextShowRiddleButton &&
react18Demo === nextReact18Demo
react17Demo === nextReact17Demo
) {
return false;
}
@ -90,9 +90,9 @@ class ComponentDoc extends React.Component {
};
handleDemoVersionToggle = () => {
const { react18Demo } = this.state;
const { react17Demo } = this.state;
this.setState({
react18Demo: !react18Demo,
react17Demo: !react17Demo,
});
};
@ -108,7 +108,7 @@ class ComponentDoc extends React.Component {
} = this.props;
const { content, meta } = doc;
const demoValues = Object.keys(demos).map(key => demos[key]);
const { expandAll, visibleAll, showRiddleButton, react18Demo } = this.state;
const { expandAll, visibleAll, showRiddleButton, react17Demo } = this.state;
const isSingleCol = meta.cols === 1;
const leftChildren = [];
const rightChildren = [];
@ -131,7 +131,7 @@ class ComponentDoc extends React.Component {
location={location}
theme={theme}
setIframeTheme={setIframeTheme}
react18={react18Demo}
react18={react17Demo}
/>
);
if (index % 2 === 0 || isSingleCol) {
@ -224,12 +224,12 @@ class ComponentDoc extends React.Component {
title={
<FormattedMessage
id={`app.component.examples.${
react18Demo ? 'openDemoNotReact18' : 'openDemoWithReact18'
react17Demo ? 'openDemoWithReact18' : 'openDemoNotReact18'
}`}
/>
}
>
{react18Demo ? (
{react17Demo ? (
<ExperimentFilled
className={expandTriggerClass}
onClick={this.handleDemoVersionToggle}