mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
Merge branch 'master' into next-merge-master
This commit is contained in:
commit
e6335e233c
@ -1,4 +1,4 @@
|
||||
{
|
||||
"sandboxes": ["antd-reproduction-template-6e93z"],
|
||||
"sandboxes": ["antd-reproduction-template-y9vgcf"],
|
||||
"node": "14"
|
||||
}
|
||||
|
2
.github/workflows/release-helper.yml
vendored
2
.github/workflows/release-helper.yml
vendored
@ -40,7 +40,7 @@ jobs:
|
||||
triger: 'tag'
|
||||
changelogs: 'CHANGELOG.en-US.md, CHANGELOG.zh-CN.md'
|
||||
branch: 'master'
|
||||
dingding-token: ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }}
|
||||
dingding-token: ${{ secrets.DINGDING_BOT_BIGFISH_TOKEN }} ${{ secrets.DINGDING_BOT_YUNFENGDIE_TOKEN }}
|
||||
dingding-msg: 'CHANGELOG.zh-CN.md'
|
||||
dingding-delay-minute: 10
|
||||
release: false
|
||||
|
@ -15,6 +15,31 @@ timeline: true
|
||||
|
||||
---
|
||||
|
||||
## 4.22.5
|
||||
|
||||
`2022-08-15`
|
||||
|
||||
- 🇭🇺 Form add defaultValidateMessages.i18n az_AZ language #23369. [#36967](https://github.com/ant-design/ant-design/pull/36967) [@YMiemie-cy](https://github.com/YMiemie-cy)
|
||||
- 💄 Popover remove Popover incorrect box-shadow. [#37030](https://github.com/ant-design/ant-design/pull/37030) [@jerrykingxyz](https://github.com/jerrykingxyz)
|
||||
- 🐞 Fix Steps tail position bug when `labelPlacement="vertical"`. [#36996](https://github.com/ant-design/ant-design/pull/36996)
|
||||
- 💄 Removed exception error border for `Pagination`. [#36972](https://github.com/ant-design/ant-design/pull/36972) [@hydraZty](https://github.com/hydraZty)
|
||||
- 🐞 Fix Upload trigger mistake files status when upload multiple files at same time in React 18. [#36968](https://github.com/ant-design/ant-design/pull/36968)
|
||||
- TypeScript
|
||||
- 🤖 Fix type error of useWatch. [#37013](https://github.com/ant-design/ant-design/pull/37013) [@LiZhiHao97](https://github.com/LiZhiHao97)
|
||||
|
||||
## 4.22.4
|
||||
|
||||
`2022-08-08`
|
||||
|
||||
- 🐞 Fix Drawer `zIndex` prop not working. [#36958](https://github.com/ant-design/ant-design/pull/36958)
|
||||
- 💄 Fix PageHeader focus style stay after clicking it. [#36902](https://github.com/ant-design/ant-design/pull/36902)
|
||||
- 💄 Fix Drawer nesting content-wrapper style. [#36845](https://github.com/ant-design/ant-design/pull/36845) [@yanm1ng](https://github.com/yanm1ng)
|
||||
- 🐞 Fix Mentions popup shift when contain scrollbar. [#36898](https://github.com/ant-design/ant-design/pull/36898) [@JarvisArt](https://github.com/JarvisArt)
|
||||
- TypeScript
|
||||
- 🤖 Fix Avatar `onClick` type missing. [#36940](https://github.com/ant-design/ant-design/pull/36940) [@kungege](https://github.com/kungege)
|
||||
- 🤖 Fix Table `onChange` argument `sorter` type error. [#36710](https://github.com/ant-design/ant-design/pull/36710) [@kungege](https://github.com/kungege)
|
||||
- 🤖 Input `data-*` should not be required. [#36858](https://github.com/ant-design/ant-design/pull/36858) [@yifanwww](https://github.com/yifanwww)
|
||||
|
||||
## 4.22.3
|
||||
|
||||
`2022-08-01`
|
||||
|
@ -15,6 +15,31 @@ timeline: true
|
||||
|
||||
---
|
||||
|
||||
## 4.22.5
|
||||
|
||||
`2022-08-15`
|
||||
|
||||
- 🇭🇺 Form `defaultValidateMessages.i18n` 增加匈牙利语。[#36967](https://github.com/ant-design/ant-design/pull/36967) [@YMiemie-cy](https://github.com/YMiemie-cy)
|
||||
- 💄 移除 Popover 中无效的 `box-shadow` 样式。[#37030](https://github.com/ant-design/ant-design/pull/37030) [@jerrykingxyz](https://github.com/jerrykingxyz)
|
||||
- 🐞 修复 Steps `labelPlacement="vertical"` 时连接线位置偏上的问题。[#36996](https://github.com/ant-design/ant-design/pull/36996)
|
||||
- 💄 去掉 Pagination 的异常错误边框。[#36972](https://github.com/ant-design/ant-design/pull/36972) [@hydraZty](https://github.com/hydraZty)
|
||||
- 🐞 修复 Upload 在 React 18 下同时上传多份文件会出现上传状态不正确的问题。[#36968](https://github.com/ant-design/ant-design/pull/36968)
|
||||
- TypeScript
|
||||
- 🤖 修复 useWatch 的类型错误。[#37013](https://github.com/ant-design/ant-design/pull/37013) [@LiZhiHao97](https://github.com/LiZhiHao97)
|
||||
|
||||
## 4.22.4
|
||||
|
||||
`2022-08-08`
|
||||
|
||||
- 🐞 修复 Drawer `zIndex` 属性失效的问题。[#36958](https://github.com/ant-design/ant-design/pull/36958)
|
||||
- 💄 修复 PageHeader 返回按钮点击后依然有聚焦样式的问题。[#36902](https://github.com/ant-design/ant-design/pull/36902)
|
||||
- 💄 修复抽屉 Drawer 组件嵌套时,子抽屉的样式被父抽屉影响的问题。[#36845](https://github.com/ant-design/ant-design/pull/36845) [@yanm1ng](https://github.com/yanm1ng)
|
||||
- 🐞 修复 Mentions 内有滚动条时弹出层偏移的问题。[#36898](https://github.com/ant-design/ant-design/pull/36898) [@JarvisArt](https://github.com/JarvisArt)
|
||||
- TypeScript
|
||||
- 🤖 修复 Avatar `onClick` 属性定义丢失的问题。[#36940](https://github.com/ant-design/ant-design/pull/36940) [@kungege](https://github.com/kungege)
|
||||
- 🤖 修复 Table `onChange` 参数 `sorter` 类型报错的问题。[#36710](https://github.com/ant-design/ant-design/pull/36710) [@kungege](https://github.com/kungege)
|
||||
- 🤖 Input `data-*` 属性将不再是必传参数。[#36858](https://github.com/ant-design/ant-design/pull/36858) [@yifanwww](https://github.com/yifanwww)
|
||||
|
||||
## 4.22.3
|
||||
|
||||
`2022-08-01`
|
||||
|
@ -1,121 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutoComplete legacy dataSource should accept react element option 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-auto-complete ant-select-single ant-select-open ant-select-show-search"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="rc_select_TEST_OR_SSR_list_-1"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="rc_select_TEST_OR_SSR_list"
|
||||
aria-expanded="true"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="rc_select_TEST_OR_SSR_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
id="rc_select_TEST_OR_SSR"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="ant-select-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
|
||||
style="opacity: 0; min-width: 0; width: 0px;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="rc_select_TEST_OR_SSR_list"
|
||||
role="listbox"
|
||||
style="height: 0px; width: 0px; overflow: hidden;"
|
||||
>
|
||||
<div
|
||||
aria-label="ReactNode"
|
||||
aria-selected="false"
|
||||
id="rc_select_TEST_OR_SSR_list_0"
|
||||
role="option"
|
||||
>
|
||||
key
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="rc-virtual-list"
|
||||
style="position: relative;"
|
||||
>
|
||||
<div
|
||||
class="rc-virtual-list-holder"
|
||||
style="max-height: 256px; overflow-y: auto;"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="rc-virtual-list-holder-inner"
|
||||
style="display: flex; flex-direction: column;"
|
||||
>
|
||||
<div
|
||||
aria-selected="false"
|
||||
class="ant-select-item ant-select-item-option"
|
||||
title="ReactNode"
|
||||
>
|
||||
<div
|
||||
class="ant-select-item-option-content"
|
||||
>
|
||||
ReactNode
|
||||
</div>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="ant-select-item-option-state"
|
||||
style="user-select: none;"
|
||||
unselectable="on"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`AutoComplete rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-rtl ant-select-auto-complete ant-select-single ant-select-show-search"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="rc_select_TEST_OR_SSR_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="rc_select_TEST_OR_SSR_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
id="rc_select_TEST_OR_SSR"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,32 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AutoComplete rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-select ant-select-rtl ant-select-auto-complete ant-select-single ant-select-show-search"
|
||||
>
|
||||
<div
|
||||
class="ant-select-selector"
|
||||
>
|
||||
<span
|
||||
class="ant-select-selection-search"
|
||||
>
|
||||
<input
|
||||
aria-activedescendant="rc_select_TEST_OR_SSR_list_0"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="rc_select_TEST_OR_SSR_list"
|
||||
aria-haspopup="listbox"
|
||||
aria-owns="rc_select_TEST_OR_SSR_list"
|
||||
autocomplete="off"
|
||||
class="ant-select-selection-search-input"
|
||||
id="rc_select_TEST_OR_SSR"
|
||||
role="combobox"
|
||||
type="search"
|
||||
value=""
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
class="ant-select-selection-placeholder"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -1,30 +1,35 @@
|
||||
import React from 'react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import AutoComplete from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import { render, screen } from '../../../tests/utils';
|
||||
import Input from '../../input';
|
||||
|
||||
describe('AutoComplete', () => {
|
||||
mountTest(AutoComplete);
|
||||
rtlTest(AutoComplete);
|
||||
|
||||
it('AutoComplete with custom Input render perfectly', () => {
|
||||
const { container } = render(
|
||||
it('AutoComplete with custom Input render perfectly', async () => {
|
||||
render(
|
||||
<AutoComplete dataSource={['12345', '23456', '34567']}>
|
||||
<textarea />
|
||||
</AutoComplete>,
|
||||
);
|
||||
|
||||
expect(container.querySelectorAll('textarea').length).toBe(1);
|
||||
fireEvent.change(container.querySelector('textarea'), { target: { value: '123' } });
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
||||
|
||||
// should not filter data source defaultly
|
||||
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(3);
|
||||
// should show options when type in input
|
||||
await userEvent.type(screen.getByRole('combobox'), '123');
|
||||
|
||||
// should not filter data source by default
|
||||
expect(screen.getByTitle('12345')).toBeInTheDocument();
|
||||
expect(screen.getByTitle('23456')).toBeInTheDocument();
|
||||
expect(screen.getByTitle('34567')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('AutoComplete should work when dataSource is object array', () => {
|
||||
const { container } = render(
|
||||
it('AutoComplete should work when dataSource is object array', async () => {
|
||||
render(
|
||||
<AutoComplete
|
||||
dataSource={[
|
||||
{ text: 'text', value: 'value' },
|
||||
@ -34,17 +39,19 @@ describe('AutoComplete', () => {
|
||||
<input />
|
||||
</AutoComplete>,
|
||||
);
|
||||
expect(container.querySelectorAll('input').length).toBe(1);
|
||||
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByRole('combobox'), 'a');
|
||||
|
||||
// should not filter data source defaultly
|
||||
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(2);
|
||||
// should not filter data source by default
|
||||
expect(screen.getByTitle('text')).toBeInTheDocument();
|
||||
expect(screen.getByTitle('abc')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('AutoComplete throws error when contains invalid dataSource', () => {
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
|
||||
|
||||
render(
|
||||
// @ts-ignore
|
||||
<AutoComplete dataSource={[() => {}]}>
|
||||
<textarea />
|
||||
</AutoComplete>,
|
||||
@ -54,22 +61,23 @@ describe('AutoComplete', () => {
|
||||
});
|
||||
|
||||
it('legacy dataSource should accept react element option', () => {
|
||||
const { asFragment } = render(
|
||||
<AutoComplete open dataSource={[<span key="key">ReactNode</span>]} />,
|
||||
);
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
render(<AutoComplete open dataSource={[<span key="key">ReactNode</span>]} />);
|
||||
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
||||
expect(screen.getByTitle(/reactnode/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('legacy AutoComplete.Option should be compatiable', () => {
|
||||
const { container } = render(
|
||||
it('legacy AutoComplete.Option should be compatiable', async () => {
|
||||
render(
|
||||
<AutoComplete>
|
||||
<AutoComplete.Option value="111">111</AutoComplete.Option>
|
||||
<AutoComplete.Option value="222">222</AutoComplete.Option>
|
||||
</AutoComplete>,
|
||||
);
|
||||
expect(container.querySelectorAll('input').length).toBe(1);
|
||||
fireEvent.change(container.querySelector('input'), { target: { value: '1' } });
|
||||
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(2);
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
||||
await userEvent.type(screen.getByRole('combobox'), '1');
|
||||
expect(screen.getByTitle(/111/i)).toBeInTheDocument();
|
||||
expect(screen.getByTitle(/222/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not warning when getInputElement is null', () => {
|
||||
@ -77,17 +85,18 @@ describe('AutoComplete', () => {
|
||||
render(<AutoComplete placeholder="input here" allowClear />);
|
||||
// eslint-disable-next-line no-console
|
||||
expect(console.warn).not.toBeCalled();
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn.mockRestore();
|
||||
});
|
||||
|
||||
it('should not override custom input className', () => {
|
||||
const { container } = render(
|
||||
render(
|
||||
<AutoComplete>
|
||||
<Input className="custom" />
|
||||
</AutoComplete>,
|
||||
);
|
||||
expect(container.querySelector('input').classList.contains('custom')).toBeTruthy();
|
||||
expect(screen.getByRole('combobox')).toHaveClass('custom');
|
||||
});
|
||||
|
||||
it('should show warning when use dropdownClassName', () => {
|
2833
components/calendar/__tests__/__snapshots__/index.test.tsx.snap
Normal file
2833
components/calendar/__tests__/__snapshots__/index.test.tsx.snap
Normal file
File diff suppressed because it is too large
Load Diff
453
components/calendar/__tests__/index.test.tsx
Normal file
453
components/calendar/__tests__/index.test.tsx
Normal file
@ -0,0 +1,453 @@
|
||||
import { mount } from 'enzyme';
|
||||
import MockDate from 'mockdate';
|
||||
import Moment from 'moment';
|
||||
import momentGenerateConfig from 'rc-picker/lib/generate/moment';
|
||||
import type { Locale } from 'rc-picker/lib/interface';
|
||||
import React from 'react';
|
||||
import Calendar from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import Group from '../../radio/group';
|
||||
import Button from '../../radio/radioButton';
|
||||
import Select from '../../select';
|
||||
import Header from '../Header';
|
||||
|
||||
describe('Calendar', () => {
|
||||
mountTest(Calendar);
|
||||
rtlTest(Calendar, true);
|
||||
|
||||
function openSelect(wrapper: HTMLElement, className: string) {
|
||||
fireEvent.mouseDown(wrapper.querySelector(className)!.querySelector('.ant-select-selector')!);
|
||||
}
|
||||
|
||||
function findSelectItem(wrapper: HTMLElement) {
|
||||
return wrapper.querySelectorAll('.ant-select-item-option')!;
|
||||
}
|
||||
|
||||
function clickSelectItem(wrapper: HTMLElement, index = 0) {
|
||||
fireEvent.click(findSelectItem(wrapper)[index]);
|
||||
}
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/30392
|
||||
it('should be able to set undefined or null', () => {
|
||||
expect(() => {
|
||||
const wrapper = mount(<Calendar />);
|
||||
wrapper.setProps({ value: null });
|
||||
}).not.toThrow();
|
||||
expect(() => {
|
||||
const wrapper = mount(<Calendar />);
|
||||
wrapper.setProps({ value: undefined });
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('Calendar should be selectable', () => {
|
||||
MockDate.set(Moment('2000-01-01').valueOf());
|
||||
|
||||
const onSelect = jest.fn();
|
||||
const onChange = jest.fn();
|
||||
const { container } = render(<Calendar onSelect={onSelect} onChange={onChange} />);
|
||||
|
||||
fireEvent.click(container.querySelector('.ant-picker-cell')!);
|
||||
expect(onSelect).toHaveBeenCalledWith(expect.anything());
|
||||
|
||||
const value = onSelect.mock.calls[0][0];
|
||||
expect(Moment.isMoment(value)).toBe(true);
|
||||
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
|
||||
MockDate.reset();
|
||||
});
|
||||
|
||||
it('only Valid range should be selectable', () => {
|
||||
const onSelect = jest.fn();
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2018-02-18')];
|
||||
const wrapper = mount(
|
||||
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />,
|
||||
);
|
||||
wrapper.find('[title="2018-02-01"]').at(0).simulate('click');
|
||||
wrapper.find('[title="2018-02-02"]').at(0).simulate('click');
|
||||
expect(onSelect.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('dates other than in valid range should be disabled', () => {
|
||||
const onSelect = jest.fn();
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2018-02-18')];
|
||||
const { container } = render(
|
||||
<Calendar onSelect={onSelect} validRange={validRange} defaultValue={Moment('2018-02-02')} />,
|
||||
);
|
||||
fireEvent.click(container.querySelector('[title="2018-02-20"]')!);
|
||||
const elem = container
|
||||
.querySelector('[title="2018-02-20"]')!
|
||||
.className.includes('ant-picker-cell-disabled');
|
||||
expect(elem).toEqual(true);
|
||||
expect(onSelect.mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('months other than in valid range should be disabled', () => {
|
||||
const onSelect = jest.fn();
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2018-05-18')];
|
||||
const { container } = render(
|
||||
<Calendar
|
||||
onSelect={onSelect}
|
||||
validRange={validRange}
|
||||
defaultValue={Moment('2018-02-02')}
|
||||
mode="year"
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
container.querySelector('[title="2018-01"]')?.className.includes('ant-picker-cell-disabled'),
|
||||
).toBe(true);
|
||||
expect(
|
||||
container.querySelector('[title="2018-02"]')?.className.includes('ant-picker-cell-disabled'),
|
||||
).toBe(false);
|
||||
expect(
|
||||
container.querySelector('[title="2018-06"]')?.className.includes('ant-picker-cell-disabled'),
|
||||
).toBe(true);
|
||||
fireEvent.click(container.querySelector('[title="2018-01"]')!);
|
||||
fireEvent.click(container.querySelector('[title="2018-03"]')!);
|
||||
expect(onSelect.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('months other than in valid range should not be shown in header', () => {
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2017-02-02'), Moment('2018-05-18')];
|
||||
const { container } = render(<Calendar validRange={validRange} />);
|
||||
openSelect(container, '.ant-picker-calendar-year-select');
|
||||
clickSelectItem(container);
|
||||
openSelect(container, '.ant-picker-calendar-month-select');
|
||||
// 2 years and 11 months
|
||||
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(13);
|
||||
});
|
||||
|
||||
it('getDateRange should returns a disabledDate function', () => {
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2018-05-18')];
|
||||
const wrapper = mount(<Calendar validRange={validRange} defaultValue={Moment('2018-02-02')} />);
|
||||
const { disabledDate } = wrapper.find('PickerPanel').props() as any;
|
||||
expect(disabledDate(Moment('2018-06-02'))).toBe(true);
|
||||
expect(disabledDate(Moment('2018-04-02'))).toBe(false);
|
||||
});
|
||||
|
||||
it('validRange should work with disabledDate function', () => {
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2018-05-18')];
|
||||
const wrapper = mount(
|
||||
<Calendar validRange={validRange} disabledDate={data => data.isSame(Moment('2018-02-03'))} />,
|
||||
);
|
||||
|
||||
const { disabledDate } = wrapper.find('PickerPanel').props() as any;
|
||||
expect(disabledDate(Moment('2018-02-01'))).toBe(true);
|
||||
expect(disabledDate(Moment('2018-02-02'))).toBe(false);
|
||||
expect(disabledDate(Moment('2018-02-03'))).toBe(true);
|
||||
expect(disabledDate(Moment('2018-02-04'))).toBe(false);
|
||||
expect(disabledDate(Moment('2018-06-01'))).toBe(true);
|
||||
});
|
||||
|
||||
it('Calendar MonthSelect should display correct label', () => {
|
||||
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2019-06-1')];
|
||||
const wrapper = mount(<Calendar validRange={validRange} defaultValue={Moment('2019-01-01')} />);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Calendar should change mode by prop', () => {
|
||||
const monthMode = 'month';
|
||||
const yearMode = 'year';
|
||||
const wrapper = mount(<Calendar />);
|
||||
expect(wrapper.find('CalendarHeader').props().mode).toEqual(monthMode);
|
||||
wrapper.setProps({ mode: yearMode });
|
||||
expect(wrapper.find('CalendarHeader').props().mode).toEqual(yearMode);
|
||||
});
|
||||
|
||||
it('Calendar should switch mode', () => {
|
||||
const monthMode = 'month';
|
||||
const yearMode = 'year';
|
||||
const onPanelChangeStub = jest.fn();
|
||||
const wrapper = mount(<Calendar mode={yearMode} onPanelChange={onPanelChangeStub} />);
|
||||
expect(wrapper.find('CalendarHeader').props().mode).toEqual(yearMode);
|
||||
wrapper.setProps({ mode: monthMode });
|
||||
expect(wrapper.find('CalendarHeader').props().mode).toEqual(monthMode);
|
||||
expect(onPanelChangeStub).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('Calendar should support locale', () => {
|
||||
MockDate.set(Moment('2018-10-19').valueOf());
|
||||
// eslint-disable-next-line global-require
|
||||
const zhCN = require('../locale/zh_CN').default;
|
||||
const wrapper = mount(<Calendar locale={zhCN} />);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
MockDate.reset();
|
||||
});
|
||||
|
||||
describe('onPanelChange', () => {
|
||||
it('trigger when click last month of date', () => {
|
||||
const onPanelChange = jest.fn();
|
||||
const date = Moment('1990-09-03');
|
||||
const wrapper = mount(<Calendar onPanelChange={onPanelChange} value={date} />);
|
||||
|
||||
wrapper.find('.ant-picker-cell').at(0).simulate('click');
|
||||
|
||||
expect(onPanelChange).toHaveBeenCalled();
|
||||
expect(onPanelChange.mock.calls[0][0].month()).toEqual(date.month() - 1);
|
||||
});
|
||||
|
||||
it('not trigger when in same month', () => {
|
||||
const onPanelChange = jest.fn();
|
||||
const date = Moment('1990-09-03');
|
||||
const wrapper = mount(<Calendar onPanelChange={onPanelChange} value={date} />);
|
||||
|
||||
wrapper.find('.ant-picker-cell').at(10).simulate('click');
|
||||
|
||||
expect(onPanelChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('switch should work correctly without prop mode', async () => {
|
||||
const onPanelChange = jest.fn();
|
||||
const date = Moment(new Date(Date.UTC(2017, 7, 9, 8)));
|
||||
const wrapper = mount(<Calendar onPanelChange={onPanelChange} value={date} />);
|
||||
|
||||
expect(wrapper.find('CalendarHeader').props().mode).toBe('month');
|
||||
expect(wrapper.find('.ant-picker-date-panel').length).toBe(1);
|
||||
expect(wrapper.find('.ant-picker-month-panel').length).toBe(0);
|
||||
|
||||
wrapper.find('.ant-radio-button-input[value="year"]').simulate('change');
|
||||
expect(wrapper.find('.ant-picker-date-panel').length).toBe(0);
|
||||
expect(wrapper.find('.ant-picker-month-panel').length).toBe(1);
|
||||
expect(onPanelChange).toHaveBeenCalled();
|
||||
expect(onPanelChange.mock.calls[0][1]).toEqual('year');
|
||||
});
|
||||
|
||||
const createWrapper = (
|
||||
start: Moment.Moment,
|
||||
end: Moment.Moment,
|
||||
value: Moment.Moment,
|
||||
onValueChange: (v: Moment.Moment) => void,
|
||||
) => {
|
||||
const wrapper = render(
|
||||
// @ts-ignore
|
||||
<Header
|
||||
prefixCls="ant-picker-calendar"
|
||||
generateConfig={momentGenerateConfig}
|
||||
onChange={onValueChange}
|
||||
value={value}
|
||||
validRange={[start, end]}
|
||||
locale={{ year: '年' } as Locale}
|
||||
/>,
|
||||
);
|
||||
openSelect(wrapper.container, '.ant-picker-calendar-year-select');
|
||||
clickSelectItem(wrapper.container);
|
||||
};
|
||||
|
||||
it('if value.month > end.month, set value.month to end.month', () => {
|
||||
const value = Moment('1990-01-03');
|
||||
const start = Moment('2019-04-01');
|
||||
const end = Moment('2019-11-01');
|
||||
const onValueChange = jest.fn();
|
||||
createWrapper(start, end, value, onValueChange);
|
||||
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month('3'));
|
||||
});
|
||||
|
||||
it('if start.month > value.month, set value.month to start.month', () => {
|
||||
const value = Moment('1990-01-03');
|
||||
const start = Moment('2019-11-01');
|
||||
const end = Moment('2019-03-01');
|
||||
const onValueChange = jest.fn();
|
||||
createWrapper(start, end, value, onValueChange);
|
||||
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month('10'));
|
||||
});
|
||||
|
||||
it('if change year and month > end month, set value.month to end.month', () => {
|
||||
const value = Moment('2018-11-03');
|
||||
const start = Moment('2000-01-01');
|
||||
const end = Moment('2019-03-01');
|
||||
const onValueChange = jest.fn();
|
||||
const wrapper = render(
|
||||
<Header
|
||||
prefixCls="ant-picker-calendar"
|
||||
generateConfig={momentGenerateConfig}
|
||||
onChange={onValueChange}
|
||||
value={value}
|
||||
validRange={[start, end]}
|
||||
// @ts-ignore
|
||||
locale={{ year: '年' }}
|
||||
/>,
|
||||
);
|
||||
openSelect(wrapper.container, '.ant-picker-calendar-year-select');
|
||||
fireEvent.click(
|
||||
Array.from(wrapper.container.querySelectorAll('.ant-select-item-option')).at(-1)!,
|
||||
);
|
||||
expect(onValueChange).toHaveBeenCalledWith(value.year(2019).month('2'));
|
||||
});
|
||||
|
||||
it('onMonthChange should work correctly', () => {
|
||||
const start = Moment('2018-11-01');
|
||||
const end = Moment('2019-03-01');
|
||||
const value = Moment('2018-12-03');
|
||||
const onValueChange = jest.fn();
|
||||
const wrapper = render(
|
||||
<Header
|
||||
prefixCls="ant-picker-calendar"
|
||||
generateConfig={momentGenerateConfig}
|
||||
onChange={onValueChange}
|
||||
value={value}
|
||||
validRange={[start, end]}
|
||||
// @ts-ignore
|
||||
locale={{ year: '年', locale: 'zh_CN' }}
|
||||
mode="month"
|
||||
/>,
|
||||
);
|
||||
openSelect(wrapper.container, '.ant-picker-calendar-month-select');
|
||||
clickSelectItem(wrapper.container);
|
||||
expect(onValueChange).toHaveBeenCalledWith(value.month(10));
|
||||
});
|
||||
|
||||
it('onTypeChange should work correctly', () => {
|
||||
const onTypeChange = jest.fn();
|
||||
const value = Moment('2018-12-03');
|
||||
const wrapper = render(
|
||||
<Header
|
||||
prefixCls="ant-picker-calendar"
|
||||
generateConfig={momentGenerateConfig}
|
||||
onModeChange={onTypeChange}
|
||||
locale={{ year: '年', month: '月', locale: 'zh_CN' } as any}
|
||||
value={value}
|
||||
// @ts-ignore
|
||||
type="date"
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(
|
||||
Array.from(wrapper.container.querySelectorAll(`.ant-radio-button-input`)).at(1)!,
|
||||
);
|
||||
expect(onTypeChange).toHaveBeenCalledWith('year');
|
||||
});
|
||||
|
||||
it('headerRender should work correctly', () => {
|
||||
const onMonthChange = jest.fn();
|
||||
const onYearChange = jest.fn();
|
||||
const onTypeChange = jest.fn();
|
||||
|
||||
// Year
|
||||
const headerRender = jest.fn(({ value }) => {
|
||||
const year = value.year();
|
||||
const options = [];
|
||||
for (let i = year - 100; i < year + 100; i += 1) {
|
||||
options.push(
|
||||
<Select.Option className="year-item" key={i} value={i}>
|
||||
{i}
|
||||
</Select.Option>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
size="small"
|
||||
dropdownMatchSelectWidth={false}
|
||||
className="my-year-select"
|
||||
onChange={onYearChange}
|
||||
value={String(year)}
|
||||
>
|
||||
{options}
|
||||
</Select>
|
||||
);
|
||||
});
|
||||
const uiWithYear = <Calendar fullscreen={false} headerRender={headerRender} />;
|
||||
const wrapperWithYear = render(uiWithYear);
|
||||
|
||||
openSelect(wrapperWithYear.container, '.ant-select');
|
||||
wrapperWithYear.rerender(uiWithYear);
|
||||
|
||||
fireEvent.click(Array.from(findSelectItem(wrapperWithYear.container)).at(-1)!);
|
||||
|
||||
expect(onYearChange).toHaveBeenCalled();
|
||||
|
||||
// Month
|
||||
const headerRenderWithMonth = jest.fn(({ value }) => {
|
||||
const start = 0;
|
||||
const end = 12;
|
||||
const monthOptions = [];
|
||||
const current = value.clone();
|
||||
const localeData = value.localeData();
|
||||
const months = [];
|
||||
for (let i = 0; i < 12; i += 1) {
|
||||
current.month(i);
|
||||
months.push(localeData.monthsShort(current));
|
||||
}
|
||||
|
||||
for (let index = start; index < end; index += 1) {
|
||||
monthOptions.push(
|
||||
<Select.Option className="month-item" key={`${index}`} value={index}>
|
||||
{months[index]}
|
||||
</Select.Option>,
|
||||
);
|
||||
}
|
||||
|
||||
const month = value.month();
|
||||
return (
|
||||
<Select
|
||||
size="small"
|
||||
dropdownMatchSelectWidth={false}
|
||||
className="my-month-select"
|
||||
onChange={onMonthChange}
|
||||
value={String(month)}
|
||||
>
|
||||
{monthOptions}
|
||||
</Select>
|
||||
);
|
||||
});
|
||||
const uiWithMonth = <Calendar fullscreen={false} headerRender={headerRenderWithMonth} />;
|
||||
const wrapperWithMonth = render(uiWithMonth);
|
||||
openSelect(wrapperWithMonth.container, '.ant-select');
|
||||
wrapperWithMonth.rerender(uiWithMonth);
|
||||
|
||||
fireEvent.click(Array.from(findSelectItem(wrapperWithMonth.container)).at(-1)!);
|
||||
|
||||
expect(onMonthChange).toHaveBeenCalled();
|
||||
|
||||
// Type
|
||||
const headerRenderWithTypeChange = jest.fn(({ type }) => (
|
||||
<Group size="small" onChange={onTypeChange} value={type}>
|
||||
<Button value="month">Month</Button>
|
||||
<Button value="year">Year</Button>
|
||||
</Group>
|
||||
));
|
||||
|
||||
const wrapperWithTypeChange = render(
|
||||
<Calendar fullscreen={false} headerRender={headerRenderWithTypeChange} />,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
Array.from(wrapperWithTypeChange.container.querySelectorAll('.ant-radio-button-input')).at(
|
||||
-1,
|
||||
)!,
|
||||
);
|
||||
expect(onTypeChange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('dateFullCellRender', () => {
|
||||
const { container } = render(
|
||||
<Calendar dateFullCellRender={() => <div className="light">Bamboo</div>} />,
|
||||
);
|
||||
expect(container.querySelectorAll('.light')[0].innerHTML).toEqual('Bamboo');
|
||||
});
|
||||
|
||||
it('monthFullCellRender', () => {
|
||||
const { container } = render(
|
||||
<Calendar mode="year" monthFullCellRender={() => <div className="bamboo">Light</div>} />,
|
||||
);
|
||||
expect(container.querySelectorAll('.bamboo')[0].innerHTML).toEqual('Light');
|
||||
});
|
||||
|
||||
it('when fullscreen is false, the element returned by dateFullCellRender should be interactive', () => {
|
||||
const onClick = jest.fn();
|
||||
const { container } = render(
|
||||
<Calendar
|
||||
fullscreen={false}
|
||||
dateFullCellRender={() => (
|
||||
<div className="bamboo" onClick={onClick}>
|
||||
Light
|
||||
</div>
|
||||
)}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(container.querySelectorAll('.bamboo')[0]);
|
||||
expect(onClick).toBeCalled();
|
||||
});
|
||||
});
|
@ -1,9 +1,9 @@
|
||||
import { mount } from 'enzyme';
|
||||
import MockDate from 'mockdate';
|
||||
import React from 'react';
|
||||
import Descriptions from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
import { render } from '../../../tests/utils';
|
||||
|
||||
describe('Descriptions', () => {
|
||||
mountTest(Descriptions);
|
||||
@ -20,7 +20,7 @@ describe('Descriptions', () => {
|
||||
});
|
||||
|
||||
it('when max-width: 575px,column=1', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
|
||||
@ -29,14 +29,14 @@ describe('Descriptions', () => {
|
||||
<Descriptions.Item>No-Label</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.find('tr')).toHaveLength(5);
|
||||
expect(wrapper.find('.ant-descriptions-item-label')).toHaveLength(4);
|
||||
expect(wrapper.container.querySelectorAll('tr')).toHaveLength(5);
|
||||
expect(wrapper.container.querySelectorAll('.ant-descriptions-item-label')).toHaveLength(4);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('when max-width: 575px,column=2', () => {
|
||||
// eslint-disable-next-line global-require
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions column={{ xs: 2 }}>
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
|
||||
@ -44,26 +44,26 @@ describe('Descriptions', () => {
|
||||
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.find('tr')).toHaveLength(2);
|
||||
expect(wrapper.container.querySelectorAll('tr')).toHaveLength(2);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('column is number', () => {
|
||||
// eslint-disable-next-line global-require
|
||||
const wrapper = mount(
|
||||
<Descriptions column="3">
|
||||
const wrapper = render(
|
||||
<Descriptions column={3}>
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
|
||||
<Descriptions.Item label="time">18:00:00</Descriptions.Item>
|
||||
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('when typeof column is object', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions column={{ xs: 8, sm: 16, md: 24 }}>
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
|
||||
@ -71,14 +71,19 @@ describe('Descriptions', () => {
|
||||
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.find('td').reduce((total, td) => total + td.props().colSpan, 0)).toBe(8);
|
||||
expect(
|
||||
Array.from(wrapper.container.querySelectorAll('td'))
|
||||
.map(i => Number(i.getAttribute('colspan')))
|
||||
.filter(Boolean)
|
||||
.reduce((total, cur) => total + cur, 0),
|
||||
).toBe(8);
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('warning if ecceed the row span', () => {
|
||||
resetWarned();
|
||||
|
||||
mount(
|
||||
render(
|
||||
<Descriptions column={3}>
|
||||
<Descriptions.Item label="Product" span={2}>
|
||||
Cloud Database
|
||||
@ -95,7 +100,7 @@ describe('Descriptions', () => {
|
||||
|
||||
it('when item is rendered conditionally', () => {
|
||||
const hasDiscount = false;
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
|
||||
@ -104,13 +109,13 @@ describe('Descriptions', () => {
|
||||
{hasDiscount && <Descriptions.Item label="Discount">$20.00</Descriptions.Item>}
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('vertical layout', () => {
|
||||
// eslint-disable-next-line global-require
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions layout="vertical">
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
<Descriptions.Item label="Billing">Prepaid</Descriptions.Item>
|
||||
@ -118,47 +123,46 @@ describe('Descriptions', () => {
|
||||
<Descriptions.Item label="Amount">$80.00</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
it('Descriptions.Item support className', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item label="Product" className="my-class">
|
||||
Cloud Database
|
||||
</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Descriptions support colon', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions colon={false}>
|
||||
<Descriptions.Item label="Product">Cloud Database</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('Descriptions support style', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions style={{ backgroundColor: '#e8e8e8' }}>
|
||||
<Descriptions.Item>Cloud Database</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('keep key', () => {
|
||||
const wrapper = mount(
|
||||
render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item key="bamboo" />
|
||||
<Descriptions.Item key="bamboo">1</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('Cell').key()).toBe('item-bamboo');
|
||||
expect(jest.spyOn(document, 'createElement')).not.toBeCalled();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/19887
|
||||
@ -166,7 +170,7 @@ describe('Descriptions', () => {
|
||||
if (!React.Fragment) {
|
||||
return;
|
||||
}
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
|
||||
<>
|
||||
@ -176,12 +180,12 @@ describe('Descriptions', () => {
|
||||
</Descriptions>,
|
||||
);
|
||||
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/20255
|
||||
it('columns 5 with customize', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions layout="vertical" column={4}>
|
||||
{/* 1 1 1 1 */}
|
||||
<Descriptions.Item label="bamboo">bamboo</Descriptions.Item>
|
||||
@ -203,12 +207,12 @@ describe('Descriptions', () => {
|
||||
</Descriptions>,
|
||||
);
|
||||
|
||||
function matchSpan(rowIndex, spans) {
|
||||
const tr = wrapper.find('tr').at(rowIndex);
|
||||
const tds = tr.find('th');
|
||||
function matchSpan(rowIndex: number, spans: number[]) {
|
||||
const trs = Array.from(wrapper.container.querySelectorAll('tr')).at(rowIndex);
|
||||
const tds = Array.from(trs?.querySelectorAll('th')!);
|
||||
expect(tds).toHaveLength(spans.length);
|
||||
tds.forEach((td, index) => {
|
||||
expect(td.props().colSpan).toEqual(spans[index]);
|
||||
expect(Number(td.getAttribute('colSpan'))).toEqual(spans[index]);
|
||||
});
|
||||
}
|
||||
|
||||
@ -218,35 +222,38 @@ describe('Descriptions', () => {
|
||||
});
|
||||
|
||||
it('number value should render correct', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions bordered>
|
||||
<Descriptions.Item label={0}>{0}</Descriptions.Item>
|
||||
<Descriptions.Item label={0}>0</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
|
||||
expect(wrapper.find('th').hasClass('ant-descriptions-item-label')).toBeTruthy();
|
||||
expect(wrapper.find('td').hasClass('ant-descriptions-item-content')).toBeTruthy();
|
||||
expect(wrapper.container.querySelector('th')).toHaveClass('ant-descriptions-item-label');
|
||||
expect(wrapper.container.querySelector('td')).toHaveClass('ant-descriptions-item-content');
|
||||
});
|
||||
|
||||
it('Descriptions support extra', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper1 = render(
|
||||
<Descriptions extra="Edit">
|
||||
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(true);
|
||||
wrapper.setProps({ extra: undefined });
|
||||
expect(wrapper.find('.ant-descriptions-extra').exists()).toBe(false);
|
||||
const wrapper2 = render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item label="UserName">Zhou Maomao</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper1.container.querySelector('.ant-descriptions-extra')).toBeTruthy();
|
||||
expect(wrapper2.container.querySelector('.ant-descriptions-extra')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('number 0 should render correct', () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<Descriptions>
|
||||
<Descriptions.Item label={0} labelStyle={{ color: 'red' }} contentStyle={{ color: 'red' }}>
|
||||
{0}
|
||||
</Descriptions.Item>
|
||||
</Descriptions>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(wrapper.container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -198,4 +198,11 @@ describe('Drawer', () => {
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('zIndex should work', () => {
|
||||
const { container } = render(<Drawer getContainer={false} visible zIndex={903} />);
|
||||
expect(container.querySelector('.ant-drawer')).toHaveStyle({
|
||||
zIndex: 903,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ function Drawer({
|
||||
drawerStyle,
|
||||
visible,
|
||||
children,
|
||||
zIndex,
|
||||
style,
|
||||
title,
|
||||
headerStyle,
|
||||
onClose,
|
||||
|
@ -315,7 +315,7 @@ export default () => {
|
||||
|
||||
### Form.useWatch
|
||||
|
||||
`type Form.useWatch = (namePath: NamePath, formInstance: FormInstance): Value`
|
||||
`type Form.useWatch = (namePath: NamePath, formInstance?: FormInstance): Value`
|
||||
|
||||
Added in `4.20.0`. Watch the value of a field. You can use this to interactive with other hooks like `useSWR` to reduce develop cost:
|
||||
|
||||
@ -343,9 +343,9 @@ const Demo = () => {
|
||||
Added in `4.22.0`. Could be used to get validate status of Form.Item. If this hook is not used under Form.Item, `status` would be `undefined`:
|
||||
|
||||
```tsx
|
||||
const CustomInput = ({ value }) => {
|
||||
const CustomInput = ({ value, onChange }) => {
|
||||
const { status } = Form.Item.useStatus();
|
||||
return <input value={value} className={`custom-input-${status}`} />;
|
||||
return <input value={value} onChange={onChange} className={`custom-input-${status}`} />;
|
||||
};
|
||||
|
||||
export default () => (
|
||||
|
@ -314,7 +314,7 @@ export default () => {
|
||||
|
||||
### Form.useWatch
|
||||
|
||||
`type Form.useWatch = (namePath: NamePath, formInstance: FormInstance): Value`
|
||||
`type Form.useWatch = (namePath: NamePath, formInstance?: FormInstance): Value`
|
||||
|
||||
`4.20.0` 新增,用于直接获取 form 中字段对应的值。通过该 Hooks 可以与诸如 `useSWR` 进行联动从而降低维护成本:
|
||||
|
||||
@ -342,9 +342,9 @@ const Demo = () => {
|
||||
`4.22.0` 新增,可用于获取当前 Form.Item 的校验状态,如果上层没有 Form.Item,`status` 将会返回 `undefined`:
|
||||
|
||||
```tsx
|
||||
const CustomInput = ({ value }) => {
|
||||
const CustomInput = ({ value, onChange }) => {
|
||||
const { status } = Form.Item.useStatus();
|
||||
return <input value={value} className={`custom-input-${status}`} />;
|
||||
return <input value={value} onChange={onChange} className={`custom-input-${status}`} />;
|
||||
};
|
||||
|
||||
export default () => (
|
||||
|
@ -8,6 +8,7 @@ exports[`renders ./components/image/demo/basic.md extend context correctly 1`] =
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -58,9 +59,11 @@ Array [
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
|
||||
style="display:none"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
style="display:none"
|
||||
>
|
||||
<div
|
||||
class="ant-image-mask-info"
|
||||
@ -98,8 +101,10 @@ exports[`renders ./components/image/demo/fallback.md extend context correctly 1`
|
||||
>
|
||||
<img
|
||||
class="ant-image-img"
|
||||
height="200"
|
||||
src="error"
|
||||
style="height:200px"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -147,6 +152,7 @@ exports[`renders ./components/image/demo/placeholder.md extend context correctly
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?undefined"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@ -159,6 +165,7 @@ exports[`renders ./components/image/demo/placeholder.md extend context correctly
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
|
||||
width="200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -216,6 +223,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -253,6 +261,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -295,6 +304,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
|
||||
width="150"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -332,6 +342,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
|
||||
width="150"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -369,6 +380,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
width="150"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -411,6 +423,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -564,6 +577,7 @@ exports[`renders ./components/image/demo/preview-mask.md extend context correctl
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
width="96"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask customize-mask"
|
||||
@ -613,6 +627,7 @@ exports[`renders ./components/image/demo/previewSrc.md extend context correctly
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
|
@ -8,6 +8,7 @@ exports[`renders ./components/image/demo/basic.md correctly 1`] = `
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -58,9 +59,11 @@ Array [
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
|
||||
style="display:none"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
style="display:none"
|
||||
>
|
||||
<div
|
||||
class="ant-image-mask-info"
|
||||
@ -98,8 +101,10 @@ exports[`renders ./components/image/demo/fallback.md correctly 1`] = `
|
||||
>
|
||||
<img
|
||||
class="ant-image-img"
|
||||
height="200"
|
||||
src="error"
|
||||
style="height:200px"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -147,6 +152,7 @@ exports[`renders ./components/image/demo/placeholder.md correctly 1`] = `
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?undefined"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
@ -159,6 +165,7 @@ exports[`renders ./components/image/demo/placeholder.md correctly 1`] = `
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
|
||||
width="200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -216,6 +223,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -253,6 +261,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -295,6 +304,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg"
|
||||
width="150"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -332,6 +342,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/aPkFc8Sj7n/method-draw-image.svg"
|
||||
width="150"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -369,6 +380,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
width="150"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -411,6 +423,7 @@ Array [
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://gw.alipayobjects.com/zos/antfincdn/LlvErxo8H9/photo-1503185912284-5271ff81b9a8.webp"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
@ -564,6 +577,7 @@ exports[`renders ./components/image/demo/preview-mask.md correctly 1`] = `
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
|
||||
width="96"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask customize-mask"
|
||||
@ -613,6 +627,7 @@ exports[`renders ./components/image/demo/previewSrc.md correctly 1`] = `
|
||||
<img
|
||||
class="ant-image-img"
|
||||
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png?x-oss-process=image/blur,r_50,s_50/quality,q_1/resize,m_mfit,h_200,w_200"
|
||||
width="200"
|
||||
/>
|
||||
<div
|
||||
class="ant-image-mask"
|
||||
|
@ -1,9 +1,12 @@
|
||||
/* eslint-disable no-template-curly-in-string */
|
||||
import Pagination from 'rc-pagination/lib/locale/az_AZ';
|
||||
import Calendar from '../calendar/locale/az_AZ';
|
||||
import DatePicker from '../date-picker/locale/az_AZ';
|
||||
import type { Locale } from '../locale-provider';
|
||||
import TimePicker from '../time-picker/locale/az_AZ';
|
||||
|
||||
const typeTemplate = '${label}Hökmlü deyil${type}';
|
||||
|
||||
const localeValues: Locale = {
|
||||
locale: 'az',
|
||||
Pagination,
|
||||
@ -43,6 +46,56 @@ const localeValues: Locale = {
|
||||
uploadError: 'Yükləmə xətası',
|
||||
previewFile: 'Fayla önbaxış',
|
||||
},
|
||||
Form: {
|
||||
optional: '(Seçimli)',
|
||||
defaultValidateMessages: {
|
||||
default: 'Sahə təsdiq xətası${label}',
|
||||
required: 'Xahiş edirik daxil olun${label}',
|
||||
enum: '${label}Onlardan biri olmalıdır[${enum}]',
|
||||
whitespace: '${label}Null xarakter ola bilməz',
|
||||
date: {
|
||||
format: '${label}Tarix formatı hökmlü deyil',
|
||||
parse: '${label}Tarixi döndərmək mümkün deyil',
|
||||
invalid: '${label}səhv tarixdir',
|
||||
},
|
||||
types: {
|
||||
string: typeTemplate,
|
||||
method: typeTemplate,
|
||||
array: typeTemplate,
|
||||
object: typeTemplate,
|
||||
number: typeTemplate,
|
||||
date: typeTemplate,
|
||||
boolean: typeTemplate,
|
||||
integer: typeTemplate,
|
||||
float: typeTemplate,
|
||||
regexp: typeTemplate,
|
||||
email: typeTemplate,
|
||||
url: typeTemplate,
|
||||
hex: typeTemplate,
|
||||
},
|
||||
string: {
|
||||
len: '${label}Olmalıdır${len}işarələr',
|
||||
min: '${label}ən az${min}işarələr',
|
||||
max: '${label}ən çox${max}işarələr',
|
||||
range: '${label}Olmalıdır${min}-${max}hərflər arasında',
|
||||
},
|
||||
number: {
|
||||
len: '${label}Bərabər olmalıdır${len}',
|
||||
min: '${label}Minimal dəyəri${min}',
|
||||
max: '${label}Maksimal qiymət:${max}',
|
||||
range: '${label}Olmalıdır${min}-${max}aralarında',
|
||||
},
|
||||
array: {
|
||||
len: 'Olmalıdır${len}parça${label}',
|
||||
min: 'ən az${min}parça${label}',
|
||||
max: 'ən çox${max}parça${label}',
|
||||
range: '${label}miqdarıOlmalıdır${min}-${max}aralarında',
|
||||
},
|
||||
pattern: {
|
||||
mismatch: '${label}Şablona uyğun gəlmir${pattern}',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default localeValues;
|
||||
|
@ -1,97 +0,0 @@
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import Mentions from '..';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act } from '../../../tests/utils';
|
||||
|
||||
const { getMentions } = Mentions;
|
||||
|
||||
function simulateInput(wrapper, text, keyEvent) {
|
||||
const lastChar = text[text.length - 1];
|
||||
const myKeyEvent = keyEvent || {
|
||||
which: lastChar.charCodeAt(0),
|
||||
key: lastChar,
|
||||
target: {
|
||||
value: text,
|
||||
selectionStart: text.length,
|
||||
},
|
||||
};
|
||||
|
||||
wrapper.find('textarea').simulate('keyDown', myKeyEvent);
|
||||
|
||||
const textareaInstance = wrapper.find('textarea').instance();
|
||||
textareaInstance.value = text;
|
||||
textareaInstance.selectionStart = text.length;
|
||||
textareaInstance.selectionStart = text.length;
|
||||
|
||||
if (!keyEvent) {
|
||||
wrapper.find('textarea').simulate('change', {
|
||||
target: { value: text },
|
||||
});
|
||||
}
|
||||
|
||||
wrapper.find('textarea').simulate('keyUp', myKeyEvent);
|
||||
wrapper.update();
|
||||
}
|
||||
|
||||
describe('Mentions', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('getMentions', () => {
|
||||
const mentions = getMentions('@light #bamboo cat', { prefix: ['@', '#'] });
|
||||
expect(mentions).toEqual([
|
||||
{
|
||||
prefix: '@',
|
||||
value: 'light',
|
||||
},
|
||||
{
|
||||
prefix: '#',
|
||||
value: 'bamboo',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('focus', () => {
|
||||
const onFocus = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
|
||||
const wrapper = mount(<Mentions onFocus={onFocus} onBlur={onBlur} />);
|
||||
wrapper.find('textarea').simulate('focus');
|
||||
expect(wrapper.find('.ant-mentions').hasClass('ant-mentions-focused')).toBeTruthy();
|
||||
expect(onFocus).toHaveBeenCalled();
|
||||
|
||||
wrapper.find('textarea').simulate('blur');
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('.ant-mentions').hasClass('ant-mentions-focused')).toBeFalsy();
|
||||
expect(onBlur).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
focusTest(Mentions, { refFocus: true });
|
||||
mountTest(Mentions);
|
||||
rtlTest(Mentions);
|
||||
|
||||
it('loading', () => {
|
||||
const wrapper = mount(<Mentions loading />);
|
||||
simulateInput(wrapper, '@');
|
||||
expect(wrapper.find('li.ant-mentions-dropdown-menu-item').length).toBe(1);
|
||||
expect(wrapper.find('.ant-spin').length).toBeTruthy();
|
||||
});
|
||||
|
||||
it('notFoundContent', () => {
|
||||
const wrapper = mount(<Mentions notFoundContent={<span className="bamboo-light" />} />);
|
||||
simulateInput(wrapper, '@');
|
||||
expect(wrapper.find('li.ant-mentions-dropdown-menu-item').length).toBe(1);
|
||||
expect(wrapper.find('.bamboo-light').length).toBeTruthy();
|
||||
});
|
||||
});
|
87
components/mentions/__tests__/index.test.tsx
Normal file
87
components/mentions/__tests__/index.test.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import Mentions from '..';
|
||||
import focusTest from '../../../tests/shared/focusTest';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, render, fireEvent } from '../../../tests/utils';
|
||||
|
||||
const { getMentions } = Mentions;
|
||||
|
||||
function simulateInput(wrapper: ReturnType<typeof render>, text: string, keyEvent?: Event): void {
|
||||
const lastChar = text[text.length - 1];
|
||||
const myKeyEvent = keyEvent || {
|
||||
which: lastChar.charCodeAt(0),
|
||||
key: lastChar,
|
||||
target: {
|
||||
value: text,
|
||||
selectionStart: text.length,
|
||||
},
|
||||
};
|
||||
|
||||
fireEvent.keyDown(wrapper.container.querySelector('textarea')!, myKeyEvent);
|
||||
|
||||
const textareaInstance = wrapper.container.querySelector('textarea');
|
||||
|
||||
if (textareaInstance) {
|
||||
textareaInstance.value = text;
|
||||
textareaInstance.selectionStart = text.length;
|
||||
textareaInstance.selectionStart = text.length;
|
||||
}
|
||||
|
||||
if (!keyEvent) {
|
||||
fireEvent.change(wrapper.container.querySelector('textarea')!, { target: { value: text } });
|
||||
}
|
||||
fireEvent.keyUp(wrapper.container.querySelector('textarea')!, myKeyEvent);
|
||||
}
|
||||
|
||||
describe('Mentions', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('getMentions', () => {
|
||||
const mentions = getMentions('@light #bamboo cat', { prefix: ['@', '#'] });
|
||||
expect(mentions).toEqual([
|
||||
{ prefix: '@', value: 'light' },
|
||||
{ prefix: '#', value: 'bamboo' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('focus', () => {
|
||||
const onFocus = jest.fn();
|
||||
const onBlur = jest.fn();
|
||||
|
||||
const { container } = render(<Mentions onFocus={onFocus} onBlur={onBlur} />);
|
||||
fireEvent.focus(container.querySelector('textarea')!);
|
||||
expect(container.querySelector('.ant-mentions')).toHaveClass('ant-mentions-focused');
|
||||
expect(onFocus).toHaveBeenCalled();
|
||||
fireEvent.blur(container.querySelector('textarea')!);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(container.querySelector('.ant-mentions')).not.toHaveClass('ant-mentions-focused');
|
||||
expect(onBlur).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
focusTest(Mentions, { refFocus: true });
|
||||
mountTest(Mentions);
|
||||
rtlTest(Mentions);
|
||||
|
||||
it('loading', () => {
|
||||
const wrapper = render(<Mentions loading />);
|
||||
simulateInput(wrapper, '@');
|
||||
expect(wrapper.container.querySelectorAll('li.ant-mentions-dropdown-menu-item').length).toBe(1);
|
||||
expect(wrapper.container.querySelectorAll('.ant-spin').length).toBeTruthy();
|
||||
});
|
||||
|
||||
it('notFoundContent', () => {
|
||||
const wrapper = render(<Mentions notFoundContent={<span className="bamboo-light" />} />);
|
||||
simulateInput(wrapper, '@');
|
||||
expect(wrapper.container.querySelectorAll('li.ant-mentions-dropdown-menu-item').length).toBe(1);
|
||||
expect(wrapper.container.querySelectorAll('.bamboo-light').length).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,237 +1,277 @@
|
||||
import React from 'react';
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import message, { actWrapper } from '..';
|
||||
import { act, fireEvent, sleep } from '../../../tests/utils';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
import message, { getInstance, type MessageType } from '..';
|
||||
import { act, render, fireEvent } from '../../../tests/utils';
|
||||
|
||||
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
|
||||
writable: true,
|
||||
value: true,
|
||||
});
|
||||
|
||||
describe('message', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up
|
||||
message.destroy();
|
||||
await triggerMotionEnd();
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
|
||||
await awaitPromise();
|
||||
act(() => {
|
||||
message.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to hide manually', async () => {
|
||||
const hide1 = message.info('whatever', 0);
|
||||
const hide2 = message.info('whatever', 0);
|
||||
let hide1!: MessageType;
|
||||
let hide2!: MessageType;
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(2);
|
||||
act(() => {
|
||||
hide1 = message.info('whatever', 0);
|
||||
});
|
||||
act(() => {
|
||||
hide2 = message.info('whatever', 0);
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(2);
|
||||
hide1();
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(getInstance()!.component.state.notices).toHaveLength(1);
|
||||
|
||||
hide2();
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(getInstance()!.component.state.notices).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should be able to remove manually with a unique key', async () => {
|
||||
it('should be able to remove manually with a unique key', () => {
|
||||
const key1 = 'key1';
|
||||
const key2 = 'key2';
|
||||
|
||||
message.info({ content: 'Message1', key: 'key1', duration: 0 });
|
||||
message.info({ content: 'Message2', key: 'key2', duration: 0 });
|
||||
act(() => {
|
||||
message.info({ content: 'Message1', key: 'key1', duration: 0 });
|
||||
});
|
||||
act(() => {
|
||||
message.info({ content: 'Message2', key: 'key2', duration: 0 });
|
||||
});
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(2);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(2);
|
||||
|
||||
message.destroy(key1);
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(getInstance()!.component.state.notices).toHaveLength(1);
|
||||
|
||||
message.destroy(key2);
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(getInstance()!.component.state.notices).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should be able to destroy globally', async () => {
|
||||
message.info('whatever', 0);
|
||||
message.info('whatever', 0);
|
||||
it('should be able to destroy globally', () => {
|
||||
act(() => {
|
||||
message.info('whatever', 0);
|
||||
});
|
||||
act(() => {
|
||||
message.info('whatever', 0);
|
||||
});
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-message')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(2);
|
||||
expect(document.querySelectorAll('.ant-message').length).toBe(1);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(2);
|
||||
|
||||
message.destroy();
|
||||
await triggerMotionEnd();
|
||||
|
||||
expect(document.querySelectorAll('.ant-message')).toHaveLength(0);
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not need to use duration argument when using the onClose arguments', async () => {
|
||||
const onClose = jest.fn();
|
||||
const close = message.info('whatever', onClose);
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
close();
|
||||
await triggerMotionEnd();
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should have the default duration when using the onClose arguments', async () => {
|
||||
const onClose = jest.fn();
|
||||
|
||||
message.info('whatever', onClose);
|
||||
await awaitPromise();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(2500);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelector('.ant-message-move-up-leave')).toBeFalsy();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
expect(document.querySelector('.ant-message-move-up-leave')).toBeTruthy();
|
||||
expect(document.querySelectorAll('.ant-message').length).toBe(0);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(0);
|
||||
});
|
||||
|
||||
it('trigger onClick method', async () => {
|
||||
it('should not need to use duration argument when using the onClose arguments', () => {
|
||||
message.info('whatever', () => {});
|
||||
});
|
||||
|
||||
it('should have the default duration when using the onClose arguments', done => {
|
||||
jest.useRealTimers();
|
||||
const defaultDuration = 3;
|
||||
const now = Date.now();
|
||||
message.info('whatever', () => {
|
||||
// calculate the approximately duration value
|
||||
const aboutDuration = parseInt(String((Date.now() - now) / 1000), 10);
|
||||
expect(aboutDuration).toBe(defaultDuration);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('trigger onClick method', () => {
|
||||
const onClick = jest.fn();
|
||||
message.info({
|
||||
onClick,
|
||||
duration: 0,
|
||||
content: 'message info',
|
||||
});
|
||||
class Test extends React.Component {
|
||||
static isFirstRender = true;
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
fireEvent.click(document.querySelector('.ant-message-notice')!);
|
||||
componentDidMount() {
|
||||
if (Test.isFirstRender) {
|
||||
Test.isFirstRender = false;
|
||||
message.info({
|
||||
onClick,
|
||||
duration: 0,
|
||||
content: 'message info',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>test message onClick method</div>;
|
||||
}
|
||||
}
|
||||
render(<Test />);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(1);
|
||||
fireEvent.click(document.querySelectorAll('.ant-message-notice')[0]);
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should be called like promise', async () => {
|
||||
const onClose = jest.fn();
|
||||
message.info('whatever').then(onClose);
|
||||
await awaitPromise();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(2500);
|
||||
it('should be called like promise', done => {
|
||||
jest.useRealTimers();
|
||||
const defaultDuration = 3;
|
||||
const now = Date.now();
|
||||
message.info('whatever').then(() => {
|
||||
// calculate the approximately duration value
|
||||
const aboutDuration = parseInt(String((Date.now() - now) / 1000), 10);
|
||||
expect(aboutDuration).toBe(defaultDuration);
|
||||
done();
|
||||
});
|
||||
expect(onClose).not.toHaveBeenCalled();
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
await sleep(); // Wait to let event loop run
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/8201
|
||||
it('should hide message correctly', async () => {
|
||||
const hide = message.loading('Action in progress..', 0);
|
||||
await awaitPromise();
|
||||
it('should hide message correctly', () => {
|
||||
let hide!: MessageType;
|
||||
class Test extends React.Component {
|
||||
static isFirstRender = true;
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
componentDidMount() {
|
||||
act(() => {
|
||||
if (Test.isFirstRender) {
|
||||
Test.isFirstRender = false;
|
||||
hide = message.loading('Action in progress..', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hide!();
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
|
||||
render() {
|
||||
return <div>test</div>;
|
||||
}
|
||||
}
|
||||
render(<Test />);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(1);
|
||||
|
||||
act(() => {
|
||||
hide();
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(getInstance()!.component.state.notices).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should allow custom icon', async () => {
|
||||
message.open({ content: 'Message', icon: <SmileOutlined /> });
|
||||
|
||||
await awaitPromise();
|
||||
expect(document.querySelector('.anticon-smile')).toBeTruthy();
|
||||
it('should allow custom icon', () => {
|
||||
act(() => {
|
||||
message.open({ content: 'Message', icon: <SmileOutlined /> });
|
||||
});
|
||||
expect(document.querySelectorAll('.anticon-smile').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should have no icon', async () => {
|
||||
it('should have no icon', () => {
|
||||
message.open({ content: 'Message', icon: <span /> });
|
||||
|
||||
await awaitPromise();
|
||||
expect(document.querySelector('.ant-message-notice .anticon')).toBeFalsy();
|
||||
expect(document.querySelectorAll('.ant-message-notice .anticon').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should have no icon when not pass icon props', async () => {
|
||||
it('should have no icon when not pass icon props', () => {
|
||||
message.open({ content: 'Message' });
|
||||
|
||||
await awaitPromise();
|
||||
expect(document.querySelector('.ant-message-notice .anticon')).toBeFalsy();
|
||||
expect(document.querySelectorAll('.ant-message-notice .anticon').length).toBe(0);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/8201
|
||||
it('should destroy messages correctly', async () => {
|
||||
message.loading('Action in progress1..', 0);
|
||||
message.loading('Action in progress2..', 0);
|
||||
setTimeout(() => message.destroy(), 1000);
|
||||
await awaitPromise();
|
||||
it('should destroy messages correctly', () => {
|
||||
class Test extends React.Component {
|
||||
static isFirstRender = true;
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(2);
|
||||
componentDidMount() {
|
||||
if (Test.isFirstRender) {
|
||||
Test.isFirstRender = false;
|
||||
message.loading('Action in progress1..', 0);
|
||||
message.loading('Action in progress2..', 0);
|
||||
setTimeout(() => message.destroy(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
|
||||
render() {
|
||||
return <div>test</div>;
|
||||
}
|
||||
}
|
||||
render(<Test />);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(2);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should support update message content with a unique key', async () => {
|
||||
it('should support update message content with a unique key', () => {
|
||||
const key = 'updatable';
|
||||
class Test extends React.Component {
|
||||
componentDidMount() {
|
||||
message.loading({ content: 'Loading...', key });
|
||||
// Testing that content of the message should be updated.
|
||||
setTimeout(() => message.success({ content: 'Loaded', key }), 1000);
|
||||
setTimeout(() => message.destroy(), 3000);
|
||||
}
|
||||
|
||||
message.loading({ content: 'Loading...', key });
|
||||
// Testing that content of the message should be updated.
|
||||
setTimeout(() => message.success({ content: 'Loaded', key }), 1000);
|
||||
setTimeout(() => message.destroy(), 3000);
|
||||
await awaitPromise();
|
||||
render() {
|
||||
return <div>test</div>;
|
||||
}
|
||||
}
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
render(<Test />);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(1);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1500);
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
expect(document.querySelector('.ant-message-move-up-leave')).toBeFalsy();
|
||||
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('update message content with a unique key and cancel manually', async () => {
|
||||
const key = 'updatable';
|
||||
|
||||
const hideLoading = message.loading({ content: 'Loading...', key, duration: 0 });
|
||||
await awaitPromise();
|
||||
|
||||
setTimeout(() => {
|
||||
act(() => {
|
||||
hideLoading();
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
|
||||
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(1);
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1500);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(document.querySelectorAll('.ant-message-move-up-leave')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not throw error when pass null', async () => {
|
||||
it('update message content with a unique key and cancel manually', () => {
|
||||
const key = 'updatable';
|
||||
class Test extends React.Component {
|
||||
componentDidMount() {
|
||||
let hideLoading: MessageType;
|
||||
act(() => {
|
||||
hideLoading = message.loading({ content: 'Loading...', key, duration: 0 });
|
||||
});
|
||||
// Testing that content of the message should be cancel manually.
|
||||
setTimeout(() => {
|
||||
act(() => {
|
||||
hideLoading();
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div>test</div>;
|
||||
}
|
||||
}
|
||||
|
||||
render(<Test />);
|
||||
expect(document.querySelectorAll('.ant-message-notice').length).toBe(1);
|
||||
|
||||
jest.advanceTimersByTime(1500);
|
||||
expect(getInstance()!.component.state.notices).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not throw error when pass null', () => {
|
||||
message.error(null);
|
||||
await awaitPromise();
|
||||
});
|
||||
});
|
||||
|
@ -1,292 +1,339 @@
|
||||
import React from 'react';
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import notification, { actWrapper } from '..';
|
||||
import notification, { getInstance, type NotificationInstance } from '..';
|
||||
import { sleep, act } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
import { act, fireEvent } from '../../../tests/utils';
|
||||
import { awaitPromise, triggerMotionEnd } from './util';
|
||||
|
||||
Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
|
||||
writable: true,
|
||||
value: true,
|
||||
});
|
||||
|
||||
type NotificationWithIconType = keyof Omit<NotificationInstance, 'open'>;
|
||||
|
||||
describe('notification', () => {
|
||||
beforeAll(() => {
|
||||
actWrapper(act);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
jest.clearAllTimers();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up
|
||||
notification.destroy();
|
||||
await triggerMotionEnd();
|
||||
|
||||
notification.config({
|
||||
prefixCls: null,
|
||||
getContainer: null,
|
||||
afterEach(() => {
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
jest.useRealTimers();
|
||||
|
||||
await awaitPromise();
|
||||
act(() => {
|
||||
notification.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
it('not duplicate create holder', async () => {
|
||||
notification.config({
|
||||
prefixCls: 'additional-holder',
|
||||
});
|
||||
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
prefixCls: 'additional-holder',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
await awaitPromise();
|
||||
await sleep();
|
||||
|
||||
const count = document.querySelectorAll('.additional-holder').length;
|
||||
expect(count).toEqual(1);
|
||||
});
|
||||
|
||||
it('should be able to hide manually', async () => {
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title 1',
|
||||
duration: 0,
|
||||
key: '1',
|
||||
});
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.additional-holder')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should be able to hide manually', async () => {
|
||||
notification.open({
|
||||
message: 'Notification Title 1',
|
||||
duration: 0,
|
||||
key: '1',
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
notification.open({
|
||||
message: 'Notification Title 2',
|
||||
duration: 0,
|
||||
key: '2',
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title 2',
|
||||
duration: 0,
|
||||
key: '2',
|
||||
});
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(2);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
// Close 1
|
||||
notification.destroy('1');
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(document.querySelectorAll('.ant-notification-notice').length).toBe(2);
|
||||
|
||||
await triggerMotionEnd();
|
||||
act(() => {
|
||||
notification.close('1');
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(1);
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect((await getInstance('ant-notification-topRight'))!.component.state.notices).toHaveLength(
|
||||
1,
|
||||
);
|
||||
|
||||
// Close 2
|
||||
notification.destroy('2');
|
||||
act(() => {
|
||||
notification.close('2');
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
await triggerMotionEnd();
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(0);
|
||||
expect((await getInstance('ant-notification-topRight'))!.component.state.notices).toHaveLength(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
it('should be able to destroy globally', async () => {
|
||||
notification.open({
|
||||
message: 'Notification Title 1',
|
||||
duration: 0,
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
notification.open({
|
||||
message: 'Notification Title 2',
|
||||
duration: 0,
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
});
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(2);
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
});
|
||||
});
|
||||
|
||||
notification.destroy();
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await triggerMotionEnd();
|
||||
expect(document.querySelectorAll('.ant-notification').length).toBe(1);
|
||||
expect(document.querySelectorAll('.ant-notification-notice').length).toBe(2);
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification')).toHaveLength(0);
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(0);
|
||||
act(() => {
|
||||
notification.destroy();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification').length).toBe(0);
|
||||
expect(document.querySelectorAll('.ant-notification-notice').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should be able to destroy after config', () => {
|
||||
notification.config({
|
||||
bottom: 100,
|
||||
act(() => {
|
||||
notification.config({
|
||||
bottom: 100,
|
||||
});
|
||||
});
|
||||
|
||||
notification.destroy();
|
||||
act(() => {
|
||||
notification.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to config rtl', async () => {
|
||||
notification.config({
|
||||
rtl: true,
|
||||
it('should be able to config rtl', () => {
|
||||
act(() => {
|
||||
notification.config({
|
||||
rtl: true,
|
||||
});
|
||||
});
|
||||
|
||||
notification.open({
|
||||
message: 'whatever',
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'whatever',
|
||||
});
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification-rtl')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.ant-notification-rtl').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should be able to global config rootPrefixCls', async () => {
|
||||
it('should be able to global config rootPrefixCls', () => {
|
||||
ConfigProvider.config({ prefixCls: 'prefix-test', iconPrefixCls: 'bamboo' });
|
||||
|
||||
notification.success({ message: 'Notification Title', duration: 0 });
|
||||
await awaitPromise();
|
||||
act(() => {
|
||||
notification.success({ message: 'Notification Title', duration: 0 });
|
||||
});
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(0);
|
||||
expect(document.querySelectorAll('.prefix-test-notification-notice')).toHaveLength(1);
|
||||
expect(document.querySelectorAll('.bamboo-check-circle')).toHaveLength(1);
|
||||
|
||||
ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: null! });
|
||||
ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: '' });
|
||||
});
|
||||
|
||||
it('should be able to config prefixCls', async () => {
|
||||
it('should be able to config prefixCls', () => {
|
||||
notification.config({
|
||||
prefixCls: 'prefix-test',
|
||||
});
|
||||
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
});
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(0);
|
||||
expect(document.querySelectorAll('.prefix-test-notice')).toHaveLength(1);
|
||||
|
||||
notification.config({
|
||||
prefixCls: null,
|
||||
prefixCls: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to open with icon', async () => {
|
||||
const iconPrefix = '.ant-notification-notice-icon';
|
||||
|
||||
const list = ['success', 'info', 'warning', 'error'] as const;
|
||||
|
||||
list.forEach(type => {
|
||||
notification[type]({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
description: 'This is the content of the notification.',
|
||||
const openNotificationWithIcon = async (type: NotificationWithIconType) => {
|
||||
act(() => {
|
||||
notification[type]({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
description: 'This is the content of the notification.',
|
||||
});
|
||||
jest.runAllTimers();
|
||||
});
|
||||
};
|
||||
|
||||
const list: Array<NotificationWithIconType> = ['success', 'info', 'warning', 'error'];
|
||||
|
||||
const promises = list.map(type => openNotificationWithIcon(type));
|
||||
|
||||
await act(async () => {
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
list.forEach(type => {
|
||||
expect(document.querySelectorAll(`${iconPrefix}-${type}`)).toHaveLength(1);
|
||||
expect(document.querySelectorAll(`${iconPrefix}-${type}`).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to add parent class for different notification types', async () => {
|
||||
const list = ['success', 'info', 'warning', 'error'] as const;
|
||||
const openNotificationWithIcon = async (type: NotificationWithIconType) => {
|
||||
act(() => {
|
||||
notification[type]({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
description: 'This is the content of the notification.',
|
||||
});
|
||||
jest.runAllTimers();
|
||||
});
|
||||
};
|
||||
|
||||
const list: Array<NotificationWithIconType> = ['success', 'info', 'warning', 'error'];
|
||||
const promises = list.map(type => openNotificationWithIcon(type));
|
||||
|
||||
await act(async () => {
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
list.forEach(type => {
|
||||
notification[type]({
|
||||
expect(document.querySelectorAll(`.ant-notification-notice-${type}`).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('trigger onClick', () => {
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
description: 'This is the content of the notification.',
|
||||
});
|
||||
});
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
list.forEach(type => {
|
||||
expect(document.querySelectorAll(`.ant-notification-notice-${type}`)).toHaveLength(1);
|
||||
});
|
||||
expect(document.querySelectorAll('.ant-notification').length).toBe(1);
|
||||
});
|
||||
|
||||
it('trigger onClick', async () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
onClick,
|
||||
it('support closeIcon', () => {
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
closeIcon: <span className="test-customize-icon" />,
|
||||
});
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.ant-notification')).toHaveLength(1);
|
||||
|
||||
fireEvent.click(document.querySelector('.ant-notification-notice')!);
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
expect(document.querySelectorAll('.test-customize-icon').length).toBe(1);
|
||||
});
|
||||
|
||||
it('support closeIcon', async () => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
closeIcon: <span className="test-customize-icon" />,
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelectorAll('.test-customize-icon')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('support config closeIcon', async () => {
|
||||
it('support config closeIcon', () => {
|
||||
notification.config({
|
||||
closeIcon: <span className="test-customize-icon" />,
|
||||
});
|
||||
|
||||
// Global Icon
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelector('.test-customize-icon')).toBeTruthy();
|
||||
|
||||
// Notice Icon
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
closeIcon: <span className="replace-icon" />,
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
closeIcon: <span className="test-customize-icon" />,
|
||||
});
|
||||
});
|
||||
|
||||
expect(document.querySelector('.replace-icon')).toBeTruthy();
|
||||
|
||||
notification.config({
|
||||
closeIcon: null,
|
||||
});
|
||||
expect(document.querySelectorAll('.test-customize-icon').length).toBe(1);
|
||||
});
|
||||
|
||||
it('closeIcon should be update', async () => {
|
||||
const list = ['1', '2'];
|
||||
list.forEach(type => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
closeIcon: <span className={`test-customize-icon-${type}`} />,
|
||||
duration: 0,
|
||||
const openNotificationWithCloseIcon = async (type: '1' | '2') => {
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
closeIcon: <span className={`test-customize-icon-${type}`} />,
|
||||
});
|
||||
jest.runAllTimers();
|
||||
});
|
||||
};
|
||||
|
||||
const list: Array<'1' | '2'> = ['1', '2'];
|
||||
const promises = list.map(type => openNotificationWithCloseIcon(type));
|
||||
|
||||
await act(async () => {
|
||||
await Promise.all(promises);
|
||||
});
|
||||
|
||||
await awaitPromise();
|
||||
|
||||
list.forEach(type => {
|
||||
expect(document.querySelector(`.test-customize-icon-${type}`)).toBeTruthy();
|
||||
expect(document.querySelectorAll(`.test-customize-icon-${type}`).length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('support config duration', async () => {
|
||||
it('support config duration', () => {
|
||||
notification.config({
|
||||
duration: 0,
|
||||
});
|
||||
|
||||
notification.open({
|
||||
message: 'whatever',
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'whatever',
|
||||
});
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelector('.ant-notification')).toBeTruthy();
|
||||
expect(document.querySelectorAll('.ant-notification').length).toBe(1);
|
||||
});
|
||||
|
||||
it('support icon', async () => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
icon: <UserOutlined />,
|
||||
it('support icon', () => {
|
||||
act(() => {
|
||||
notification.open({
|
||||
message: 'Notification Title',
|
||||
duration: 0,
|
||||
icon: <UserOutlined />,
|
||||
});
|
||||
});
|
||||
await awaitPromise();
|
||||
|
||||
expect(document.querySelector('.anticon-user')).toBeTruthy();
|
||||
expect(document.querySelectorAll('.anticon-user').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,40 +1,39 @@
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import Popover from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import { render } from '../../../tests/utils';
|
||||
import { render, fireEvent } from '../../../tests/utils';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
|
||||
describe('Popover', () => {
|
||||
mountTest(Popover);
|
||||
|
||||
it('should show overlay when trigger is clicked', () => {
|
||||
const ref = React.createRef();
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const popover = mount(
|
||||
const popover = render(
|
||||
<Popover ref={ref} content="console.log('hello world')" title="code" trigger="click">
|
||||
<span>show me your code</span>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
expect(ref.current.getPopupDomNode()).toBe(null);
|
||||
|
||||
popover.find('span').simulate('click');
|
||||
expect(popover.find('Trigger PopupInner').props().visible).toBeTruthy();
|
||||
expect(popover.container.querySelector('.ant-popover-inner-content')).toBeFalsy();
|
||||
fireEvent.click(popover.container.querySelector('span')!);
|
||||
expect(popover.container.querySelector('.ant-popover-inner-content')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('shows content for render functions', () => {
|
||||
const renderTitle = () => 'some-title';
|
||||
const renderContent = () => 'some-content';
|
||||
const ref = React.createRef();
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const popover = mount(
|
||||
const popover = render(
|
||||
<Popover ref={ref} content={renderContent} title={renderTitle} trigger="click">
|
||||
<span>show me your code</span>
|
||||
<span>show me your code </span>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
popover.find('span').simulate('click');
|
||||
fireEvent.click(popover.container.querySelector('span')!);
|
||||
|
||||
const popup = ref.current.getPopupDomNode();
|
||||
expect(popup).not.toBe(null);
|
||||
@ -44,30 +43,31 @@ describe('Popover', () => {
|
||||
});
|
||||
|
||||
it('handles empty title/content props safely', () => {
|
||||
const ref = React.createRef();
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const popover = mount(
|
||||
const popover = render(
|
||||
<Popover trigger="click" ref={ref}>
|
||||
<span>show me your code</span>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
popover.find('span').simulate('click');
|
||||
fireEvent.click(popover.container.querySelector('span')!);
|
||||
|
||||
const popup = ref.current.getPopupDomNode();
|
||||
expect(popup).toBe(null);
|
||||
});
|
||||
|
||||
it('should not render popover when the title & content props is empty', () => {
|
||||
const ref = React.createRef();
|
||||
const ref = React.createRef<any>();
|
||||
|
||||
const popover = mount(
|
||||
const popover = render(
|
||||
<Popover trigger="click" ref={ref} content="">
|
||||
<span>show me your code</span>
|
||||
</Popover>,
|
||||
);
|
||||
|
||||
popover.find('span').simulate('click');
|
||||
fireEvent.click(popover.container.querySelector('span')!);
|
||||
|
||||
const popup = ref.current.getPopupDomNode();
|
||||
|
||||
expect(popup).toBe(null);
|
||||
@ -88,13 +88,13 @@ describe('Popover', () => {
|
||||
});
|
||||
|
||||
it(`should be rendered correctly in RTL direction`, () => {
|
||||
const wrapper = mount(
|
||||
const wrapper = render(
|
||||
<ConfigProvider direction="rtl">
|
||||
<Popover title="RTL" visible>
|
||||
<span>show me your Rtl demo</span>
|
||||
</Popover>
|
||||
</ConfigProvider>,
|
||||
);
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
expect(Array.from(wrapper.container.children)).toMatchSnapshot();
|
||||
});
|
||||
});
|
@ -59,6 +59,7 @@ const genStatusStyle = (
|
||||
rootSelectCls: string,
|
||||
token: {
|
||||
componentCls: string;
|
||||
antCls: string;
|
||||
borderHoverColor: string;
|
||||
outlineColor: string;
|
||||
controlOutlineWidth: number;
|
||||
@ -66,7 +67,7 @@ const genStatusStyle = (
|
||||
},
|
||||
overwriteDefaultBorder: boolean = false,
|
||||
): CSSObject => {
|
||||
const { componentCls, borderHoverColor, outlineColor } = token;
|
||||
const { componentCls, borderHoverColor, outlineColor, antCls } = token;
|
||||
|
||||
const overwriteStyle: CSSObject = overwriteDefaultBorder
|
||||
? {
|
||||
@ -78,21 +79,22 @@ const genStatusStyle = (
|
||||
|
||||
return {
|
||||
[rootSelectCls]: {
|
||||
[`&:not(${componentCls}-disabled):not(${componentCls}-customize-input)`]: {
|
||||
...overwriteStyle,
|
||||
[`&:not(${componentCls}-disabled):not(${componentCls}-customize-input):not(${antCls}-pagination-size-changer)`]:
|
||||
{
|
||||
...overwriteStyle,
|
||||
|
||||
[`${componentCls}-focused& ${componentCls}-selector`]: {
|
||||
borderColor: borderHoverColor,
|
||||
boxShadow: `0 0 0 ${token.controlOutlineWidth}px ${outlineColor}`,
|
||||
borderInlineEndWidth: `${token.controlLineWidth}px !important`,
|
||||
outline: 0,
|
||||
},
|
||||
[`${componentCls}-focused& ${componentCls}-selector`]: {
|
||||
borderColor: borderHoverColor,
|
||||
boxShadow: `0 0 0 ${token.controlOutlineWidth}px ${outlineColor}`,
|
||||
borderInlineEndWidth: `${token.controlLineWidth}px !important`,
|
||||
outline: 0,
|
||||
},
|
||||
|
||||
[`&:hover ${componentCls}-selector`]: {
|
||||
borderColor: borderHoverColor,
|
||||
borderInlineEndWidth: `${token.controlLineWidth}px !important`,
|
||||
[`&:hover ${componentCls}-selector`]: {
|
||||
borderColor: borderHoverColor,
|
||||
borderInlineEndWidth: `${token.controlLineWidth}px !important`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -844,6 +844,435 @@ exports[`renders ./components/steps/demo/icon.md extend context correctly 1`] =
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/steps/demo/label-placement.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-steps ant-steps-horizontal ant-steps-label-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-finish"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="check"
|
||||
class="anticon anticon-check ant-steps-finish-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Finished
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-process ant-steps-item-active"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
In Progress
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-wait"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Waiting
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-steps ant-steps-horizontal ant-steps-with-progress ant-steps-label-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-finish"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="check"
|
||||
class="anticon anticon-check ant-steps-finish-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Finished
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-process ant-steps-item-active"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-progress-icon"
|
||||
>
|
||||
<div
|
||||
class="ant-progress ant-progress-circle ant-progress-status-normal ant-progress-show-info ant-progress-default"
|
||||
>
|
||||
<div
|
||||
class="ant-progress-inner"
|
||||
style="width:40px;height:40px;font-size:12px"
|
||||
>
|
||||
<svg
|
||||
class="ant-progress-circle"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<circle
|
||||
class="ant-progress-circle-trail"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="48"
|
||||
stroke-linecap="round"
|
||||
stroke-width="4"
|
||||
style="stroke-dasharray:301.59289474462014px 301.59289474462014;stroke-dashoffset:0;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;fill-opacity:0"
|
||||
/>
|
||||
<circle
|
||||
class="ant-progress-circle-path"
|
||||
cx="50"
|
||||
cy="50"
|
||||
opacity="1"
|
||||
r="48"
|
||||
stroke-linecap="round"
|
||||
stroke-width="4"
|
||||
style="stroke-dasharray:301.59289474462014px 301.59289474462014;stroke-dashoffset:122.63715789784806;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;fill-opacity:0"
|
||||
/>
|
||||
<circle
|
||||
class="ant-progress-circle-path"
|
||||
cx="50"
|
||||
cy="50"
|
||||
opacity="0"
|
||||
r="48"
|
||||
stroke-linecap="round"
|
||||
stroke-width="4"
|
||||
style="stroke:#52C41A;stroke-dasharray:301.59289474462014px 301.59289474462014;stroke-dashoffset:301.58289474462015;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;fill-opacity:0"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="ant-progress-text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
In Progress
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-wait"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Waiting
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-steps ant-steps-horizontal ant-steps-small ant-steps-label-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-finish"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="check"
|
||||
class="anticon anticon-check ant-steps-finish-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Finished
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-process ant-steps-item-active"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
In Progress
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-wait"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Waiting
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/steps/demo/nav.md extend context correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
@ -724,6 +724,435 @@ exports[`renders ./components/steps/demo/icon.md correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/steps/demo/label-placement.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
class="ant-steps ant-steps-horizontal ant-steps-label-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-finish"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="check"
|
||||
class="anticon anticon-check ant-steps-finish-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Finished
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-process ant-steps-item-active"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
In Progress
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-wait"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Waiting
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-steps ant-steps-horizontal ant-steps-with-progress ant-steps-label-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-finish"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="check"
|
||||
class="anticon anticon-check ant-steps-finish-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Finished
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-process ant-steps-item-active"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-progress-icon"
|
||||
>
|
||||
<div
|
||||
class="ant-progress ant-progress-circle ant-progress-status-normal ant-progress-show-info ant-progress-default"
|
||||
>
|
||||
<div
|
||||
class="ant-progress-inner"
|
||||
style="width:40px;height:40px;font-size:12px"
|
||||
>
|
||||
<svg
|
||||
class="ant-progress-circle"
|
||||
viewBox="0 0 100 100"
|
||||
>
|
||||
<circle
|
||||
class="ant-progress-circle-trail"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="48"
|
||||
stroke-linecap="round"
|
||||
stroke-width="4"
|
||||
style="stroke-dasharray:301.59289474462014px 301.59289474462014;stroke-dashoffset:0;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;fill-opacity:0"
|
||||
/>
|
||||
<circle
|
||||
class="ant-progress-circle-path"
|
||||
cx="50"
|
||||
cy="50"
|
||||
opacity="1"
|
||||
r="48"
|
||||
stroke-linecap="round"
|
||||
stroke-width="4"
|
||||
style="stroke-dasharray:301.59289474462014px 301.59289474462014;stroke-dashoffset:122.63715789784806;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;fill-opacity:0"
|
||||
/>
|
||||
<circle
|
||||
class="ant-progress-circle-path"
|
||||
cx="50"
|
||||
cy="50"
|
||||
opacity="0"
|
||||
r="48"
|
||||
stroke-linecap="round"
|
||||
stroke-width="4"
|
||||
style="stroke:#52C41A;stroke-dasharray:301.59289474462014px 301.59289474462014;stroke-dashoffset:301.58289474462015;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s;fill-opacity:0"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="ant-progress-text"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
In Progress
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-wait"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Waiting
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
<br />,
|
||||
<div
|
||||
class="ant-steps ant-steps-horizontal ant-steps-small ant-steps-label-vertical"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-finish"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
<span
|
||||
aria-label="check"
|
||||
class="anticon anticon-check ant-steps-finish-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5 207 474a32 32 0 00-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Finished
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-process ant-steps-item-active"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
In Progress
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item ant-steps-item-wait"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-container"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-tail"
|
||||
/>
|
||||
<div
|
||||
class="ant-steps-item-icon"
|
||||
>
|
||||
<span
|
||||
class="ant-steps-icon"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-content"
|
||||
>
|
||||
<div
|
||||
class="ant-steps-item-title"
|
||||
>
|
||||
Waiting
|
||||
</div>
|
||||
<div
|
||||
class="ant-steps-item-description"
|
||||
>
|
||||
This is a description.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/steps/demo/nav.md correctly 1`] = `
|
||||
Array [
|
||||
<div
|
||||
|
45
components/steps/demo/label-placement.md
Normal file
45
components/steps/demo/label-placement.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
order: 14
|
||||
title:
|
||||
zh-CN: 标签放置位置
|
||||
en-US: Label Placement
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
修改标签放置位置为 `vertical`。
|
||||
|
||||
## en-US
|
||||
|
||||
Set labelPlacement to `vertical`.
|
||||
|
||||
```tsx
|
||||
import { Steps } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const { Step } = Steps;
|
||||
|
||||
const App: React.FC = () => (
|
||||
<>
|
||||
<Steps current={1} labelPlacement="vertical">
|
||||
<Step title="Finished" description="This is a description." />
|
||||
<Step title="In Progress" description="This is a description." />
|
||||
<Step title="Waiting" description="This is a description." />
|
||||
</Steps>
|
||||
<br />
|
||||
<Steps current={1} percent={60} labelPlacement="vertical">
|
||||
<Step title="Finished" description="This is a description." />
|
||||
<Step title="In Progress" description="This is a description." />
|
||||
<Step title="Waiting" description="This is a description." />
|
||||
</Steps>
|
||||
<br />
|
||||
<Steps current={1} size="small" labelPlacement="vertical">
|
||||
<Step title="Finished" description="This is a description." />
|
||||
<Step title="In Progress" description="This is a description." />
|
||||
<Step title="Waiting" description="This is a description." />
|
||||
</Steps>
|
||||
</>
|
||||
);
|
||||
|
||||
export default App;
|
||||
```
|
@ -15,9 +15,17 @@ const genStepsProgressStyle: GenerateStyle<StepsToken, CSSObject> = token => {
|
||||
},
|
||||
},
|
||||
|
||||
[`&${componentCls}-horizontal ${componentCls}-item:first-child`]: {
|
||||
paddingBottom: token.paddingXXS,
|
||||
paddingInlineStart: token.paddingXXS,
|
||||
[`&${componentCls}-horizontal`]: {
|
||||
[`${componentCls}-item:first-child`]: {
|
||||
paddingBottom: token.paddingXXS,
|
||||
paddingInlineStart: token.paddingXXS,
|
||||
},
|
||||
},
|
||||
|
||||
[`&${componentCls}-label-vertical`]: {
|
||||
[`${componentCls}-item ${componentCls}-item-tail`]: {
|
||||
top: token.margin - 2 * token.lineWidth,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-item-icon`]: {
|
||||
|
@ -3,6 +3,7 @@ import type { UploadProps as RcUploadProps } from 'rc-upload';
|
||||
import RcUpload from 'rc-upload';
|
||||
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
||||
import * as React from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import DisabledContext from '../config-provider/DisabledContext';
|
||||
import LocaleReceiver from '../locale-provider/LocaleReceiver';
|
||||
@ -103,7 +104,11 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
|
||||
cloneList = cloneList.slice(0, maxCount);
|
||||
}
|
||||
|
||||
setMergedFileList(cloneList);
|
||||
// Prevent React18 auto batch since input[upload] trigger process at same time
|
||||
// which makes fileList closure problem
|
||||
flushSync(() => {
|
||||
setMergedFileList(cloneList);
|
||||
});
|
||||
|
||||
const changeInfo: UploadChangeParam<UploadFile> = {
|
||||
file: file as UploadFile,
|
||||
|
@ -914,7 +914,7 @@ describe('Upload', () => {
|
||||
throw new TypeError("Object doesn't support this action");
|
||||
};
|
||||
|
||||
jest.spyOn(global, 'File').mockImplementationOnce(fileConstructor);
|
||||
const spyIE = jest.spyOn(global, 'File').mockImplementationOnce(fileConstructor);
|
||||
fireEvent.change(container.querySelector('input'), {
|
||||
target: {
|
||||
files: [{ file: 'foo.png' }],
|
||||
@ -925,6 +925,7 @@ describe('Upload', () => {
|
||||
await sleep();
|
||||
|
||||
expect(onChange.mock.calls[0][0].fileList).toHaveLength(1);
|
||||
spyIE.mockRestore();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/33819
|
||||
@ -965,4 +966,58 @@ describe('Upload', () => {
|
||||
const { container: wrapper2 } = render(<Upload prefixCls="custom-upload" />);
|
||||
expect(wrapper2.querySelectorAll('.custom-upload-list').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/36869
|
||||
it('Prevent auto batch', async () => {
|
||||
const mockFile1 = new File(['bamboo'], 'bamboo.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const mockFile2 = new File(['light'], 'light.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
|
||||
let info1;
|
||||
let info2;
|
||||
|
||||
const onChange = jest.fn();
|
||||
const { container } = render(
|
||||
<Upload
|
||||
customRequest={info => {
|
||||
if (info.file === mockFile1) {
|
||||
info1 = info;
|
||||
} else {
|
||||
info2 = info;
|
||||
}
|
||||
}}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.change(container.querySelector('input'), {
|
||||
target: {
|
||||
files: [mockFile1, mockFile2],
|
||||
},
|
||||
});
|
||||
|
||||
// React 18 is async now
|
||||
await act(async () => {
|
||||
await sleep();
|
||||
});
|
||||
onChange.mockReset();
|
||||
|
||||
// Processing
|
||||
act(() => {
|
||||
info1.onProgress({ percent: 10 }, mockFile1);
|
||||
info2.onProgress({ percent: 20 }, mockFile2);
|
||||
});
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
fileList: [
|
||||
expect.objectContaining({ percent: 10 }),
|
||||
expect.objectContaining({ percent: 20 }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -16,7 +16,7 @@ Finally, if you are working in a local development environment, please refer to
|
||||
Here is a simple online codesandbox demo of an Ant Design component to show the usage of Ant Design React.
|
||||
|
||||
<iframe
|
||||
src="https://codesandbox.io/embed/antd-reproduction-template-6e93z?autoresize=1&fontsize=14&hidenavigation=1&theme=light"
|
||||
src="https://codesandbox.io/embed/antd-reproduction-template-y9vgcf?autoresize=1&fontsize=14&hidenavigation=1&theme=light"
|
||||
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
|
||||
title="antd reproduction template"
|
||||
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
|
||||
|
@ -14,7 +14,7 @@ Ant Design React 致力于提供给程序员**愉悦**的开发体验。
|
||||
这是一个最简单的 Ant Design 组件的在线 codesandbox 演示。
|
||||
|
||||
<iframe
|
||||
src="https://codesandbox.io/embed/antd-reproduction-template-6e93z?autoresize=1&fontsize=14&hidenavigation=1&theme=light"
|
||||
src="https://codesandbox.io/embed/antd-reproduction-template-y9vgcf?autoresize=1&fontsize=14&hidenavigation=1&theme=light"
|
||||
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
|
||||
title="antd reproduction template"
|
||||
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
|
||||
|
@ -115,6 +115,7 @@
|
||||
"@ant-design/react-slick": "~0.29.1",
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@ctrl/tinycolor": "^3.4.0",
|
||||
"@testing-library/user-event": "^14.4.2",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
"dayjs": "^1.11.1",
|
||||
@ -124,7 +125,7 @@
|
||||
"rc-checkbox": "~2.3.0",
|
||||
"rc-collapse": "~3.3.0",
|
||||
"rc-dialog": "~8.9.0",
|
||||
"rc-drawer": "~5.1.0-alpha.1",
|
||||
"rc-drawer": "~5.1.0",
|
||||
"rc-dropdown": "~4.0.0",
|
||||
"rc-field-form": "~1.27.0",
|
||||
"rc-image": "~5.7.0",
|
||||
@ -288,7 +289,7 @@
|
||||
"stylelint": "^14.9.0",
|
||||
"stylelint-config-prettier": "^9.0.2",
|
||||
"stylelint-config-rational-order": "^0.1.2",
|
||||
"stylelint-config-standard": "^26.0.0",
|
||||
"stylelint-config-standard": "^27.0.0",
|
||||
"stylelint-declaration-block-no-ignored-properties": "^2.1.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"theme-switcher": "^1.0.2",
|
||||
|
@ -58,7 +58,10 @@ async function printLog() {
|
||||
type: 'list',
|
||||
name: 'fromVersion',
|
||||
message: '🏷 Please choose tag to compare with current branch:',
|
||||
choices: tags.all.reverse().slice(0, 10),
|
||||
choices: tags.all
|
||||
.filter(item => !item.includes('experimental'))
|
||||
.reverse()
|
||||
.slice(0, 10),
|
||||
},
|
||||
]);
|
||||
let { toVersion } = await inquirer.prompt([
|
||||
|
@ -216,6 +216,8 @@ class Demo extends React.Component {
|
||||
.replace(/import\s+(?:React,\s+)?{(\s+[^}]*\s+)}\s+from\s+'react'/, `const { $1 } = React;`)
|
||||
.replace(/import\s+{(\s+[^}]*\s+)}\s+from\s+'antd';/, 'const { $1 } = antd;')
|
||||
.replace(/import\s+{(\s+[^}]*\s+)}\s+from\s+'@ant-design\/icons';/, 'const { $1 } = icons;')
|
||||
.replace("import moment from 'moment';", '')
|
||||
.replace("import React from 'react';", '')
|
||||
.replace(/import\s+{\s+(.*)\s+}\s+from\s+'react-router';/, 'const { $1 } = ReactRouter;')
|
||||
.replace(
|
||||
/import\s+{\s+(.*)\s+}\s+from\s+'react-router-dom';/,
|
||||
@ -250,9 +252,11 @@ class Demo extends React.Component {
|
||||
const riddlePrefillConfig = {
|
||||
title: `${localizedTitle} - antd@${dependencies.antd}`,
|
||||
js: `${
|
||||
/import React(\D*)from 'react';/.test(sourceCode) ? '' : `import React from 'react';\n`
|
||||
}${
|
||||
react18
|
||||
? `import React from 'react';\nimport { createRoot } from 'react-dom/client';\n`
|
||||
: ''
|
||||
? `import { createRoot } from 'react-dom/client';\n`
|
||||
: `import ReactDOM from 'react-dom';\n`
|
||||
}${sourceCode.replace(/export default/, 'const ComponentDemo =')}\n\n${
|
||||
react18
|
||||
? 'createRoot(mountNode).render(<ComponentDemo />)'
|
||||
|
Loading…
Reference in New Issue
Block a user