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
strategy:
matrix:
react: ['16', '17']
# react: ['17', '18']
react: ['16', '17', '18']
module: ['dom', 'node', 'dist']
env:
REACT: ${{ matrix.react }}
@ -349,8 +348,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
react: ['16', '17']
# react: ['17', '18']
react: ['16', '17', '18']
module: [lib, es]
env:
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 { act } from 'react-dom/test-utils';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Avatar from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -144,9 +146,10 @@ describe('Avatar Render', () => {
it('should warning when pass a string as icon props', () => {
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Avatar size={64} icon="aa" />);
render(<Avatar size={64} icon="aa" />);
expect(warnSpy).not.toHaveBeenCalled();
mount(<Avatar size={64} icon="user" />);
render(<Avatar size={64} icon="user" />);
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`,
);

View File

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

View File

@ -1,5 +1,7 @@
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 { SearchOutlined } from '@ant-design/icons';
import { resetWarned } from 'rc-util/lib/warning';
@ -39,7 +41,7 @@ describe('Button', () => {
const mockWarn = jest.fn();
jest.spyOn(console, 'warn').mockImplementation(mockWarn);
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.mock.calls[0][0]).toMatchObject({
message: 'unreachable case: "who am I"',
@ -51,12 +53,14 @@ describe('Button', () => {
// should not insert space when there is icon
expect(mount(<Button icon={<SearchOutlined />}></Button>).render()).toMatchSnapshot();
// should not insert space when there is icon
expect(mount(
<Button>
<SearchOutlined />
</Button>,
).render()).toMatchSnapshot();
expect(
mount(
<Button>
<SearchOutlined />
</Button>,
).render(),
).toMatchSnapshot();
// should not insert space when there is icon
expect(mount(<Button icon={<SearchOutlined />}></Button>).render()).toMatchSnapshot();
// 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', () => {
resetWarned();
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Button type="primary" icon="ab" />);
render(<Button type="primary" icon="ab" />);
expect(warnSpy).not.toHaveBeenCalled();
mount(<Button type="primary" icon="search" />);
render(<Button type="primary" icon="search" />);
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`,
);
warnSpy.mockRestore();
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Result from '..';
import Button from '../../button';
import mountTest from '../../../tests/shared/mountTest';
@ -60,12 +62,15 @@ describe('Result', () => {
it('should warning when pass a string as icon props', () => {
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
mount(<Result title="404" icon="ab" />);
render(<Result title="404" icon="ab" />);
expect(warnSpy).not.toHaveBeenCalled();
mount(<Result title="404" icon="smile" />);
render(<Result title="404" icon="smile" />);
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`,
);
warnSpy.mockRestore();
});
});

View File

@ -2,6 +2,8 @@
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import { render, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Table from '..';
import Input from '../../input';
import Tooltip from '../../tooltip';
@ -101,8 +103,8 @@ describe('Table.filter', () => {
it('renders empty menu correctly', () => {
jest.useFakeTimers();
jest.spyOn(console, 'error').mockImplementation(() => undefined);
const wrapper = mount(
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
const { container } = render(
createTable({
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(() => {
jest.runAllTimers();
wrapper.update();
});
expect(wrapper.find('Empty').length).toBeTruthy();
// eslint-disable-next-line no-console
expect(console.error).not.toHaveBeenCalled();
// eslint-disable-next-line no-console
console.error.mockRestore();
expect(container.querySelector('.ant-empty')).toBeTruthy();
expect(errorSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
jest.useRealTimers();
});
@ -1964,7 +1965,11 @@ describe('Table.filter', () => {
expect(wrapper.find('.ant-tree-checkbox').at(0).hasClass('ant-tree-checkbox-checked')).toBe(
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', () => {
@ -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(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 { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Table from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -258,7 +260,7 @@ describe('Table', () => {
dataIndex: 'name',
},
];
mount(<Table columns={columns} rowKey={record => record.key} />);
render(<Table columns={columns} rowKey={record => record.key} />);
expect(warnSpy).not.toBeCalled();
});
@ -275,7 +277,7 @@ describe('Table', () => {
const ref = React.useRef();
return <Table ref={ref} columns={columns} />;
};
mount(<Wrapper />);
render(<Wrapper />);
expect(warnSpy).not.toBeCalled();
});
});

View File

@ -1,5 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import Transfer from '../index';
describe('Transfer.Customize', () => {
@ -15,9 +17,9 @@ describe('Transfer.Customize', () => {
it('props#body does not work anymore', () => {
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();
});

View File

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

View File

@ -1,5 +1,7 @@
import React from 'react';
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 KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning';
@ -398,7 +400,7 @@ describe('Typography', () => {
it('no italic warning', () => {
resetWarned();
mount(<Text italic>Little</Text>);
render(<Text italic>Little</Text>);
expect(errorSpy).not.toHaveBeenCalled();
});

View File

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

View File

@ -101,7 +101,7 @@
"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-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"
},
"browserslist": [
@ -165,8 +165,8 @@
"@docsearch/react": "^3.0.0-alpha.39",
"@qixian.cs/github-contributors-list": "^1.0.3",
"@stackblitz/sdk": "^1.3.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.0.0",
"@types/enzyme": "^3.10.5",
"@types/gtag.js": "^0.0.10",
"@types/jest": "^27.0.0",