fix: react 18 test fixing (#34787)

* fix: try fix

* chore: ci

* test: recover

* test: more test case

* test: more and mote

* test: btn test

* fix: react 18 compitable

* chore: more test

* test: all confirm test

* chore: tmp

* chore: test lib

* chore: tmp

* chore: tmp

* test: back of part

* test: back of menu index test

* test: more test

* test: form test

* test: rm IE11 test case

* chore: fix compatible

* chore: clean up

* chore: back of all test case

* test: ignore 18 lines

* chore: remove render test of enzyme in upload

* test: back of IE11 test case to fit 100% coverage

* chore: fix pkg deps
This commit is contained in:
二货机器人 2022-04-06 11:07:15 +08:00 committed by GitHub
parent 08e962db4e
commit a67c0d28d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 832 additions and 503 deletions

View File

@ -231,8 +231,7 @@ jobs:
name: test name: test
strategy: strategy:
matrix: matrix:
react: ['16', '17'] react: ['16', '17', '18']
# react: ['17', '18']
module: ['dom', 'node', 'dist'] module: ['dom', 'node', 'dist']
env: env:
REACT: ${{ matrix.react }} REACT: ${{ matrix.react }}
@ -349,8 +348,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
react: ['16', '17'] react: ['16', '17', '18']
# react: ['17', '18']
module: [lib, es] module: [lib, es]
env: env:
REACT: ${{ matrix.react }} REACT: ${{ matrix.react }}

View File

@ -0,0 +1,50 @@
/* eslint-disable import/prefer-default-export */
import * as React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import type { Root } from 'react-dom/client';
// import * as reactDomClient from 'react-dom/client';
let createRoot: (container: ContainerType) => Root;
try {
// eslint-disable-next-line global-require, import/no-unresolved
createRoot = require('react-dom/client').createRoot;
} catch (e) {
// Do nothing;
}
const MARK = '__antd_react_root__';
type ContainerType = (Element | DocumentFragment) & {
[MARK]?: Root;
};
export function reactRender(node: React.ReactElement, container: ContainerType) {
// React 17 test will not reach here
/* istanbul ignore next */
if (createRoot !== undefined) {
const root = container[MARK] || createRoot(container);
root.render(node);
container[MARK] = root;
return;
}
render(node, container);
}
export function reactUnmount(container: ContainerType) {
// React 17 test will not reach here
/* istanbul ignore next */
if (createRoot !== undefined) {
// Delay to unmount to avoid React 18 sync warning
Promise.resolve().then(() => {
container[MARK]?.unmount();
delete container[MARK];
});
return;
}
unmountComponentAtNode(container);
}

View File

@ -2,6 +2,8 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Avatar from '..'; import Avatar from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
@ -144,9 +146,10 @@ describe('Avatar Render', () => {
it('should warning when pass a string as icon props', () => { it('should warning when pass a string as icon props', () => {
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Avatar size={64} icon="aa" />); render(<Avatar size={64} icon="aa" />);
expect(warnSpy).not.toHaveBeenCalled(); expect(warnSpy).not.toHaveBeenCalled();
mount(<Avatar size={64} icon="user" />);
render(<Avatar size={64} icon="user" />);
expect(warnSpy).toHaveBeenCalledWith( expect(warnSpy).toHaveBeenCalledWith(
`Warning: [antd: Avatar] \`icon\` is using ReactNode instead of string naming in v4. Please check \`user\` at https://ant.design/components/icon`, `Warning: [antd: Avatar] \`icon\` is using ReactNode instead of string naming in v4. Please check \`user\` at https://ant.design/components/icon`,
); );

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount, render } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Breadcrumb from '../index'; import Breadcrumb from '../index';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
@ -21,7 +23,7 @@ describe('Breadcrumb', () => {
// https://github.com/airbnb/enzyme/issues/875 // https://github.com/airbnb/enzyme/issues/875
it('warns on non-Breadcrumb.Item and non-Breadcrumb.Separator children', () => { it('warns on non-Breadcrumb.Item and non-Breadcrumb.Separator children', () => {
const MyCom = () => <div>foo</div>; const MyCom = () => <div>foo</div>;
mount( render(
<Breadcrumb> <Breadcrumb>
<MyCom /> <MyCom />
</Breadcrumb>, </Breadcrumb>,
@ -34,7 +36,7 @@ describe('Breadcrumb', () => {
// https://github.com/ant-design/ant-design/issues/5015 // https://github.com/ant-design/ant-design/issues/5015
it('should allow Breadcrumb.Item is null or undefined', () => { it('should allow Breadcrumb.Item is null or undefined', () => {
const wrapper = render( const { asFragment } = render(
<Breadcrumb> <Breadcrumb>
{null} {null}
<Breadcrumb.Item>Home</Breadcrumb.Item> <Breadcrumb.Item>Home</Breadcrumb.Item>
@ -42,24 +44,24 @@ describe('Breadcrumb', () => {
</Breadcrumb>, </Breadcrumb>,
); );
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
expect(wrapper).toMatchSnapshot(); expect(asFragment().firstChild).toMatchSnapshot();
}); });
// https://github.com/ant-design/ant-design/issues/5542 // https://github.com/ant-design/ant-design/issues/5542
it('should not display Breadcrumb Item when its children is falsy', () => { it('should not display Breadcrumb Item when its children is falsy', () => {
const wrapper = render( const wrapper = mount(
<Breadcrumb> <Breadcrumb>
<Breadcrumb.Item /> <Breadcrumb.Item />
<Breadcrumb.Item>xxx</Breadcrumb.Item> <Breadcrumb.Item>xxx</Breadcrumb.Item>
<Breadcrumb.Item>yyy</Breadcrumb.Item> <Breadcrumb.Item>yyy</Breadcrumb.Item>
</Breadcrumb>, </Breadcrumb>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
// https://github.com/ant-design/ant-design/issues/18260 // https://github.com/ant-design/ant-design/issues/18260
it('filter React.Fragment', () => { it('filter React.Fragment', () => {
const wrapper = render( const wrapper = mount(
<Breadcrumb separator=""> <Breadcrumb separator="">
<Breadcrumb.Item>Location</Breadcrumb.Item> <Breadcrumb.Item>Location</Breadcrumb.Item>
<Breadcrumb.Separator>:</Breadcrumb.Separator> <Breadcrumb.Separator>:</Breadcrumb.Separator>
@ -69,7 +71,7 @@ describe('Breadcrumb', () => {
</> </>
</Breadcrumb>, </Breadcrumb>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('should render a menu', () => { it('should render a menu', () => {
@ -104,27 +106,27 @@ describe('Breadcrumb', () => {
path: 'third', path: 'third',
}, },
]; ];
const wrapper = render(<Breadcrumb routes={routes} />); const wrapper = mount(<Breadcrumb routes={routes} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('should accept undefined routes', () => { it('should accept undefined routes', () => {
const wrapper = render(<Breadcrumb routes={undefined} />); const wrapper = mount(<Breadcrumb routes={undefined} />);
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('should support custom attribute', () => { it('should support custom attribute', () => {
const wrapper = render( const wrapper = mount(
<Breadcrumb data-custom="custom"> <Breadcrumb data-custom="custom">
<Breadcrumb.Item data-custom="custom-item">xxx</Breadcrumb.Item> <Breadcrumb.Item data-custom="custom-item">xxx</Breadcrumb.Item>
<Breadcrumb.Item>yyy</Breadcrumb.Item> <Breadcrumb.Item>yyy</Breadcrumb.Item>
</Breadcrumb>, </Breadcrumb>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('should support React.Fragment and falsy children', () => { it('should support React.Fragment and falsy children', () => {
const wrapper = render( const wrapper = mount(
<Breadcrumb> <Breadcrumb>
<> <>
<Breadcrumb.Item>yyy</Breadcrumb.Item> <Breadcrumb.Item>yyy</Breadcrumb.Item>
@ -136,7 +138,7 @@ describe('Breadcrumb', () => {
{undefined} {undefined}
</Breadcrumb>, </Breadcrumb>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
// https://github.com/ant-design/ant-design/issues/25975 // https://github.com/ant-design/ant-design/issues/25975
@ -146,13 +148,13 @@ describe('Breadcrumb', () => {
<Breadcrumb.Item>Mock Node</Breadcrumb.Item> <Breadcrumb.Item>Mock Node</Breadcrumb.Item>
</span> </span>
); );
const wrapper = render( const wrapper = mount(
<Breadcrumb> <Breadcrumb>
<Breadcrumb.Item>Location</Breadcrumb.Item> <Breadcrumb.Item>Location</Breadcrumb.Item>
<MockComponent /> <MockComponent />
<Breadcrumb.Item>Application Center</Breadcrumb.Item> <Breadcrumb.Item>Application Center</Breadcrumb.Item>
</Breadcrumb>, </Breadcrumb>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
}); });

View File

@ -1,5 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { mount, render } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { SearchOutlined } from '@ant-design/icons'; import { SearchOutlined } from '@ant-design/icons';
import { resetWarned } from 'rc-util/lib/warning'; import { resetWarned } from 'rc-util/lib/warning';
@ -39,7 +41,7 @@ describe('Button', () => {
const mockWarn = jest.fn(); const mockWarn = jest.fn();
jest.spyOn(console, 'warn').mockImplementation(mockWarn); jest.spyOn(console, 'warn').mockImplementation(mockWarn);
const size = 'who am I' as any as SizeType; const size = 'who am I' as any as SizeType;
render(<Button.Group size={size} />); mount(<Button.Group size={size} />);
expect(mockWarn).toHaveBeenCalledTimes(1); expect(mockWarn).toHaveBeenCalledTimes(1);
expect(mockWarn.mock.calls[0][0]).toMatchObject({ expect(mockWarn.mock.calls[0][0]).toMatchObject({
message: 'unreachable case: "who am I"', message: 'unreachable case: "who am I"',
@ -51,12 +53,14 @@ describe('Button', () => {
// should not insert space when there is icon // should not insert space when there is icon
expect(mount(<Button icon={<SearchOutlined />}></Button>).render()).toMatchSnapshot(); expect(mount(<Button icon={<SearchOutlined />}></Button>).render()).toMatchSnapshot();
// should not insert space when there is icon // should not insert space when there is icon
expect(mount( expect(
mount(
<Button> <Button>
<SearchOutlined /> <SearchOutlined />
</Button>, </Button>,
).render()).toMatchSnapshot(); ).render(),
).toMatchSnapshot();
// should not insert space when there is icon // should not insert space when there is icon
expect(mount(<Button icon={<SearchOutlined />}></Button>).render()).toMatchSnapshot(); expect(mount(<Button icon={<SearchOutlined />}></Button>).render()).toMatchSnapshot();
// should not insert space when there is icon while loading // should not insert space when there is icon while loading
@ -261,12 +265,15 @@ describe('Button', () => {
it('should warning when pass a string as icon props', () => { it('should warning when pass a string as icon props', () => {
resetWarned(); resetWarned();
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Button type="primary" icon="ab" />);
render(<Button type="primary" icon="ab" />);
expect(warnSpy).not.toHaveBeenCalled(); expect(warnSpy).not.toHaveBeenCalled();
mount(<Button type="primary" icon="search" />);
render(<Button type="primary" icon="search" />);
expect(warnSpy).toHaveBeenCalledWith( expect(warnSpy).toHaveBeenCalledWith(
`Warning: [antd: Button] \`icon\` is using ReactNode instead of string naming in v4. Please check \`search\` at https://ant.design/components/icon`, `Warning: [antd: Button] \`icon\` is using ReactNode instead of string naming in v4. Please check \`search\` at https://ant.design/components/icon`,
); );
warnSpy.mockRestore(); warnSpy.mockRestore();
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import ConfigProvider from '..'; import ConfigProvider from '..';
import zhCN from '../../locale/zh_CN'; import zhCN from '../../locale/zh_CN';
@ -15,10 +17,10 @@ describe('ConfigProvider.Form', () => {
}); });
describe('form validateMessages', () => { describe('form validateMessages', () => {
const wrapperComponent = ({ validateMessages }) => { const renderComponent = ({ validateMessages }) => {
const formRef = React.createRef(); const formRef = React.createRef();
const wrapper = mount( const { container } = render(
<ConfigProvider locale={zhCN} form={{ validateMessages }}> <ConfigProvider locale={zhCN} form={{ validateMessages }}>
<Form ref={formRef} initialValues={{ age: 18 }}> <Form ref={formRef} initialValues={{ age: 18 }}>
<Form.Item name="test" label="姓名" rules={[{ required: true }]}> <Form.Item name="test" label="姓名" rules={[{ required: true }]}>
@ -31,11 +33,11 @@ describe('ConfigProvider.Form', () => {
</ConfigProvider>, </ConfigProvider>,
); );
return [wrapper, formRef]; return [container, formRef];
}; };
it('set locale zhCN', async () => { it('set locale zhCN', async () => {
const [wrapper, formRef] = wrapperComponent({}); const [container, formRef] = renderComponent({});
await act(async () => { await act(async () => {
try { try {
@ -47,15 +49,14 @@ describe('ConfigProvider.Form', () => {
await act(async () => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update();
await Promise.resolve(); await Promise.resolve();
}); });
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('请输入姓名'); expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('请输入姓名');
}); });
it('set locale zhCN and set form validateMessages one item, other use default message', async () => { it('set locale zhCN and set form validateMessages one item, other use default message', async () => {
const [wrapper, formRef] = wrapperComponent({ validateMessages: { required: '必须' } }); const [container, formRef] = renderComponent({ validateMessages: { required: '必须' } });
await act(async () => { await act(async () => {
try { try {
@ -67,12 +68,13 @@ describe('ConfigProvider.Form', () => {
await act(async () => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update();
await Promise.resolve(); await Promise.resolve();
}); });
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('必须'); const explains = Array.from(container.querySelectorAll('.ant-form-item-explain'));
expect(wrapper.find('.ant-form-item-explain').last().text()).toEqual('年龄必须等于17');
expect(explains[0]).toHaveTextContent('必须');
expect(explains[explains.length - 1]).toHaveTextContent('年龄必须等于17');
}); });
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { render, mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Drawer from '..'; import Drawer from '..';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
@ -18,12 +20,12 @@ describe('Drawer', () => {
rtlTest(Drawer); rtlTest(Drawer);
it('render correctly', () => { it('render correctly', () => {
const wrapper = render( const wrapper = mount(
<Drawer visible width={400} getContainer={false}> <Drawer visible width={400} getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('getContainer return undefined', () => { it('getContainer return undefined', () => {
@ -34,55 +36,55 @@ describe('Drawer', () => {
}); });
it('render top drawer', () => { it('render top drawer', () => {
const wrapper = render( const wrapper = mount(
<Drawer visible height={400} placement="top" getContainer={false}> <Drawer visible height={400} placement="top" getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('have a title', () => { it('have a title', () => {
const wrapper = render( const wrapper = mount(
<Drawer visible title="Test Title" getContainer={false}> <Drawer visible title="Test Title" getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('closable is false', () => { it('closable is false', () => {
const wrapper = render( const wrapper = mount(
<Drawer visible closable={false} getContainer={false}> <Drawer visible closable={false} getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('destroyOnClose is true', () => { it('destroyOnClose is true', () => {
const wrapper = render( const wrapper = mount(
<Drawer destroyOnClose visible={false} getContainer={false}> <Drawer destroyOnClose visible={false} getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('className is test_drawer', () => { it('className is test_drawer', () => {
const wrapper = render( const wrapper = mount(
<Drawer destroyOnClose visible={false} className="test_drawer" getContainer={false}> <Drawer destroyOnClose visible={false} className="test_drawer" getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('style/drawerStyle/headerStyle/bodyStyle should work', () => { it('style/drawerStyle/headerStyle/bodyStyle should work', () => {
const style = { const style = {
backgroundColor: '#08c', backgroundColor: '#08c',
}; };
const wrapper = render( const wrapper = mount(
<Drawer <Drawer
visible visible
style={style} style={style}
@ -94,16 +96,16 @@ describe('Drawer', () => {
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('have a footer', () => { it('have a footer', () => {
const wrapper = render( const wrapper = mount(
<Drawer visible footer="Test Footer" getContainer={false}> <Drawer visible footer="Test Footer" getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('forceRender works', () => { it('forceRender works', () => {
@ -126,18 +128,18 @@ describe('Drawer', () => {
}); });
it('support closeIcon', () => { it('support closeIcon', () => {
const wrapper = render( const wrapper = mount(
<Drawer visible closable closeIcon={<span>close</span>} width={400} getContainer={false}> <Drawer visible closable closeIcon={<span>close</span>} width={400} getContainer={false}>
Here is content of Drawer Here is content of Drawer
</Drawer>, </Drawer>,
); );
expect(wrapper).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
it('ConfigProvider should not warning', () => { it('ConfigProvider should not warning', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount( render(
<ConfigProvider virtual> <ConfigProvider virtual>
<Drawer visible>Bamboo is Light</Drawer> <Drawer visible>Bamboo is Light</Drawer>
</ConfigProvider>, </ConfigProvider>,

View File

@ -13,14 +13,14 @@ exports[`Drawer className is test_drawer 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px" style="transform: translateX(100%); width: 378px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
> >
<div <div
class="ant-drawer-wrapper-body" class="ant-drawer-wrapper-body"
style="opacity:0;transition:opacity .3s" style="opacity: 0; transition: opacity .3s;"
> >
<div <div
class="ant-drawer-header ant-drawer-header-close-only" class="ant-drawer-header ant-drawer-header-close-only"
@ -72,7 +72,7 @@ exports[`Drawer closable is false 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-right" class="ant-drawer ant-drawer-right ant-drawer-open"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -80,7 +80,7 @@ exports[`Drawer closable is false 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px" style="width: 378px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
@ -113,14 +113,14 @@ exports[`Drawer destroyOnClose is true 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px" style="transform: translateX(100%); width: 378px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
> >
<div <div
class="ant-drawer-wrapper-body" class="ant-drawer-wrapper-body"
style="opacity:0;transition:opacity .3s" style="opacity: 0; transition: opacity .3s;"
> >
<div <div
class="ant-drawer-header ant-drawer-header-close-only" class="ant-drawer-header ant-drawer-header-close-only"
@ -242,7 +242,7 @@ exports[`Drawer have a footer 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-right" class="ant-drawer ant-drawer-right ant-drawer-open"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -250,7 +250,7 @@ exports[`Drawer have a footer 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px" style="width: 378px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
@ -313,7 +313,7 @@ exports[`Drawer have a title 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-right" class="ant-drawer ant-drawer-right ant-drawer-open"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -321,7 +321,7 @@ exports[`Drawer have a title 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px" style="width: 378px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
@ -384,7 +384,7 @@ exports[`Drawer render correctly 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-right" class="ant-drawer ant-drawer-right ant-drawer-open"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -392,7 +392,7 @@ exports[`Drawer render correctly 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:400px" style="width: 400px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
@ -450,7 +450,7 @@ exports[`Drawer render top drawer 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-top" class="ant-drawer ant-drawer-top ant-drawer-open"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -458,7 +458,7 @@ exports[`Drawer render top drawer 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateY(-100%);-ms-transform:translateY(-100%);height:400px" style="height: 400px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
@ -518,8 +518,8 @@ exports[`Drawer style/drawerStyle/headerStyle/bodyStyle should work 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-right" class="ant-drawer ant-drawer-right ant-drawer-open"
style="background-color:#08c" style="background-color: rgb(0, 136, 204);"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -527,18 +527,18 @@ exports[`Drawer style/drawerStyle/headerStyle/bodyStyle should work 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:378px" style="width: 378px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"
> >
<div <div
class="ant-drawer-wrapper-body" class="ant-drawer-wrapper-body"
style="background-color:#08c" style="background-color: rgb(0, 136, 204);"
> >
<div <div
class="ant-drawer-header ant-drawer-header-close-only" class="ant-drawer-header ant-drawer-header-close-only"
style="background-color:#08c" style="background-color: rgb(0, 136, 204);"
> >
<div <div
class="ant-drawer-header-title" class="ant-drawer-header-title"
@ -572,7 +572,7 @@ exports[`Drawer style/drawerStyle/headerStyle/bodyStyle should work 1`] = `
</div> </div>
<div <div
class="ant-drawer-body" class="ant-drawer-body"
style="background-color:#08c" style="background-color: rgb(0, 136, 204);"
> >
Here is content of Drawer Here is content of Drawer
</div> </div>
@ -588,7 +588,7 @@ exports[`Drawer support closeIcon 1`] = `
class="" class=""
> >
<div <div
class="ant-drawer ant-drawer-right" class="ant-drawer ant-drawer-right ant-drawer-open"
tabindex="-1" tabindex="-1"
> >
<div <div
@ -596,7 +596,7 @@ exports[`Drawer support closeIcon 1`] = `
/> />
<div <div
class="ant-drawer-content-wrapper" class="ant-drawer-content-wrapper"
style="transform:translateX(100%);-ms-transform:translateX(100%);width:400px" style="width: 400px;"
> >
<div <div
class="ant-drawer-content" class="ant-drawer-content"

View File

@ -1,5 +1,7 @@
import React, { Component, useState } from 'react'; import React, { Component, useState } from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import scrollIntoView from 'scroll-into-view-if-needed'; import scrollIntoView from 'scroll-into-view-if-needed';
import Form from '..'; import Form from '..';
@ -27,15 +29,18 @@ describe('Form', () => {
scrollIntoView.mockImplementation(() => {}); scrollIntoView.mockImplementation(() => {});
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
async function change(wrapper, index, value, executeMockTimer) { async function change(container, index, value, executeMockTimer) {
wrapper.find(Input).at(index).simulate('change', { target: { value } }); fireEvent.change(container.querySelectorAll('input')[index], {
target: { value },
});
await sleep(200); await sleep(200);
if (executeMockTimer) { if (executeMockTimer) {
for (let i = 0; i < 10; i += 1) {
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update();
}); });
}
await sleep(1); await sleep(1);
} }
} }
@ -60,19 +65,19 @@ describe('Form', () => {
const onChange = jest.fn(); const onChange = jest.fn();
const wrapper = mount( const { container } = render(
<Form> <Form>
<Form.Item> <Form.Item>
<Form.Item name="test" rules={[{ required: true }]}> <Form.Item name="test" initialValue="bamboo" rules={[{ required: true }]}>
<Input onChange={onChange} /> <Input onChange={onChange} />
</Form.Item> </Form.Item>
</Form.Item> </Form.Item>
</Form>, </Form>,
); );
await change(wrapper, 0, '', true); await change(container, 0, '', true);
expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy(); expect(container.querySelectorAll('.ant-form-item-with-help').length).toBeTruthy();
expect(wrapper.find('.ant-form-item-has-error').length).toBeTruthy(); expect(container.querySelectorAll('.ant-form-item-has-error').length).toBeTruthy();
expect(onChange).toHaveBeenCalled(); expect(onChange).toHaveBeenCalled();
@ -124,13 +129,13 @@ describe('Form', () => {
); );
}; };
const wrapper = mount(<Demo />); const { container } = render(<Demo />);
await change(wrapper, 0, '1', true); await change(container, 0, '1', true);
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa'); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('aaa');
await change(wrapper, 0, '2', true); await change(container, 0, '2', true);
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('ccc'); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('ccc');
await change(wrapper, 0, '1', true); await change(container, 0, '1', true);
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa'); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('aaa');
jest.useRealTimers(); jest.useRealTimers();
}); });
@ -353,7 +358,7 @@ describe('Form', () => {
it('Error change should work', async () => { it('Error change should work', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const wrapper = mount( const { container } = render(
<Form> <Form>
<Form.Item <Form.Item
name="name" name="name"
@ -376,13 +381,15 @@ describe('Form', () => {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
for (let i = 0; i < 3; i += 1) { for (let i = 0; i < 3; i += 1) {
await change(wrapper, 0, '', true); await change(container, 0, 'bamboo', true);
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual("'name' is required"); await change(container, 0, '', true);
expect(container.querySelector('.ant-form-item-explain').textContent).toEqual(
"'name' is required",
);
await change(wrapper, 0, 'p', true); await change(container, 0, 'p', true);
await sleep(100); await sleep(100);
wrapper.update(); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('not a p');
expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('not a p');
} }
/* eslint-enable */ /* eslint-enable */
@ -470,17 +477,22 @@ describe('Form', () => {
it('Form.Item with `help` should display error style when validate failed', async () => { it('Form.Item with `help` should display error style when validate failed', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const wrapper = mount( const { container } = render(
<Form> <Form>
<Form.Item name="test" help="help" rules={[{ required: true, message: 'message' }]}> <Form.Item
name="test"
help="help"
initialValue="bamboo"
rules={[{ required: true, message: 'message' }]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
</Form>, </Form>,
); );
await change(wrapper, 0, '', true); await change(container, 0, '', true);
expect(wrapper.find('.ant-form-item').first().hasClass('ant-form-item-has-error')).toBeTruthy(); expect(container.querySelector('.ant-form-item')).toHaveClass('ant-form-item-has-error');
expect(wrapper.find('.ant-form-item-explain').text()).toEqual('help'); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('help');
jest.useRealTimers(); jest.useRealTimers();
}); });
@ -488,23 +500,22 @@ describe('Form', () => {
it('clear validation message when ', async () => { it('clear validation message when ', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const wrapper = mount( const { container } = render(
<Form> <Form>
<Form.Item name="username" rules={[{ required: true, message: 'message' }]}> <Form.Item name="username" rules={[{ required: true, message: 'message' }]}>
<Input /> <Input />
</Form.Item> </Form.Item>
</Form>, </Form>,
); );
await change(wrapper, 0, '1', true); await change(container, 0, '1', true);
expect(wrapper.find('.ant-form-item-explain').length).toBeFalsy(); expect(container.querySelectorAll('.ant-form-item-explain').length).toBeFalsy();
await change(wrapper, 0, '', true); await change(container, 0, '', true);
expect(wrapper.find('.ant-form-item-explain').length).toBeTruthy(); expect(container.querySelectorAll('.ant-form-item-explain').length).toBeTruthy();
await change(wrapper, 0, '123', true); await change(container, 0, '123', true);
await sleep(800); await sleep(800);
wrapper.update(); expect(container.querySelectorAll('.ant-form-item-explain').length).toBeFalsy();
expect(wrapper.find('.ant-form-item-explain').length).toBeFalsy();
jest.useRealTimers(); jest.useRealTimers();
}); });
@ -591,8 +602,8 @@ describe('Form', () => {
); );
}; };
const wrapper = mount(<Demo />); const { container } = render(<Demo />);
wrapper.find('button').simulate('click'); fireEvent.click(container.querySelector('button'));
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
}); });
@ -794,7 +805,7 @@ describe('Form', () => {
}); });
it('no warning of initialValue & getValueProps & preserve', () => { it('no warning of initialValue & getValueProps & preserve', () => {
mount( render(
<Form> <Form>
<Form.Item initialValue="bamboo" getValueProps={() => null} preserve={false}> <Form.Item initialValue="bamboo" getValueProps={() => null} preserve={false}>
<Input /> <Input />
@ -973,19 +984,23 @@ describe('Form', () => {
it('warningOnly validate', async () => { it('warningOnly validate', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const wrapper = mount( const { container } = render(
<Form> <Form>
<Form.Item> <Form.Item>
<Form.Item name="test" rules={[{ required: true, warningOnly: true }]}> <Form.Item
name="test"
initialValue="bamboo"
rules={[{ required: true, warningOnly: true }]}
>
<Input /> <Input />
</Form.Item> </Form.Item>
</Form.Item> </Form.Item>
</Form>, </Form>,
); );
await change(wrapper, 0, '', true); await change(container, 0, '', true);
expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy(); expect(container.querySelectorAll('.ant-form-item-with-help').length).toBeTruthy();
expect(wrapper.find('.ant-form-item-has-warning').length).toBeTruthy(); expect(container.querySelectorAll('.ant-form-item-has-warning').length).toBeTruthy();
jest.useRealTimers(); jest.useRealTimers();
}); });
@ -994,12 +1009,13 @@ describe('Form', () => {
jest.useFakeTimers(); jest.useFakeTimers();
let rejectFn = null; let rejectFn = null;
const wrapper = mount( const { container, unmount } = render(
<Form> <Form>
<Form.Item> <Form.Item>
<Form.Item <Form.Item
noStyle noStyle
name="test" name="test"
initialValue="bamboo"
rules={[ rules={[
{ {
validator: () => validator: () =>
@ -1015,9 +1031,9 @@ describe('Form', () => {
</Form>, </Form>,
); );
await change(wrapper, 0, '', true); await change(container, 0, '', true);
wrapper.unmount(); unmount();
// Delay validate failed // Delay validate failed
rejectFn(new Error('delay failed')); rejectFn(new Error('delay failed'));

View File

@ -64,9 +64,11 @@ describe('Form.List', () => {
expect(wrapper.find(Input).length).toBe(3); expect(wrapper.find(Input).length).toBe(3);
await change(wrapper, 2, ''); await change(wrapper, 2, '');
for (let i = 0; i < 10; i += 1) {
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
}
wrapper.update(); wrapper.update();
expect(wrapper.find('.ant-form-item-explain div').length).toBe(1); expect(wrapper.find('.ant-form-item-explain div').length).toBe(1);

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import ReactDOMServer from 'react-dom/server'; import ReactDOMServer from 'react-dom/server';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { Col, Row } from '..'; import { Col, Row } from '..';
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@ -42,12 +44,12 @@ describe('Grid.Gap', () => {
const ssrTxt = ReactDOMServer.renderToString(<Demo />); const ssrTxt = ReactDOMServer.renderToString(<Demo />);
div.innerHTML = ssrTxt; div.innerHTML = ssrTxt;
const wrapper = mount(<Demo />, { hydrateIn: div }); const { unmount } = render(<Demo />, { container: div, hydrate: true });
expect(warnSpy).not.toHaveBeenCalled(); expect(warnSpy).not.toHaveBeenCalled();
warnSpy.mockRestore(); warnSpy.mockRestore();
wrapper.unmount(); unmount();
}); });
}); });

View File

@ -1,5 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import Form from '../../form'; import Form from '../../form';
import Input, { InputProps, InputRef } from '..'; import Input, { InputProps, InputRef } from '..';
@ -54,11 +56,11 @@ describe('Input', () => {
describe('focus trigger warning', () => { describe('focus trigger warning', () => {
it('not trigger', () => { it('not trigger', () => {
const wrapper = mount(<Input suffix="bamboo" />); const { container, rerender } = render(<Input suffix="bamboo" />);
(wrapper.find('input').instance() as any).focus();
wrapper.setProps({ fireEvent.focus(container.querySelector('input')!);
suffix: 'light',
}); rerender(<Input suffix="light" />);
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
}); });
it('trigger warning', () => { it('trigger warning', () => {

View File

@ -1,5 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import RcTextArea from 'rc-textarea'; import RcTextArea from 'rc-textarea';
import Input from '..'; import Input from '..';
import focusTest from '../../../tests/shared/focusTest'; import focusTest from '../../../tests/shared/focusTest';
@ -35,18 +37,32 @@ describe('TextArea', () => {
const ref = React.createRef(); const ref = React.createRef();
const wrapper = mount( const genTextArea = (props = {}) => (
<TextArea value="" readOnly autoSize={{ minRows: 2, maxRows: 6 }} wrap="off" ref={ref} />, <TextArea
value=""
readOnly
autoSize={{ minRows: 2, maxRows: 6 }}
wrap="off"
ref={ref}
{...props}
/>
); );
const { container, rerender } = render(genTextArea());
const mockFunc = jest.spyOn(ref.current.resizableTextArea, 'resizeTextarea'); const mockFunc = jest.spyOn(ref.current.resizableTextArea, 'resizeTextarea');
wrapper.setProps({ value: '1111\n2222\n3333' });
rerender(genTextArea({ value: '1111\n2222\n3333' }));
// wrapper.setProps({ value: '1111\n2222\n3333' });
await sleep(0); await sleep(0);
expect(mockFunc).toHaveBeenCalledTimes(1); expect(mockFunc).toHaveBeenCalledTimes(1);
wrapper.setProps({ value: '1111' });
rerender(genTextArea({ value: '1111' }));
// wrapper.setProps({ value: '1111' });
await sleep(0); await sleep(0);
expect(mockFunc).toHaveBeenCalledTimes(2); expect(mockFunc).toHaveBeenCalledTimes(2);
wrapper.update();
expect(wrapper.find('textarea').props().style.overflow).toBeFalsy(); expect(container.querySelector('textarea').style.overflow).toBeFalsy();
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
errorSpy.mockRestore(); errorSpy.mockRestore();

View File

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
import { Modal } from '../..'; import { Modal } from '../..';
import { sleep } from '../../../tests/utils';
import zhCN from '../zh_CN'; import zhCN from '../zh_CN';
class Demo extends React.Component { class Demo extends React.Component {
@ -19,7 +21,7 @@ class Demo extends React.Component {
} }
describe('Locale Provider demo', () => { describe('Locale Provider demo', () => {
it('change type', () => { it('change type', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const BasicExample = () => { const BasicExample = () => {
@ -48,10 +50,19 @@ describe('Locale Provider demo', () => {
); );
}; };
const wrapper = mount(<BasicExample />); const wrapper = mount(<BasicExample />);
wrapper.find('.about').at(0).simulate('click'); wrapper.find('.about').at(0).simulate('click');
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
wrapper.find('.dashboard').at(0).simulate('click'); wrapper.find('.dashboard').at(0).simulate('click');
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
expect(document.body.querySelectorAll('.ant-btn-primary span')[0].textContent).toBe('确 定'); expect(document.body.querySelectorAll('.ant-btn-primary span')[0].textContent).toBe('确 定');
Modal.destroyAll(); Modal.destroyAll();
jest.useRealTimers(); jest.useRealTimers();

View File

@ -7,6 +7,8 @@ import {
PieChartOutlined, PieChartOutlined,
UserOutlined, UserOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import Menu from '..'; import Menu from '..';
import Layout from '../../layout'; import Layout from '../../layout';
@ -15,62 +17,75 @@ import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import collapseMotion from '../../_util/motion'; import collapseMotion from '../../_util/motion';
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
const { SubMenu } = Menu; const { SubMenu } = Menu;
const noop = () => {}; const noop = () => {};
const expectSubMenuBehavior = (menu, enter = noop, leave = noop) => { describe('Menu', () => {
if (!menu.prop('openKeys') && !menu.prop('defaultOpenKeys')) { function triggerAllTimer() {
expect(menu.find('ul.ant-menu-sub').length).toBe(0); for (let i = 0; i < 10; i += 1) {
act(() => {
jest.runAllTimers();
});
} }
menu.update(); }
expect(menu.find('ul.ant-menu-sub').length).toBe(0);
const expectSubMenuBehavior = (defaultProps, instance, enter = noop, leave = noop) => {
const { container } = instance;
expect(container.querySelectorAll('ul.ant-menu-sub')).toHaveLength(0);
const AnimationClassNames = { const AnimationClassNames = {
horizontal: 'ant-slide-up-leave', horizontal: 'ant-slide-up-leave',
inline: 'ant-motion-collapse-leave', inline: 'ant-motion-collapse-leave',
vertical: 'ant-zoom-big-leave', vertical: 'ant-zoom-big-leave',
}; };
const mode = menu.prop('mode') || 'horizontal'; const mode = defaultProps.mode || 'horizontal';
act(() => { act(() => {
enter(); enter();
jest.runAllTimers();
menu.update();
}); });
// React concurrent may delay creat this
triggerAllTimer();
function getSubMenu() { function getSubMenu() {
if (mode === 'inline') { if (mode === 'inline') {
return menu.find('ul.ant-menu-sub.ant-menu-inline').hostNodes().at(0); return container.querySelector('ul.ant-menu-sub.ant-menu-inline');
} }
return menu.find('div.ant-menu-submenu-popup').hostNodes().at(0); return container.querySelector('div.ant-menu-submenu-popup');
} }
expect( expect(
getSubMenu().hasClass('ant-menu-hidden') || getSubMenu().hasClass(AnimationClassNames[mode]), getSubMenu().classList.contains('ant-menu-hidden') ||
getSubMenu().classList.contains(AnimationClassNames[mode]),
).toBeFalsy(); ).toBeFalsy();
act(() => { act(() => {
leave(); leave();
jest.runAllTimers();
menu.update();
}); });
if (getSubMenu().length) { // React concurrent may delay creat this
triggerAllTimer();
if (getSubMenu()) {
expect( expect(
getSubMenu().hasClass('ant-menu-hidden') || getSubMenu().hasClass(AnimationClassNames[mode]), getSubMenu().classList.contains('ant-menu-hidden') ||
getSubMenu().classList.contains(AnimationClassNames[mode]),
).toBeTruthy(); ).toBeTruthy();
} }
}; };
describe('Menu', () => { // window.requestAnimationFrame = callback => window.setTimeout(callback, 16);
window.requestAnimationFrame = callback => window.setTimeout(callback, 16); // window.cancelAnimationFrame = window.clearTimeout;
window.cancelAnimationFrame = window.clearTimeout;
beforeAll(() => { beforeEach(() => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.clearAllTimers();
}); });
afterAll(() => { afterEach(() => {
jest.useRealTimers(); jest.useRealTimers();
}); });
@ -236,54 +251,73 @@ describe('Menu', () => {
expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy(); expect(wrapper.find('PopupTrigger').first().prop('visible')).toBeTruthy();
}); });
it('test submenu in mode horizontal', () => { it('test submenu in mode horizontal', async () => {
const wrapper = mount( const defaultProps = {
<Menu mode="horizontal"> mode: 'horizontal',
};
const Demo = props => (
<Menu {...defaultProps} {...props}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => wrapper.setProps({ openKeys: ['1'] }), instance,
() => wrapper.setProps({ openKeys: [] }), () => instance.rerender(<Demo openKeys={['1']} />),
() => instance.rerender(<Demo openKeys={[]} />),
); );
instance.rerender(<Demo openKeys={['1']} />);
}); });
it('test submenu in mode inline', () => { it('test submenu in mode inline', () => {
const wrapper = mount( const defaultProps = { mode: 'inline' };
<Menu mode="inline">
const Demo = props => (
<Menu {...defaultProps} {...props}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => wrapper.setProps({ openKeys: ['1'] }), instance,
() => wrapper.setProps({ openKeys: [] }), () => instance.rerender(<Demo openKeys={['1']} />),
() => instance.rerender(<Demo openKeys={[]} />),
); );
}); });
it('test submenu in mode vertical', () => { it('test submenu in mode vertical', () => {
const wrapper = mount( const defaultProps = { mode: 'vertical', openTransitionName: '' };
<Menu mode="vertical" openTransitionName="">
const Demo = props => (
<Menu {...defaultProps} {...props}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => wrapper.setProps({ openKeys: ['1'] }), instance,
() => wrapper.setProps({ openKeys: [] }), () => instance.rerender(<Demo openKeys={['1']} />),
() => instance.rerender(<Demo openKeys={[]} />),
); );
}); });
@ -292,7 +326,7 @@ describe('Menu', () => {
menuModesWithPopupSubMenu.forEach(menuMode => { menuModesWithPopupSubMenu.forEach(menuMode => {
it(`when menu is mode ${menuMode}`, () => { it(`when menu is mode ${menuMode}`, () => {
const wrapper = mount( const { container } = render(
<Menu mode={menuMode} openKeys={['1']} theme="dark"> <Menu mode={menuMode} openKeys={['1']} theme="dark">
<SubMenu key="1" title="submenu1" theme="light"> <SubMenu key="1" title="submenu1" theme="light">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
@ -304,11 +338,10 @@ describe('Menu', () => {
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update();
}); });
expect(wrapper.find('ul.ant-menu-root').hasClass('ant-menu-dark')).toBeTruthy(); expect(container.querySelector('ul.ant-menu-root')).toHaveClass('ant-menu-dark');
expect(wrapper.find('div.ant-menu-submenu-popup').hasClass('ant-menu-light')).toBeTruthy(); expect(container.querySelector('div.ant-menu-submenu-popup')).toHaveClass('ant-menu-light');
}); });
}); });
}); });
@ -317,7 +350,7 @@ describe('Menu', () => {
// https://github.com/ant-design/ant-design/issues/4692 // https://github.com/ant-design/ant-design/issues/4692
// TypeError: Cannot read property 'indexOf' of undefined // TypeError: Cannot read property 'indexOf' of undefined
it('pr #4677 and issue #4692', () => { it('pr #4677 and issue #4692', () => {
const wrapper = mount( render(
<Menu mode="horizontal"> <Menu mode="horizontal">
<SubMenu title="submenu"> <SubMenu title="submenu">
<Menu.Item key="1">menu1</Menu.Item> <Menu.Item key="1">menu1</Menu.Item>
@ -325,25 +358,32 @@ describe('Menu', () => {
</SubMenu> </SubMenu>
</Menu>, </Menu>,
); );
wrapper.update();
act(() => {
jest.runAllTimers();
});
// just expect no error emit // just expect no error emit
}); });
it('should always follow openKeys when mode is switched', () => { it('should always follow openKeys when mode is switched', () => {
const wrapper = mount( const Demo = props => (
<Menu openKeys={['1']} mode="inline"> <Menu openKeys={['1']} mode="inline" {...props}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).toBe(false);
wrapper.setProps({ mode: 'vertical' }); const { container, rerender } = render(<Demo />);
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).toBe(false); expect(container.querySelector('ul.ant-menu-sub')).not.toHaveClass('ant-menu-hidden');
wrapper.setProps({ mode: 'inline' });
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).toBe(false); rerender(<Demo mode="vertical" />);
expect(container.querySelector('ul.ant-menu-sub')).not.toHaveClass('ant-menu-hidden');
rerender(<Demo mode="inline" />);
expect(container.querySelector('ul.ant-menu-sub')).not.toHaveClass('ant-menu-hidden');
}); });
it('should always follow openKeys when inlineCollapsed is switched', () => { it('should always follow openKeys when inlineCollapsed is switched', () => {
@ -383,8 +423,8 @@ describe('Menu', () => {
}); });
it('inlineCollapsed should works well when specify a not existed default openKeys', () => { it('inlineCollapsed should works well when specify a not existed default openKeys', () => {
const wrapper = mount( const Demo = props => (
<Menu defaultOpenKeys={['not-existed']} mode="inline"> <Menu defaultOpenKeys={['not-existed']} mode="inline" {...props}>
<Menu.Item key="menu1" icon={<InboxOutlined />}> <Menu.Item key="menu1" icon={<InboxOutlined />}>
Option Option
</Menu.Item> </Menu.Item>
@ -392,28 +432,30 @@ describe('Menu', () => {
<Menu.Item key="submenu1">Option</Menu.Item> <Menu.Item key="submenu1">Option</Menu.Item>
<Menu.Item key="submenu2">Option</Menu.Item> <Menu.Item key="submenu2">Option</Menu.Item>
</SubMenu> </SubMenu>
</Menu>, </Menu>
); );
expect(wrapper.find('.ant-menu-sub').length).toBe(0); const { container, rerender } = render(<Demo />);
wrapper.setProps({ inlineCollapsed: true });
jest.runAllTimers();
wrapper.update();
wrapper.simulate('transitionEnd', { propertyName: 'width' });
expect(container.querySelectorAll('.ant-menu-sub')).toHaveLength(0);
rerender(<Demo inlineCollapsed />);
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update();
}); });
wrapper.find('.ant-menu-submenu-title').at(0).simulate('mouseEnter'); const transitionEndEvent = new Event('transitionend');
fireEvent(container.querySelector('ul'), transitionEndEvent);
act(() => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update(); });
expect(wrapper.find('.ant-menu-submenu').at(0).hasClass('ant-menu-submenu-vertical')).toBe(
true, fireEvent.mouseEnter(container.querySelector('.ant-menu-submenu-title'));
); triggerAllTimer();
expect(wrapper.find('.ant-menu-submenu').at(0).hasClass('ant-menu-submenu-open')).toBe(true);
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-vertical')).toBe(true); expect(container.querySelector('.ant-menu-submenu')).toHaveClass('ant-menu-submenu-vertical');
expect(wrapper.find('ul.ant-menu-sub').at(0).hasClass('ant-menu-hidden')).toBe(false); expect(container.querySelector('.ant-menu-submenu')).toHaveClass('ant-menu-submenu-open');
expect(container.querySelector('ul.ant-menu-sub')).toHaveClass('ant-menu-vertical');
expect(container.querySelector('ul.ant-menu-sub')).not.toHaveClass('ant-menu-hidden');
}); });
it('inlineCollapsed Menu.Item Tooltip can be removed', () => { it('inlineCollapsed Menu.Item Tooltip can be removed', () => {
@ -451,26 +493,32 @@ describe('Menu', () => {
}); });
describe('open submenu when click submenu title', () => { describe('open submenu when click submenu title', () => {
const toggleMenu = (wrapper, index, event) => { const toggleMenu = (instance, index, event) => {
wrapper.find('.ant-menu-submenu-title').at(index).simulate(event); fireEvent[event](instance.container.querySelectorAll('.ant-menu-submenu-title')[index]);
jest.runAllTimers();
wrapper.update(); triggerAllTimer();
}; };
it('inline', () => { it('inline', () => {
const wrapper = mount( const defaultProps = { mode: 'inline' };
<Menu mode="inline">
const Demo = props => (
<Menu {...defaultProps} {...props}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => toggleMenu(wrapper, 0, 'click'), instance,
() => toggleMenu(wrapper, 0, 'click'), () => toggleMenu(instance, 0, 'click'),
() => toggleMenu(instance, 0, 'click'),
); );
}); });
@ -483,7 +531,7 @@ describe('Menu', () => {
const onOpenChange = jest.fn(); const onOpenChange = jest.fn();
const onEnterEnd = jest.spyOn(cloneMotion, 'onEnterEnd'); const onEnterEnd = jest.spyOn(cloneMotion, 'onEnterEnd');
const wrapper = mount( const { container } = render(
<Menu mode="inline" motion={cloneMotion} onOpenChange={onOpenChange}> <Menu mode="inline" motion={cloneMotion} onOpenChange={onOpenChange}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
@ -493,90 +541,105 @@ describe('Menu', () => {
</Menu>, </Menu>,
); );
wrapper.find('div.ant-menu-submenu-title').simulate('click'); fireEvent.click(container.querySelector('.ant-menu-submenu-title'));
act(() => { triggerAllTimer();
jest.runAllTimers();
wrapper.update();
});
expect(onOpenChange).toHaveBeenCalled(); expect(onOpenChange).toHaveBeenCalled();
expect(onEnterEnd).toHaveBeenCalledTimes(1); expect(onEnterEnd).toHaveBeenCalledTimes(1);
}); });
it('vertical with hover(default)', () => { it('vertical with hover(default)', () => {
const wrapper = mount( const defaultProps = { mode: 'vertical' };
<Menu mode="vertical">
const Demo = () => (
<Menu {...defaultProps}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => toggleMenu(wrapper, 0, 'mouseenter'), instance,
() => toggleMenu(wrapper, 0, 'mouseleave'), () => toggleMenu(instance, 0, 'mouseEnter'),
() => toggleMenu(instance, 0, 'mouseLeave'),
); );
}); });
it('vertical with click', () => { it('vertical with click', () => {
const wrapper = mount( const defaultProps = { mode: 'vertical', triggerSubMenuAction: 'click' };
<Menu mode="vertical" triggerSubMenuAction="click"> const Demo = () => (
<Menu {...defaultProps}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => toggleMenu(wrapper, 0, 'click'), instance,
() => toggleMenu(wrapper, 0, 'click'), () => toggleMenu(instance, 0, 'click'),
() => toggleMenu(instance, 0, 'click'),
); );
}); });
it('horizontal with hover(default)', () => { it('horizontal with hover(default)', () => {
jest.useFakeTimers(); const defaultProps = { mode: 'horizontal' };
const wrapper = mount( const Demo = () => (
<Menu mode="horizontal"> <Menu {...defaultProps}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => toggleMenu(wrapper, 0, 'mouseenter'), instance,
() => toggleMenu(wrapper, 0, 'mouseleave'), () => toggleMenu(instance, 0, 'mouseEnter'),
() => toggleMenu(instance, 0, 'mouseLeave'),
); );
}); });
it('horizontal with click', () => { it('horizontal with click', () => {
jest.useFakeTimers(); const defaultProps = { mode: 'horizontal', triggerSubMenuAction: 'click' };
const wrapper = mount( const Demo = () => (
<Menu mode="horizontal" triggerSubMenuAction="click"> <Menu {...defaultProps}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>
<Menu.Item key="submenu2">Option 2</Menu.Item> <Menu.Item key="submenu2">Option 2</Menu.Item>
</SubMenu> </SubMenu>
<Menu.Item key="2">menu2</Menu.Item> <Menu.Item key="2">menu2</Menu.Item>
</Menu>, </Menu>
); );
const instance = render(<Demo />);
expectSubMenuBehavior( expectSubMenuBehavior(
wrapper, defaultProps,
() => toggleMenu(wrapper, 0, 'click'), instance,
() => toggleMenu(wrapper, 0, 'click'), () => toggleMenu(instance, 0, 'click'),
() => toggleMenu(instance, 0, 'click'),
); );
}); });
}); });
it('inline title', () => { it('inline title', () => {
jest.useFakeTimers();
const wrapper = mount( const wrapper = mount(
<Menu mode="inline" inlineCollapsed> <Menu mode="inline" inlineCollapsed>
<Menu.Item key="1" title="bamboo lucky" icon={<PieChartOutlined />}> <Menu.Item key="1" title="bamboo lucky" icon={<PieChartOutlined />}>
@ -591,15 +654,11 @@ describe('Menu', () => {
); );
wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter'); wrapper.find('.ant-menu-item').hostNodes().simulate('mouseenter');
act(() => { triggerAllTimer();
jest.runAllTimers();
});
wrapper.update(); wrapper.update();
const text = wrapper.find('.ant-tooltip-inner').text(); const text = wrapper.find('.ant-tooltip-inner').text();
expect(text).toBe('bamboo lucky'); expect(text).toBe('bamboo lucky');
jest.useRealTimers();
}); });
it('render correctly when using with Layout.Sider', () => { it('render correctly when using with Layout.Sider', () => {
@ -632,9 +691,7 @@ describe('Menu', () => {
expect(wrapper.find(Menu).at(0).getDOMNode().classList.contains('ant-menu-inline')).toBe(true); expect(wrapper.find(Menu).at(0).getDOMNode().classList.contains('ant-menu-inline')).toBe(true);
wrapper.find('.ant-menu-submenu-title').simulate('click'); wrapper.find('.ant-menu-submenu-title').simulate('click');
wrapper.find('.ant-layout-sider-trigger').simulate('click'); wrapper.find('.ant-layout-sider-trigger').simulate('click');
act(() => { triggerAllTimer();
jest.runAllTimers();
});
wrapper.update(); wrapper.update();
expect(wrapper.find(Menu).getDOMNode().classList.contains('ant-menu-inline-collapsed')).toBe( expect(wrapper.find(Menu).getDOMNode().classList.contains('ant-menu-inline-collapsed')).toBe(
true, true,
@ -735,7 +792,7 @@ describe('Menu', () => {
const onOpen = jest.fn(); const onOpen = jest.fn();
const onClose = jest.fn(); const onClose = jest.fn();
mount( render(
<Menu defaultOpenKeys={['1']} mode="inline" onOpen={onOpen} onClose={onClose}> <Menu defaultOpenKeys={['1']} mode="inline" onOpen={onOpen} onClose={onClose}>
<SubMenu key="1" title="submenu1"> <SubMenu key="1" title="submenu1">
<Menu.Item key="submenu1">Option 1</Menu.Item> <Menu.Item key="submenu1">Option 1</Menu.Item>

View File

@ -10,6 +10,8 @@ import destroyFns from '../destroyFns';
import { sleep } from '../../../tests/utils'; import { sleep } from '../../../tests/utils';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
const { confirm } = Modal; const { confirm } = Modal;
jest.mock('rc-motion'); jest.mock('rc-motion');
@ -21,13 +23,23 @@ describe('Modal.confirm triggers callbacks correctly', () => {
CSSMotion[key] = MockCSSMotion[key]; CSSMotion[key] = MockCSSMotion[key];
}); });
// Mock for rc-util raf // // Mock for rc-util raf
window.requestAnimationFrame = callback => window.setTimeout(callback, 16); // window.requestAnimationFrame = callback => {
window.cancelAnimationFrame = id => { // const ret = window.setTimeout(callback, 16);
window.clearTimeout(id); // return ret;
}; // };
// window.cancelAnimationFrame = id => {
// window.clearTimeout(id);
// };
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); // jest.spyOn(window, 'requestAnimationFrame').mockImplementation(callback => {
// const id = window.setTimeout(callback);
// console.log('Mock Raf:', id);
// return id;
// });
// jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(id => window.clearTimeout(id));
const errorSpy = jest.spyOn(console, 'error');
/* eslint-disable no-console */ /* eslint-disable no-console */
// Hack error to remove act warning // Hack error to remove act warning
@ -42,10 +54,13 @@ describe('Modal.confirm triggers callbacks correctly', () => {
}; };
/* eslint-enable */ /* eslint-enable */
afterEach(() => { afterEach(async () => {
jest.clearAllTimers();
errorSpy.mockReset(); errorSpy.mockReset();
document.body.innerHTML = '';
Modal.destroyAll(); Modal.destroyAll();
await sleep();
document.body.innerHTML = '';
}); });
afterAll(() => { afterAll(() => {
@ -77,69 +92,88 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useRealTimers(); jest.useRealTimers();
}); });
it('trigger onCancel once when click on cancel button', () => { it('trigger onCancel once when click on cancel button', async () => {
const onCancel = jest.fn(); const onCancel = jest.fn();
const onOk = jest.fn(); const onOk = jest.fn();
open({ open({
onCancel, onCancel,
onOk, onOk,
}); });
// first Modal // first Modal
await sleep();
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
expect(onCancel.mock.calls.length).toBe(1); expect(onCancel.mock.calls.length).toBe(1);
expect(onOk.mock.calls.length).toBe(0); expect(onOk.mock.calls.length).toBe(0);
}); });
it('trigger onOk once when click on ok button', () => { it('trigger onOk once when click on ok button', async () => {
const onCancel = jest.fn(); const onCancel = jest.fn();
const onOk = jest.fn(); const onOk = jest.fn();
open({ open({
onCancel, onCancel,
onOk, onOk,
}); });
// second Modal // second Modal
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
expect(onCancel.mock.calls.length).toBe(0); expect(onCancel.mock.calls.length).toBe(0);
expect(onOk.mock.calls.length).toBe(1); expect(onOk.mock.calls.length).toBe(1);
}); });
it('should allow Modal.confirm without onCancel been set', () => { it('should allow Modal.confirm without onCancel been set', async () => {
open(); open();
await sleep();
// Third Modal // Third Modal
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
}); });
it('should allow Modal.confirm without onOk been set', () => { it('should allow Modal.confirm without onOk been set', async () => {
open(); open();
// Fourth Modal // Fourth Modal
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
}); });
it('should close confirm modal when press ESC', () => { it('should close confirm modal when press ESC', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.clearAllTimers();
const onCancel = jest.fn(); const onCancel = jest.fn();
Modal.confirm({ Modal.confirm({
title: 'title', title: 'title',
content: 'content', content: 'content',
onCancel, onCancel,
}); });
jest.runAllTimers(); jest.runAllTimers();
await sleep();
jest.runAllTimers();
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1); expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
TestUtils.Simulate.keyDown($$('.ant-modal')[0], { TestUtils.Simulate.keyDown($$('.ant-modal')[0], {
keyCode: KeyCode.ESC, keyCode: KeyCode.ESC,
}); });
jest.runAllTimers(); jest.runAllTimers();
await sleep(0);
jest.runAllTimers();
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0); expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
expect(onCancel).toHaveBeenCalledTimes(1); expect(onCancel).toHaveBeenCalledTimes(1);
jest.useRealTimers(); jest.useRealTimers();
}); });
it('should not hide confirm when onOk return Promise.resolve', () => { it('should not hide confirm when onOk return Promise.resolve', async () => {
open({ open({
onOk: () => Promise.resolve(''), onOk: () => Promise.resolve(''),
}); });
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
expect($$('.ant-modal-confirm')).toHaveLength(1); expect($$('.ant-modal-confirm')).toHaveLength(1);
}); });
@ -149,6 +183,8 @@ describe('Modal.confirm triggers callbacks correctly', () => {
open({ open({
onOk: () => Promise.reject(error), onOk: () => Promise.reject(error),
}); });
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
// wait promise // wait promise
@ -157,52 +193,72 @@ describe('Modal.confirm triggers callbacks correctly', () => {
expect(errorSpy).toHaveBeenCalledWith(error); expect(errorSpy).toHaveBeenCalledWith(error);
}); });
it('shows animation when close', () => { it('shows animation when close', async () => {
open(); open();
jest.useFakeTimers(); jest.useFakeTimers();
expect($$('.ant-modal-confirm')).toHaveLength(1);
$$('.ant-btn')[0].click();
act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); await sleep();
jest.runAllTimers();
expect($$('.ant-modal-confirm')).toHaveLength(1);
await sleep();
$$('.ant-btn')[0].click();
jest.runAllTimers();
await sleep();
jest.runAllTimers();
expect($$('.ant-modal-confirm')).toHaveLength(0); expect($$('.ant-modal-confirm')).toHaveLength(0);
jest.useRealTimers(); jest.useRealTimers();
}); });
it('ok only', () => { it('ok only', async () => {
open({ okCancel: false }); open({ okCancel: false });
await sleep();
expect($$('.ant-btn')).toHaveLength(1); expect($$('.ant-btn')).toHaveLength(1);
expect($$('.ant-btn')[0].innerHTML).toContain('OK'); expect($$('.ant-btn')[0].innerHTML).toContain('OK');
}); });
it('allows extra props on buttons', () => { it('allows extra props on buttons', async () => {
open({ okButtonProps: { disabled: true }, cancelButtonProps: { 'data-test': 'baz' } }); open({ okButtonProps: { disabled: true }, cancelButtonProps: { 'data-test': 'baz' } });
await sleep();
expect($$('.ant-btn')).toHaveLength(2); expect($$('.ant-btn')).toHaveLength(2);
expect($$('.ant-btn')[0].attributes['data-test'].value).toBe('baz'); expect($$('.ant-btn')[0].attributes['data-test'].value).toBe('baz');
expect($$('.ant-btn')[1].disabled).toBe(true); expect($$('.ant-btn')[1].disabled).toBe(true);
}); });
it('should close modals when click confirm button', () => { describe('should close modals when click confirm button', () => {
jest.useFakeTimers();
['info', 'success', 'warning', 'error'].forEach(type => { ['info', 'success', 'warning', 'error'].forEach(type => {
it(type, async () => {
jest.useFakeTimers();
Modal[type]({ Modal[type]({
title: 'title', title: 'title',
content: 'content', content: 'content',
}); });
act(() => {
await act(async () => {
jest.runAllTimers();
await sleep();
jest.runAllTimers(); jest.runAllTimers();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
act(() => { await act(async () => {
jest.runAllTimers();
await sleep();
jest.runAllTimers(); jest.runAllTimers();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
});
jest.useRealTimers(); jest.useRealTimers();
}); });
});
});
it('should close confirm modal when click cancel button', () => { it('should close confirm modal when click cancel button', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const onCancel = jest.fn(); const onCancel = jest.fn();
Modal.confirm({ Modal.confirm({
@ -215,15 +271,16 @@ describe('Modal.confirm triggers callbacks correctly', () => {
}); });
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1); expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(1);
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0); expect($$(`.ant-modal-confirm-confirm`)).toHaveLength(0);
expect(onCancel).toHaveBeenCalledTimes(1); expect(onCancel).toHaveBeenCalledTimes(1);
jest.useRealTimers(); jest.useRealTimers();
}); });
it('should close confirm modal when click close button', () => { it('should close confirm modal when click close button', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const onCancel = jest.fn(); const onCancel = jest.fn();
Modal.confirm({ Modal.confirm({
@ -238,44 +295,52 @@ describe('Modal.confirm triggers callbacks correctly', () => {
}); });
expect($$(`.ant-modal-close`)).toHaveLength(1); expect($$(`.ant-modal-close`)).toHaveLength(1);
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-close`)).toHaveLength(0); expect($$(`.ant-modal-close`)).toHaveLength(0);
expect(onCancel).toHaveBeenCalledTimes(1); expect(onCancel).toHaveBeenCalledTimes(1);
jest.useRealTimers(); jest.useRealTimers();
}); });
it('should not close modals when click confirm button when onOk has argument', () => { describe('should not close modals when click confirm button when onOk has argument', () => {
jest.useFakeTimers();
['info', 'success', 'warning', 'error'].forEach(type => { ['info', 'success', 'warning', 'error'].forEach(type => {
it(type, async () => {
jest.useFakeTimers();
Modal[type]({ Modal[type]({
title: 'title', title: 'title',
content: 'content', content: 'content',
onOk: close => null, // eslint-disable-line no-unused-vars onOk: close => null, // eslint-disable-line no-unused-vars
}); });
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
act(() => {
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
});
jest.useRealTimers(); jest.useRealTimers();
}); });
});
});
it('could be update by new config', () => { describe('could be update by new config', () => {
jest.useFakeTimers();
['info', 'success', 'warning', 'error'].forEach(type => { ['info', 'success', 'warning', 'error'].forEach(type => {
it(type, async () => {
jest.useFakeTimers();
const instance = Modal[type]({ const instance = Modal[type]({
title: 'title', title: 'title',
content: 'content', content: 'content',
}); });
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title'); expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('title');
@ -284,21 +349,25 @@ describe('Modal.confirm triggers callbacks correctly', () => {
title: 'new title', title: 'new title',
content: 'new content', content: 'new content',
}); });
act(() => {
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('new title'); expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('new title');
expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('new content'); expect($$('.ant-modal-confirm-content')[0].innerHTML).toBe('new content');
instance.destroy(); instance.destroy();
jest.runAllTimers(); jest.runAllTimers();
});
jest.useRealTimers(); jest.useRealTimers();
}); });
});
});
it('could be update by call function', () => { describe('could be update by call function', () => {
jest.useFakeTimers();
['info', 'success', 'warning', 'error'].forEach(type => { ['info', 'success', 'warning', 'error'].forEach(type => {
it(type, () => {
jest.useFakeTimers();
const instance = Modal[type]({ const instance = Modal[type]({
title: 'title', title: 'title',
okButtonProps: { okButtonProps: {
@ -335,54 +404,74 @@ describe('Modal.confirm triggers callbacks correctly', () => {
expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red'); expect($$('.ant-modal-confirm-btns .ant-btn-primary')[0].style.color).toBe('red');
instance.destroy(); instance.destroy();
jest.runAllTimers(); jest.runAllTimers();
});
jest.useRealTimers(); jest.useRealTimers();
}); });
});
});
it('could be destroy', () => { describe('could be destroy', () => {
jest.useFakeTimers();
['info', 'success', 'warning', 'error'].forEach(type => { ['info', 'success', 'warning', 'error'].forEach(type => {
jest.useFakeTimers();
it(type, async () => {
const instance = Modal[type]({ const instance = Modal[type]({
title: 'title', title: 'title',
content: 'content', content: 'content',
}); });
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
instance.destroy(); instance.destroy();
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
}); });
jest.useRealTimers(); jest.useRealTimers();
}); });
});
it('could be Modal.destroyAll', () => { it('could be Modal.destroyAll', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
// Show
['info', 'success', 'warning', 'error'].forEach(type => { ['info', 'success', 'warning', 'error'].forEach(type => {
Modal[type]({ Modal[type]({
title: 'title', title: 'title',
content: 'content', content: 'content',
}); });
act(() => {
jest.runAllTimers();
}); });
await act(async () => {
jest.runAllTimers();
await sleep();
});
['info', 'success', 'warning', 'error'].forEach(type => {
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1);
}); });
// Destroy
Modal.destroyAll(); Modal.destroyAll();
['info', 'success', 'warning', 'error'].forEach(type => {
act(() => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
}); });
['info', 'success', 'warning', 'error'].forEach(type => {
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0); expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0);
}); });
jest.useRealTimers(); jest.useRealTimers();
}); });
it('prefixCls', () => { it('prefixCls', async () => {
open({ prefixCls: 'custom-modal' }); open({ prefixCls: 'custom-modal' });
await sleep();
expect($$('.custom-modal-mask')).toHaveLength(1); expect($$('.custom-modal-mask')).toHaveLength(1);
expect($$('.custom-modal-wrap')).toHaveLength(1); expect($$('.custom-modal-wrap')).toHaveLength(1);
expect($$('.custom-modal-confirm')).toHaveLength(1); expect($$('.custom-modal-confirm')).toHaveLength(1);
@ -428,20 +517,30 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useRealTimers(); jest.useRealTimers();
}); });
it('should warning when pass a string as icon props', () => { it('should warning when pass a string as icon props', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
confirm({ confirm({
content: 'some descriptions', content: 'some descriptions',
icon: 'ab', icon: 'ab',
}); });
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
expect(warnSpy).not.toHaveBeenCalled(); expect(warnSpy).not.toHaveBeenCalled();
confirm({ confirm({
content: 'some descriptions', content: 'some descriptions',
icon: 'question', icon: 'question',
}); });
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
expect(warnSpy).toHaveBeenCalledWith( expect(warnSpy).toHaveBeenCalledWith(
`Warning: [antd: Modal] \`icon\` is using ReactNode instead of string naming in v4. Please check \`question\` at https://ant.design/components/icon`, `Warning: [antd: Modal] \`icon\` is using ReactNode instead of string naming in v4. Please check \`question\` at https://ant.design/components/icon`,
); );
@ -449,16 +548,18 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useRealTimers(); jest.useRealTimers();
}); });
it('ok button should trigger onOk once when click it many times quickly', () => { it('ok button should trigger onOk once when click it many times quickly', async () => {
const onOk = jest.fn(); const onOk = jest.fn();
open({ onOk }); open({ onOk });
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
expect(onOk).toHaveBeenCalledTimes(1); expect(onOk).toHaveBeenCalledTimes(1);
}); });
// https://github.com/ant-design/ant-design/issues/23358 // https://github.com/ant-design/ant-design/issues/23358
it('ok button should trigger onOk multiple times when onOk has close argument', () => { it('ok button should trigger onOk multiple times when onOk has close argument', async () => {
const onOk = jest.fn(); const onOk = jest.fn();
open({ open({
onOk: close => { onOk: close => {
@ -466,17 +567,24 @@ describe('Modal.confirm triggers callbacks correctly', () => {
(() => {})(close); // do nothing (() => {})(close); // do nothing
}, },
}); });
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
expect(onOk).toHaveBeenCalledTimes(3); expect(onOk).toHaveBeenCalledTimes(3);
}); });
it('should be able to global config rootPrefixCls', () => { it('should be able to global config rootPrefixCls', async () => {
jest.useFakeTimers(); jest.useFakeTimers();
ConfigProvider.config({ prefixCls: 'my', iconPrefixCls: 'bamboo' }); ConfigProvider.config({ prefixCls: 'my', iconPrefixCls: 'bamboo' });
confirm({ title: 'title', icon: <SmileOutlined /> }); confirm({ title: 'title', icon: <SmileOutlined /> });
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
expect(document.querySelectorAll('.ant-btn').length).toBe(0); expect(document.querySelectorAll('.ant-btn').length).toBe(0);
expect(document.querySelectorAll('.my-btn').length).toBe(2); expect(document.querySelectorAll('.my-btn').length).toBe(2);
expect(document.querySelectorAll('.bamboo-smile').length).toBe(1); expect(document.querySelectorAll('.bamboo-smile').length).toBe(1);
@ -485,7 +593,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useRealTimers(); jest.useRealTimers();
}); });
it('should be able to config rootPrefixCls', () => { it('should be able to config rootPrefixCls', async () => {
resetWarned(); resetWarned();
jest.useFakeTimers(); jest.useFakeTimers();
@ -500,7 +608,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
confirm({ confirm({
title: 'title', title: 'title',
}); });
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
expect(document.querySelectorAll('.ant-btn').length).toBe(0); expect(document.querySelectorAll('.ant-btn').length).toBe(0);
expect(document.querySelectorAll('.my-btn').length).toBe(2); expect(document.querySelectorAll('.my-btn').length).toBe(2);
expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1); expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
@ -510,7 +623,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
confirm({ confirm({
title: 'title', title: 'title',
}); });
await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
await sleep();
});
expect(document.querySelectorAll('.ant-btn').length).toBe(0); expect(document.querySelectorAll('.ant-btn').length).toBe(0);
expect(document.querySelectorAll('.my-btn').length).toBe(2); expect(document.querySelectorAll('.my-btn').length).toBe(2);
expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1); expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
@ -528,6 +646,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
afterClose, afterClose,
}); });
// first Modal // first Modal
await sleep();
$$('.ant-btn')[0].click(); $$('.ant-btn')[0].click();
expect(afterClose).not.toHaveBeenCalled(); expect(afterClose).not.toHaveBeenCalled();
await sleep(500); await sleep(500);
@ -539,7 +658,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
open({ open({
afterClose, afterClose,
}); });
// second Modal // second Modal
await sleep();
$$('.ant-btn-primary')[0].click(); $$('.ant-btn-primary')[0].click();
expect(afterClose).not.toHaveBeenCalled(); expect(afterClose).not.toHaveBeenCalled();
await sleep(500); await sleep(500);
@ -548,6 +669,8 @@ describe('Modal.confirm triggers callbacks correctly', () => {
it('bodyStyle', async () => { it('bodyStyle', async () => {
open({ bodyStyle: { width: 500 } }); open({ bodyStyle: { width: 500 } });
await sleep();
const { width } = $$('.ant-modal-body')[0].style; const { width } = $$('.ant-modal-body')[0].style;
expect(width).toBe('500px'); expect(width).toBe('500px');
}); });

View File

@ -1,5 +1,4 @@
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom';
import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined'; import InfoCircleOutlined from '@ant-design/icons/InfoCircleOutlined';
import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined'; import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
import CloseCircleOutlined from '@ant-design/icons/CloseCircleOutlined'; import CloseCircleOutlined from '@ant-design/icons/CloseCircleOutlined';
@ -9,6 +8,7 @@ import type { ModalFuncProps } from './Modal';
import ConfirmDialog from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog';
import { globalConfig } from '../config-provider'; import { globalConfig } from '../config-provider';
import devWarning from '../_util/devWarning'; import devWarning from '../_util/devWarning';
import { reactRender, reactUnmount } from '../_util/compatible';
import destroyFns from './destroyFns'; import destroyFns from './destroyFns';
let defaultRootPrefixCls = ''; let defaultRootPrefixCls = '';
@ -32,7 +32,6 @@ export default function confirm(config: ModalFuncProps) {
let currentConfig = { ...config, close, visible: true } as any; let currentConfig = { ...config, close, visible: true } as any;
function destroy(...args: any[]) { function destroy(...args: any[]) {
ReactDOM.unmountComponentAtNode(container);
const triggerCancel = args.some(param => param && param.triggerCancel); const triggerCancel = args.some(param => param && param.triggerCancel);
if (config.onCancel && triggerCancel) { if (config.onCancel && triggerCancel) {
config.onCancel(...args); config.onCancel(...args);
@ -45,6 +44,8 @@ export default function confirm(config: ModalFuncProps) {
break; break;
} }
} }
reactUnmount(container);
} }
function render({ okText, cancelText, prefixCls: customizePrefixCls, ...props }: any) { function render({ okText, cancelText, prefixCls: customizePrefixCls, ...props }: any) {
@ -61,7 +62,7 @@ export default function confirm(config: ModalFuncProps) {
const prefixCls = customizePrefixCls || `${rootPrefixCls}-modal`; const prefixCls = customizePrefixCls || `${rootPrefixCls}-modal`;
const iconPrefixCls = getIconPrefixCls(); const iconPrefixCls = getIconPrefixCls();
ReactDOM.render( reactRender(
<ConfirmDialog <ConfirmDialog
{...props} {...props}
prefixCls={prefixCls} prefixCls={prefixCls}
@ -83,6 +84,7 @@ export default function confirm(config: ModalFuncProps) {
if (typeof config.afterClose === 'function') { if (typeof config.afterClose === 'function') {
config.afterClose(); config.afterClose();
} }
destroy.apply(this, args); destroy.apply(this, args);
}, },
}; };

View File

@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { spyElementPrototype } from 'rc-util/lib/test/domHook'; import { spyElementPrototype } from 'rc-util/lib/test/domHook';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Popconfirm from '..'; import Popconfirm from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import { sleep } from '../../../tests/utils'; import { sleep } from '../../../tests/utils';
@ -251,18 +253,19 @@ describe('Popconfirm', () => {
return <Button>Unmounted</Button>; return <Button>Unmounted</Button>;
}; };
const wrapper = mount( const { container } = render(
<div> <div>
<Test /> <Test />
</div>, </div>,
); );
expect(wrapper.text()).toEqual('Test'); expect(container.textContent).toEqual('Test');
const triggerNode = wrapper.find('.clickTarget').at(0);
triggerNode.simulate('click'); fireEvent.click(container.querySelector('.clickTarget'));
wrapper.find('.ant-btn-primary').simulate('click'); fireEvent.click(container.querySelector('.ant-btn-primary'));
await sleep(500); await sleep(500);
expect(wrapper.text()).toEqual('Unmounted'); expect(container.textContent).toEqual('Unmounted');
expect(error).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled();
}); });
}); });

View File

@ -9,8 +9,8 @@ Array [
</span>, </span>,
<div> <div>
<div <div
class="ant-popover ant-popover-rtl" class="ant-popover ant-popover-rtl ant-zoom-big-appear ant-zoom-big-appear-prepare ant-zoom-big"
style="opacity:0;pointer-events:none" style="opacity: 0; pointer-events: none;"
> >
<div <div
class="ant-popover-content" class="ant-popover-content"

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { render, mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Popover from '..'; import Popover from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import ConfigProvider from '../../config-provider'; import ConfigProvider from '../../config-provider';
@ -76,13 +78,13 @@ describe('Popover', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const overlay = jest.fn(); const overlay = jest.fn();
mount( render(
<Popover content="console.log('hello world')" title="code" trigger="click"> <Popover content="console.log('hello world')" title="code" trigger="click">
<span>show me your code</span> <span>show me your code</span>
</Popover>, </Popover>,
); );
expect(errorSpy.mock.calls.length).toBe(0); expect(errorSpy).not.toHaveBeenCalled();
expect(overlay).not.toHaveBeenCalled(); expect(overlay).not.toHaveBeenCalled();
}); });
@ -94,6 +96,6 @@ describe('Popover', () => {
</Popover> </Popover>
</ConfigProvider>, </ConfigProvider>,
); );
expect(render(wrapper)).toMatchSnapshot(); expect(wrapper.render()).toMatchSnapshot();
}); });
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Result from '..'; import Result from '..';
import Button from '../../button'; import Button from '../../button';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
@ -60,12 +62,15 @@ describe('Result', () => {
it('should warning when pass a string as icon props', () => { it('should warning when pass a string as icon props', () => {
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Result title="404" icon="ab" />);
render(<Result title="404" icon="ab" />);
expect(warnSpy).not.toHaveBeenCalled(); expect(warnSpy).not.toHaveBeenCalled();
mount(<Result title="404" icon="smile" />);
render(<Result title="404" icon="smile" />);
expect(warnSpy).toHaveBeenCalledWith( expect(warnSpy).toHaveBeenCalledWith(
`Warning: [antd: Result] \`icon\` is using ReactNode instead of string naming in v4. Please check \`smile\` at https://ant.design/components/icon`, `Warning: [antd: Result] \`icon\` is using ReactNode instead of string naming in v4. Please check \`smile\` at https://ant.design/components/icon`,
); );
warnSpy.mockRestore(); warnSpy.mockRestore();
}); });
}); });

View File

@ -2,6 +2,8 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Table from '..'; import Table from '..';
import Input from '../../input'; import Input from '../../input';
import Tooltip from '../../tooltip'; import Tooltip from '../../tooltip';
@ -101,8 +103,8 @@ describe('Table.filter', () => {
it('renders empty menu correctly', () => { it('renders empty menu correctly', () => {
jest.useFakeTimers(); jest.useFakeTimers();
jest.spyOn(console, 'error').mockImplementation(() => undefined); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
const wrapper = mount( const { container } = render(
createTable({ createTable({
columns: [ columns: [
{ {
@ -112,17 +114,16 @@ describe('Table.filter', () => {
], ],
}), }),
); );
wrapper.find('span.ant-dropdown-trigger').simulate('click', nativeEvent);
fireEvent.click(container.querySelector('span.ant-dropdown-trigger'), nativeEvent);
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
wrapper.update();
}); });
expect(wrapper.find('Empty').length).toBeTruthy(); expect(container.querySelector('.ant-empty')).toBeTruthy();
// eslint-disable-next-line no-console expect(errorSpy).not.toHaveBeenCalled();
expect(console.error).not.toHaveBeenCalled(); errorSpy.mockRestore();
// eslint-disable-next-line no-console
console.error.mockRestore();
jest.useRealTimers(); jest.useRealTimers();
}); });
@ -1964,7 +1965,11 @@ describe('Table.filter', () => {
expect(wrapper.find('.ant-tree-checkbox').at(0).hasClass('ant-tree-checkbox-checked')).toBe( expect(wrapper.find('.ant-tree-checkbox').at(0).hasClass('ant-tree-checkbox-checked')).toBe(
true, true,
); );
expect(wrapper.find('.ant-table-filter-dropdown-checkall .ant-checkbox').hasClass('ant-checkbox-indeterminate')).toBe(true); expect(
wrapper
.find('.ant-table-filter-dropdown-checkall .ant-checkbox')
.hasClass('ant-checkbox-indeterminate'),
).toBe(true);
}); });
it('select-all checkbox should change when all items are selected', () => { it('select-all checkbox should change when all items are selected', () => {
@ -1991,7 +1996,11 @@ describe('Table.filter', () => {
}); });
wrapper.find('.ant-tree-node-content-wrapper').at(0).simulate('click'); wrapper.find('.ant-tree-node-content-wrapper').at(0).simulate('click');
wrapper.find('.ant-tree-node-content-wrapper').at(1).simulate('click'); wrapper.find('.ant-tree-node-content-wrapper').at(1).simulate('click');
expect(wrapper.find('.ant-table-filter-dropdown-checkall .ant-checkbox').hasClass('ant-checkbox-checked')).toBe(true); expect(
wrapper
.find('.ant-table-filter-dropdown-checkall .ant-checkbox')
.hasClass('ant-checkbox-checked'),
).toBe(true);
}); });
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Table from '..'; import Table from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
@ -258,7 +260,7 @@ describe('Table', () => {
dataIndex: 'name', dataIndex: 'name',
}, },
]; ];
mount(<Table columns={columns} rowKey={record => record.key} />); render(<Table columns={columns} rowKey={record => record.key} />);
expect(warnSpy).not.toBeCalled(); expect(warnSpy).not.toBeCalled();
}); });
@ -275,7 +277,7 @@ describe('Table', () => {
const ref = React.useRef(); const ref = React.useRef();
return <Table ref={ref} columns={columns} />; return <Table ref={ref} columns={columns} />;
}; };
mount(<Wrapper />); render(<Wrapper />);
expect(warnSpy).not.toBeCalled(); expect(warnSpy).not.toBeCalled();
}); });
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Transfer from '../index'; import Transfer from '../index';
describe('Transfer.Customize', () => { describe('Transfer.Customize', () => {
@ -15,9 +17,9 @@ describe('Transfer.Customize', () => {
it('props#body does not work anymore', () => { it('props#body does not work anymore', () => {
const body = jest.fn(); const body = jest.fn();
mount(<Transfer body={body} />); render(<Transfer body={body} />);
expect(errorSpy.mock.calls.length).toBe(0); expect(errorSpy).not.toHaveBeenCalled();
expect(body).not.toHaveBeenCalled(); expect(body).not.toHaveBeenCalled();
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Search from '../search'; import Search from '../search';
import Transfer from '../index'; import Transfer from '../index';
@ -66,14 +68,14 @@ describe('Transfer.Search', () => {
it('legacy props#onSearchChange doesnot work anymore', () => { it('legacy props#onSearchChange doesnot work anymore', () => {
const onSearchChange = jest.fn(); const onSearchChange = jest.fn();
const wrapper = mount( const { container } = render(
<Transfer render={item => item.title} onSearchChange={onSearchChange} showSearch />, <Transfer render={item => item.title} onSearchChange={onSearchChange} showSearch />,
); );
wrapper
.find('.ant-input') fireEvent.change(container.querySelector('.ant-input'), {
.at(0) target: { value: 'a' },
.simulate('change', { target: { value: 'a' } }); });
expect(errorSpy.mock.calls.length).toBe(0); expect(errorSpy).not.toHaveBeenCalled();
expect(onSearchChange).not.toHaveBeenCalled(); expect(onSearchChange).not.toHaveBeenCalled();
}); });

View File

@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { SmileOutlined, LikeOutlined, HighlightOutlined, CheckOutlined } from '@ant-design/icons'; import { SmileOutlined, LikeOutlined, HighlightOutlined, CheckOutlined } from '@ant-design/icons';
import KeyCode from 'rc-util/lib/KeyCode'; import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning'; import { resetWarned } from 'rc-util/lib/warning';
@ -398,7 +400,7 @@ describe('Typography', () => {
it('no italic warning', () => { it('no italic warning', () => {
resetWarned(); resetWarned();
mount(<Text italic>Little</Text>); render(<Text italic>Little</Text>);
expect(errorSpy).not.toHaveBeenCalled(); expect(errorSpy).not.toHaveBeenCalled();
}); });

View File

@ -1,6 +1,8 @@
/* eslint-disable react/no-string-refs, react/prefer-es6-class */ /* eslint-disable react/no-string-refs, react/prefer-es6-class */
import React from 'react'; import React from 'react';
import { mount, render } from 'enzyme'; import { mount } from 'enzyme';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { act } from 'react-dom/test-utils'; import { act } from 'react-dom/test-utils';
import produce from 'immer'; import produce from 'immer';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
@ -13,6 +15,8 @@ import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { sleep } from '../../../tests/utils'; import { sleep } from '../../../tests/utils';
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
describe('Upload', () => { describe('Upload', () => {
mountTest(Upload); mountTest(Upload);
rtlTest(Upload); rtlTest(Upload);
@ -296,7 +300,7 @@ describe('Upload', () => {
url: 'http://www.baidu.com/xxx.png', url: 'http://www.baidu.com/xxx.png',
}, },
]; ];
render(<Upload fileList={fileList} />); mount(<Upload fileList={fileList} />);
fileList.forEach(file => { fileList.forEach(file => {
expect(file.uid).toBeDefined(); expect(file.uid).toBeDefined();
}); });
@ -851,19 +855,22 @@ describe('Upload', () => {
// IE11 Does not support the File constructor // IE11 Does not support the File constructor
it('should not break in IE if beforeUpload returns false', async () => { it('should not break in IE if beforeUpload returns false', async () => {
const onChange = jest.fn(); const onChange = jest.fn();
const wrapper = mount(<Upload beforeUpload={() => false} fileList={[]} onChange={onChange} />); const { container } = render(
<Upload beforeUpload={() => false} fileList={[]} onChange={onChange} />,
);
const fileConstructor = () => { const fileConstructor = () => {
throw new TypeError("Object doesn't support this action"); throw new TypeError("Object doesn't support this action");
}; };
global.File = jest.fn().mockImplementationOnce(fileConstructor);
await act(async () => jest.spyOn(global, 'File').mockImplementationOnce(fileConstructor);
wrapper.find('input').simulate('change', { fireEvent.change(container.querySelector('input'), {
target: { target: {
files: [{ file: 'foo.png' }], files: [{ file: 'foo.png' }],
}, },
}), });
);
// React 18 is async now
await sleep();
expect(onChange.mock.calls[0][0].fileList).toHaveLength(1); expect(onChange.mock.calls[0][0].fileList).toHaveLength(1);
}); });

View File

@ -101,7 +101,7 @@
"version": "node ./scripts/generate-version", "version": "node ./scripts/generate-version",
"install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16 enzyme-adapter-react-16", "install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16 enzyme-adapter-react-16",
"install-react-17": "npm i --no-save --legacy-peer-deps react@17 react-dom@17", "install-react-17": "npm i --no-save --legacy-peer-deps react@17 react-dom@17",
"install-react-18": "npm i --no-save --legacy-peer-deps react@18 react-dom@18", "install-react-18": "npm i --no-save --legacy-peer-deps react@18 react-dom@18 @testing-library/react@13",
"argos": "argos upload imageSnapshots" "argos": "argos upload imageSnapshots"
}, },
"browserslist": [ "browserslist": [
@ -165,8 +165,8 @@
"@docsearch/react": "^3.0.0-alpha.39", "@docsearch/react": "^3.0.0-alpha.39",
"@qixian.cs/github-contributors-list": "^1.0.3", "@qixian.cs/github-contributors-list": "^1.0.3",
"@stackblitz/sdk": "^1.3.0", "@stackblitz/sdk": "^1.3.0",
"@testing-library/jest-dom": "^5.16.2", "@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4", "@testing-library/react": "^12.0.0",
"@types/enzyme": "^3.10.5", "@types/enzyme": "^3.10.5",
"@types/gtag.js": "^0.0.10", "@types/gtag.js": "^0.0.10",
"@types/jest": "^27.0.0", "@types/jest": "^27.0.0",