Merge branch 'master' into next-merge-master

This commit is contained in:
MadCcc 2022-09-05 21:26:04 +08:00
commit d5c17a9a7c
197 changed files with 2727 additions and 2188 deletions

View File

@ -205,12 +205,18 @@ jobs:
if: ${{ matrix.module == 'dom' }}
run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
- name: coverage
uses: codecov/codecov-action@v3
- name: persist coverages
if: ${{ matrix.module == 'dom' && matrix.react == '17' }}
run: |
mkdir persist-coverage
mv coverage/coverage-final.json persist-coverage/react-${{matrix.react}}-test-${{matrix.module}}-${{strategy.job-index}}.json
- uses: actions/upload-artifact@v3
if: ${{ matrix.module == 'dom' && matrix.react == '17' }}
name: upload coverages
with:
# use own token to upload coverage reports
token: ${{ secrets.CODECOV_TOKEN }}
name: coverage-artifacts
path: persist-coverage/
# node test
- name: node test
@ -225,6 +231,28 @@ jobs:
LIB_DIR: dist
needs: [setup, dist]
############################ Test Coverage ###########################
upload-test-coverage:
name: test-coverage
runs-on: ubuntu-latest
needs: [normal-test]
steps:
- uses: actions/checkout@v3
- uses: actions/download-artifact@v3
with:
name: coverage-artifacts
path: persist-coverage
- name: Merge Code Coverage
run: |
npx nyc merge persist-coverage/ coverage/coverage-final.json
npx nyc report --reporter text -t coverage --report-dir coverage
rm -rf persist-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@v3
with:
# use own token to upload coverage reports
token: ${{ secrets.CODECOV_TOKEN }}
########################### Compile & Test ###########################
compile:
runs-on: ubuntu-latest

View File

@ -15,6 +15,37 @@ timeline: true
---
## 4.23.0
`2022-09-04`
- 🆕 Tooltip support nested Fragment child nodes to display bubbles. [#37045](https://github.com/ant-design/ant-design/pull/37045) [@HQ-Lin](https://github.com/HQ-Lin)
- 🆕 Dropdown.Button support `danger` props. [#36810](https://github.com/ant-design/ant-design/pull/36810) [@nuintun](https://github.com/nuintun)
- 🆕 Input.TextArea add `value` parameter to `showCount.formatter`. [#36793](https://github.com/ant-design/ant-design/pull/36793) [@JarvisArt](https://github.com/JarvisArt)
- 🆕 Table support `expandable.columnTitle` now. [#36794](https://github.com/ant-design/ant-design/pull/36794) [@losgif](https://github.com/losgif)
- Deprecate `visible` in all components and change to `open`.
- 🛠 Dropdown changes `visible` to `open`. [#37232](https://github.com/ant-design/ant-design/pull/37232) [@yykoypj](https://github.com/yykoypj)
- 🛠 Modal changes `visible` to `open`. [#37084](https://github.com/ant-design/ant-design/pull/37084) [@yykoypj](https://github.com/yykoypj)
- 🛠 Drawer changes `visible` to `open`. [#37047](https://github.com/ant-design/ant-design/pull/37047) [@yykoypj](https://github.com/yykoypj)
- 🛠 Table changes `filterDropdownVisible` to `filterDropdownOpen`. [#37026](https://github.com/ant-design/ant-design/pull/37026) [@yykoypj](https://github.com/yykoypj)
- 🛠 Slider add `tooltip` prop for all props related with Tooltip. [#37000](https://github.com/ant-design/ant-design/pull/37000) [@yykoypj](https://github.com/yykoypj)
- 🛠 Tooltip Popover and Popconfirm change `visible` to `open`. [#37241](https://github.com/ant-design/ant-design/pull/37241) [@yykoypj](https://github.com/yykoypj)
- 🛠 Remove `visible` prop of Tag. [#36934](https://github.com/ant-design/ant-design/pull/36934) [@yykoypj](https://github.com/yykoypj)
- 🛠 Deprecate `dropdownClassName` prop of all components and change to `popupClassName`. [#36880](https://github.com/ant-design/ant-design/pull/36880) [@heiyu4585](https://github.com/heiyu4585)
- 🛠 Tabs support `items` props and origin jsx usage will be depreacted. [#36889](https://github.com/ant-design/ant-design/pull/36889)
- 🐞 Fix that some css variables are not consistent with less variables.
- [#37064](https://github.com/ant-design/ant-design/pull/37064) [@TrickyPi](https://github.com/TrickyPi)
- [#37304](https://github.com/ant-design/ant-design/pull/37304) [@peritot](https://github.com/peritot)
- 🐞 Fix Menu disabled item focus style. [#37332](https://github.com/ant-design/ant-design/pull/37332)
- 💄 `@border-radius-sm` should not follow `@border-radius-base` by default. [#37309](https://github.com/ant-design/ant-design/pull/37309)
- 💄 add `@slider-handle-margin-left` to custom type. [#37001](https://github.com/ant-design/ant-design/pull/37001) [@alanhaledc](https://github.com/alanhaledc)
- 💄 Replace Tabs with fade switch motion to import switch experience. [#36943](https://github.com/ant-design/ant-design/pull/36943)
- ⌨️ Improve Form validation accessibility experience. [#36762](https://github.com/ant-design/ant-design/pull/36762) [@VladimirOtroshchenko](https://github.com/VladimirOtroshchenko)
- 🌐 Add missing translations for filterCheckall in ru_RU. [#37311](https://github.com/ant-design/ant-design/pull/37311) [@HelLuv](https://github.com/HelLuv)
- 🌐 Add missing translations in `cs_CZ`. [#37388](https://github.com/ant-design/ant-design/pull/37388) [@ZdenekKrcal](https://github.com/ZdenekKrcal)
---
## 4.22.8
`2022-08-26`

View File

@ -15,6 +15,37 @@ timeline: true
---
## 4.23.0
`2022-09-04`
- 🆕 Tooltip 支持 Fragment 子节点展示气泡。[#37045](https://github.com/ant-design/ant-design/pull/37045) [@HQ-Lin](https://github.com/HQ-Lin)
- 🆕 Dropdown.Button 支持 `danger` 样式。[#36810](https://github.com/ant-design/ant-design/pull/36810) [@nuintun](https://github.com/nuintun)
- 🆕 Input.TextArea 组件 `showCount.formatter` API 添加 `value` 参数。[#36793](https://github.com/ant-design/ant-design/pull/36793) [@JarvisArt](https://github.com/JarvisArt)
- 🆕 Table 新增 `expandable.columnTitle` 属性以支持自定义展开列表头。[#36794](https://github.com/ant-design/ant-design/pull/36794) [@losgif](https://github.com/losgif)
- 🛠 废弃所有弹窗组件的 `visible` 属性,统一为 `open`
- 🛠 Dropdown 的 `visible` 改为 `open`。[#37232](https://github.com/ant-design/ant-design/pull/37232) [@yykoypj](https://github.com/yykoypj)
- 🛠 Modal 组件的 `visible` 改为 `open`。[#37084](https://github.com/ant-design/ant-design/pull/37084) [@yykoypj](https://github.com/yykoypj)
- 🛠 Drawer 的 `visible` 改为 `open`。[#37047](https://github.com/ant-design/ant-design/pull/37047) [@yykoypj](https://github.com/yykoypj)
- 🛠 Table 组件 `columns` 中的 `filterDropdownVisible` 改为 `filterDropdownOpen`。[#37026](https://github.com/ant-design/ant-design/pull/37026) [@yykoypj](https://github.com/yykoypj)
- 🛠 Tooltip, Popover 和 Popconfirm 中的 `visible` 改为 `open`。[#37241](https://github.com/ant-design/ant-design/pull/37241) [@yykoypj](https://github.com/yykoypj)
- 🛠 Slider 的 `tooltip` 相关属性合并到 `tooltip` 属性中。[#37000](https://github.com/ant-design/ant-design/pull/37000) [@yykoypj](https://github.com/yykoypj)
- 🛠 移除 Tag 组件的 `visible` 属性。[#36934](https://github.com/ant-design/ant-design/pull/36934) [@yykoypj](https://github.com/yykoypj)
- 🛠 废弃所有组件的 `dropdownClassName`,统一为 `popupClassName`。[#36880](https://github.com/ant-design/ant-design/pull/36880) [@heiyu4585](https://github.com/heiyu4585)
- 🛠 Tabs 支持 `items` 属性,并且废弃原 jsx 语法糖用法。[#36889](https://github.com/ant-design/ant-design/pull/36889)
- 🐞 修复 css 变量与 less 变量不一致的问题。
- [#37064](https://github.com/ant-design/ant-design/pull/37064) [@TrickyPi](https://github.com/TrickyPi)
- [#37304](https://github.com/ant-design/ant-design/pull/37304) [@peritot](https://github.com/peritot)
- 🐞 修复 Menu 禁用项依然有 focus 样式的问题。[#37332](https://github.com/ant-design/ant-design/pull/37332)
- 💄 `@border-radius-sm` 变量默认值不与 `@border-radius-base` 关联,以修复 Checkbox 等组件圆角样式异常。[#37309](https://github.com/ant-design/ant-design/pull/37309)
- 💄 支持使用 `@slider-handle-margin-left` 定制样式。[#37001](https://github.com/ant-design/ant-design/pull/37001) [@alanhaledc](https://github.com/alanhaledc)
- 💄 替换 Tabs 切换样式为渐隐过渡,以提升在切换时的体验。[#36943](https://github.com/ant-design/ant-design/pull/36943)
- ⌨️ 改进 Form 校验无障碍体验。[#36762](https://github.com/ant-design/ant-design/pull/36762) [@VladimirOtroshchenko](https://github.com/VladimirOtroshchenko)
- 🌐 补全 `ru_RU``filterCheckall` 的翻译。[#37311](https://github.com/ant-design/ant-design/pull/37311) [@HelLuv](https://github.com/HelLuv)
- 🌐 补全 `cs_CZ` 的翻译。[#37388](https://github.com/ant-design/ant-design/pull/37388) [@ZdenekKrcal](https://github.com/ZdenekKrcal)
---
## 4.22.8
`2022-08-26`

View File

@ -1,11 +1,9 @@
import type { ReactWrapper } from 'enzyme';
import { mount } from 'enzyme';
import React from 'react';
import type { AffixProps, AffixState, InternalAffixClass } from '..';
import type { InternalAffixClass } from '..';
import Affix from '..';
import accessibilityTest from '../../../tests/shared/accessibilityTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render, sleep } from '../../../tests/utils';
import { render, sleep, triggerResize } from '../../../tests/utils';
import Button from '../../button';
import { getObserverEntities } from '../utils';
@ -16,11 +14,10 @@ class AffixMounter extends React.Component<{
offsetTop?: number;
onTestUpdatePosition?(): void;
onChange?: () => void;
getInstance?: (inst: InternalAffixClass) => void;
}> {
private container: HTMLDivElement;
public affix: React.Component<AffixProps, AffixState>;
componentDidMount() {
this.container.addEventListener = jest
.fn()
@ -32,6 +29,7 @@ class AffixMounter extends React.Component<{
getTarget = () => this.container;
render() {
const { getInstance, ...restProps } = this.props;
return (
<div
ref={node => {
@ -43,9 +41,9 @@ class AffixMounter extends React.Component<{
className="fixed"
target={this.getTarget}
ref={ele => {
this.affix = ele!;
getInstance?.(ele!);
}}
{...this.props}
{...restProps}
>
<Button type="primary">Fixed at the top of container</Button>
</Affix>
@ -59,7 +57,6 @@ describe('Affix Render', () => {
accessibilityTest(Affix);
const domMock = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
let affixMounterWrapper: ReactWrapper<unknown, unknown, AffixMounter>;
const classRect: Record<string, DOMRect> = {
container: {
@ -194,34 +191,43 @@ describe('Affix Render', () => {
});
describe('updatePosition when size changed', () => {
it.each([
{ name: 'inner', index: 0 },
{ name: 'outer', index: 1 },
])('inner or outer', async ({ index }) => {
it('add class automatically', async () => {
document.body.innerHTML = '<div id="mounter" />';
const updateCalled = jest.fn();
affixMounterWrapper = mount(
<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />,
let affixInstance: InternalAffixClass | null = null;
render(
<AffixMounter
getInstance={inst => {
affixInstance = inst;
}}
offsetBottom={0}
/>,
{
attachTo: document.getElementById('mounter'),
container: document.getElementById('mounter')!,
},
);
await sleep(20);
await movePlaceholder(300);
expect(
(affixMounterWrapper.find(AffixMounter).instance() as any).affix.state.affixStyle,
).toBeTruthy();
await sleep(20);
affixMounterWrapper.update();
expect(affixInstance!.state.affixStyle).toBeTruthy();
});
// Trigger inner and outer element for the two <ResizeObserver>s.
it.each([
{ selector: '.ant-btn' }, // inner
{ selector: '.fixed' }, // outer
])('trigger listener when size change', async ({ selector }) => {
const updateCalled = jest.fn();
const { container } = render(
<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />,
{
container: document.getElementById('mounter')!,
},
);
// Mock trigger resize
updateCalled.mockReset();
(affixMounterWrapper as any).triggerResize(index);
triggerResize(container.querySelector(selector)!);
await sleep(20);
expect(updateCalled).toHaveBeenCalled();
});
});

View File

@ -8,7 +8,7 @@ describe('AutoComplete children could be focus', () => {
jest.useFakeTimers();
});
let container;
let container: HTMLDivElement;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
@ -24,10 +24,8 @@ describe('AutoComplete children could be focus', () => {
it('focus() and onFocus', () => {
const handleFocus = jest.fn();
const { container: wrapper } = render(<AutoComplete onFocus={handleFocus} />, {
attachTo: container,
});
wrapper.querySelector('input').focus();
const { container: wrapper } = render(<AutoComplete onFocus={handleFocus} />, { container });
wrapper.querySelector('input')?.focus();
act(() => {
jest.runAllTimers();
});
@ -36,14 +34,12 @@ describe('AutoComplete children could be focus', () => {
it('blur() and onBlur', () => {
const handleBlur = jest.fn();
const { container: wrapper } = render(<AutoComplete onBlur={handleBlur} />, {
attachTo: container,
});
wrapper.querySelector('input').focus();
const { container: wrapper } = render(<AutoComplete onBlur={handleBlur} />, { container });
wrapper.querySelector('input')?.focus();
act(() => {
jest.runAllTimers();
});
wrapper.querySelector('input').blur();
wrapper.querySelector('input')?.blur();
act(() => {
jest.runAllTimers();
});
@ -61,17 +57,13 @@ describe('AutoComplete children could be focus', () => {
});
it('child.ref instance should support be focused and blured', () => {
let inputRef;
const inputRef = React.createRef<HTMLInputElement>();
render(
<AutoComplete dataSource={[]}>
<input
ref={node => {
inputRef = node;
}}
/>
<input ref={inputRef} />
</AutoComplete>,
);
expect(typeof inputRef.focus).toBe('function');
expect(typeof inputRef.blur).toBe('function');
expect(typeof inputRef.current?.focus).toBe('function');
expect(typeof inputRef.current?.blur).toBe('function');
});
});

View File

@ -10,14 +10,14 @@ describe('BackTop', () => {
it('should scroll to top after click it', async () => {
const { container } = render(<BackTop visibilityHeight={-1} />);
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((_, y) => {
window.scrollY = y;
window.pageYOffset = y;
document.documentElement.scrollTop = y;
});
window.scrollTo(0, 400);
expect(document.documentElement.scrollTop).toBe(400);
fireEvent.click(container.querySelector('.ant-back-top'));
fireEvent.click(container.querySelector('.ant-back-top')!);
await sleep(500);
expect(document.documentElement.scrollTop).toBe(0);
scrollToSpy.mockRestore();
@ -26,23 +26,21 @@ describe('BackTop', () => {
it('support onClick', async () => {
const onClick = jest.fn();
const { container } = render(<BackTop onClick={onClick} visibilityHeight={-1} />);
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((_, y) => {
window.scrollY = y;
window.pageYOffset = y;
});
document.dispatchEvent(new Event('scroll'));
window.scrollTo(0, 400);
fireEvent.click(container.querySelector('.ant-back-top'));
fireEvent.click(container.querySelector('.ant-back-top')!);
expect(onClick).toHaveBeenCalled();
scrollToSpy.mockRestore();
});
it('invalid target', async () => {
const onClick = jest.fn();
const { container } = render(
<BackTop onClick={onClick} visible target={() => ({ documentElement: {} })} />,
);
fireEvent.click(container.querySelector('.ant-back-top'));
const { container } = render(<BackTop onClick={onClick} visible target={undefined} />);
fireEvent.click(container.querySelector('.ant-back-top')!);
expect(onClick).toHaveBeenCalled();
});
});

View File

@ -3,6 +3,7 @@ import accessibilityTest from '../../../tests/shared/accessibilityTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render } from '../../../tests/utils';
import type { Route } from '../Breadcrumb';
import Breadcrumb from '../index';
describe('Breadcrumb', () => {
@ -22,7 +23,7 @@ describe('Breadcrumb', () => {
// https://github.com/airbnb/enzyme/issues/875
it('warns on non-Breadcrumb.Item and non-Breadcrumb.Separator children', () => {
const MyCom = () => <div>foo</div>;
const MyCom: React.FC = () => <div>foo</div>;
render(
<Breadcrumb>
<MyCom />
@ -74,7 +75,7 @@ describe('Breadcrumb', () => {
});
it('should render a menu', () => {
const routes = [
const routes: Route[] = [
{
path: 'index',
breadcrumbName: 'home',
@ -103,6 +104,7 @@ describe('Breadcrumb', () => {
},
{
path: 'third',
breadcrumbName: '',
},
];
const { asFragment } = render(<Breadcrumb routes={routes} />);
@ -142,7 +144,7 @@ describe('Breadcrumb', () => {
// https://github.com/ant-design/ant-design/issues/25975
it('should support Breadcrumb.Item default separator', () => {
const MockComponent = () => (
const MockComponent: React.FC = () => (
<span>
<Breadcrumb.Item>Mock Node</Breadcrumb.Item>
</span>

View File

@ -1,9 +1,10 @@
import React from 'react';
import React, { useMemo } from 'react';
import type { RouterProps } from 'react-router-dom';
import { Link, MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import { fireEvent, render } from '../../../tests/utils';
import Breadcrumb from '../index';
const Apps = () => (
const Apps: React.FC = () => (
<ul className="app-list">
<li>
<Link to="/apps/1">Application1</Link><Link to="/apps/1/detail">Detail</Link>
@ -33,7 +34,7 @@ describe('react router', () => {
// https://github.com/airbnb/enzyme/issues/875
it('react router 6', () => {
const Home = () => {
const Home: React.FC = () => {
const location = useLocation();
const navigate = useNavigate();
const pathSnippets = location.pathname.split('/').filter(i => i);
@ -41,7 +42,7 @@ describe('react router', () => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
return (
<Breadcrumb.Item key={url}>
<Link to={url}>{breadcrumbNameMap[url]}</Link>
<Link to={url}>{breadcrumbNameMap[url as keyof typeof breadcrumbNameMap]}</Link>
</Breadcrumb.Item>
);
});
@ -50,6 +51,14 @@ describe('react router', () => {
<Link to="/">Home</Link>
</Breadcrumb.Item>,
].concat(extraBreadcrumbItems);
const componentProps = useMemo<RouterProps>(
() => ({ component: Apps } as unknown as RouterProps),
[],
);
const renderProps = useMemo<RouterProps>(
() => ({ render: () => <span>Home Page</span> } as unknown as RouterProps),
[],
);
return (
<div className="demo">
<div className="demo-nav">
@ -57,8 +66,8 @@ describe('react router', () => {
<a onClick={() => navigate('/apps')}>Application List</a>
</div>
<Routes>
<Route path="/apps" component={Apps} />
<Route render={() => <span>Home Page</span>} />
<Route path="/apps" {...componentProps} />
<Route {...renderProps} />
</Routes>
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
</div>

View File

@ -1,4 +1,6 @@
import React from 'react';
import type { SingleValueType } from 'rc-cascader/lib/Cascader';
import type { BaseOptionType, DefaultOptionType } from '..';
import Cascader from '..';
import excludeAllWarning from '../../../tests/shared/excludeWarning';
import focusTest from '../../../tests/shared/focusTest';
@ -9,22 +11,27 @@ import { fireEvent, render } from '../../../tests/utils';
const { SHOW_CHILD, SHOW_PARENT } = Cascader;
function toggleOpen(container) {
fireEvent.mouseDown(container.querySelector('.ant-select-selector'));
function toggleOpen(container: ReturnType<typeof render>['container']) {
fireEvent.mouseDown(container.querySelector('.ant-select-selector')!);
}
function isOpen(container) {
return container.querySelector('.ant-cascader').className.includes('ant-select-open');
function isOpen(container: ReturnType<typeof render>['container']) {
return container.querySelector('.ant-cascader')?.className.includes('ant-select-open');
}
function getDropdown(container) {
function getDropdown(container: ReturnType<typeof render>['container']) {
return container.querySelector('.ant-select-dropdown');
}
function clickOption(container, menuIndex, itemIndex, type = 'click') {
function clickOption(
container: ReturnType<typeof render>['container'],
menuIndex: number,
itemIndex: number,
type = 'click',
) {
const menu = container.querySelectorAll('ul.ant-cascader-menu')[menuIndex];
const itemList = menu.querySelectorAll('li.ant-cascader-menu-item');
fireEvent[type](itemList[itemIndex]);
fireEvent?.[type as keyof typeof fireEvent]?.(itemList[itemIndex]);
}
const options = [
@ -62,8 +69,11 @@ const options = [
},
];
function filter(inputValue, path) {
return path.some(option => option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
function filter<OptionType extends BaseOptionType = DefaultOptionType>(
inputValue: string,
path: OptionType[],
): boolean {
return path.some(option => option.label.toLowerCase().includes(inputValue.toLowerCase()));
}
describe('Cascader', () => {
@ -133,31 +143,25 @@ describe('Cascader', () => {
it('backspace should work with `Cascader[showSearch]`', () => {
const { container } = render(<Cascader options={options} showSearch />);
fireEvent.change(container.querySelector('input'), { target: { value: '123' } });
fireEvent.change(container.querySelector('input')!, { target: { value: '123' } });
expect(isOpen(container)).toBeTruthy();
fireEvent.keyDown(container.querySelector('input'), {
key: 'Backspace',
keyCode: 8,
});
fireEvent.keyDown(container.querySelector('input')!, { key: 'Backspace', keyCode: 8 });
expect(isOpen(container)).toBeTruthy();
fireEvent.change(container.querySelector('input'), { target: { value: '' } });
fireEvent.change(container.querySelector('input')!, { target: { value: '' } });
expect(isOpen(container)).toBeTruthy();
fireEvent.keyDown(container.querySelector('input'), {
key: 'Backspace',
keyCode: 8,
});
fireEvent.keyDown(container.querySelector('input')!, { key: 'Backspace', keyCode: 8 });
expect(isOpen(container)).toBeFalsy();
});
it('should highlight keyword and filter when search in Cascader', () => {
const { container } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'z' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'z' } });
// React 18 with testing lib will have additional space. We have to compare innerHTML. Sad.
expect(getDropdown(container).innerHTML).toMatchSnapshot();
expect(getDropdown(container)?.innerHTML).toMatchSnapshot();
});
it('should highlight keyword and filter when search in Cascader with same field name of label and value', () => {
@ -180,8 +184,11 @@ describe('Cascader', () => {
],
},
];
function customFilter(inputValue, path) {
return path.some(option => option.name.toLowerCase().indexOf(inputValue.toLowerCase()) > -1);
function customFilter<OptionType extends BaseOptionType = DefaultOptionType>(
inputValue: string,
path: OptionType[],
): boolean {
return path.some(option => option.name.toLowerCase().includes(inputValue.toLowerCase()));
}
const { container } = render(
<Cascader
@ -190,15 +197,15 @@ describe('Cascader', () => {
showSearch={{ filter: customFilter }}
/>,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'z' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'z' } });
// React 18 with testing lib will have additional space. We have to compare innerHTML. Sad.
expect(getDropdown(container).innerHTML).toMatchSnapshot();
expect(getDropdown(container)?.innerHTML).toMatchSnapshot();
});
it('should render not found content', () => {
const { container } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), {
fireEvent.change(container.querySelector('input')!, {
target: { value: '__notfoundkeyword__' },
});
expect(getDropdown(container)).toMatchSnapshot();
@ -208,10 +215,10 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} />,
);
expect(container.querySelector('.ant-select-selection-item').textContent).toEqual(
expect(container.querySelector('.ant-select-selection-item')?.textContent).toEqual(
'Zhejiang / Hangzhou',
);
fireEvent.mouseDown(container.querySelector('.ant-select-clear'));
fireEvent.mouseDown(container.querySelector('.ant-select-clear')!);
expect(container.querySelector('.ant-select-selection-item')).toBeFalsy();
});
@ -219,14 +226,14 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} defaultValue={['zhejiang', 'hangzhou']} showSearch />,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'xxx' } });
fireEvent.mouseDown(container.querySelector('.ant-select-clear'));
expect(container.querySelector('input').value).toEqual('');
fireEvent.change(container.querySelector('input')!, { target: { value: 'xxx' } });
fireEvent.mouseDown(container.querySelector('.ant-select-clear')!);
expect(container.querySelector('input')?.value).toEqual('');
});
it('should change filtered item when options are changed', () => {
const { container, rerender } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item').length).toBe(2);
rerender(<Cascader options={[options[0]]} showSearch={{ filter }} />);
@ -235,14 +242,11 @@ describe('Cascader', () => {
it('should select item immediately when searching and pressing down arrow key', () => {
const { container } = render(<Cascader options={options} showSearch={{ filter }} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item').length).toBe(2);
expect(container.querySelectorAll('.ant-cascader-menu-item-active').length).toBe(0);
fireEvent.keyDown(container.querySelector('input'), {
key: 'Down',
keyCode: 40,
});
fireEvent.keyDown(container.querySelector('input')!, { key: 'Down', keyCode: 40 });
expect(container.querySelectorAll('.ant-cascader-menu-item-active').length).toBe(1);
});
@ -300,14 +304,14 @@ describe('Cascader', () => {
clickOption(container, 0, 0);
clickOption(container, 1, 0);
clickOption(container, 2, 0);
expect(container.querySelector('.ant-select-selection-item').textContent).toEqual(
expect(container.querySelector('.ant-select-selection-item')?.textContent).toEqual(
'Zhejiang / Hangzhou / West Lake',
);
expect(onChange).toHaveBeenCalledWith(['zhejiang', 'hangzhou', 'xihu'], expect.anything());
});
it('should show not found content when options.length is 0', () => {
const customerOptions = [];
const customerOptions: any[] = [];
const { container } = render(<Cascader options={customerOptions} />);
toggleOpen(container);
expect(getDropdown(container)).toMatchSnapshot();
@ -329,7 +333,7 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} showSearch={{ filter, limit: 1 }} />,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item')).toHaveLength(1);
});
@ -337,7 +341,7 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} showSearch={{ filter, limit: false }} />,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item')).toHaveLength(2);
});
@ -345,8 +349,8 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} showSearch={{ filter, limit: -1 }} />,
);
fireEvent.click(container.querySelector('input'));
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
fireEvent.click(container.querySelector('input')!);
fireEvent.change(container.querySelector('input')!, { target: { value: 'a' } });
expect(container.querySelectorAll('.ant-cascader-menu-item')).toHaveLength(2);
});
});
@ -384,11 +388,11 @@ describe('Cascader', () => {
it('placeholder works correctly', () => {
const { container, rerender } = render(<Cascader options={[]} />);
expect(container.querySelector('.ant-select-selection-placeholder').textContent).toEqual('');
expect(container.querySelector('.ant-select-selection-placeholder')?.textContent).toEqual('');
const customPlaceholder = 'Custom placeholder';
rerender(<Cascader options={[]} placeholder={customPlaceholder} />);
expect(container.querySelector('.ant-select-selection-placeholder').textContent).toEqual(
expect(container.querySelector('.ant-select-selection-placeholder')?.textContent).toEqual(
customPlaceholder,
);
});
@ -410,7 +414,7 @@ describe('Cascader', () => {
toggleOpen(container);
// Inject in tests/__mocks__/rc-trigger.js
expect(global.triggerProps.popupPlacement).toEqual('topRight');
expect((global as any)?.triggerProps.popupPlacement).toEqual('topRight');
});
it('popup correctly with defaultValue RTL', () => {
@ -489,7 +493,7 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} defaultValue={['options1', 'options2']} />,
);
expect(container.querySelector('.ant-select-selection-item').textContent).toEqual(
expect(container.querySelector('.ant-select-selection-item')?.textContent).toEqual(
'options1 / options2',
);
});
@ -497,7 +501,7 @@ describe('Cascader', () => {
it('can be selected when showSearch', () => {
const onChange = jest.fn();
const { container } = render(<Cascader options={options} onChange={onChange} showSearch />);
fireEvent.change(container.querySelector('input'), { target: { value: 'Zh' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'Zh' } });
expect(container.querySelectorAll('.ant-cascader-menu').length).toBe(1);
clickOption(container, 0, 0);
@ -506,14 +510,11 @@ describe('Cascader', () => {
it('options should open after press esc and then search', () => {
const { container } = render(<Cascader options={options} showSearch />);
fireEvent.change(container.querySelector('input'), { target: { value: 'jin' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'jin' } });
expect(isOpen(container)).toBeTruthy();
fireEvent.keyDown(container.querySelector('input'), {
key: 'Esc',
keyCode: 27,
});
fireEvent.keyDown(container.querySelector('input')!, { key: 'Esc', keyCode: 27 });
expect(isOpen(container)).toBeFalsy();
fireEvent.change(container.querySelector('input'), { target: { value: 'jin' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'jin' } });
expect(isOpen(container)).toBeTruthy();
});
@ -523,7 +524,7 @@ describe('Cascader', () => {
const { container } = render(
<Cascader options={options} onChange={onChange} showSearch fieldNames={sameNames} />,
);
fireEvent.change(container.querySelector('input'), { target: { value: 'est' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'est' } });
clickOption(container, 0, 0);
expect(onChange).toHaveBeenCalledWith(['Zhejiang', 'Hangzhou', 'West Lake'], expect.anything());
});
@ -533,14 +534,14 @@ describe('Cascader', () => {
toggleOpen(container);
// Inject in tests/__mocks__/rc-trigger.js
expect(global.triggerProps.popupPlacement).toEqual('bottomRight');
expect((global as any).triggerProps.popupPlacement).toEqual('bottomRight');
});
describe('legacy props', () => {
it('popupClassName', () => {
render(<Cascader open popupPlacement="bottomLeft" />);
// Inject in tests/__mocks__/rc-trigger.js
expect(global.triggerProps.popupPlacement).toEqual('bottomLeft');
expect((global as any).triggerProps.popupPlacement).toEqual('bottomLeft');
});
it('should support showCheckedStrategy child', () => {
@ -583,8 +584,8 @@ describe('Cascader', () => {
},
];
let selectedValue;
const onChange = function onChange(value) {
let selectedValue: SingleValueType[];
const onChange = function onChange(value: SingleValueType[]) {
selectedValue = value;
};
@ -603,9 +604,9 @@ describe('Cascader', () => {
clickOption(container, 1, 0);
clickOption(container, 2, 0);
clickOption(container, 2, 1);
expect(selectedValue[0].join(',')).toBe('zhejiang,hangzhou,xihu');
expect(selectedValue[1].join(',')).toBe('zhejiang,hangzhou,donghu');
expect(selectedValue.join(',')).toBe('zhejiang,hangzhou,xihu,zhejiang,hangzhou,donghu');
expect(selectedValue![0].join(',')).toBe('zhejiang,hangzhou,xihu');
expect(selectedValue![1].join(',')).toBe('zhejiang,hangzhou,donghu');
expect(selectedValue!.join(',')).toBe('zhejiang,hangzhou,xihu,zhejiang,hangzhou,donghu');
});
it('should support showCheckedStrategy parent', () => {
@ -648,8 +649,8 @@ describe('Cascader', () => {
},
];
let selectedValue;
const onChange = function onChange(value) {
let selectedValue: SingleValueType[];
const onChange = function onChange(value: SingleValueType[]) {
selectedValue = value;
};
@ -668,8 +669,8 @@ describe('Cascader', () => {
clickOption(container, 2, 0);
clickOption(container, 2, 1);
expect(selectedValue.length).toBe(1);
expect(selectedValue.join(',')).toBe('zhejiang');
expect(selectedValue!.length).toBe(1);
expect(selectedValue!.join(',')).toBe('zhejiang');
});
});
});

View File

@ -19,10 +19,10 @@ describe('Checkbox', () => {
<Checkbox onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />,
);
fireEvent.mouseEnter(container.querySelector('label'));
fireEvent.mouseEnter(container.querySelector('label')!);
expect(onMouseEnter).toHaveBeenCalled();
fireEvent.mouseLeave(container.querySelector('label'));
fireEvent.mouseLeave(container.querySelector('label')!);
expect(onMouseLeave).toHaveBeenCalled();
});

View File

@ -6,6 +6,8 @@ import Collapse from '../../collapse';
import Input from '../../input';
import Table from '../../table';
import Checkbox from '../index';
import type { CheckboxValueType } from '../Group';
import type { CheckboxGroupProps } from '../index';
describe('CheckboxGroup', () => {
mountTest(Checkbox.Group);
@ -60,9 +62,11 @@ describe('CheckboxGroup', () => {
it('all children should have a name property', () => {
const { container } = render(<Checkbox.Group name="checkboxgroup" options={['Yes', 'No']} />);
[...container.querySelectorAll('input[type="checkbox"]')].forEach(el => {
expect(el.getAttribute('name')).toEqual('checkboxgroup');
});
Array.from(container.querySelectorAll<HTMLInputElement>('input[type="checkbox"]')).forEach(
el => {
expect(el.getAttribute('name')).toEqual('checkboxgroup');
},
);
});
it('passes prefixCls down to checkbox', () => {
@ -81,10 +85,10 @@ describe('CheckboxGroup', () => {
{ label: 'Apple', value: 'Apple' },
{ label: 'Orange', value: 'Orange' },
];
const renderCheckbox = props => <Checkbox.Group {...props} />;
const renderCheckbox = (props: CheckboxGroupProps) => <Checkbox.Group {...props} />;
const { container, rerender } = render(renderCheckbox({ options }));
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(0);
rerender(renderCheckbox({ options, value: 'Apple' }));
rerender(renderCheckbox({ options, value: 'Apple' as unknown as CheckboxValueType[] }));
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(1);
});
@ -116,7 +120,7 @@ describe('CheckboxGroup', () => {
<Checkbox key={2} value={2} />
</Checkbox.Group>,
);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(onChange).toHaveBeenCalledWith([2]);
});
@ -164,7 +168,7 @@ describe('CheckboxGroup', () => {
const { container } = render(
<Checkbox.Group>
<Collapse bordered={false}>
<Collapse.Panel header="test panel">
<Collapse.Panel key="test panel" header="test panel">
<div>
<Checkbox value="1">item</Checkbox>
</div>
@ -174,11 +178,11 @@ describe('CheckboxGroup', () => {
);
fireEvent.click(
container.querySelector('.ant-collapse-item').querySelector('.ant-collapse-header'),
container.querySelector('.ant-collapse-item')?.querySelector('.ant-collapse-header')!,
);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(1);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(container.querySelectorAll('.ant-checkbox-checked').length).toBe(0);
});
@ -210,12 +214,12 @@ describe('CheckboxGroup', () => {
});
it('should get div ref', () => {
const refCalls = [];
const refCalls: HTMLDivElement[] = [];
render(
<Checkbox.Group
options={['Apple', 'Pear', 'Orange']}
ref={node => {
refCalls.push(node);
refCalls.push(node!);
}}
/>,
);
@ -230,18 +234,18 @@ describe('CheckboxGroup', () => {
<Checkbox.Group options={[1, 'Pear', 'Orange']} onChange={onChange} />,
);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(onChange).toHaveBeenCalledWith([1]);
});
it('should store latest checkbox value if changed', () => {
const onChange = jest.fn();
const Demo = () => {
const [v, setV] = useState('');
const Demo: React.FC = () => {
const [v, setV] = useState<string>('');
React.useEffect(() => {
setTimeout(setV('1'), 1000);
setTimeout(setV('1') as unknown as TimerHandler, 1000);
}, []);
return (
@ -257,12 +261,12 @@ describe('CheckboxGroup', () => {
};
const { container } = render(<Demo />);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(onChange).toHaveBeenCalledWith([]);
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(onChange).toHaveBeenCalledWith(['length1']);
fireEvent.change(container.querySelector('.ant-input'), { target: { value: '' } });
fireEvent.click(container.querySelector('.ant-checkbox-input'));
fireEvent.change(container.querySelector('.ant-input')!, { target: { value: '' } });
fireEvent.click(container.querySelector('.ant-checkbox-input')!);
expect(onChange).toHaveBeenCalledWith(['A']);
});
});

View File

@ -15519,11 +15519,10 @@ exports[`ConfigProvider components Form configProvider 1`] = `
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15575,11 +15574,10 @@ exports[`ConfigProvider components Form configProvider componentDisabled 1`] = `
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15630,11 +15628,10 @@ exports[`ConfigProvider components Form configProvider componentSize large 1`] =
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15685,11 +15682,10 @@ exports[`ConfigProvider components Form configProvider componentSize middle 1`]
>
<div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15740,11 +15736,10 @@ exports[`ConfigProvider components Form configProvider virtual and dropdownMatch
>
<div
class="ant-form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help ant-form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item ant-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15795,11 +15790,10 @@ exports[`ConfigProvider components Form normal 1`] = `
>
<div
class="ant-form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help ant-form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item ant-form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light
@ -15850,11 +15844,10 @@ exports[`ConfigProvider components Form prefixCls 1`] = `
>
<div
class="prefix-Form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help prefix-Form-item-explain-connected"
style="height: 0px; opacity: 0;"
role="alert"
>
<div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item prefix-Form-item-explain-error"
role="alert"
style="height: 0px; opacity: 0;"
>
Bamboo is Light

View File

@ -30,8 +30,8 @@ export function getStyle(globalPrefixCls: string, theme: Theme) {
variables[`${type}-color-hover`] = colorPalettes[4];
variables[`${type}-color-active`] = colorPalettes[6];
variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString();
variables[`${type}-color-deprecated-bg`] = colorPalettes[1];
variables[`${type}-color-deprecated-border`] = colorPalettes[3];
variables[`${type}-color-deprecated-bg`] = colorPalettes[0];
variables[`${type}-color-deprecated-border`] = colorPalettes[2];
};
// ================ Primary Color ================

View File

@ -1,12 +1,13 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import type { DrawerProps } from '..';
import Drawer from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
const DrawerTest = ({ getContainer }) => (
const DrawerTest: React.FC<DrawerProps> = ({ getContainer }) => (
<div>
<Drawer open width={400} getContainer={getContainer}>
Here is content of Drawer
@ -59,35 +60,37 @@ describe('Drawer', () => {
});
it('getContainer return undefined', () => {
const { container: wrapper, rerender } = render(<DrawerTest getContainer={() => undefined} />);
const { container, rerender } = render(
<DrawerTest getContainer={() => undefined as unknown as HTMLElement} />,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
rerender(<DrawerTest getContainer={false} />);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
it('render top drawer', () => {
const { container: wrapper } = render(
<Drawer open height={400} placement="top" getContainer={false}>
const { container } = render(
<Drawer visible height={400} placement="top" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
it('have a title', () => {
const { container: wrapper } = render(
const { container } = render(
<Drawer open title="Test Title" getContainer={false}>
Here is content of Drawer
</Drawer>,
);
triggerMotion();
expect(wrapper.firstChild).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
it('closable is false', () => {

View File

@ -1,14 +1,15 @@
import React from 'react';
import type { DrawerProps } from '..';
import Drawer from '..';
import { act, fireEvent, render } from '../../../tests/utils';
describe('Drawer', () => {
const getDrawer = props => (
<Drawer open getContainer={false} {...props}>
Here is content of Drawer
</Drawer>
);
const DrawerTest: React.FC<DrawerProps> = props => (
<Drawer open getContainer={false} {...props}>
Here is content of Drawer
</Drawer>
);
describe('Drawer', () => {
beforeEach(() => {
jest.useFakeTimers();
});
@ -18,12 +19,12 @@ describe('Drawer', () => {
});
it('render correctly', () => {
const { container, asFragment, rerender } = render(getDrawer());
const { container, asFragment, rerender } = render(<DrawerTest />);
expect(container.querySelector('.ant-drawer-body')).toBeTruthy();
rerender(getDrawer({ open: false }));
rerender(<DrawerTest open={false} />);
expect(container.querySelector('.ant-drawer-body').textContent).toEqual(
expect(container.querySelector('.ant-drawer-body')?.textContent).toEqual(
'Here is content of Drawer',
);
@ -32,33 +33,33 @@ describe('Drawer', () => {
it('mask trigger onClose', () => {
const onClose = jest.fn();
const { container } = render(getDrawer({ onClose }));
const { container } = render(<DrawerTest onClose={onClose} />);
fireEvent.click(container.querySelector('.ant-drawer-mask'));
fireEvent.click(container.querySelector('.ant-drawer-mask')!);
expect(onClose).toHaveBeenCalled();
});
it('close button trigger onClose', () => {
const onClose = jest.fn();
const { container } = render(getDrawer({ onClose }));
const { container } = render(<DrawerTest onClose={onClose} />);
fireEvent.click(container.querySelector('.ant-drawer-close'));
fireEvent.click(container.querySelector('.ant-drawer-close')!);
expect(onClose).toHaveBeenCalled();
});
it('maskClosable no trigger onClose', () => {
const onClose = jest.fn();
const { container } = render(getDrawer({ onClose, maskClosable: false }));
const { container } = render(<DrawerTest onClose={onClose} maskClosable={false} />);
fireEvent.click(container.querySelector('.ant-drawer-mask'));
fireEvent.click(container.querySelector('.ant-drawer-mask')!);
expect(onClose).not.toHaveBeenCalled();
});
it('dom should be removed after close when destroyOnClose is true', () => {
const { container, rerender } = render(getDrawer({ destroyOnClose: true }));
const { container, rerender } = render(<DrawerTest destroyOnClose />);
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ destroyOnClose: true, open: false }));
rerender(<DrawerTest destroyOnClose open={false} />);
act(() => {
jest.runAllTimers();
});
@ -67,53 +68,53 @@ describe('Drawer', () => {
});
it('dom should be existed after close when destroyOnClose is false', () => {
const { container, rerender } = render(getDrawer());
const { container, rerender } = render(<DrawerTest />);
expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ open: false }));
rerender(<DrawerTest open={false} />);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content'));
fireEvent.animationEnd(container.querySelector('.ant-drawer-content')!);
expect(container.querySelector('.ant-drawer')).toBeTruthy();
});
it('dom should be existed after close twice when getContainer is false', () => {
const { container, rerender } = render(getDrawer({ open: true, getContainer: false }));
const { container, rerender } = render(<DrawerTest open getContainer={false} />);
expect(container.querySelector('.ant-drawer-content')).toBeTruthy();
// Hide
rerender(getDrawer({ open: false, getContainer: false }));
rerender(<DrawerTest open={false} getContainer={false} />);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper'));
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper')!);
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
// Show
rerender(getDrawer({ open: true, getContainer: false }));
rerender(<DrawerTest open getContainer={false} />);
expect(container.querySelector('.ant-drawer-content-wrapper')).toBeTruthy();
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeFalsy();
// Hide
rerender(getDrawer({ open: false, getContainer: false }));
rerender(<DrawerTest open={false} getContainer={false} />);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper'));
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper')!);
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
});
it('test afterOpenChange', async () => {
it('test afterVisibleChange', async () => {
const afterOpenChange = jest.fn();
const { container, rerender } = render(getDrawer({ afterOpenChange, open: true }));
rerender(getDrawer({ afterOpenChange, open: false }));
const { container, rerender } = render(<DrawerTest open afterOpenChange={afterOpenChange} />);
rerender(<DrawerTest open={false} afterOpenChange={afterOpenChange} />);
act(() => {
jest.runAllTimers();
});
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper'));
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper')!);
expect(afterOpenChange).toHaveBeenCalledTimes(1);
});
@ -121,18 +122,18 @@ describe('Drawer', () => {
it('should support children ref', () => {
const fn = jest.fn();
const refCallback = ref => {
const refCallback = (ref: HTMLDivElement | null) => {
expect(typeof ref).toBe('object');
fn();
};
const RefDemo = () => {
const ref = React.useRef();
const RefDemo: React.FC = () => {
const ref = React.useRef<HTMLDivElement>(null);
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
if (open) {
refCallback(ref.current);
refCallback(ref.current!);
}
}, [open]);
@ -146,7 +147,7 @@ describe('Drawer', () => {
);
};
const { container } = render(<RefDemo />);
fireEvent.click(container.querySelector('a'));
fireEvent.click(container.querySelector('a')!);
expect(fn).toHaveBeenCalled();
});
});

View File

@ -1,194 +0,0 @@
import React from 'react';
import Drawer from '..';
import { fireEvent, render } from '../../../tests/utils';
import Button from '../../button';
class MultiDrawer extends React.Component {
state = { open: false, childrenDrawer: false, hasChildren: true };
showDrawer = () => {
this.setState({
open: true,
hasChildren: true,
});
};
onClose = () => {
this.setState({
open: false,
});
};
showChildrenDrawer = () => {
this.setState({
childrenDrawer: true,
hasChildren: true,
});
};
onChildrenDrawerClose = () => {
this.setState({
childrenDrawer: false,
});
};
onRemoveChildDrawer = () => {
this.setState({
hasChildren: false,
});
};
render() {
const { childrenDrawer, open, hasChildren } = this.state;
const { placement, push } = this.props;
return (
<div>
<Button type="primary" id="open_drawer" onClick={this.showDrawer}>
Open drawer
</Button>
<Button type="primary" id="remove_drawer" onClick={this.onRemoveChildDrawer}>
rm child drawer
</Button>
<Drawer
title="Multi-level drawer"
className="test_drawer"
width={520}
onClose={this.onClose}
getContainer={false}
placement={placement}
open={open}
push={push}
>
<Button type="primary" id="open_two_drawer" onClick={this.showChildrenDrawer}>
Two-level drawer
</Button>
{hasChildren && (
<Drawer
title="Two-level Drawer"
width={320}
className="Two-level"
getContainer={false}
placement={placement}
onClose={this.onChildrenDrawerClose}
open={childrenDrawer}
>
<div id="two_drawer_text">This is two-level drawer</div>
</Drawer>
)}
<div
style={{
position: 'absolute',
bottom: 0,
width: '100%',
borderTop: '1px solid #e8e8e8',
padding: '10px 16px',
textAlign: 'right',
left: 0,
background: '#fff',
borderRadius: '0 0 4px 4px',
}}
>
<Button
style={{
marginRight: 8,
}}
onClick={this.onClose}
>
Cancel
</Button>
<Button onClick={this.onClose} type="primary">
Submit
</Button>
</div>
</Drawer>
<div className="childrenDrawer">{String(childrenDrawer)}</div>
</div>
);
}
}
describe('Drawer', () => {
it('render right MultiDrawer', () => {
const { container: wrapper } = render(<MultiDrawer placement="right" />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
});
it('render left MultiDrawer', () => {
const { container: wrapper } = render(<MultiDrawer placement="left" />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
fireEvent.click(wrapper.querySelector('.Two-level .ant-drawer-close'));
expect(wrapper.querySelector('.childrenDrawer').innerHTML).toEqual('false');
});
it('render top MultiDrawer', () => {
const { container: wrapper } = render(<MultiDrawer placement="top" />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateY(180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
});
it('render MultiDrawer is child in unmount', () => {
const { container: wrapper } = render(<MultiDrawer placement="top" mask={false} />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
fireEvent.click(wrapper.querySelector('button#remove_drawer'));
// Strange, testing-lib get wrong style in next branch.
expect(wrapper.querySelector('.ant-drawer-content-wrapper').style).toEqual(
expect.objectContaining({
transform: '',
}),
);
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateY(180px)',
});
expect(wrapper.querySelectorAll('#two_drawer_text').length).toBe(1);
});
it('custom MultiDrawer push distance', () => {
const { container: wrapper } = render(<MultiDrawer push={{ distance: 256 }} />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-256px)',
});
});
it('custom MultiDrawer push with true', () => {
const { container: wrapper } = render(<MultiDrawer push />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-180px)',
});
});
it('custom MultiDrawer push with false', () => {
const { container: wrapper } = render(<MultiDrawer push={false} />);
fireEvent.click(wrapper.querySelector('button#open_drawer'));
fireEvent.click(wrapper.querySelector('button#open_two_drawer'));
expect(wrapper.querySelector('.ant-drawer-content-wrapper').style).toEqual(
expect.objectContaining({
transform: '',
}),
);
});
});

View File

@ -0,0 +1,193 @@
import type { DrawerPopupProps } from 'rc-drawer/lib/DrawerPopup';
import React from 'react';
import Drawer from '..';
import { fireEvent, render } from '../../../tests/utils';
import Button from '../../button';
interface DrawerPropsType {
push?: DrawerPopupProps['push'];
placement?: DrawerPopupProps['placement'];
}
interface DrawerStateType {
open: boolean;
hasChildren: boolean;
childrenDrawer: boolean;
}
class MultiDrawer extends React.Component<DrawerPropsType, DrawerStateType> {
state = { open: false, childrenDrawer: false, hasChildren: true };
showDrawer = () => {
this.setState({
open: true,
hasChildren: true,
});
};
onClose = () => {
this.setState({
open: false,
});
};
showChildrenDrawer = () => {
this.setState({
childrenDrawer: true,
hasChildren: true,
});
};
onChildrenDrawerClose = () => {
this.setState({
childrenDrawer: false,
});
};
onRemoveChildDrawer = () => {
this.setState({
hasChildren: false,
});
};
render() {
const { childrenDrawer, open, hasChildren } = this.state;
const { placement, push } = this.props;
return (
<div>
<Button type="primary" id="open_drawer" onClick={this.showDrawer}>
Open drawer
</Button>
<Button type="primary" id="remove_drawer" onClick={this.onRemoveChildDrawer}>
rm child drawer
</Button>
<Drawer
title="Multi-level drawer"
className="test_drawer"
width={520}
onClose={this.onClose}
getContainer={false}
placement={placement}
open={open}
push={push}
>
<Button type="primary" id="open_two_drawer" onClick={this.showChildrenDrawer}>
Two-level drawer
</Button>
{hasChildren && (
<Drawer
title="Two-level Drawer"
width={320}
className="Two-level"
getContainer={false}
placement={placement}
onClose={this.onChildrenDrawerClose}
open={childrenDrawer}
>
<div id="two_drawer_text">This is two-level drawer</div>
</Drawer>
)}
<div
style={{
position: 'absolute',
bottom: 0,
width: '100%',
borderTop: '1px solid #e8e8e8',
padding: '10px 16px',
textAlign: 'right',
left: 0,
backgroundColor: '#fff',
borderRadius: '0 0 4px 4px',
}}
>
<Button style={{ marginRight: 8 }} onClick={this.onClose}>
Cancel
</Button>
<Button onClick={this.onClose} type="primary">
Submit
</Button>
</div>
</Drawer>
<div className="childrenDrawer">{String(childrenDrawer)}</div>
</div>
);
}
}
describe('Drawer', () => {
it('render right MultiDrawer', () => {
const { container } = render(<MultiDrawer placement="right" />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-180px)',
});
expect(container.querySelectorAll('#two_drawer_text').length).toBe(1);
});
it('render left MultiDrawer', () => {
const { container } = render(<MultiDrawer placement="left" />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(180px)',
});
expect(container.querySelectorAll('#two_drawer_text').length).toBe(1);
fireEvent.click(container.querySelector('.Two-level .ant-drawer-close')!);
expect(container.querySelector('.childrenDrawer')?.innerHTML).toEqual('false');
});
it('render top MultiDrawer', () => {
const { container } = render(<MultiDrawer placement="top" />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateY(180px)',
});
expect(container.querySelectorAll('#two_drawer_text').length).toBe(1);
});
it('render MultiDrawer is child in unmount', () => {
const mask = { mask: false };
const { container } = render(<MultiDrawer placement="top" {...mask} />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
fireEvent.click(container.querySelector('button#remove_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({ transform: '' });
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateY(180px)',
});
expect(container.querySelectorAll('#two_drawer_text').length).toBe(1);
});
it('custom MultiDrawer push distance', () => {
const { container } = render(<MultiDrawer push={{ distance: 256 }} />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-256px)',
});
});
it('custom MultiDrawer push with true', () => {
const { container } = render(<MultiDrawer push />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({
transform: 'translateX(-180px)',
});
});
it('custom MultiDrawer push with false', () => {
const { container } = render(<MultiDrawer push={false} />);
fireEvent.click(container.querySelector('button#open_drawer')!);
fireEvent.click(container.querySelector('button#open_two_drawer')!);
expect(container.querySelector('.ant-drawer-content-wrapper')).toHaveStyle({ transform: '' });
});
});

View File

@ -600,6 +600,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="name"
placeholder="Please enter user name"
@ -655,6 +656,7 @@ HTMLCollection [
http://
</span>
<input
aria-required="true"
class="ant-input"
id="url"
placeholder="Please enter url"
@ -710,6 +712,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -724,6 +727,7 @@ HTMLCollection [
aria-controls="owner_list"
aria-haspopup="listbox"
aria-owns="owner_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="owner"
@ -887,6 +891,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -901,6 +906,7 @@ HTMLCollection [
aria-controls="type_list"
aria-haspopup="listbox"
aria-owns="type_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="type"
@ -1069,6 +1075,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -1083,6 +1090,7 @@ HTMLCollection [
aria-controls="approver_list"
aria-haspopup="listbox"
aria-owns="approver_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="approver"
@ -1246,6 +1254,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
style="width: 100%;"
>
@ -2470,6 +2479,7 @@ HTMLCollection [
class="ant-form-item-control-input-content"
>
<textarea
aria-required="true"
class="ant-input"
id="description"
placeholder="please enter url description"

View File

@ -47,21 +47,24 @@ describe('DropdownButton', () => {
onOpenChange: () => {},
};
render(<DropdownButton {...props} />);
const { rerender } = render(<DropdownButton {...props} />);
Object.keys(props).forEach((key: keyof DropdownProps) => {
expect(dropdownProps[key]).toBe(props[key]);
});
rerender(<DropdownButton overlay={<div>123</div>} visible />);
expect(dropdownProps.open).toBe(true);
});
it("don't pass visible to Dropdown if it's not exits", () => {
it("don't pass open to Dropdown if it's not exits", () => {
const menu = (
<Menu>
<Menu.Item key="1">foo</Menu.Item>
</Menu>
);
render(<DropdownButton overlay={menu} />);
expect('visible' in dropdownProps).toBe(false);
expect('open' in dropdownProps).toBe(false);
});
it('should support href like Button', () => {

View File

@ -95,6 +95,7 @@ describe('Dropdown', () => {
expect(error).toHaveBeenCalledWith(
expect.stringContaining("[antd: Dropdown] You are using 'topCenter'"),
);
error.mockRestore();
});
// zombieJ: when replaced with react test lib, it may be mock fully content
@ -166,4 +167,27 @@ describe('Dropdown', () => {
jest.useRealTimers();
});
it('deprecated warning', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const { rerender } = render(
<Dropdown visible overlay={<div>menu</div>}>
<a />
</Dropdown>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `visible` is deprecated which will be removed in next major version, please use `open` instead.',
);
rerender(
<Dropdown onVisibleChange={() => {}} overlay={<div>menu</div>}>
<a />
</Dropdown>,
);
expect(errSpy).toHaveBeenCalledWith(
'Warning: [antd: Dropdown] `onVisibleChange` is deprecated which will be removed in next major version, please use `onOpenChange` instead.',
);
errSpy.mockRestore();
});
});

View File

@ -27,8 +27,8 @@ When there are more than a few options to choose from, you can wrap them in a `D
| overlayStyle | The style of the dropdown root element | CSSProperties | - | |
| placement | Placement of popup menu: `bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | The trigger mode which executes the dropdown action. Note that hover can't be used on touchscreens | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| open | Whether the dropdown menu is currently open | boolean | - | |
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item | (open: boolean) => void | - | |
| open | Whether the dropdown menu is currently open | boolean | - | 4.23.0 |
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item | (open: boolean) => void | - | 4.23.0 |
You should use [Menu](/components/menu/) as `overlay`. The menu items and dividers are also available by using `Menu.Item` and `Menu.Divider`.
@ -50,6 +50,6 @@ You should use [Menu](/components/menu/) as `overlay`. The menu items and divide
| size | Size of the button, the same as [Button](/components/button/#API) | string | `default` | |
| trigger | The trigger mode which executes the dropdown action | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| type | Type of the button, the same as [Button](/components/button/#API) | string | `default` | |
| open | Whether the dropdown menu is currently open | boolean | - | |
| open | Whether the dropdown menu is currently open | boolean | - | 4.23.0 |
| onClick | The same as [Button](/components/button/#API): called when you click the button on the left | (event) => void | - | |
| onOpenChange | Called when the open state is changed | (open: boolean) => void | - | |
| onOpenChange | Called when the open state is changed | (open: boolean) => void | - | 4.23.0 |

View File

@ -31,8 +31,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| overlayStyle | 下拉根元素的样式 | CSSProperties | - | |
| placement | 菜单弹出位置:`bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | 触发下拉的行为, 移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| open | 菜单是否显示 | boolean | - | |
| onOpenChange | 菜单显示状态改变时调用,参数为 `open`。点击菜单按钮导致的消失不会触发 | (open: boolean) => void | - | |
| open | 菜单是否显示 | boolean | - | 4.23.0 |
| onOpenChange | 菜单显示状态改变时调用,参数为 `visible`。点击菜单按钮导致的消失不会触发 | (open: boolean) => void | - | 4.23.0 |
`overlay` 菜单使用 [Menu](/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`
@ -54,6 +54,6 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| size | 按钮大小,和 [Button](/components/button/#API) 一致 | string | `default` | |
| trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| type | 按钮类型,和 [Button](/components/button/#API) 一致 | string | `default` | |
| open | 菜单是否显示 | boolean | - | |
| open | 菜单是否显示 | boolean | - | 4.23.0 |
| onClick | 点击左侧按钮的回调,和 [Button](/components/button/#API) 一致 | (event) => void | - | |
| onOpenChange | 菜单显示状态改变时调用,参数为 `open` | (open: boolean) => void | - | |
| onOpenChange | 菜单显示状态改变时调用,参数为 `visible` | (open: boolean) => void | - | 4.23.0 |

View File

@ -11,7 +11,7 @@ describe('Empty', () => {
it('image size should change', () => {
const { container } = render(<Empty imageStyle={{ height: 20 }} />);
expect(container.querySelector('.ant-empty-image').style.height).toBe('20px');
expect(container.querySelector<HTMLDivElement>('.ant-empty-image')?.style.height).toBe('20px');
});
it('description can be false', () => {

View File

@ -30,6 +30,7 @@ function toErrorEntity(
}
export interface ErrorListProps {
fieldId?: string;
help?: React.ReactNode;
helpStatus?: ValidateStatus;
errors?: React.ReactNode[];
@ -44,6 +45,7 @@ export default function ErrorList({
errors = EMPTY_LIST,
warnings = EMPTY_LIST,
className: rootClassName,
fieldId,
onVisibleChanged,
}: ErrorListProps) {
const { prefixCls } = React.useContext(FormItemPrefixContext);
@ -72,6 +74,12 @@ export default function ErrorList({
];
}, [help, helpStatus, debounceErrors, debounceWarnings]);
const helpProps: { id?: string } = {};
if (fieldId) {
helpProps.id = `${fieldId}_help`;
}
return (
<CSSMotion
{...collapseMotion}
@ -85,8 +93,10 @@ export default function ErrorList({
return (
<div
{...helpProps}
className={classNames(baseClassName, holderClassName, rootClassName)}
style={holderStyle}
role="alert"
>
<CSSMotionList
keys={fullKeyList}
@ -106,7 +116,6 @@ export default function ErrorList({
return (
<div
key={key}
role="alert"
className={classNames(itemClassName, {
[`${baseClassName}-${errorStatus}`]: errorStatus,
})}

View File

@ -40,11 +40,16 @@ interface MemoInputProps {
value: any;
update: any;
children: React.ReactNode;
childProps: any[];
}
const MemoInput = React.memo(
({ children }: MemoInputProps) => children as JSX.Element,
(prev, next) => prev.value === next.value && prev.update === next.update,
(prev, next) =>
prev.value === next.value &&
prev.update === next.update &&
prev.childProps.length === next.childProps.length &&
prev.childProps.every((value, index) => value === next.childProps[index]),
);
export interface FormItemProps<Values = any>
@ -313,6 +318,25 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
childProps.id = fieldId;
}
if (props.help || mergedErrors.length > 0 || mergedWarnings.length > 0 || props.extra) {
const describedbyArr = [];
if (props.help || mergedErrors.length > 0) {
describedbyArr.push(`${fieldId}_help`);
}
if (props.extra) {
describedbyArr.push(`${fieldId}_extra`);
}
childProps['aria-describedby'] = describedbyArr.join(' ');
}
if (mergedErrors.length > 0) {
childProps['aria-invalid'] = 'true';
}
if (isRequired) {
childProps['aria-required'] = 'true';
}
if (supportRef(children)) {
childProps.ref = getItemRef(mergedName, children);
}
@ -330,8 +354,19 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
};
});
// List of props that need to be watched for changes -> if changes are detected in MemoInput -> rerender
const watchingChildProps = [
childProps['aria-required'],
childProps['aria-invalid'],
childProps['aria-describedby'],
];
childNode = (
<MemoInput value={mergedControl[props.valuePropName || 'value']} update={children}>
<MemoInput
value={mergedControl[props.valuePropName || 'value']}
update={children}
childProps={watchingChildProps}
>
{cloneElement(children, childProps)}
</MemoInput>
);

View File

@ -32,6 +32,7 @@ export interface FormItemInputProps {
extra?: React.ReactNode;
status?: ValidateStatus;
help?: React.ReactNode;
fieldId?: string;
}
const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = props => {
@ -45,6 +46,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
_internalItemRender: formItemRender,
extra,
help,
fieldId,
marginBottom,
onErrorVisibleChanged,
} = props;
@ -72,6 +74,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
<div style={{ display: 'flex', flexWrap: 'nowrap' }}>
<FormItemPrefixContext.Provider value={formItemContext}>
<ErrorList
fieldId={fieldId}
errors={errors}
warnings={warnings}
help={help}
@ -84,9 +87,19 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
</div>
) : null;
const extraProps: { id?: string } = {};
if (fieldId) {
extraProps.id = `${fieldId}_extra`;
}
// If extra = 0, && will goes wrong
// 0&&error -> 0
const extraDom = extra ? <div className={`${baseClassName}-extra`}>{extra}</div> : null;
const extraDom = extra ? (
<div {...extraProps} className={`${baseClassName}-extra`}>
{extra}
</div>
) : null;
const dom =
formItemRender && formItemRender.mark === 'pro_table_render' && formItemRender.render ? (

View File

@ -41,6 +41,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-0"
placeholder="placeholder"
@ -84,6 +85,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -98,6 +100,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
aria-controls="advanced_search_field-1_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-1_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-1"
@ -262,6 +265,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-2"
placeholder="placeholder"
@ -305,6 +309,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-3"
placeholder="placeholder"
@ -348,6 +353,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -362,6 +368,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
aria-controls="advanced_search_field-4_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-4_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-4"
@ -526,6 +533,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-5"
placeholder="placeholder"
@ -631,6 +639,7 @@ exports[`renders ./components/form/demo/basic.md extend context correctly 1`] =
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -672,6 +681,7 @@ exports[`renders ./components/form/demo/basic.md extend context correctly 1`] =
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -815,6 +825,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -856,6 +867,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -1184,6 +1196,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="responsive_username"
type="text"
@ -1225,6 +1238,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="responsive_password"
type="password"
@ -1601,6 +1615,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-hooks_note"
type="text"
@ -1638,6 +1653,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1652,6 +1668,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
aria-controls="control-hooks_gender_list"
aria-haspopup="listbox"
aria-owns="control-hooks_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-hooks_gender"
@ -1879,6 +1896,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-ref_note"
type="text"
@ -1916,6 +1934,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1930,6 +1949,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
aria-controls="control-ref_gender_list"
aria-haspopup="listbox"
aria-owns="control-ref_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-ref_gender"
@ -5972,6 +5992,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md extend con
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -5986,6 +6007,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md extend con
aria-controls="dynamic_form_nest_item_area_list"
aria-haspopup="listbox"
aria-owns="dynamic_form_nest_item_area_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="dynamic_form_nest_item_area"
@ -6347,6 +6369,7 @@ exports[`renders ./components/form/demo/dynamic-rule.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="dynamic_rule_username"
placeholder="Please input your name"
@ -6497,6 +6520,7 @@ exports[`renders ./components/form/demo/form-context.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basicForm_group"
type="text"
@ -6648,6 +6672,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="global_state_username"
type="text"
@ -6721,6 +6746,7 @@ exports[`renders ./components/form/demo/inline-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_username"
placeholder="Username"
@ -6775,6 +6801,7 @@ exports[`renders ./components/form/demo/inline-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_password"
placeholder="Password"
@ -7209,6 +7236,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_username"
type="text"
@ -7248,6 +7276,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md extend context correc
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_password"
type="text"
@ -7333,6 +7362,7 @@ exports[`renders ./components/form/demo/nest-messages.md extend context correctl
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="nest-messages_user_name"
type="text"
@ -7635,6 +7665,7 @@ exports[`renders ./components/form/demo/normal-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_username"
placeholder="Username"
@ -7689,6 +7720,7 @@ exports[`renders ./components/form/demo/normal-login.md extend context correctly
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_password"
placeholder="Password"
@ -7902,6 +7934,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_email"
type="text"
@ -7943,6 +7976,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_password"
type="password"
@ -8012,6 +8046,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_confirm"
type="password"
@ -8124,6 +8159,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_nickname"
type="text"
@ -8161,6 +8197,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-cascader ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -8174,6 +8211,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_residence_list"
aria-haspopup="listbox"
aria-owns="register_residence_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_residence"
@ -8521,6 +8559,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
</div>
</span>
<input
aria-required="true"
class="ant-input"
id="register_phone"
type="text"
@ -8631,6 +8670,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-input-number-input-wrap"
>
<input
aria-required="true"
autocomplete="off"
class="ant-input-number-input"
id="register_donation"
@ -8818,6 +8858,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
>
<div
@ -8832,6 +8873,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_website_list"
aria-haspopup="listbox"
aria-owns="register_website_list"
aria-required="true"
autocomplete="off"
class="ant-input ant-select-selection-search-input"
id="register_website"
@ -8897,6 +8939,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
data-count="0 / 100"
>
<textarea
aria-required="true"
class="ant-input"
id="register_intro"
/>
@ -8933,6 +8976,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -8947,6 +8991,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_gender_list"
aria-haspopup="listbox"
aria-owns="register_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_gender"
@ -9129,6 +9174,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
style="padding-left:4px;padding-right:4px"
>
<input
aria-required="true"
class="ant-input"
id="register_captcha"
type="text"
@ -10986,6 +11032,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-picker"
placeholder="Select date"
@ -11611,6 +11658,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-time-picker"
placeholder="Select date"
@ -13589,6 +13637,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_month-picker"
placeholder="Select month"
@ -13844,6 +13893,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -15054,6 +15104,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -17085,6 +17136,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_time-picker"
placeholder="Select time"
@ -18748,6 +18800,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow"
>
<div
@ -18762,6 +18815,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
aria-controls="validate_other_select_list"
aria-haspopup="listbox"
aria-owns="validate_other_select_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select"
@ -18920,6 +18974,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search"
>
<div
@ -18942,6 +18997,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
aria-controls="validate_other_select-multiple_list"
aria-haspopup="listbox"
aria-owns="validate_other_select-multiple_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select-multiple"
@ -19476,6 +19532,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-radio-group ant-radio-group-outline"
id="validate_other_radio-button"
>
@ -20095,6 +20152,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
>
<input
accept=""
aria-describedby="validate_other_upload_extra"
id="validate_other_upload"
style="display:none"
type="file"
@ -20136,6 +20194,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
</div>
<div
class="ant-form-item-extra"
id="validate_other_upload_extra"
>
longgggggggggggggggggggggggggggggggggg
</div>
@ -26409,6 +26468,7 @@ exports[`renders ./components/form/demo/warning-only.md extend context correctly
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="url"
placeholder="input placeholder"

View File

@ -41,6 +41,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-0"
placeholder="placeholder"
@ -84,6 +85,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -98,6 +100,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
aria-controls="advanced_search_field-1_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-1_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-1"
@ -180,6 +183,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-2"
placeholder="placeholder"
@ -223,6 +227,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-3"
placeholder="placeholder"
@ -266,6 +271,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -280,6 +286,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
aria-controls="advanced_search_field-4_list"
aria-haspopup="listbox"
aria-owns="advanced_search_field-4_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="advanced_search_field-4"
@ -362,6 +369,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="advanced_search_field-5"
placeholder="placeholder"
@ -467,6 +475,7 @@ exports[`renders ./components/form/demo/basic.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -508,6 +517,7 @@ exports[`renders ./components/form/demo/basic.md correctly 1`] = `
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -651,6 +661,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basic_username"
type="text"
@ -692,6 +703,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="basic_password"
type="password"
@ -913,6 +925,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="responsive_username"
type="text"
@ -954,6 +967,7 @@ Array [
>
<input
action="click"
aria-required="true"
class="ant-input"
id="responsive_password"
type="password"
@ -1223,6 +1237,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-hooks_note"
type="text"
@ -1260,6 +1275,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1274,6 +1290,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
aria-controls="control-hooks_gender_list"
aria-haspopup="listbox"
aria-owns="control-hooks_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-hooks_gender"
@ -1402,6 +1419,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="control-ref_note"
type="text"
@ -1439,6 +1457,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -1453,6 +1472,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
aria-controls="control-ref_gender_list"
aria-haspopup="listbox"
aria-owns="control-ref_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="control-ref_gender"
@ -3455,6 +3475,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md correctly
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -3469,6 +3490,7 @@ exports[`renders ./components/form/demo/dynamic-form-items-complex.md correctly
aria-controls="dynamic_form_nest_item_area_list"
aria-haspopup="listbox"
aria-owns="dynamic_form_nest_item_area_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="dynamic_form_nest_item_area"
@ -3748,6 +3770,7 @@ exports[`renders ./components/form/demo/dynamic-rule.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="dynamic_rule_username"
placeholder="Please input your name"
@ -3898,6 +3921,7 @@ exports[`renders ./components/form/demo/form-context.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="basicForm_group"
type="text"
@ -4049,6 +4073,7 @@ Array [
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="global_state_username"
type="text"
@ -4122,6 +4147,7 @@ exports[`renders ./components/form/demo/inline-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_username"
placeholder="Username"
@ -4176,6 +4202,7 @@ exports[`renders ./components/form/demo/inline-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="horizontal_login_password"
placeholder="Password"
@ -4610,6 +4637,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_username"
type="text"
@ -4649,6 +4677,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="wrap_password"
type="text"
@ -4734,6 +4763,7 @@ exports[`renders ./components/form/demo/nest-messages.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="nest-messages_user_name"
type="text"
@ -5036,6 +5066,7 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_username"
placeholder="Username"
@ -5090,6 +5121,7 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</span>
</span>
<input
aria-required="true"
class="ant-input"
id="normal_login_password"
placeholder="Password"
@ -5303,6 +5335,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_email"
type="text"
@ -5344,6 +5377,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_password"
type="password"
@ -5413,6 +5447,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
>
<input
action="click"
aria-required="true"
class="ant-input"
id="register_confirm"
type="password"
@ -5501,6 +5536,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="register_nickname"
type="text"
@ -5538,6 +5574,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-cascader ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
>
<div
@ -5551,6 +5588,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_residence_list"
aria-haspopup="listbox"
aria-owns="register_residence_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_residence"
@ -5726,6 +5764,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
</div>
</span>
<input
aria-required="true"
class="ant-input"
id="register_phone"
type="text"
@ -5836,6 +5875,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-input-number-input-wrap"
>
<input
aria-required="true"
autocomplete="off"
class="ant-input-number-input"
id="register_donation"
@ -5941,6 +5981,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
>
<div
@ -5955,6 +5996,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_website_list"
aria-haspopup="listbox"
aria-owns="register_website_list"
aria-required="true"
autocomplete="off"
class="ant-input ant-select-selection-search-input"
id="register_website"
@ -6006,6 +6048,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
data-count="0 / 100"
>
<textarea
aria-required="true"
class="ant-input"
id="register_intro"
/>
@ -6042,6 +6085,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
>
<div
@ -6056,6 +6100,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_gender_list"
aria-haspopup="listbox"
aria-owns="register_gender_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="register_gender"
@ -6139,6 +6184,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
style="padding-left:4px;padding-right:4px"
>
<input
aria-required="true"
class="ant-input"
id="register_captcha"
type="text"
@ -7186,6 +7232,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-picker"
placeholder="Select date"
@ -7257,6 +7304,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_date-time-picker"
placeholder="Select date"
@ -7328,6 +7376,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_month-picker"
placeholder="Select month"
@ -7393,6 +7442,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -7506,6 +7556,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-picker ant-picker-range"
>
<div
@ -7625,6 +7676,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input"
>
<input
aria-required="true"
autocomplete="off"
id="time_related_controls_time-picker"
placeholder="Select time"
@ -7923,6 +7975,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow"
>
<div
@ -7937,6 +7990,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
aria-controls="validate_other_select_list"
aria-haspopup="listbox"
aria-owns="validate_other_select_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select"
@ -8013,6 +8067,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search"
>
<div
@ -8035,6 +8090,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
aria-controls="validate_other_select-multiple_list"
aria-haspopup="listbox"
aria-owns="validate_other_select-multiple_list"
aria-required="true"
autocomplete="off"
class="ant-select-selection-search-input"
id="validate_other_select-multiple"
@ -8464,6 +8520,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<div
aria-required="true"
class="ant-radio-group ant-radio-group-outline"
id="validate_other_radio-button"
>
@ -9083,6 +9140,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
>
<input
accept=""
aria-describedby="validate_other_upload_extra"
id="validate_other_upload"
style="display:none"
type="file"
@ -9124,6 +9182,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
</div>
<div
class="ant-form-item-extra"
id="validate_other_upload_extra"
>
longgggggggggggggggggggggggggggggggggg
</div>
@ -11066,6 +11125,7 @@ exports[`renders ./components/form/demo/warning-only.md correctly 1`] = `
class="ant-form-item-control-input-content"
>
<input
aria-required="true"
class="ant-input"
id="url"
placeholder="input placeholder"

View File

@ -203,6 +203,158 @@ describe('Form', () => {
);
});
it('input element should have the prop aria-describedby pointing to the help id when there is a help message', () => {
const wrapper = mount(
<Form>
<Form.Item name="test" help="This is a help">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('test_help');
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBe('test_help');
});
it('input element should not have the prop aria-describedby pointing to the help id when there is a help message and name is not defined', () => {
const wrapper = mount(
<Form>
<Form.Item help="This is a help">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBeUndefined();
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBeUndefined();
});
it('input element should have the prop aria-describedby concatenated with the form name pointing to the help id when there is a help message', () => {
const wrapper = mount(
<Form name="form">
<Form.Item name="test" help="This is a help">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('form_test_help');
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBe('form_test_help');
});
it('input element should have the prop aria-describedby pointing to the help id when there are errors', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" rules={[{ len: 3 }, { type: 'number' }]}>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
input.simulate('change', { target: { value: 'Invalid number' } });
await sleep(800);
wrapper.update();
const inputChanged = wrapper.find('input');
expect(inputChanged.prop('aria-describedby')).toBe('test_help');
const help = wrapper.find('.ant-form-item-explain');
expect(help.prop('id')).toBe('test_help');
});
it('input element should have the prop aria-invalid when there are errors', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" rules={[{ len: 3 }, { type: 'number' }]}>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
input.simulate('change', { target: { value: 'Invalid number' } });
await sleep(800);
wrapper.update();
const inputChanged = wrapper.find('input');
expect(inputChanged.prop('aria-invalid')).toBe('true');
});
it('input element should have the prop aria-required when the prop `required` is true', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" required>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-required')).toBe('true');
});
it('input element should have the prop aria-required when there is a rule with required', async () => {
const wrapper = mount(
<Form>
<Form.Item name="test" rules={[{ required: true }]}>
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-required')).toBe('true');
});
it('input element should have the prop aria-describedby pointing to the extra id when there is a extra message', () => {
const wrapper = mount(
<Form>
<Form.Item name="test" extra="This is a extra message">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('test_extra');
const extra = wrapper.find('.ant-form-item-extra');
expect(extra.prop('id')).toBe('test_extra');
});
it('input element should not have the prop aria-describedby pointing to the extra id when there is a extra message and name is not defined', () => {
const wrapper = mount(
<Form>
<Form.Item extra="This is a extra message">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBeUndefined();
const extra = wrapper.find('.ant-form-item-extra');
expect(extra.prop('id')).toBeUndefined();
});
it('input element should have the prop aria-describedby pointing to the help and extra id when there is a help and extra message', () => {
const wrapper = mount(
<Form>
<Form.Item name="test" help="This is a help" extra="This is a extra message">
<input />
</Form.Item>
</Form>,
);
const input = wrapper.find('input');
expect(input.prop('aria-describedby')).toBe('test_help test_extra');
});
describe('scrollToField', () => {
function test(name, genForm) {
it(name, () => {
@ -710,9 +862,7 @@ describe('Form', () => {
await sleep(100);
wrapper.update();
await sleep(100);
expect(wrapper.find('.ant-form-item-explain div').getDOMNode().getAttribute('role')).toBe(
'alert',
);
expect(wrapper.find('.ant-form-item-explain').getDOMNode().getAttribute('role')).toBe('alert');
});
it('return same form instance', () => {

View File

@ -0,0 +1,17 @@
import React from 'react';
import Icon from '..';
import { render } from '../../../tests/utils';
// v3 兼容性测试
describe('Icon', () => {
it('should render Icon', () => {
const { container } = render(<Icon />);
expect(container.firstChild).toBe(null);
});
it('should throw Error', () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(<Icon />);
expect(errSpy).toHaveBeenCalled();
});
});

View File

@ -1,6 +1,6 @@
import warning from '../_util/warning';
const Icon = () => {
const Icon: React.FC = () => {
warning(false, 'Icon', 'Empty Icon');
return null;
};

View File

@ -11,60 +11,58 @@ describe('Image', () => {
mountTest(Image);
rtlTest(Image);
it('Image preview props set false', () => {
const { container: wrapper } = render(<Image src={src} preview={false} />);
const { container } = render(<Image src={src} preview={false} />);
fireEvent.click(wrapper.querySelector('.ant-image'));
expect(wrapper.querySelector('.ant-image-preview-root')).toBe(null);
fireEvent.click(container.querySelector('.ant-image')!);
expect(container.querySelector('.ant-image-preview-root')).toBe(null);
});
it('Group preview props set false', () => {
const { container: wrapper } = render(
const { container } = render(
<Image.PreviewGroup preview={false}>
<Image src={src} />
</Image.PreviewGroup>,
);
fireEvent.click(wrapper.querySelector('.ant-image'));
fireEvent.click(container.querySelector('.ant-image')!);
expect(wrapper.querySelector('.ant-image-preview-root')).toBe(null);
expect(container.querySelector('.ant-image-preview-root')).toBe(null);
});
it('Default preview props', () => {
const { container: wrapper, baseElement } = render(
<Image src={src} preview={{ visible: true }} />,
);
const { container, baseElement } = render(<Image src={src} preview={{ visible: true }} />);
fireEvent.click(wrapper.querySelector('.ant-image'));
fireEvent.click(container.querySelector('.ant-image')!);
expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('ant-fade');
expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('ant-zoom');
});
it('Default Group preview props', () => {
const { container: wrapper, baseElement } = render(
const { container, baseElement } = render(
<Image.PreviewGroup preview={{ visible: true }}>
<Image src={src} />
</Image.PreviewGroup>,
);
fireEvent.click(wrapper.querySelector('.ant-image'));
fireEvent.click(container.querySelector('.ant-image')!);
expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('ant-fade');
expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('ant-zoom');
});
it('Customize preview props', () => {
const { container: wrapper, baseElement } = render(
const { container, baseElement } = render(
<Image
src={src}
preview={{ visible: true, transitionName: 'abc', maskTransitionName: 'def' }}
/>,
);
fireEvent.click(wrapper.querySelector('.ant-image'));
fireEvent.click(container.querySelector('.ant-image')!);
expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('abc');
expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('def');
});
it('Customize Group preview props', () => {
const { container: wrapper, baseElement } = render(
const { container, baseElement } = render(
<Image.PreviewGroup
preview={{ visible: true, transitionName: 'abc', maskTransitionName: 'def' }}
>
@ -72,22 +70,21 @@ describe('Image', () => {
</Image.PreviewGroup>,
);
fireEvent.click(wrapper.querySelector('.ant-image'));
fireEvent.click(container.querySelector('.ant-image')!);
expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('abc');
expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('def');
});
it('ConfigProvider getPopupContainer', () => {
const { container: wrapper, baseElement } = render(
const { container, baseElement } = render(
<>
<div className="container" />
<ConfigProvider getPopupContainer={() => document.querySelector('.container')}>
<ConfigProvider getPopupContainer={() => document.querySelector('.container')!}>
<Image src={src} />
</ConfigProvider>
</>,
);
fireEvent.click(wrapper.querySelector('.ant-image'));
const containerElement = baseElement.querySelector('.container');
expect(containerElement.children.length).not.toBe(0);
fireEvent.click(container.querySelector('.ant-image')!);
expect(baseElement.querySelector('.container')?.children.length).not.toBe(0);
});
});

View File

@ -15,18 +15,18 @@ describe('InputNumber', () => {
it('should return null when blur a empty input number', () => {
const onChange = jest.fn();
const { container } = render(<InputNumber defaultValue="1" onChange={onChange} />);
fireEvent.change(container.querySelector('input'), { target: { value: '' } });
fireEvent.change(container.querySelector('input')!, { target: { value: '' } });
expect(onChange).toHaveBeenLastCalledWith(null);
});
it('should call onStep when press up or down button', () => {
const onStep = jest.fn();
const { container } = render(<InputNumber defaultValue={1} onStep={onStep} />);
fireEvent.mouseDown(container.querySelector('.ant-input-number-handler-up'));
fireEvent.mouseDown(container.querySelector('.ant-input-number-handler-up')!);
expect(onStep).toHaveBeenCalledTimes(1);
expect(onStep).toHaveBeenLastCalledWith(2, { offset: 1, type: 'up' });
fireEvent.mouseDown(container.querySelector('.ant-input-number-handler-down'));
fireEvent.mouseDown(container.querySelector('.ant-input-number-handler-down')!);
expect(onStep).toHaveBeenCalledTimes(2);
expect(onStep).toHaveBeenLastCalledWith(1, { offset: 1, type: 'down' });
});

View File

@ -10,15 +10,15 @@ describe('prefix', () => {
);
it('should support className when has prefix', () => {
const { container } = render(<InputNumber prefix="suffix" className="my-class-name" />);
expect(container.firstChild?.className.includes('my-class-name')).toBe(true);
expect((container.firstChild as HTMLElement)?.className.includes('my-class-name')).toBe(true);
expect(container.querySelector('input')?.className.includes('my-class-name')).toBe(false);
});
it('should trigger focus when prefix is clicked', () => {
const { container } = render(<InputNumber prefix={<i>123</i>} />);
const mockFocus = jest.spyOn(container.querySelector('input'), 'focus');
fireEvent.mouseUp(container.querySelector('i'));
const mockFocus = jest.spyOn(container.querySelector('input')!, 'focus');
fireEvent.mouseUp(container.querySelector('i')!);
expect(mockFocus).toHaveBeenCalled();
});
});

View File

@ -1,5 +1,5 @@
import React from 'react';
// eslint-disable-next-line import/no-unresolved
import type { InputRef } from '..';
import Input from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
@ -13,12 +13,12 @@ describe('Input.Password', () => {
rtlTest(Input.Password);
it('should get input element from ref', () => {
const ref = React.createRef();
const ref = React.createRef<InputRef>();
const onSelect = jest.fn();
const { container } = render(<Input.Password onSelect={onSelect} ref={ref} />);
expect(ref.current.input instanceof HTMLInputElement).toBe(true);
fireEvent.select(container.querySelector('input'));
expect(ref.current?.input instanceof HTMLInputElement).toBe(true);
fireEvent.select(container.querySelector('input')!);
expect(onSelect).toHaveBeenCalled();
});
@ -30,13 +30,13 @@ describe('Input.Password', () => {
it('should change type when click', () => {
const { asFragment, container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: '111' } });
fireEvent.change(container.querySelector('input')!, { target: { value: '111' } });
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-password-icon'));
fireEvent.click(container.querySelector('.ant-input-password-icon')!);
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-password-icon'));
fireEvent.click(container.querySelector('.ant-input-password-icon')!);
expect(asFragment().firstChild).toMatchSnapshot();
});
@ -50,7 +50,7 @@ describe('Input.Password', () => {
it('should not toggle visibility when disabled prop is true', () => {
const { container } = render(<Input.Password disabled />);
expect(container.querySelectorAll('.anticon-eye-invisible').length).toBe(1);
fireEvent.click(container.querySelector('.anticon-eye-invisible'));
fireEvent.click(container.querySelector('.anticon-eye-invisible')!);
expect(container.querySelectorAll('.anticon-eye').length).toBe(0);
});
@ -59,43 +59,43 @@ describe('Input.Password', () => {
container: document.body,
});
expect(document.activeElement).toBe(container.querySelector('input'));
document.activeElement.setSelectionRange(2, 2);
expect(document.activeElement.selectionStart).toBe(2);
fireEvent.mouseDown(container.querySelector('.ant-input-password-icon'));
fireEvent.mouseUp(container.querySelector('.ant-input-password-icon'));
fireEvent.click(container.querySelector('.ant-input-password-icon'));
(document?.activeElement as any)?.setSelectionRange(2, 2);
expect((document?.activeElement as any)?.selectionStart).toBe(2);
fireEvent.mouseDown(container.querySelector('.ant-input-password-icon')!);
fireEvent.mouseUp(container.querySelector('.ant-input-password-icon')!);
fireEvent.click(container.querySelector('.ant-input-password-icon')!);
expect(document.activeElement).toBe(container.querySelector('input'));
expect(document.activeElement.selectionStart).toBe(2);
expect((document?.activeElement as any).selectionStart).toBe(2);
unmount();
});
// https://github.com/ant-design/ant-design/issues/20541
it('should not show value attribute in input element', async () => {
const { container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'value' } });
await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
});
// https://github.com/ant-design/ant-design/issues/24526
it('should not show value attribute in input element after blur it', async () => {
const { container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'value' } });
await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
fireEvent.blur(container.querySelector('input'));
expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
fireEvent.blur(container.querySelector('input')!);
await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
fireEvent.focus(container.querySelector('input'));
expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
fireEvent.focus(container.querySelector('input')!);
await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
});
// https://github.com/ant-design/ant-design/issues/20541
it('could be unmount without errors', () => {
expect(() => {
const { container, unmount } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } });
fireEvent.change(container.querySelector('input')!, { target: { value: 'value' } });
unmount();
}).not.toThrow();
});
@ -104,6 +104,6 @@ describe('Input.Password', () => {
it('should not contain value attribute in input element with defaultValue', async () => {
const { container } = render(<Input.Password defaultValue="value" />);
await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
});
});

View File

@ -4,6 +4,7 @@ import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import Button from '../../button';
import type { InputRef } from '../Input';
import Search from '../Search';
describe('Input.Search', () => {
@ -42,24 +43,16 @@ describe('Input.Search', () => {
const { container } = render(
<Search defaultValue="search text" onSearch={onSearch} disabled />,
);
fireEvent.click(container.querySelector('button'));
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(0);
});
it('should trigger onSearch when click search icon', () => {
const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.click(container.querySelector('button'));
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
});
it('should trigger onSearch when click search button', () => {
@ -67,17 +60,9 @@ describe('Input.Search', () => {
const { container } = render(
<Search defaultValue="search text" enterButton onSearch={onSearch} />,
);
fireEvent.click(container.querySelector('button'));
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
});
it('should trigger onSearch when click search button with text', () => {
@ -85,17 +70,9 @@ describe('Input.Search', () => {
const { container } = render(
<Search defaultValue="search text" enterButton="button text" onSearch={onSearch} />,
);
fireEvent.click(container.querySelector('button'));
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
});
it('should trigger onSearch when click search button with customize button', () => {
@ -107,17 +84,9 @@ describe('Input.Search', () => {
onSearch={onSearch}
/>,
);
fireEvent.click(container.querySelector('button'));
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
});
it('should trigger onSearch when click search button of native', () => {
@ -134,56 +103,32 @@ describe('Input.Search', () => {
onSearch={onSearch}
/>,
);
fireEvent.click(container.querySelector('button'));
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
expect(onButtonClick).toHaveBeenCalledTimes(1);
});
it('should trigger onSearch when press enter', () => {
const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 });
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'keydown',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
});
// https://github.com/ant-design/ant-design/issues/34844
it('should not trigger onSearch when press enter using chinese inputting method', () => {
const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.compositionStart(container.querySelector('input'));
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 });
fireEvent.compositionStart(container.querySelector('input')!);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled();
fireEvent.compositionEnd(container.querySelector('input'));
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 });
fireEvent.compositionEnd(container.querySelector('input')!);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'keydown',
// preventDefault: expect.any(Function),
// }),
);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
});
// https://github.com/ant-design/ant-design/issues/14785
@ -204,7 +149,7 @@ describe('Input.Search', () => {
const { container } = render(
<Search allowClear defaultValue="value" onSearch={onSearch} onChange={onChange} />,
);
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(onSearch).toHaveBeenLastCalledWith('', expect.anything());
expect(onChange).toHaveBeenCalled();
});
@ -236,13 +181,13 @@ describe('Input.Search', () => {
});
it('should prevent search button mousedown event', () => {
const ref = React.createRef();
const ref = React.createRef<InputRef>();
const { container } = render(<Search ref={ref} enterButton="button text" />, {
container: document.body,
});
ref.current.focus();
ref.current?.focus();
expect(document.activeElement).toBe(container.querySelector('input'));
fireEvent.mouseDown(container.querySelector('button'));
fireEvent.mouseDown(container.querySelector('button')!);
expect(document.activeElement).toBe(container.querySelector('input'));
});
@ -250,7 +195,7 @@ describe('Input.Search', () => {
const ref = jest.fn();
const { container } = render(<Search ref={ref} enterButton />);
expect(() => {
fireEvent.mouseDown(container.querySelector('button'));
fireEvent.mouseDown(container.querySelector('button')!);
}).not.toThrow();
});
@ -258,10 +203,10 @@ describe('Input.Search', () => {
it('Search with allowClear should have one className only', () => {
const { container } = render(<Search allowClear className="className" />);
expect(
container.querySelector('.ant-input-group-wrapper').classList.contains('className'),
container.querySelector('.ant-input-group-wrapper')?.classList.contains('className'),
).toBe(true);
expect(
container.querySelector('.ant-input-affix-wrapper').classList.contains('className'),
container.querySelector('.ant-input-affix-wrapper')?.classList.contains('className'),
).toBe(false);
});
});

View File

@ -1,8 +1,11 @@
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import type { ChangeEventHandler, TextareaHTMLAttributes } from 'react';
import React, { useState } from 'react';
import Input from '..';
import focusTest from '../../../tests/shared/focusTest';
import type { RenderOptions } from '../../../tests/utils';
import { fireEvent, render, sleep, triggerResize } from '../../../tests/utils';
import type { TextAreaRef } from '../TextArea';
const { TextArea } = Input;
@ -12,27 +15,23 @@ describe('TextArea', () => {
const originalGetComputedStyle = window.getComputedStyle;
beforeAll(() => {
Object.defineProperty(window, 'getComputedStyle', {
value: node => ({
getPropertyValue: prop => {
if (prop === 'box-sizing') {
return originalGetComputedStyle(node)[prop] || 'border-box';
}
return originalGetComputedStyle(node)[prop];
},
value: (node: Element) => ({
getPropertyValue: (prop: PropertyKey) =>
prop === 'box-sizing'
? originalGetComputedStyle(node)[prop as unknown as number] || 'border-box'
: originalGetComputedStyle(node)[prop as unknown as number],
}),
});
});
afterAll(() => {
Object.defineProperty(window, 'getComputedStyle', {
value: originalGetComputedStyle,
});
Object.defineProperty(window, 'getComputedStyle', { value: originalGetComputedStyle });
});
it('should auto calculate height according to content length', async () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const ref = React.createRef();
const ref = React.createRef<TextAreaRef>();
const genTextArea = (props = {}) => (
<TextArea
@ -47,7 +46,7 @@ describe('TextArea', () => {
const { container, rerender } = render(genTextArea());
const mockFunc = jest.spyOn(ref.current.resizableTextArea, 'resizeTextarea');
const mockFunc = jest.spyOn(ref.current?.resizableTextArea!, 'resizeTextarea');
rerender(genTextArea({ value: '1111\n2222\n3333' }));
// wrapper.setProps({ value: '1111\n2222\n3333' });
@ -59,7 +58,7 @@ describe('TextArea', () => {
await sleep(0);
expect(mockFunc).toHaveBeenCalledTimes(2);
expect(container.querySelector('textarea').style.overflow).toBeFalsy();
expect(container.querySelector('textarea')?.style.overflow).toBeFalsy();
expect(errorSpy).not.toHaveBeenCalled();
errorSpy.mockRestore();
@ -72,12 +71,12 @@ describe('TextArea', () => {
<TextArea onKeyDown={fakeHandleKeyDown} onPressEnter={fakeHandlePressEnter} />,
);
/** KeyCode 65 is A */
fireEvent.keyDown(container.querySelector('textarea'), { keyCode: 65 });
fireEvent.keyDown(container.querySelector('textarea')!, { keyCode: 65 });
expect(fakeHandleKeyDown).toHaveBeenCalledTimes(1);
expect(fakeHandlePressEnter).toHaveBeenCalledTimes(0);
/** KeyCode 13 is Enter */
fireEvent.keyDown(container.querySelector('textarea'), { keyCode: 13 });
fireEvent.keyDown(container.querySelector('textarea')!, { keyCode: 13 });
expect(fakeHandleKeyDown).toHaveBeenCalledTimes(2);
expect(fakeHandlePressEnter).toHaveBeenCalledTimes(1);
});
@ -95,7 +94,7 @@ describe('TextArea', () => {
it('maxLength should not block control', () => {
const { container } = render(<TextArea maxLength={1} value="light" />);
expect(container.querySelector('textarea').value).toEqual('light');
expect(container.querySelector('textarea')?.value).toEqual('light');
});
it('should limit correctly when in control', () => {
@ -105,21 +104,21 @@ describe('TextArea', () => {
};
const { container } = render(<Demo />);
fireEvent.change(container.querySelector('textarea'), { target: { value: 'light' } });
fireEvent.change(container.querySelector('textarea')!, { target: { value: 'light' } });
expect(container.querySelector('textarea').value).toEqual('l');
expect(container.querySelector('textarea')?.value).toEqual('l');
});
it('should exceed maxLength when use IME', () => {
const onChange = jest.fn();
const { container } = render(<TextArea maxLength={1} onChange={onChange} />);
fireEvent.compositionStart(container.querySelector('textarea'));
fireEvent.change(container.querySelector('textarea'), { target: { value: 'zhu' } });
fireEvent.compositionEnd(container.querySelector('textarea'), {
fireEvent.compositionStart(container.querySelector('textarea')!);
fireEvent.change(container.querySelector('textarea')!, { target: { value: 'zhu' } });
fireEvent.compositionEnd(container.querySelector('textarea')!, {
currentTarget: { value: '竹' },
});
fireEvent.change(container.querySelector('textarea'), { target: { value: '竹' } });
fireEvent.change(container.querySelector('textarea')!, { target: { value: '竹' } });
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({ target: expect.objectContaining({ value: '竹' }) }),
@ -132,13 +131,13 @@ describe('TextArea', () => {
const { container } = render(
<TextArea maxLength={6} defaultValue="123456" onChange={onChange} />,
);
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 1, value: 'w123456' },
});
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 3, value: 'w123456' },
});
expect(container.querySelector('textarea').value).toBe('123456');
expect(container.querySelector('textarea')?.value).toBe('123456');
});
// 拼音输入
@ -148,21 +147,21 @@ describe('TextArea', () => {
const { container } = render(
<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />,
);
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 4, value: '1234' },
});
fireEvent.compositionStart(container.querySelector('textarea'));
fireEvent.compositionStart(container.querySelector('textarea')!);
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 9, value: '1234z z z' },
});
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 7, value: '1234组织者' },
});
fireEvent.compositionEnd(container.querySelector('textarea'));
fireEvent.compositionEnd(container.querySelector('textarea')!);
expect(container.querySelector('textarea').value).toBe('1234组织');
expect(container.querySelector('textarea')?.value).toBe('1234组织');
});
// 2. 光标位于中间或开头且当前字符数未达到6个若选中的字符 + 原字符的长度超过6个则显示原有字符
@ -171,29 +170,29 @@ describe('TextArea', () => {
const { container } = render(
<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />,
);
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 2, value: '1234' },
});
fireEvent.compositionStart(container.querySelector('textarea'));
fireEvent.compositionStart(container.querySelector('textarea')!);
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 2, value: '12z z z34' },
});
fireEvent.change(container.querySelector('textarea'), {
fireEvent.change(container.querySelector('textarea')!, {
target: { selectionStart: 5, value: '12组织者34' },
});
fireEvent.compositionEnd(container.querySelector('textarea'));
fireEvent.compositionEnd(container.querySelector('textarea')!);
expect(container.querySelector('textarea').value).toBe('1234');
expect(container.querySelector('textarea')?.value).toBe('1234');
});
});
it('when prop value not in this.props, resizeTextarea should be called', async () => {
const ref = React.createRef();
const ref = React.createRef<TextAreaRef>();
const { container } = render(<TextArea aria-label="textarea" ref={ref} />);
const resizeTextarea = jest.spyOn(ref.current.resizableTextArea, 'resizeTextarea');
fireEvent.change(container.querySelector('textarea'), { target: { value: 'test' } });
const resizeTextarea = jest.spyOn(ref.current?.resizableTextArea!, 'resizeTextarea');
fireEvent.change(container.querySelector('textarea')!, { target: { value: 'test' } });
expect(resizeTextarea).toHaveBeenCalled();
});
@ -203,7 +202,7 @@ describe('TextArea', () => {
const { container } = render(
<TextArea onPressEnter={onPressEnter} onKeyDown={onKeyDown} aria-label="textarea" />,
);
fireEvent.keyDown(container.querySelector('textarea'), { keyCode: 13 });
fireEvent.keyDown(container.querySelector('textarea')!, { keyCode: 13 });
expect(onPressEnter).toHaveBeenCalled();
expect(onKeyDown).toHaveBeenCalled();
@ -211,18 +210,15 @@ describe('TextArea', () => {
it('should trigger onResize', async () => {
const onResize = jest.fn();
const ref = React.createRef();
const ref = React.createRef<TextAreaRef>();
render(<TextArea ref={ref} onResize={onResize} autoSize />);
await sleep(100);
const target = ref.current.resizableTextArea.textArea;
const target = ref.current?.resizableTextArea?.textArea!;
triggerResize(target);
await Promise.resolve();
expect(onResize).toHaveBeenCalledWith(
expect.objectContaining({
width: expect.any(Number),
height: expect.any(Number),
}),
expect.objectContaining({ width: expect.any(Number), height: expect.any(Number) }),
);
});
@ -233,24 +229,24 @@ describe('TextArea', () => {
);
inputRerender(<Input value={undefined} />);
textareaRerender(<TextArea value={undefined} />);
expect(textareaContainer.querySelector('textarea').value).toBe(
inputContainer.querySelector('input').value,
expect(textareaContainer.querySelector('textarea')?.value).toBe(
inputContainer.querySelector('input')?.value,
);
});
describe('should support showCount', () => {
it('maxLength', () => {
const { container } = render(<TextArea maxLength={5} showCount value="12345" />);
expect(container.querySelector('textarea').value).toBe('12345');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
expect(container.querySelector('textarea')?.value).toBe('12345');
expect(container.querySelector('.ant-input-textarea')?.getAttribute('data-count')).toBe(
'5 / 5',
);
});
it('control exceed maxLength', () => {
const { container } = render(<TextArea maxLength={5} showCount value="12345678" />);
expect(container.querySelector('textarea').value).toBe('12345678');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
expect(container.querySelector('textarea')?.value).toBe('12345678');
expect(container.querySelector('.ant-input-textarea')?.getAttribute('data-count')).toBe(
'8 / 5',
);
});
@ -258,29 +254,29 @@ describe('TextArea', () => {
describe('emoji', () => {
it('should minimize value between emoji length and maxLength', () => {
const { container } = render(<TextArea maxLength={1} showCount value="👀" />);
expect(container.querySelector('textarea').value).toBe('👀');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
expect(container.querySelector('textarea')?.value).toBe('👀');
expect(container.querySelector('.ant-input-textarea')?.getAttribute('data-count')).toBe(
'1 / 1',
);
// fix: 当 maxLength 长度为 2 的时候,输入 emoji 之后 showCount 会显示 1/2但是不能再输入了
// zombieJ: 逻辑统一了emoji 现在也可以正确计数了
const { container: container1 } = render(<TextArea maxLength={2} showCount value="👀" />);
expect(container1.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
expect(container1.querySelector('.ant-input-textarea')?.getAttribute('data-count')).toBe(
'1 / 2',
);
});
it('defaultValue should slice', () => {
const { container } = render(<TextArea maxLength={1} defaultValue="🧐cut" />);
expect(container.querySelector('textarea').value).toBe('🧐');
expect(container.querySelector('textarea')?.value).toBe('🧐');
});
// 修改TextArea value截取规则后新增单测
it('slice emoji', () => {
const { container } = render(<TextArea maxLength={5} showCount value="1234😂" />);
expect(container.querySelector('textarea').value).toBe('1234😂');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
expect(container.querySelector('textarea')?.value).toBe('1234😂');
expect(container.querySelector('.ant-input-textarea')?.getAttribute('data-count')).toBe(
'5 / 5',
);
});
@ -292,12 +288,12 @@ describe('TextArea', () => {
);
// Outer
expect(container.querySelector('div').classList.contains('bamboo')).toBeTruthy();
expect(container.querySelector('div').style.background).toEqual('red');
expect(container.querySelector('div')?.classList.contains('bamboo')).toBeTruthy();
expect(container.querySelector('div')?.style.background).toEqual('red');
// Inner
expect(container.querySelector('.ant-input').classList.contains('bamboo')).toBeFalsy();
expect(container.querySelector('.ant-input').style.background).toBeFalsy();
expect(container.querySelector('.ant-input')?.classList.contains('bamboo')).toBeFalsy();
expect(container.querySelector<HTMLDivElement>('.ant-input')?.style.background).toBeFalsy();
});
it('count formatter', () => {
@ -310,8 +306,8 @@ describe('TextArea', () => {
value="12345"
/>,
);
expect(container.querySelector('textarea').value).toBe('12345');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe(
expect(container.querySelector('textarea')?.value).toBe('12345');
expect(container.querySelector('.ant-input-textarea')?.getAttribute('data-count')).toBe(
'12345, 5, 5',
);
});
@ -319,36 +315,40 @@ describe('TextArea', () => {
it('should support size', async () => {
const { asFragment, container } = render(<TextArea size="large" />);
expect(container.querySelector('textarea').classList.contains('ant-input-lg')).toBe(true);
expect(container.querySelector('textarea')?.classList.contains('ant-input-lg')).toBe(true);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('set mouse cursor position', () => {
const defaultValue = '11111';
const valLength = defaultValue.length;
const ref = React.createRef();
const ref = React.createRef<TextAreaRef>();
render(<TextArea autoFocus ref={ref} defaultValue={defaultValue} />);
ref.current.resizableTextArea.textArea.setSelectionRange(valLength, valLength);
expect(ref.current.resizableTextArea.textArea.selectionStart).toEqual(5);
expect(ref.current.resizableTextArea.textArea.selectionEnd).toEqual(5);
ref.current?.resizableTextArea?.textArea.setSelectionRange(valLength, valLength);
expect(ref.current?.resizableTextArea?.textArea.selectionStart).toEqual(5);
expect(ref.current?.resizableTextArea?.textArea.selectionEnd).toEqual(5);
});
});
describe('TextArea allowClear', () => {
it('should change type when click', () => {
const { asFragment, container } = render(<TextArea allowClear />);
fireEvent.change(container.querySelector('textarea'), { target: { value: '111' } });
expect(container.querySelector('textarea').value).toEqual('111');
fireEvent.change(container.querySelector('textarea')!, { target: { value: '111' } });
expect(container.querySelector('textarea')?.value).toEqual('111');
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(asFragment().firstChild).toMatchSnapshot();
expect(container.querySelector('textarea').value).toEqual('');
expect(container.querySelector('textarea')?.value).toEqual('');
});
it('should not show icon if value is undefined, null or empty string', () => {
const wrappers = [null, undefined, ''].map(val => render(<TextArea allowClear value={val} />));
const wrappers = [null, undefined, ''].map(val =>
render(
<TextArea allowClear value={val as TextareaHTMLAttributes<HTMLTextAreaElement>['value']} />,
),
);
wrappers.forEach(({ asFragment, container }) => {
expect(container.querySelector('textarea').value).toEqual('');
expect(container.querySelector('textarea')?.value).toEqual('');
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
@ -356,10 +356,15 @@ describe('TextArea allowClear', () => {
it('should not show icon if defaultValue is undefined, null or empty string', () => {
const wrappers = [null, undefined, ''].map(val =>
render(<TextArea allowClear defaultValue={val} />),
render(
<TextArea
allowClear
defaultValue={val as TextareaHTMLAttributes<HTMLTextAreaElement>['value']}
/>,
),
);
wrappers.forEach(({ asFragment, container }) => {
expect(container.querySelector('textarea').value).toEqual('');
expect(container.querySelector('textarea')?.value).toEqual('');
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
@ -368,36 +373,36 @@ describe('TextArea allowClear', () => {
it('should trigger event correctly', () => {
let argumentEventObjectType;
let argumentEventObjectValue;
const onChange = e => {
const onChange: ChangeEventHandler<HTMLTextAreaElement> = e => {
argumentEventObjectType = e.type;
argumentEventObjectValue = e.target.value;
};
const { container } = render(<TextArea allowClear defaultValue="111" onChange={onChange} />);
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(argumentEventObjectType).toBe('click');
expect(argumentEventObjectValue).toBe('');
expect(container.querySelector('textarea').value).toBe('');
expect(container.querySelector('textarea')?.value).toBe('');
});
it('should trigger event correctly on controlled mode', () => {
let argumentEventObjectType;
let argumentEventObjectValue;
const onChange = e => {
const onChange: ChangeEventHandler<HTMLTextAreaElement> = e => {
argumentEventObjectType = e.type;
argumentEventObjectValue = e.target.value;
};
const { container } = render(<TextArea allowClear value="111" onChange={onChange} />);
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(argumentEventObjectType).toBe('click');
expect(argumentEventObjectValue).toBe('');
expect(container.querySelector('textarea').value).toBe('111');
expect(container.querySelector('textarea')?.value).toBe('111');
});
it('should focus textarea after clear', () => {
const { container, unmount } = render(<TextArea allowClear defaultValue="111" />, {
container: document.body,
});
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(document.activeElement).toBe(container.querySelector('textarea'));
unmount();
});
@ -409,30 +414,30 @@ describe('TextArea allowClear', () => {
it('not block input when `value` is undefined', () => {
const { container, rerender } = render(<Input value={undefined} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'Bamboo' } });
expect(container.querySelector('input').value).toEqual('Bamboo');
fireEvent.change(container.querySelector('input')!, { target: { value: 'Bamboo' } });
expect(container.querySelector('input')?.value).toEqual('Bamboo');
// Controlled
rerender(<Input value="Light" />);
fireEvent.change(container.querySelector('input'), { target: { value: 'Bamboo' } });
expect(container.querySelector('input').value).toEqual('Light');
fireEvent.change(container.querySelector('input')!, { target: { value: 'Bamboo' } });
expect(container.querySelector('input')?.value).toEqual('Light');
});
it('scroll to bottom when autoSize', async () => {
const ref = React.createRef();
const ref = React.createRef<TextAreaRef>();
const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, {
container: document.body,
legacyRoot: true,
});
fireEvent.focus(container.querySelector('textarea'));
container.querySelector('textarea').focus();
} as RenderOptions);
fireEvent.focus(container.querySelector('textarea')!);
container.querySelector('textarea')?.focus();
const setSelectionRangeFn = jest.spyOn(
container.querySelector('textarea'),
container.querySelector('textarea')!,
'setSelectionRange',
);
fireEvent.input(container.querySelector('textarea'), { target: { value: '\n1' } });
const target = ref.current.resizableTextArea.textArea;
fireEvent.input(container.querySelector('textarea')!, { target: { value: '\n1' } });
const target = ref.current?.resizableTextArea?.textArea!;
triggerResize(target);
await sleep(100);
expect(setSelectionRangeFn).toHaveBeenCalled();
@ -442,7 +447,7 @@ describe('TextArea allowClear', () => {
// https://github.com/ant-design/ant-design/issues/26308
it('should display defaultValue when value is undefined', () => {
const { container } = render(<Input.TextArea defaultValue="Light" value={undefined} />);
expect(container.querySelector('textarea').value).toBe('Light');
expect(container.querySelector('textarea')?.value).toBe('Light');
});
it('onChange event should return HTMLTextAreaElement', () => {
@ -451,25 +456,23 @@ describe('TextArea allowClear', () => {
function isNativeElement() {
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({
target: expect.any(HTMLTextAreaElement),
}),
expect.objectContaining({ target: expect.any(HTMLTextAreaElement) }),
);
onChange.mockReset();
}
// Change
fireEvent.change(container.querySelector('textarea'), { target: { value: 'bamboo' } });
fireEvent.change(container.querySelector('textarea')!, { target: { value: 'bamboo' } });
isNativeElement();
// Composition End
fireEvent.change(container.querySelector('textarea'), { target: { value: 'light' } });
fireEvent.compositionEnd(container.querySelector('textarea'));
fireEvent.change(container.querySelector('textarea')!, { target: { value: 'light' } });
fireEvent.compositionEnd(container.querySelector('textarea')!);
isNativeElement();
// Reset
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
isNativeElement();
});
@ -489,12 +492,12 @@ describe('TextArea allowClear', () => {
};
const { container, unmount } = render(<App />);
container.querySelector('textarea').focus();
fireEvent.change(container.querySelector('textarea'), { target: { value: '111' } });
expect(container.querySelector('textarea').value).toEqual('111');
container.querySelector('textarea')?.focus();
fireEvent.change(container.querySelector('textarea')!, { target: { value: '111' } });
expect(container.querySelector('textarea')?.value).toEqual('111');
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(container.querySelector('textarea').value).toEqual('');
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(container.querySelector('textarea')?.value).toEqual('');
unmount();
});
@ -508,12 +511,12 @@ describe('TextArea allowClear', () => {
container: document.body,
},
);
container.querySelector('textarea').focus();
fireEvent.mouseDown(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.mouseUp(container.querySelector('.ant-input-clear-icon'));
fireEvent.focus(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
container.querySelector('textarea')?.focus();
fireEvent.mouseDown(container.querySelector('.ant-input-clear-icon')!);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
fireEvent.mouseUp(container.querySelector('.ant-input-clear-icon')!);
fireEvent.focus(container.querySelector('.ant-input-clear-icon')!);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(onBlur).not.toHaveBeenCalled();
unmount();
});
@ -522,16 +525,20 @@ describe('TextArea allowClear', () => {
const { container, unmount } = render(<TextArea allowClear defaultValue="111" />, {
container: document.body,
});
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(document.activeElement).toBe(container.querySelector('textarea'));
unmount();
});
it('should display boolean value as string', () => {
const { container, rerender } = render(<TextArea value />);
expect(container.querySelector('textarea').value).toBe('true');
rerender(<TextArea value={false} />);
expect(container.querySelector('textarea').value).toBe('false');
const { container, rerender } = render(
<TextArea value={true as unknown as TextareaHTMLAttributes<HTMLTextAreaElement>['value']} />,
);
expect(container.querySelector('textarea')?.value).toBe('true');
rerender(
<TextArea value={false as unknown as TextareaHTMLAttributes<HTMLTextAreaElement>['value']} />,
);
expect(container.querySelector('textarea')?.value).toBe('false');
});
it('should focus when clearBtn is clicked in controlled case', () => {
@ -541,18 +548,17 @@ describe('TextArea allowClear', () => {
focus: handleFocus,
});
const Demo = () => {
const Demo: React.FC = () => {
const [value, setValue] = React.useState('');
return <Input.TextArea allowClear value={value} onChange={e => setValue(e.target.value)} />;
};
const { container } = render(<Demo />);
fireEvent.change(container.querySelector('textarea'), { target: { value: 'test' } });
fireEvent.change(container.querySelector('textarea')!, { target: { value: 'test' } });
expect(container.querySelector('.ant-input-clear-icon')?.className).not.toContain(
'ant-input-clear-icon-hidden',
);
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(handleFocus).toHaveBeenCalledTimes(1);
textareaSpy.mockRestore();

View File

@ -1,9 +1,9 @@
import React, { useState } from 'react';
import { act } from 'react-dom/test-utils';
import { UserOutlined } from '@ant-design/icons';
import Layout from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import Icon from '../../icon';
import Menu from '../../menu';
import { fireEvent, render } from '../../../tests/utils';
@ -31,14 +31,14 @@ describe('Layout', () => {
<Content>Content</Content>
</Layout>,
);
expect(container.querySelector('.ant-layout').className.includes('ant-layout-has-sider')).toBe(
expect(container.querySelector('.ant-layout')?.className.includes('ant-layout-has-sider')).toBe(
true,
);
unmount();
});
it('umount from multiple siders', async () => {
const App = () => {
const App: React.FC = () => {
const [hide1, setHide1] = useState(false);
const [hide2, setHide2] = useState(false);
return (
@ -57,15 +57,15 @@ describe('Layout', () => {
);
};
const { container } = render(<App />);
expect(container.querySelector('.ant-layout').className.includes('ant-layout-has-sider')).toBe(
expect(container.querySelector('.ant-layout')?.className.includes('ant-layout-has-sider')).toBe(
true,
);
fireEvent.click(container.querySelectorAll('button')[0]);
expect(container.querySelector('.ant-layout').className.includes('ant-layout-has-sider')).toBe(
expect(container.querySelector('.ant-layout')?.className.includes('ant-layout-has-sider')).toBe(
true,
);
fireEvent.click(container.querySelectorAll('button')[1]);
expect(container.querySelector('.ant-layout').className.includes('ant-layout-has-sider')).toBe(
expect(container.querySelector('.ant-layout')?.className.includes('ant-layout-has-sider')).toBe(
false,
);
});
@ -79,7 +79,7 @@ describe('Layout', () => {
<Content>Content</Content>
</Layout>,
);
expect(container.querySelector('.ant-layout').className.includes('ant-layout-has-sider')).toBe(
expect(container.querySelector('.ant-layout')?.className.includes('ant-layout-has-sider')).toBe(
true,
);
});
@ -96,7 +96,7 @@ describe('Layout', () => {
expect(
container
.querySelector('.ant-layout-sider')
.className.includes('ant-layout-sider-has-trigger'),
?.className.includes('ant-layout-sider-has-trigger'),
).toBe(true);
});
@ -109,8 +109,8 @@ describe('Layout', () => {
<Content>Content</Content>
</Layout>,
);
expect(container.querySelector('.ant-layout-sider').style.width).toBe('50%');
expect(container.querySelector('.ant-layout-sider').style.flex).toBe('0 0 50%');
expect(container.querySelector<HTMLElement>('.ant-layout-sider')?.style.width).toBe('50%');
expect(container.querySelector<HTMLElement>('.ant-layout-sider')?.style.flex).toBe('0 0 50%');
});
describe('zeroWidth', () => {
@ -126,7 +126,7 @@ describe('Layout', () => {
expect(
container
.querySelector('.ant-layout-sider')
.className.includes('ant-layout-sider-zero-width'),
?.className.includes('ant-layout-sider-zero-width'),
).toBe(true);
});
@ -144,12 +144,12 @@ describe('Layout', () => {
);
onCollapse.mockReset();
fireEvent.click(container.querySelector('.ant-layout-sider-zero-width-trigger'));
fireEvent.click(container.querySelector('.ant-layout-sider-zero-width-trigger')!);
expect(onCollapse).toHaveBeenCalledTimes(1);
});
it('controlled', () => {
const Demo = () => {
const Demo: React.FC = () => {
const [collapsed, setCollapsed] = React.useState(true);
return (
@ -170,7 +170,7 @@ describe('Layout', () => {
const { container } = render(<Demo />);
expect(container.querySelector('.ant-layout-sider-collapsed')).toBeTruthy();
fireEvent.click(container.querySelector('.ant-layout-sider-zero-width-trigger'));
fireEvent.click(container.querySelector('.ant-layout-sider-zero-width-trigger')!);
expect(container.querySelector('.ant-layout-sider-collapsed')).toBeFalsy();
});
});
@ -179,14 +179,14 @@ describe('Layout', () => {
it('detect ant-layout-sider-dark as default theme', async () => {
const { container } = render(<Sider>Sider</Sider>);
expect(
container.querySelector('.ant-layout-sider').className.includes('ant-layout-sider-dark'),
container.querySelector('.ant-layout-sider')?.className.includes('ant-layout-sider-dark'),
).toBe(true);
});
it('detect ant-layout-sider-light when set light theme', async () => {
const { container } = render(<Sider theme="light">Sider</Sider>);
expect(
container.querySelector('.ant-layout-sider').className.includes('ant-layout-sider-light'),
container.querySelector('.ant-layout-sider')?.className.includes('ant-layout-sider-light'),
).toBe(true);
});
@ -208,7 +208,7 @@ describe('Layout', () => {
<Sider>Sider</Sider>
</Layout>,
);
expect(container.querySelector('.ant-layout').className.includes('ant-layout-has-sider')).toBe(
expect(container.querySelector('.ant-layout')?.className.includes('ant-layout-has-sider')).toBe(
false,
);
});
@ -219,30 +219,29 @@ describe('Layout', () => {
<Sider collapsible collapsed={false}>
<Menu mode="inline">
<Menu.Item key="1">
<Icon type="user" />
<UserOutlined />
<span>Light</span>
</Menu.Item>
</Menu>
</Sider>,
);
fireEvent.mouseEnter(container.querySelector('.ant-menu-item'));
fireEvent.mouseEnter(container.querySelector('.ant-menu-item')!);
act(() => {
jest.runAllTimers();
});
expect(container.querySelectorAll('.ant-tooltip-inner').length).toBeFalsy();
rerender(
<Sider collapsible collapsed>
<Menu mode="inline">
<Menu.Item key="1">
<Icon type="user" />
<UserOutlined />
<span>Light</span>
</Menu.Item>
</Menu>
</Sider>,
);
fireEvent.mouseEnter(container.querySelector('.ant-menu-item'));
fireEvent.mouseEnter(container.querySelector('.ant-menu-item')!);
act(() => {
jest.runAllTimers();
});
@ -290,14 +289,15 @@ describe('Sider', () => {
<Sider collapsedWidth={0} collapsible zeroWidthTriggerStyle={{ background: '#F96' }}>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Icon type="user" />
<UserOutlined />
<span>nav 1</span>
</Menu.Item>
</Menu>
</Sider>,
);
expect(
container.querySelector('.ant-layout-sider-zero-width-trigger').style.background,
container.querySelector<HTMLDivElement>('.ant-layout-sider-zero-width-trigger')?.style
.background,
).toEqual('rgb(255, 153, 102)');
});
@ -306,24 +306,23 @@ describe('Sider', () => {
<Sider collapsedWidth={0} collapsible trigger={<span className="my-trigger" />}>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Icon type="user" />
<UserOutlined />
<span>nav 1</span>
</Menu.Item>
</Menu>
</Sider>,
);
expect(
container.querySelector('.ant-layout-sider-zero-width-trigger').querySelector('.my-trigger'),
container.querySelector('.ant-layout-sider-zero-width-trigger')?.querySelector('.my-trigger'),
).toBeTruthy();
});
['Layout', 'Header', 'Footer', 'Sider'].forEach(tag => {
const ComponentMap = { Layout, Header, Footer, Sider };
it(`should get ${tag} element from ref`, () => {
const ref = React.createRef();
const ref = React.createRef<any>();
const onSelect = jest.fn();
const Component = ComponentMap[tag];
const Component = ComponentMap[tag as keyof typeof ComponentMap];
render(
<Component onSelect={onSelect} ref={ref}>
{tag}

View File

@ -49,7 +49,7 @@ describe('List Item Layout', () => {
/>,
);
expect(
wrapper.querySelector('.ant-list-item').classList.contains('ant-list-item-no-flex'),
wrapper.querySelector('.ant-list-item')?.classList.contains('ant-list-item-no-flex'),
).toBe(false);
});
@ -188,17 +188,17 @@ describe('List Item Layout', () => {
});
it('should ref', () => {
const ref = React.createRef();
const ref = React.createRef<HTMLElement>();
render(<List.Item ref={ref}>Item</List.Item>);
expect(ref.current).toHaveClass('ant-list-item');
});
it('should grid ref', () => {
const ref = React.createRef();
const ref = React.createRef<HTMLElement>();
render(
<List grid>
<List grid={{}}>
<List.Item ref={ref}>Item</List.Item>,
</List>,
);

View File

@ -1,4 +1,5 @@
import React from 'react';
import type { ListProps } from '..';
import List from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -13,12 +14,12 @@ describe('List', () => {
it('locale not passed to internal div', async () => {
const locale = { emptyText: 'Custom text' };
const renderItem = item => <List.Item>{item}</List.Item>;
const dataSource = [];
const renderItem: ListProps<any>['renderItem'] = item => <List.Item>{item}</List.Item>;
const dataSource: ListProps<any>['dataSource'] = [];
const { container } = render(
<List renderItem={renderItem} dataSource={dataSource} locale={locale} />,
);
expect(container.querySelector('div.ant-list').getAttribute('locale')).toBe(null);
expect(container.querySelector('div.ant-list')?.getAttribute('locale')).toBe(null);
});
});

View File

@ -6,9 +6,7 @@ import List from '..';
describe('List', () => {
it('renders empty loading', () => {
const loading = {
spinning: true,
};
const loading = { spinning: true };
const { container: wrapper } = render(
<List loading={loading} dataSource={[]} renderItem={() => <List.Item />} />,
);

View File

@ -1,10 +1,16 @@
import React from 'react';
import type { ListProps } from '..';
import List from '..';
import { fireEvent, render } from '../../../tests/utils';
import { noop } from '../../_util/warning';
interface DataSourceItem {
name: string;
key: React.Key;
}
describe('List.pagination', () => {
const data = [
const data: ListProps<DataSourceItem>['dataSource'] = [
{ key: 0, name: 'Jack' },
{ key: 1, name: 'Lucy' },
{ key: 2, name: 'Tom' },
@ -13,7 +19,7 @@ describe('List.pagination', () => {
const pagination = { className: 'my-page', pageSize: 2 };
function createList(props) {
function createList(props?: ListProps<DataSourceItem>) {
return (
<List
itemLayout="vertical"
@ -25,10 +31,10 @@ describe('List.pagination', () => {
);
}
function renderedNames(wrapper) {
function renderedNames(container: ReturnType<typeof render>['container']) {
return Array.prototype.map.call(
wrapper.querySelectorAll('.ant-list-item'),
row => row.textContent,
container.querySelectorAll('.ant-list-item'),
(row: HTMLDivElement) => row.textContent,
);
}
@ -39,12 +45,7 @@ describe('List.pagination', () => {
it('should not show pager if pagination.hideOnSinglePage is true and only 1 page', () => {
const { container: wrapper, rerender } = render(
createList({
pagination: {
pageSize: 3,
hideOnSinglePage: true,
},
}),
createList({ pagination: { pageSize: 3, hideOnSinglePage: true } }),
);
expect(wrapper.querySelectorAll('.ant-pagination')).toHaveLength(1);
rerender(createList({ pagination: { pageSize: 3, hideOnSinglePage: false } }));
@ -107,13 +108,13 @@ describe('List.pagination', () => {
expect(wrapper.querySelectorAll('.ant-pagination')).toHaveLength(1);
expect(wrapper.querySelectorAll('.ant-pagination-item')).toHaveLength(2);
fireEvent.click(wrapper.querySelector('.ant-pagination-item-2'));
fireEvent.click(wrapper.querySelector('.ant-pagination-item-2')!);
expect(renderedNames(wrapper)).toEqual(['Tom', 'Jerry']);
rerender(createList({ pagination: false }));
expect(wrapper.querySelectorAll('.ant-pagination')).toHaveLength(0);
rerender(createList({ pagination: true }));
rerender(createList({ pagination: true as ListProps<DataSourceItem>['pagination'] }));
expect(wrapper.querySelectorAll('.ant-pagination')).toHaveLength(1);
// Legacy code will make pageSize ping with 10, here we fixed to keep sync by current one
expect(wrapper.querySelectorAll('.ant-pagination-item')).toHaveLength(2);
@ -123,7 +124,7 @@ describe('List.pagination', () => {
// https://github.com/ant-design/ant-design/issues/5259
it('change to correct page when data source changes', () => {
const { container: wrapper, rerender } = render(createList({ pagination: { pageSize: 1 } }));
fireEvent.click(wrapper.querySelector('.ant-pagination-item-3'));
fireEvent.click(wrapper.querySelector('.ant-pagination-item-3')!);
rerender(createList({ dataSource: [data[0]] }));
expect(wrapper.querySelector('.ant-pagination-item-1')).toHaveClass(
'ant-pagination-item-active',
@ -134,20 +135,20 @@ describe('List.pagination', () => {
const { container: wrapper, rerender } = render(
createList({ pagination: { position: 'top' } }),
);
expect(wrapper.querySelector('.ant-list').querySelectorAll('.ant-pagination')).toHaveLength(1);
expect(wrapper.querySelector('.ant-list')?.querySelectorAll('.ant-pagination')).toHaveLength(1);
rerender(createList({ pagination: { position: 'bottom' } }));
expect(
wrapper.querySelector('.ant-list').lastElementChild.querySelectorAll('.ant-pagination'),
wrapper.querySelector('.ant-list')?.lastElementChild?.querySelectorAll('.ant-pagination'),
).toHaveLength(1);
rerender(createList({ pagination: { position: 'both' } }));
expect(wrapper.querySelectorAll('.ant-pagination')).toHaveLength(2);
expect(
wrapper.querySelector('.ant-list').firstElementChild.querySelectorAll('.ant-pagination'),
wrapper.querySelector('.ant-list')?.firstElementChild?.querySelectorAll('.ant-pagination'),
).toHaveLength(1);
expect(
wrapper.querySelector('.ant-list').lastElementChild.querySelectorAll('.ant-pagination'),
wrapper.querySelector('.ant-list')?.lastElementChild?.querySelectorAll('.ant-pagination'),
).toHaveLength(1);
});
@ -155,10 +156,10 @@ describe('List.pagination', () => {
const { container: wrapper } = render(createList({ pagination: { showSizeChanger: true } }));
expect(wrapper.querySelector('.ant-pagination')).toMatchSnapshot();
fireEvent.mouseDown(wrapper.querySelector('.ant-select-selector'));
fireEvent.mouseDown(wrapper.querySelector('.ant-select-selector')!);
fireEvent.click(wrapper.querySelectorAll('.ant-select-item-option')[2]);
fireEvent.mouseDown(wrapper.querySelector('.ant-select-selector'));
fireEvent.mouseDown(wrapper.querySelector('.ant-select-selector')!);
expect(wrapper.querySelector('.ant-pagination')).toMatchSnapshot();
});
@ -178,7 +179,7 @@ describe('List.pagination', () => {
}),
);
fireEvent.mouseDown(wrapper.querySelector('.ant-select-selector'));
fireEvent.mouseDown(wrapper.querySelector('.ant-select-selector')!);
fireEvent.click(wrapper.querySelectorAll('.ant-select-item-option')[1]);
expect(handlePaginationChange).toHaveBeenCalledWith(1, 10);
});
@ -199,10 +200,6 @@ describe('List.pagination', () => {
});
it('should not crash when pagination is null', () => {
render(
createList({
pagination: null,
}),
);
render(createList({ pagination: null as unknown as ListProps<DataSourceItem>['pagination'] }));
});
});

View File

@ -35871,7 +35871,7 @@ exports[`Locale Provider should display the text as cs 1`] = `
type="button"
>
<span>
Storno
Zrušit
</span>
</button>
<button
@ -37444,7 +37444,7 @@ exports[`Locale Provider should display the text as cs 1`] = `
type="button"
>
<span>
Storno
Zrušit
</span>
</button>
<button

View File

@ -1,9 +1,12 @@
/* eslint-disable no-template-curly-in-string */
import Pagination from 'rc-pagination/lib/locale/cs_CZ';
import Calendar from '../calendar/locale/cs_CZ';
import DatePicker from '../date-picker/locale/cs_CZ';
import type { Locale } from '../locale-provider';
import TimePicker from '../time-picker/locale/cs_CZ';
const typeTemplate = '${label} není platný ${type}';
const localeValues: Locale = {
locale: 'cs',
Pagination,
@ -18,8 +21,12 @@ const localeValues: Locale = {
filterConfirm: 'Potvrdit',
filterReset: 'Obnovit',
filterEmptyText: 'Žádné filtry',
filterCheckall: 'Vybrat všechny položky',
filterSearchPlaceholder: 'Vyhledat ve filtrech',
emptyText: 'Žádná data',
selectAll: 'Vybrat všechny řádky na současné stránce',
selectInvert: 'Invertovat výběr na současné stránce',
selectNone: 'Odznačit vše',
selectionAll: 'Vybrat všechny řádky',
sortTitle: 'Řadit',
expand: 'Rozbalit řádek',
@ -30,17 +37,24 @@ const localeValues: Locale = {
},
Modal: {
okText: 'OK',
cancelText: 'Storno',
cancelText: 'Zrušit',
justOkText: 'OK',
},
Popconfirm: {
okText: 'OK',
cancelText: 'Storno',
cancelText: 'Zrušit',
},
Transfer: {
titles: ['', ''],
searchPlaceholder: 'Vyhledávání',
itemUnit: 'položka',
itemsUnit: 'položek',
remove: 'Odstranit',
selectCurrent: 'Vybrat aktuální stranu',
removeCurrent: 'Smazat aktuální stranu',
selectAll: 'Označit vše',
removeAll: 'Odznačit vše',
selectInvert: 'Opačný výběr',
},
Upload: {
uploading: 'Nahrávání...',
@ -52,6 +66,71 @@ const localeValues: Locale = {
Empty: {
description: 'Žádná data',
},
Icon: {
icon: 'ikona',
},
Text: {
edit: 'Upravit',
copy: 'Kopírovat',
copied: 'Zkopírované',
expand: 'Zvětšit',
},
PageHeader: {
back: 'Zpět',
},
Form: {
optional: '(nepovinné)',
defaultValidateMessages: {
default: 'Validační chyba pole pro ${label}',
required: 'Prosím vložte ${label}',
enum: '${label} musí být jeden z [${enum}]',
whitespace: '${label} nemůže být prázdný znak',
date: {
format: '${label} formát datumu je neplatný',
parse: '${label} není možné konvertovat na datum',
invalid: '${label} je neplatné datum',
},
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} musí být ${len} znaků',
min: '${label} musí být alespoň ${min} znaků',
max: '${label} musí být do ${max} znaků',
range: '${label} musí být mezi ${min}-${max} znaky',
},
number: {
len: '${label} musí být stejný jako ${len}',
min: '${label} musí být minimálně ${min}',
max: '${label} musí být maximálně ${max}',
range: '${label} musí být mezi ${min}-${max}',
},
array: {
len: 'Musí být ${len} ${label}',
min: 'Alespoň ${min} ${label}',
max: 'Nejvíc ${max} ${label}',
range: 'Počet ${label} musí být mezi ${min}-${max}',
},
pattern: {
mismatch: '${label} neodpovídá vzoru ${pattern}',
},
},
},
Image: {
preview: 'Náhled',
},
};
export default localeValues;

View File

@ -22,6 +22,7 @@ const localeValues: Locale = {
filterConfirm: 'OK',
filterReset: 'Сбросить',
filterEmptyText: 'Без фильтров',
filterCheckall: 'Выбрать все элементы',
emptyText: 'Нет данных',
selectAll: 'Выбрать всё',
selectInvert: 'Инвертировать выбор',

View File

@ -112,6 +112,7 @@ exports[`renders ./components/mentions/demo/form.md extend context correctly 1`]
class="ant-mentions"
>
<textarea
aria-required="true"
class="rc-textarea"
id="bio"
placeholder="You can use @ to ref user here"

View File

@ -112,6 +112,7 @@ exports[`renders ./components/mentions/demo/form.md correctly 1`] = `
class="ant-mentions"
>
<textarea
aria-required="true"
class="rc-textarea"
id="bio"
placeholder="You can use @ to ref user here"

View File

@ -124,7 +124,7 @@ const getThemeStyle = (token: MenuToken): CSSInterpolation => {
},
[`${componentCls}-item, ${componentCls}-submenu-title`]: {
'&:focus-visible': {
[`&:not(${componentCls}-item-disabled):focus-visible`]: {
...accessibilityFocus(token),
},
},

View File

@ -5,12 +5,14 @@ import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning';
import * as React from 'react';
import TestUtils from 'react-dom/test-utils';
import type { ModalFuncProps } from '..';
import Modal from '..';
import { sleep, act } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import type { ModalFunc } from '../confirm';
import destroyFns from '../destroyFns';
globalThis.IS_REACT_ACT_ENVIRONMENT = true;
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
const { confirm } = Modal;
@ -20,7 +22,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
// Inject CSSMotion to replace with No transition support
const MockCSSMotion = genCSSMotion(false);
Object.keys(MockCSSMotion).forEach(key => {
CSSMotion[key] = MockCSSMotion[key];
(CSSMotion as any)[key] = (MockCSSMotion as any)[key];
});
// // Mock for rc-util raf
@ -67,11 +69,11 @@ describe('Modal.confirm triggers callbacks correctly', () => {
errorSpy.mockRestore();
});
function $$(className) {
return document.body.querySelectorAll(className);
function $$(className: string) {
return document.body.querySelectorAll<HTMLElement>(className);
}
function open(args) {
function open(args?: ModalFuncProps) {
jest.useFakeTimers();
confirm({
title: 'Want to delete these items?',
@ -242,23 +244,22 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
it('allows extra props on buttons', async () => {
open({ okButtonProps: { disabled: true }, cancelButtonProps: { 'data-test': 'baz' } });
open({
okButtonProps: { disabled: true },
cancelButtonProps: { 'data-test': 'baz' } as ModalFuncProps['cancelButtonProps'],
});
await sleep();
expect($$('.ant-btn')).toHaveLength(2);
expect($$('.ant-btn')[0].attributes['data-test'].value).toBe('baz');
expect($$('.ant-btn')[1].disabled).toBe(true);
expect(($$('.ant-btn')[0].attributes as any)['data-test'].value).toBe('baz');
expect(($$('.ant-btn')[1] as HTMLButtonElement).disabled).toBe(true);
});
describe('should close modals when click confirm button', () => {
['info', 'success', 'warning', 'error'].forEach(type => {
(['info', 'success', 'warning', 'error'] as const).forEach(type => {
it(type, async () => {
jest.useFakeTimers();
Modal[type]({
title: 'title',
content: 'content',
});
Modal[type]?.({ title: 'title', content: 'content' });
await act(async () => {
jest.runAllTimers();
await sleep();
@ -325,13 +326,13 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('should not close modals when click confirm button when onOk has argument', () => {
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach(type => {
it(type, async () => {
jest.useFakeTimers();
Modal[type]({
Modal[type]?.({
title: 'title',
content: 'content',
onOk: close => null, // eslint-disable-line no-unused-vars
onOk: _ => null, // eslint-disable-line no-unused-vars
});
await act(async () => {
jest.runAllTimers();
@ -351,10 +352,10 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('could be update by new config', () => {
['info', 'success', 'warning', 'error'].forEach(type => {
(['info', 'success', 'warning', 'error'] as const).forEach(type => {
it(type, async () => {
jest.useFakeTimers();
const instance = Modal[type]({
const instance = Modal[type]?.({
title: 'title',
content: 'content',
});
@ -387,17 +388,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('could be update by call function', () => {
['info', 'success', 'warning', 'error'].forEach(type => {
(['info', 'success', 'warning', 'error'] as const).forEach(type => {
it(type, () => {
jest.useFakeTimers();
const instance = Modal[type]({
const instance = Modal[type]?.({
title: 'title',
okButtonProps: {
loading: true,
style: {
color: 'red',
},
},
okButtonProps: { loading: true, style: { color: 'red' } },
});
act(() => {
jest.runAllTimers();
@ -434,10 +430,10 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('could be destroy', () => {
['info', 'success', 'warning', 'error'].forEach(type => {
(['info', 'success', 'warning', 'error'] as const).forEach(type => {
jest.useFakeTimers();
it(type, async () => {
const instance = Modal[type]({
const instance = Modal[type]?.({
title: 'title',
content: 'content',
});
@ -462,8 +458,8 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.useFakeTimers();
// Show
['info', 'success', 'warning', 'error'].forEach(type => {
Modal[type]({
(['info', 'success', 'warning', 'error'] as const).forEach(type => {
Modal[type]?.({
title: 'title',
content: 'content',
});
@ -515,9 +511,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
jest.runAllTimers();
});
const instances = [];
['info', 'success', 'warning', 'error'].forEach(type => {
const instance = Modal[type]({
const instances: ReturnType<ModalFunc>[] = [];
(['info', 'success', 'warning', 'error'] as const).forEach(type => {
const instance = Modal[type]?.({
title: 'title',
content: 'content',
});
@ -588,8 +584,9 @@ describe('Modal.confirm triggers callbacks correctly', () => {
it('ok button should trigger onOk multiple times when onOk has close argument', async () => {
const onOk = jest.fn();
open({
onOk: close => {
onOk(close?: any) {
onOk();
// @ts-ignore
(() => {})(close); // do nothing
},
});
@ -615,7 +612,7 @@ describe('Modal.confirm triggers callbacks correctly', () => {
expect(document.querySelectorAll('.my-btn').length).toBe(2);
expect(document.querySelectorAll('.bamboo-smile').length).toBe(1);
expect(document.querySelectorAll('.my-modal-confirm').length).toBe(1);
ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: null });
ConfigProvider.config({ prefixCls: 'ant', iconPrefixCls: undefined });
jest.useRealTimers();
});
@ -702,12 +699,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
describe('the callback close should be a method when onCancel has a close parameter', () => {
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach(type => {
it(`click the close icon to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
Modal[type]?.({
closable: true,
onCancel: close => mock(close),
});
@ -732,12 +729,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
});
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach(type => {
it(`press ESC to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
Modal[type]?.({
keyboard: true,
onCancel: close => mock(close),
});
@ -770,12 +767,12 @@ describe('Modal.confirm triggers callbacks correctly', () => {
});
});
['confirm', 'info', 'success', 'warning', 'error'].forEach(type => {
(['confirm', 'info', 'success', 'warning', 'error'] as const).forEach(type => {
it(`click the mask to trigger ${type} onCancel`, async () => {
jest.useFakeTimers();
const mock = jest.fn();
Modal[type]({
Modal[type]?.({
maskClosable: true,
onCancel: close => mock(close),
});

Some files were not shown because too many files have changed in this diff Show More