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' }} if: ${{ matrix.module == 'dom' }}
run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage run: npm test -- --maxWorkers=2 --shard=${{matrix.shard}} --coverage
- name: coverage - name: persist coverages
uses: codecov/codecov-action@v3
if: ${{ matrix.module == 'dom' && matrix.react == '17' }} 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: with:
# use own token to upload coverage reports name: coverage-artifacts
token: ${{ secrets.CODECOV_TOKEN }} path: persist-coverage/
# node test # node test
- name: node test - name: node test
@ -225,6 +231,28 @@ jobs:
LIB_DIR: dist LIB_DIR: dist
needs: [setup, 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 & Test ###########################
compile: compile:
runs-on: ubuntu-latest 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 ## 4.22.8
`2022-08-26` `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 ## 4.22.8
`2022-08-26` `2022-08-26`

View File

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

View File

@ -8,7 +8,7 @@ describe('AutoComplete children could be focus', () => {
jest.useFakeTimers(); jest.useFakeTimers();
}); });
let container; let container: HTMLDivElement;
beforeEach(() => { beforeEach(() => {
container = document.createElement('div'); container = document.createElement('div');
document.body.appendChild(container); document.body.appendChild(container);
@ -24,10 +24,8 @@ describe('AutoComplete children could be focus', () => {
it('focus() and onFocus', () => { it('focus() and onFocus', () => {
const handleFocus = jest.fn(); const handleFocus = jest.fn();
const { container: wrapper } = render(<AutoComplete onFocus={handleFocus} />, { const { container: wrapper } = render(<AutoComplete onFocus={handleFocus} />, { container });
attachTo: container, wrapper.querySelector('input')?.focus();
});
wrapper.querySelector('input').focus();
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
@ -36,14 +34,12 @@ describe('AutoComplete children could be focus', () => {
it('blur() and onBlur', () => { it('blur() and onBlur', () => {
const handleBlur = jest.fn(); const handleBlur = jest.fn();
const { container: wrapper } = render(<AutoComplete onBlur={handleBlur} />, { const { container: wrapper } = render(<AutoComplete onBlur={handleBlur} />, { container });
attachTo: container, wrapper.querySelector('input')?.focus();
});
wrapper.querySelector('input').focus();
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
wrapper.querySelector('input').blur(); wrapper.querySelector('input')?.blur();
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
@ -61,17 +57,13 @@ describe('AutoComplete children could be focus', () => {
}); });
it('child.ref instance should support be focused and blured', () => { it('child.ref instance should support be focused and blured', () => {
let inputRef; const inputRef = React.createRef<HTMLInputElement>();
render( render(
<AutoComplete dataSource={[]}> <AutoComplete dataSource={[]}>
<input <input ref={inputRef} />
ref={node => {
inputRef = node;
}}
/>
</AutoComplete>, </AutoComplete>,
); );
expect(typeof inputRef.focus).toBe('function'); expect(typeof inputRef.current?.focus).toBe('function');
expect(typeof inputRef.blur).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 () => { it('should scroll to top after click it', async () => {
const { container } = render(<BackTop visibilityHeight={-1} />); 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.scrollY = y;
window.pageYOffset = y; window.pageYOffset = y;
document.documentElement.scrollTop = y; document.documentElement.scrollTop = y;
}); });
window.scrollTo(0, 400); window.scrollTo(0, 400);
expect(document.documentElement.scrollTop).toBe(400); expect(document.documentElement.scrollTop).toBe(400);
fireEvent.click(container.querySelector('.ant-back-top')); fireEvent.click(container.querySelector('.ant-back-top')!);
await sleep(500); await sleep(500);
expect(document.documentElement.scrollTop).toBe(0); expect(document.documentElement.scrollTop).toBe(0);
scrollToSpy.mockRestore(); scrollToSpy.mockRestore();
@ -26,23 +26,21 @@ describe('BackTop', () => {
it('support onClick', async () => { it('support onClick', async () => {
const onClick = jest.fn(); const onClick = jest.fn();
const { container } = render(<BackTop onClick={onClick} visibilityHeight={-1} />); 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.scrollY = y;
window.pageYOffset = y; window.pageYOffset = y;
}); });
document.dispatchEvent(new Event('scroll')); document.dispatchEvent(new Event('scroll'));
window.scrollTo(0, 400); window.scrollTo(0, 400);
fireEvent.click(container.querySelector('.ant-back-top')); fireEvent.click(container.querySelector('.ant-back-top')!);
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
scrollToSpy.mockRestore(); scrollToSpy.mockRestore();
}); });
it('invalid target', async () => { it('invalid target', async () => {
const onClick = jest.fn(); const onClick = jest.fn();
const { container } = render( const { container } = render(<BackTop onClick={onClick} visible target={undefined} />);
<BackTop onClick={onClick} visible target={() => ({ documentElement: {} })} />, fireEvent.click(container.querySelector('.ant-back-top')!);
);
fireEvent.click(container.querySelector('.ant-back-top'));
expect(onClick).toHaveBeenCalled(); expect(onClick).toHaveBeenCalled();
}); });
}); });

View File

@ -3,6 +3,7 @@ import accessibilityTest from '../../../tests/shared/accessibilityTest';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import { render } from '../../../tests/utils'; import { render } from '../../../tests/utils';
import type { Route } from '../Breadcrumb';
import Breadcrumb from '../index'; import Breadcrumb from '../index';
describe('Breadcrumb', () => { describe('Breadcrumb', () => {
@ -22,7 +23,7 @@ describe('Breadcrumb', () => {
// https://github.com/airbnb/enzyme/issues/875 // https://github.com/airbnb/enzyme/issues/875
it('warns on non-Breadcrumb.Item and non-Breadcrumb.Separator children', () => { it('warns on non-Breadcrumb.Item and non-Breadcrumb.Separator children', () => {
const MyCom = () => <div>foo</div>; const MyCom: React.FC = () => <div>foo</div>;
render( render(
<Breadcrumb> <Breadcrumb>
<MyCom /> <MyCom />
@ -74,7 +75,7 @@ describe('Breadcrumb', () => {
}); });
it('should render a menu', () => { it('should render a menu', () => {
const routes = [ const routes: Route[] = [
{ {
path: 'index', path: 'index',
breadcrumbName: 'home', breadcrumbName: 'home',
@ -103,6 +104,7 @@ describe('Breadcrumb', () => {
}, },
{ {
path: 'third', path: 'third',
breadcrumbName: '',
}, },
]; ];
const { asFragment } = render(<Breadcrumb routes={routes} />); const { asFragment } = render(<Breadcrumb routes={routes} />);
@ -142,7 +144,7 @@ describe('Breadcrumb', () => {
// https://github.com/ant-design/ant-design/issues/25975 // https://github.com/ant-design/ant-design/issues/25975
it('should support Breadcrumb.Item default separator', () => { it('should support Breadcrumb.Item default separator', () => {
const MockComponent = () => ( const MockComponent: React.FC = () => (
<span> <span>
<Breadcrumb.Item>Mock Node</Breadcrumb.Item> <Breadcrumb.Item>Mock Node</Breadcrumb.Item>
</span> </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 { Link, MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import { fireEvent, render } from '../../../tests/utils'; import { fireEvent, render } from '../../../tests/utils';
import Breadcrumb from '../index'; import Breadcrumb from '../index';
const Apps = () => ( const Apps: React.FC = () => (
<ul className="app-list"> <ul className="app-list">
<li> <li>
<Link to="/apps/1">Application1</Link><Link to="/apps/1/detail">Detail</Link> <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 // https://github.com/airbnb/enzyme/issues/875
it('react router 6', () => { it('react router 6', () => {
const Home = () => { const Home: React.FC = () => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const pathSnippets = location.pathname.split('/').filter(i => i); const pathSnippets = location.pathname.split('/').filter(i => i);
@ -41,7 +42,7 @@ describe('react router', () => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`; const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
return ( return (
<Breadcrumb.Item key={url}> <Breadcrumb.Item key={url}>
<Link to={url}>{breadcrumbNameMap[url]}</Link> <Link to={url}>{breadcrumbNameMap[url as keyof typeof breadcrumbNameMap]}</Link>
</Breadcrumb.Item> </Breadcrumb.Item>
); );
}); });
@ -50,6 +51,14 @@ describe('react router', () => {
<Link to="/">Home</Link> <Link to="/">Home</Link>
</Breadcrumb.Item>, </Breadcrumb.Item>,
].concat(extraBreadcrumbItems); ].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 ( return (
<div className="demo"> <div className="demo">
<div className="demo-nav"> <div className="demo-nav">
@ -57,8 +66,8 @@ describe('react router', () => {
<a onClick={() => navigate('/apps')}>Application List</a> <a onClick={() => navigate('/apps')}>Application List</a>
</div> </div>
<Routes> <Routes>
<Route path="/apps" component={Apps} /> <Route path="/apps" {...componentProps} />
<Route render={() => <span>Home Page</span>} /> <Route {...renderProps} />
</Routes> </Routes>
<Breadcrumb>{breadcrumbItems}</Breadcrumb> <Breadcrumb>{breadcrumbItems}</Breadcrumb>
</div> </div>

View File

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

View File

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

View File

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

View File

@ -15519,11 +15519,10 @@ exports[`ConfigProvider components Form configProvider 1`] = `
> >
<div <div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected" 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 <div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light Bamboo is Light
@ -15575,11 +15574,10 @@ exports[`ConfigProvider components Form configProvider componentDisabled 1`] = `
> >
<div <div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected" 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 <div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light Bamboo is Light
@ -15630,11 +15628,10 @@ exports[`ConfigProvider components Form configProvider componentSize large 1`] =
> >
<div <div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected" 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 <div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light Bamboo is Light
@ -15685,11 +15682,10 @@ exports[`ConfigProvider components Form configProvider componentSize middle 1`]
> >
<div <div
class="config-form-item-explain config-show-help-appear config-show-help-appear-start config-show-help config-form-item-explain-connected" 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 <div
class="config-show-help-item-appear config-show-help-item-appear-start config-show-help-item config-form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light Bamboo is Light
@ -15740,11 +15736,10 @@ exports[`ConfigProvider components Form configProvider virtual and dropdownMatch
> >
<div <div
class="ant-form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help ant-form-item-explain-connected" 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 <div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item ant-form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light Bamboo is Light
@ -15795,11 +15790,10 @@ exports[`ConfigProvider components Form normal 1`] = `
> >
<div <div
class="ant-form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help ant-form-item-explain-connected" 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 <div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item ant-form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light Bamboo is Light
@ -15850,11 +15844,10 @@ exports[`ConfigProvider components Form prefixCls 1`] = `
> >
<div <div
class="prefix-Form-item-explain ant-show-help-appear ant-show-help-appear-start ant-show-help prefix-Form-item-explain-connected" 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 <div
class="ant-show-help-item-appear ant-show-help-item-appear-start ant-show-help-item prefix-Form-item-explain-error" 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;" style="height: 0px; opacity: 0;"
> >
Bamboo is Light 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-hover`] = colorPalettes[4];
variables[`${type}-color-active`] = colorPalettes[6]; variables[`${type}-color-active`] = colorPalettes[6];
variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString(); variables[`${type}-color-outline`] = baseColor.clone().setAlpha(0.2).toRgbString();
variables[`${type}-color-deprecated-bg`] = colorPalettes[1]; variables[`${type}-color-deprecated-bg`] = colorPalettes[0];
variables[`${type}-color-deprecated-border`] = colorPalettes[3]; variables[`${type}-color-deprecated-border`] = colorPalettes[2];
}; };
// ================ Primary Color ================ // ================ Primary Color ================

View File

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

View File

@ -1,14 +1,15 @@
import React from 'react'; import React from 'react';
import type { DrawerProps } from '..';
import Drawer from '..'; import Drawer from '..';
import { act, fireEvent, render } from '../../../tests/utils'; import { act, fireEvent, render } from '../../../tests/utils';
describe('Drawer', () => { const DrawerTest: React.FC<DrawerProps> = props => (
const getDrawer = props => ( <Drawer open getContainer={false} {...props}>
<Drawer open getContainer={false} {...props}> Here is content of Drawer
Here is content of Drawer </Drawer>
</Drawer> );
);
describe('Drawer', () => {
beforeEach(() => { beforeEach(() => {
jest.useFakeTimers(); jest.useFakeTimers();
}); });
@ -18,12 +19,12 @@ describe('Drawer', () => {
}); });
it('render correctly', () => { it('render correctly', () => {
const { container, asFragment, rerender } = render(getDrawer()); const { container, asFragment, rerender } = render(<DrawerTest />);
expect(container.querySelector('.ant-drawer-body')).toBeTruthy(); 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', 'Here is content of Drawer',
); );
@ -32,33 +33,33 @@ describe('Drawer', () => {
it('mask trigger onClose', () => { it('mask trigger onClose', () => {
const onClose = jest.fn(); 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(); expect(onClose).toHaveBeenCalled();
}); });
it('close button trigger onClose', () => { it('close button trigger onClose', () => {
const onClose = jest.fn(); 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(); expect(onClose).toHaveBeenCalled();
}); });
it('maskClosable no trigger onClose', () => { it('maskClosable no trigger onClose', () => {
const onClose = jest.fn(); 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(); expect(onClose).not.toHaveBeenCalled();
}); });
it('dom should be removed after close when destroyOnClose is true', () => { 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(); expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ destroyOnClose: true, open: false })); rerender(<DrawerTest destroyOnClose open={false} />);
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
@ -67,53 +68,53 @@ describe('Drawer', () => {
}); });
it('dom should be existed after close when destroyOnClose is false', () => { 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(); expect(container.querySelector('.ant-drawer')).toBeTruthy();
rerender(getDrawer({ open: false })); rerender(<DrawerTest open={false} />);
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
fireEvent.animationEnd(container.querySelector('.ant-drawer-content')); fireEvent.animationEnd(container.querySelector('.ant-drawer-content')!);
expect(container.querySelector('.ant-drawer')).toBeTruthy(); expect(container.querySelector('.ant-drawer')).toBeTruthy();
}); });
it('dom should be existed after close twice when getContainer is false', () => { 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(); expect(container.querySelector('.ant-drawer-content')).toBeTruthy();
// Hide // Hide
rerender(getDrawer({ open: false, getContainer: false })); rerender(<DrawerTest open={false} getContainer={false} />);
act(() => { act(() => {
jest.runAllTimers(); 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(); expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
// Show // 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')).toBeTruthy();
expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeFalsy(); expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeFalsy();
// Hide // Hide
rerender(getDrawer({ open: false, getContainer: false })); rerender(<DrawerTest open={false} getContainer={false} />);
act(() => { act(() => {
jest.runAllTimers(); 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(); expect(container.querySelector('.ant-drawer-content-wrapper-hidden')).toBeTruthy();
}); });
it('test afterOpenChange', async () => { it('test afterVisibleChange', async () => {
const afterOpenChange = jest.fn(); const afterOpenChange = jest.fn();
const { container, rerender } = render(getDrawer({ afterOpenChange, open: true })); const { container, rerender } = render(<DrawerTest open afterOpenChange={afterOpenChange} />);
rerender(getDrawer({ afterOpenChange, open: false })); rerender(<DrawerTest open={false} afterOpenChange={afterOpenChange} />);
act(() => { act(() => {
jest.runAllTimers(); jest.runAllTimers();
}); });
fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper')); fireEvent.animationEnd(container.querySelector('.ant-drawer-content-wrapper')!);
expect(afterOpenChange).toHaveBeenCalledTimes(1); expect(afterOpenChange).toHaveBeenCalledTimes(1);
}); });
@ -121,18 +122,18 @@ describe('Drawer', () => {
it('should support children ref', () => { it('should support children ref', () => {
const fn = jest.fn(); const fn = jest.fn();
const refCallback = ref => { const refCallback = (ref: HTMLDivElement | null) => {
expect(typeof ref).toBe('object'); expect(typeof ref).toBe('object');
fn(); fn();
}; };
const RefDemo = () => { const RefDemo: React.FC = () => {
const ref = React.useRef(); const ref = React.useRef<HTMLDivElement>(null);
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
React.useEffect(() => { React.useEffect(() => {
if (open) { if (open) {
refCallback(ref.current); refCallback(ref.current!);
} }
}, [open]); }, [open]);
@ -146,7 +147,7 @@ describe('Drawer', () => {
); );
}; };
const { container } = render(<RefDemo />); const { container } = render(<RefDemo />);
fireEvent.click(container.querySelector('a')); fireEvent.click(container.querySelector('a')!);
expect(fn).toHaveBeenCalled(); 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="name" id="name"
placeholder="Please enter user name" placeholder="Please enter user name"
@ -655,6 +656,7 @@ HTMLCollection [
http:// http://
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="url" id="url"
placeholder="Please enter url" placeholder="Please enter url"
@ -710,6 +712,7 @@ HTMLCollection [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -724,6 +727,7 @@ HTMLCollection [
aria-controls="owner_list" aria-controls="owner_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="owner_list" aria-owns="owner_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="owner" id="owner"
@ -887,6 +891,7 @@ HTMLCollection [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -901,6 +906,7 @@ HTMLCollection [
aria-controls="type_list" aria-controls="type_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="type_list" aria-owns="type_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="type" id="type"
@ -1069,6 +1075,7 @@ HTMLCollection [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -1083,6 +1090,7 @@ HTMLCollection [
aria-controls="approver_list" aria-controls="approver_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="approver_list" aria-owns="approver_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="approver" id="approver"
@ -1246,6 +1254,7 @@ HTMLCollection [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-picker ant-picker-range" class="ant-picker ant-picker-range"
style="width: 100%;" style="width: 100%;"
> >
@ -2470,6 +2479,7 @@ HTMLCollection [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<textarea <textarea
aria-required="true"
class="ant-input" class="ant-input"
id="description" id="description"
placeholder="please enter url description" placeholder="please enter url description"

View File

@ -47,21 +47,24 @@ describe('DropdownButton', () => {
onOpenChange: () => {}, onOpenChange: () => {},
}; };
render(<DropdownButton {...props} />); const { rerender } = render(<DropdownButton {...props} />);
Object.keys(props).forEach((key: keyof DropdownProps) => { Object.keys(props).forEach((key: keyof DropdownProps) => {
expect(dropdownProps[key]).toBe(props[key]); 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 = ( const menu = (
<Menu> <Menu>
<Menu.Item key="1">foo</Menu.Item> <Menu.Item key="1">foo</Menu.Item>
</Menu> </Menu>
); );
render(<DropdownButton overlay={menu} />); render(<DropdownButton overlay={menu} />);
expect('visible' in dropdownProps).toBe(false); expect('open' in dropdownProps).toBe(false);
}); });
it('should support href like Button', () => { it('should support href like Button', () => {

View File

@ -95,6 +95,7 @@ describe('Dropdown', () => {
expect(error).toHaveBeenCalledWith( expect(error).toHaveBeenCalledWith(
expect.stringContaining("[antd: Dropdown] You are using 'topCenter'"), expect.stringContaining("[antd: Dropdown] You are using 'topCenter'"),
); );
error.mockRestore();
}); });
// zombieJ: when replaced with react test lib, it may be mock fully content // zombieJ: when replaced with react test lib, it may be mock fully content
@ -166,4 +167,27 @@ describe('Dropdown', () => {
jest.useRealTimers(); 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 | - | | | overlayStyle | The style of the dropdown root element | CSSProperties | - | |
| placement | Placement of popup menu: `bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | | | 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`] | | | 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 | - | | | 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 | - | | | 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`. 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` | | | 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`] | | | 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` | | | 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 | - | | | 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 | - | | | overlayStyle | 下拉根元素的样式 | CSSProperties | - | |
| placement | 菜单弹出位置:`bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | | | placement | 菜单弹出位置:`bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
| trigger | 触发下拉的行为, 移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | | | trigger | 触发下拉的行为, 移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| open | 菜单是否显示 | boolean | - | | | open | 菜单是否显示 | boolean | - | 4.23.0 |
| onOpenChange | 菜单显示状态改变时调用,参数为 `open`。点击菜单按钮导致的消失不会触发 | (open: boolean) => void | - | | | onOpenChange | 菜单显示状态改变时调用,参数为 `visible`。点击菜单按钮导致的消失不会触发 | (open: boolean) => void | - | 4.23.0 |
`overlay` 菜单使用 [Menu](/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider` `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` | | | size | 按钮大小,和 [Button](/components/button/#API) 一致 | string | `default` | |
| trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | | | trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
| type | 按钮类型,和 [Button](/components/button/#API) 一致 | string | `default` | | | type | 按钮类型,和 [Button](/components/button/#API) 一致 | string | `default` | |
| open | 菜单是否显示 | boolean | - | | | open | 菜单是否显示 | boolean | - | 4.23.0 |
| onClick | 点击左侧按钮的回调,和 [Button](/components/button/#API) 一致 | (event) => void | - | | | 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', () => { it('image size should change', () => {
const { container } = render(<Empty imageStyle={{ height: 20 }} />); 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', () => { it('description can be false', () => {

View File

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

View File

@ -40,11 +40,16 @@ interface MemoInputProps {
value: any; value: any;
update: any; update: any;
children: React.ReactNode; children: React.ReactNode;
childProps: any[];
} }
const MemoInput = React.memo( const MemoInput = React.memo(
({ children }: MemoInputProps) => children as JSX.Element, ({ 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> export interface FormItemProps<Values = any>
@ -313,6 +318,25 @@ function InternalFormItem<Values = any>(props: FormItemProps<Values>): React.Rea
childProps.id = fieldId; 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)) { if (supportRef(children)) {
childProps.ref = getItemRef(mergedName, 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 = ( childNode = (
<MemoInput value={mergedControl[props.valuePropName || 'value']} update={children}> <MemoInput
value={mergedControl[props.valuePropName || 'value']}
update={children}
childProps={watchingChildProps}
>
{cloneElement(children, childProps)} {cloneElement(children, childProps)}
</MemoInput> </MemoInput>
); );

View File

@ -32,6 +32,7 @@ export interface FormItemInputProps {
extra?: React.ReactNode; extra?: React.ReactNode;
status?: ValidateStatus; status?: ValidateStatus;
help?: React.ReactNode; help?: React.ReactNode;
fieldId?: string;
} }
const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = props => { const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = props => {
@ -45,6 +46,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
_internalItemRender: formItemRender, _internalItemRender: formItemRender,
extra, extra,
help, help,
fieldId,
marginBottom, marginBottom,
onErrorVisibleChanged, onErrorVisibleChanged,
} = props; } = props;
@ -72,6 +74,7 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
<div style={{ display: 'flex', flexWrap: 'nowrap' }}> <div style={{ display: 'flex', flexWrap: 'nowrap' }}>
<FormItemPrefixContext.Provider value={formItemContext}> <FormItemPrefixContext.Provider value={formItemContext}>
<ErrorList <ErrorList
fieldId={fieldId}
errors={errors} errors={errors}
warnings={warnings} warnings={warnings}
help={help} help={help}
@ -84,9 +87,19 @@ const FormItemInput: React.FC<FormItemInputProps & FormItemInputMiscProps> = pro
</div> </div>
) : null; ) : null;
const extraProps: { id?: string } = {};
if (fieldId) {
extraProps.id = `${fieldId}_extra`;
}
// If extra = 0, && will goes wrong // If extra = 0, && will goes wrong
// 0&&error -> 0 // 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 = const dom =
formItemRender && formItemRender.mark === 'pro_table_render' && formItemRender.render ? ( 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-0" id="advanced_search_field-0"
placeholder="placeholder" placeholder="placeholder"
@ -84,6 +85,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -98,6 +100,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
aria-controls="advanced_search_field-1_list" aria-controls="advanced_search_field-1_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="advanced_search_field-1_list" aria-owns="advanced_search_field-1_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="advanced_search_field-1" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-2" id="advanced_search_field-2"
placeholder="placeholder" placeholder="placeholder"
@ -305,6 +309,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-3" id="advanced_search_field-3"
placeholder="placeholder" placeholder="placeholder"
@ -348,6 +353,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -362,6 +368,7 @@ exports[`renders ./components/form/demo/advanced-search.md extend context correc
aria-controls="advanced_search_field-4_list" aria-controls="advanced_search_field-4_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="advanced_search_field-4_list" aria-owns="advanced_search_field-4_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="advanced_search_field-4" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-5" id="advanced_search_field-5"
placeholder="placeholder" placeholder="placeholder"
@ -631,6 +639,7 @@ exports[`renders ./components/form/demo/basic.md extend context correctly 1`] =
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="basic_username" id="basic_username"
type="text" type="text"
@ -672,6 +681,7 @@ exports[`renders ./components/form/demo/basic.md extend context correctly 1`] =
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="basic_password" id="basic_password"
type="password" type="password"
@ -815,6 +825,7 @@ Array [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="basic_username" id="basic_username"
type="text" type="text"
@ -856,6 +867,7 @@ Array [
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="basic_password" id="basic_password"
type="password" type="password"
@ -1184,6 +1196,7 @@ Array [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="responsive_username" id="responsive_username"
type="text" type="text"
@ -1225,6 +1238,7 @@ Array [
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="responsive_password" id="responsive_password"
type="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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="control-hooks_note" id="control-hooks_note"
type="text" type="text"
@ -1638,6 +1653,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
> >
<div <div
@ -1652,6 +1668,7 @@ exports[`renders ./components/form/demo/control-hooks.md extend context correctl
aria-controls="control-hooks_gender_list" aria-controls="control-hooks_gender_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="control-hooks_gender_list" aria-owns="control-hooks_gender_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="control-hooks_gender" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="control-ref_note" id="control-ref_note"
type="text" type="text"
@ -1916,6 +1934,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
> >
<div <div
@ -1930,6 +1949,7 @@ exports[`renders ./components/form/demo/control-ref.md extend context correctly
aria-controls="control-ref_gender_list" aria-controls="control-ref_gender_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="control-ref_gender_list" aria-owns="control-ref_gender_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="control-ref_gender" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <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-controls="dynamic_form_nest_item_area_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="dynamic_form_nest_item_area_list" aria-owns="dynamic_form_nest_item_area_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="dynamic_form_nest_item_area" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="dynamic_rule_username" id="dynamic_rule_username"
placeholder="Please input your name" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="basicForm_group" id="basicForm_group"
type="text" type="text"
@ -6648,6 +6672,7 @@ Array [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="global_state_username" id="global_state_username"
type="text" type="text"
@ -6721,6 +6746,7 @@ exports[`renders ./components/form/demo/inline-login.md extend context correctly
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="horizontal_login_username" id="horizontal_login_username"
placeholder="Username" placeholder="Username"
@ -6775,6 +6801,7 @@ exports[`renders ./components/form/demo/inline-login.md extend context correctly
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="horizontal_login_password" id="horizontal_login_password"
placeholder="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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="wrap_username" id="wrap_username"
type="text" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="wrap_password" id="wrap_password"
type="text" type="text"
@ -7333,6 +7362,7 @@ exports[`renders ./components/form/demo/nest-messages.md extend context correctl
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="nest-messages_user_name" id="nest-messages_user_name"
type="text" type="text"
@ -7635,6 +7665,7 @@ exports[`renders ./components/form/demo/normal-login.md extend context correctly
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="normal_login_username" id="normal_login_username"
placeholder="Username" placeholder="Username"
@ -7689,6 +7720,7 @@ exports[`renders ./components/form/demo/normal-login.md extend context correctly
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="normal_login_password" id="normal_login_password"
placeholder="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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_email" id="register_email"
type="text" type="text"
@ -7943,6 +7976,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="register_password" id="register_password"
type="password" type="password"
@ -8012,6 +8046,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="register_confirm" id="register_confirm"
type="password" type="password"
@ -8124,6 +8159,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_nickname" id="register_nickname"
type="text" type="text"
@ -8161,6 +8197,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <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" class="ant-select ant-cascader ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
> >
<div <div
@ -8174,6 +8211,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_residence_list" aria-controls="register_residence_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="register_residence_list" aria-owns="register_residence_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="register_residence" id="register_residence"
@ -8521,6 +8559,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
</div> </div>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_phone" id="register_phone"
type="text" type="text"
@ -8631,6 +8670,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
class="ant-input-number-input-wrap" class="ant-input-number-input-wrap"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-input-number-input" class="ant-input-number-input"
id="register_donation" 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" class="ant-form-item-control-input-content"
> >
<div <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" class="ant-select ant-select-in-form-item ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
> >
<div <div
@ -8832,6 +8873,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_website_list" aria-controls="register_website_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="register_website_list" aria-owns="register_website_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-input ant-select-selection-search-input" class="ant-input ant-select-selection-search-input"
id="register_website" id="register_website"
@ -8897,6 +8939,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
data-count="0 / 100" data-count="0 / 100"
> >
<textarea <textarea
aria-required="true"
class="ant-input" class="ant-input"
id="register_intro" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -8947,6 +8991,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`]
aria-controls="register_gender_list" aria-controls="register_gender_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="register_gender_list" aria-owns="register_gender_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="register_gender" 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" style="padding-left:4px;padding-right:4px"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_captcha" id="register_captcha"
type="text" type="text"
@ -10986,6 +11032,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_date-picker" id="time_related_controls_date-picker"
placeholder="Select date" placeholder="Select date"
@ -11611,6 +11658,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_date-time-picker" id="time_related_controls_date-time-picker"
placeholder="Select date" placeholder="Select date"
@ -13589,6 +13637,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_month-picker" id="time_related_controls_month-picker"
placeholder="Select month" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-picker ant-picker-range" class="ant-picker ant-picker-range"
> >
<div <div
@ -15054,6 +15104,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-picker ant-picker-range" class="ant-picker ant-picker-range"
> >
<div <div
@ -17085,6 +17136,7 @@ exports[`renders ./components/form/demo/time-related-controls.md extend context
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_time-picker" id="time_related_controls_time-picker"
placeholder="Select time" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow"
> >
<div <div
@ -18762,6 +18815,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
aria-controls="validate_other_select_list" aria-controls="validate_other_select_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="validate_other_select_list" aria-owns="validate_other_select_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="validate_other_select" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search" class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search"
> >
<div <div
@ -18942,6 +18997,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
aria-controls="validate_other_select-multiple_list" aria-controls="validate_other_select-multiple_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="validate_other_select-multiple_list" aria-owns="validate_other_select-multiple_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="validate_other_select-multiple" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-radio-group ant-radio-group-outline" class="ant-radio-group ant-radio-group-outline"
id="validate_other_radio-button" id="validate_other_radio-button"
> >
@ -20095,6 +20152,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
> >
<input <input
accept="" accept=""
aria-describedby="validate_other_upload_extra"
id="validate_other_upload" id="validate_other_upload"
style="display:none" style="display:none"
type="file" type="file"
@ -20136,6 +20194,7 @@ exports[`renders ./components/form/demo/validate-other.md extend context correct
</div> </div>
<div <div
class="ant-form-item-extra" class="ant-form-item-extra"
id="validate_other_upload_extra"
> >
longgggggggggggggggggggggggggggggggggg longgggggggggggggggggggggggggggggggggg
</div> </div>
@ -26409,6 +26468,7 @@ exports[`renders ./components/form/demo/warning-only.md extend context correctly
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="url" id="url"
placeholder="input placeholder" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-0" id="advanced_search_field-0"
placeholder="placeholder" placeholder="placeholder"
@ -84,6 +85,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -98,6 +100,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
aria-controls="advanced_search_field-1_list" aria-controls="advanced_search_field-1_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="advanced_search_field-1_list" aria-owns="advanced_search_field-1_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="advanced_search_field-1" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-2" id="advanced_search_field-2"
placeholder="placeholder" placeholder="placeholder"
@ -223,6 +227,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-3" id="advanced_search_field-3"
placeholder="placeholder" placeholder="placeholder"
@ -266,6 +271,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -280,6 +286,7 @@ exports[`renders ./components/form/demo/advanced-search.md correctly 1`] = `
aria-controls="advanced_search_field-4_list" aria-controls="advanced_search_field-4_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="advanced_search_field-4_list" aria-owns="advanced_search_field-4_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="advanced_search_field-4" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="advanced_search_field-5" id="advanced_search_field-5"
placeholder="placeholder" placeholder="placeholder"
@ -467,6 +475,7 @@ exports[`renders ./components/form/demo/basic.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="basic_username" id="basic_username"
type="text" type="text"
@ -508,6 +517,7 @@ exports[`renders ./components/form/demo/basic.md correctly 1`] = `
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="basic_password" id="basic_password"
type="password" type="password"
@ -651,6 +661,7 @@ Array [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="basic_username" id="basic_username"
type="text" type="text"
@ -692,6 +703,7 @@ Array [
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="basic_password" id="basic_password"
type="password" type="password"
@ -913,6 +925,7 @@ Array [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="responsive_username" id="responsive_username"
type="text" type="text"
@ -954,6 +967,7 @@ Array [
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="responsive_password" id="responsive_password"
type="password" type="password"
@ -1223,6 +1237,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="control-hooks_note" id="control-hooks_note"
type="text" type="text"
@ -1260,6 +1275,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
> >
<div <div
@ -1274,6 +1290,7 @@ exports[`renders ./components/form/demo/control-hooks.md correctly 1`] = `
aria-controls="control-hooks_gender_list" aria-controls="control-hooks_gender_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="control-hooks_gender_list" aria-owns="control-hooks_gender_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="control-hooks_gender" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="control-ref_note" id="control-ref_note"
type="text" type="text"
@ -1439,6 +1457,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
> >
<div <div
@ -1453,6 +1472,7 @@ exports[`renders ./components/form/demo/control-ref.md correctly 1`] = `
aria-controls="control-ref_gender_list" aria-controls="control-ref_gender_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="control-ref_gender_list" aria-owns="control-ref_gender_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="control-ref_gender" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <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-controls="dynamic_form_nest_item_area_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="dynamic_form_nest_item_area_list" aria-owns="dynamic_form_nest_item_area_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="dynamic_form_nest_item_area" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="dynamic_rule_username" id="dynamic_rule_username"
placeholder="Please input your name" 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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="basicForm_group" id="basicForm_group"
type="text" type="text"
@ -4049,6 +4073,7 @@ Array [
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="global_state_username" id="global_state_username"
type="text" type="text"
@ -4122,6 +4147,7 @@ exports[`renders ./components/form/demo/inline-login.md correctly 1`] = `
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="horizontal_login_username" id="horizontal_login_username"
placeholder="Username" placeholder="Username"
@ -4176,6 +4202,7 @@ exports[`renders ./components/form/demo/inline-login.md correctly 1`] = `
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="horizontal_login_password" id="horizontal_login_password"
placeholder="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" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="wrap_username" id="wrap_username"
type="text" type="text"
@ -4649,6 +4677,7 @@ exports[`renders ./components/form/demo/layout-can-wrap.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="wrap_password" id="wrap_password"
type="text" type="text"
@ -4734,6 +4763,7 @@ exports[`renders ./components/form/demo/nest-messages.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="nest-messages_user_name" id="nest-messages_user_name"
type="text" type="text"
@ -5036,6 +5066,7 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="normal_login_username" id="normal_login_username"
placeholder="Username" placeholder="Username"
@ -5090,6 +5121,7 @@ exports[`renders ./components/form/demo/normal-login.md correctly 1`] = `
</span> </span>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="normal_login_password" id="normal_login_password"
placeholder="Password" placeholder="Password"
@ -5303,6 +5335,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_email" id="register_email"
type="text" type="text"
@ -5344,6 +5377,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="register_password" id="register_password"
type="password" type="password"
@ -5413,6 +5447,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
> >
<input <input
action="click" action="click"
aria-required="true"
class="ant-input" class="ant-input"
id="register_confirm" id="register_confirm"
type="password" type="password"
@ -5501,6 +5536,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_nickname" id="register_nickname"
type="text" type="text"
@ -5538,6 +5574,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <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" class="ant-select ant-cascader ant-select-in-form-item ant-select-single ant-select-allow-clear ant-select-show-arrow"
> >
<div <div
@ -5551,6 +5588,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_residence_list" aria-controls="register_residence_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="register_residence_list" aria-owns="register_residence_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="register_residence" id="register_residence"
@ -5726,6 +5764,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
</div> </div>
</span> </span>
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_phone" id="register_phone"
type="text" type="text"
@ -5836,6 +5875,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-input-number-input-wrap" class="ant-input-number-input-wrap"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-input-number-input" class="ant-input-number-input"
id="register_donation" id="register_donation"
@ -5941,6 +5981,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <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" class="ant-select ant-select-in-form-item ant-select-auto-complete ant-select-single ant-select-customize-input ant-select-show-search"
> >
<div <div
@ -5955,6 +5996,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_website_list" aria-controls="register_website_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="register_website_list" aria-owns="register_website_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-input ant-select-selection-search-input" class="ant-input ant-select-selection-search-input"
id="register_website" id="register_website"
@ -6006,6 +6048,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
data-count="0 / 100" data-count="0 / 100"
> >
<textarea <textarea
aria-required="true"
class="ant-input" class="ant-input"
id="register_intro" id="register_intro"
/> />
@ -6042,6 +6085,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-single ant-select-show-arrow"
> >
<div <div
@ -6056,6 +6100,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
aria-controls="register_gender_list" aria-controls="register_gender_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="register_gender_list" aria-owns="register_gender_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="register_gender" id="register_gender"
@ -6139,6 +6184,7 @@ exports[`renders ./components/form/demo/register.md correctly 1`] = `
style="padding-left:4px;padding-right:4px" style="padding-left:4px;padding-right:4px"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="register_captcha" id="register_captcha"
type="text" type="text"
@ -7186,6 +7232,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_date-picker" id="time_related_controls_date-picker"
placeholder="Select date" placeholder="Select date"
@ -7257,6 +7304,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_date-time-picker" id="time_related_controls_date-time-picker"
placeholder="Select date" placeholder="Select date"
@ -7328,6 +7376,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_month-picker" id="time_related_controls_month-picker"
placeholder="Select month" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-picker ant-picker-range" class="ant-picker ant-picker-range"
> >
<div <div
@ -7506,6 +7556,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-picker ant-picker-range" class="ant-picker ant-picker-range"
> >
<div <div
@ -7625,6 +7676,7 @@ exports[`renders ./components/form/demo/time-related-controls.md correctly 1`] =
class="ant-picker-input" class="ant-picker-input"
> >
<input <input
aria-required="true"
autocomplete="off" autocomplete="off"
id="time_related_controls_time-picker" id="time_related_controls_time-picker"
placeholder="Select time" placeholder="Select time"
@ -7923,6 +7975,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow" class="ant-select ant-select-in-form-item ant-select-has-feedback ant-select-single ant-select-show-arrow"
> >
<div <div
@ -7937,6 +7990,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
aria-controls="validate_other_select_list" aria-controls="validate_other_select_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="validate_other_select_list" aria-owns="validate_other_select_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="validate_other_select" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search" class="ant-select ant-select-in-form-item ant-select-multiple ant-select-show-search"
> >
<div <div
@ -8035,6 +8090,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
aria-controls="validate_other_select-multiple_list" aria-controls="validate_other_select-multiple_list"
aria-haspopup="listbox" aria-haspopup="listbox"
aria-owns="validate_other_select-multiple_list" aria-owns="validate_other_select-multiple_list"
aria-required="true"
autocomplete="off" autocomplete="off"
class="ant-select-selection-search-input" class="ant-select-selection-search-input"
id="validate_other_select-multiple" 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" class="ant-form-item-control-input-content"
> >
<div <div
aria-required="true"
class="ant-radio-group ant-radio-group-outline" class="ant-radio-group ant-radio-group-outline"
id="validate_other_radio-button" id="validate_other_radio-button"
> >
@ -9083,6 +9140,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
> >
<input <input
accept="" accept=""
aria-describedby="validate_other_upload_extra"
id="validate_other_upload" id="validate_other_upload"
style="display:none" style="display:none"
type="file" type="file"
@ -9124,6 +9182,7 @@ exports[`renders ./components/form/demo/validate-other.md correctly 1`] = `
</div> </div>
<div <div
class="ant-form-item-extra" class="ant-form-item-extra"
id="validate_other_upload_extra"
> >
longgggggggggggggggggggggggggggggggggg longgggggggggggggggggggggggggggggggggg
</div> </div>
@ -11066,6 +11125,7 @@ exports[`renders ./components/form/demo/warning-only.md correctly 1`] = `
class="ant-form-item-control-input-content" class="ant-form-item-control-input-content"
> >
<input <input
aria-required="true"
class="ant-input" class="ant-input"
id="url" id="url"
placeholder="input placeholder" 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', () => { describe('scrollToField', () => {
function test(name, genForm) { function test(name, genForm) {
it(name, () => { it(name, () => {
@ -710,9 +862,7 @@ describe('Form', () => {
await sleep(100); await sleep(100);
wrapper.update(); wrapper.update();
await sleep(100); await sleep(100);
expect(wrapper.find('.ant-form-item-explain div').getDOMNode().getAttribute('role')).toBe( expect(wrapper.find('.ant-form-item-explain').getDOMNode().getAttribute('role')).toBe('alert');
'alert',
);
}); });
it('return same form instance', () => { 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'; import warning from '../_util/warning';
const Icon = () => { const Icon: React.FC = () => {
warning(false, 'Icon', 'Empty Icon'); warning(false, 'Icon', 'Empty Icon');
return null; return null;
}; };

View File

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

View File

@ -15,18 +15,18 @@ describe('InputNumber', () => {
it('should return null when blur a empty input number', () => { it('should return null when blur a empty input number', () => {
const onChange = jest.fn(); const onChange = jest.fn();
const { container } = render(<InputNumber defaultValue="1" onChange={onChange} />); 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); expect(onChange).toHaveBeenLastCalledWith(null);
}); });
it('should call onStep when press up or down button', () => { it('should call onStep when press up or down button', () => {
const onStep = jest.fn(); const onStep = jest.fn();
const { container } = render(<InputNumber defaultValue={1} onStep={onStep} />); 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).toHaveBeenCalledTimes(1);
expect(onStep).toHaveBeenLastCalledWith(2, { offset: 1, type: 'up' }); 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).toHaveBeenCalledTimes(2);
expect(onStep).toHaveBeenLastCalledWith(1, { offset: 1, type: 'down' }); expect(onStep).toHaveBeenLastCalledWith(1, { offset: 1, type: 'down' });
}); });

View File

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

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
// eslint-disable-next-line import/no-unresolved import type { InputRef } from '..';
import Input from '..'; import Input from '..';
import focusTest from '../../../tests/shared/focusTest'; import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
@ -13,12 +13,12 @@ describe('Input.Password', () => {
rtlTest(Input.Password); rtlTest(Input.Password);
it('should get input element from ref', () => { it('should get input element from ref', () => {
const ref = React.createRef(); const ref = React.createRef<InputRef>();
const onSelect = jest.fn(); const onSelect = jest.fn();
const { container } = render(<Input.Password onSelect={onSelect} ref={ref} />); const { container } = render(<Input.Password onSelect={onSelect} ref={ref} />);
expect(ref.current.input instanceof HTMLInputElement).toBe(true); expect(ref.current?.input instanceof HTMLInputElement).toBe(true);
fireEvent.select(container.querySelector('input')); fireEvent.select(container.querySelector('input')!);
expect(onSelect).toHaveBeenCalled(); expect(onSelect).toHaveBeenCalled();
}); });
@ -30,13 +30,13 @@ describe('Input.Password', () => {
it('should change type when click', () => { it('should change type when click', () => {
const { asFragment, container } = render(<Input.Password />); 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(); expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-password-icon')); fireEvent.click(container.querySelector('.ant-input-password-icon')!);
expect(asFragment().firstChild).toMatchSnapshot(); expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-password-icon')); fireEvent.click(container.querySelector('.ant-input-password-icon')!);
expect(asFragment().firstChild).toMatchSnapshot(); expect(asFragment().firstChild).toMatchSnapshot();
}); });
@ -50,7 +50,7 @@ describe('Input.Password', () => {
it('should not toggle visibility when disabled prop is true', () => { it('should not toggle visibility when disabled prop is true', () => {
const { container } = render(<Input.Password disabled />); const { container } = render(<Input.Password disabled />);
expect(container.querySelectorAll('.anticon-eye-invisible').length).toBe(1); 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); expect(container.querySelectorAll('.anticon-eye').length).toBe(0);
}); });
@ -59,43 +59,43 @@ describe('Input.Password', () => {
container: document.body, container: document.body,
}); });
expect(document.activeElement).toBe(container.querySelector('input')); expect(document.activeElement).toBe(container.querySelector('input'));
document.activeElement.setSelectionRange(2, 2); (document?.activeElement as any)?.setSelectionRange(2, 2);
expect(document.activeElement.selectionStart).toBe(2); expect((document?.activeElement as any)?.selectionStart).toBe(2);
fireEvent.mouseDown(container.querySelector('.ant-input-password-icon')); fireEvent.mouseDown(container.querySelector('.ant-input-password-icon')!);
fireEvent.mouseUp(container.querySelector('.ant-input-password-icon')); fireEvent.mouseUp(container.querySelector('.ant-input-password-icon')!);
fireEvent.click(container.querySelector('.ant-input-password-icon')); fireEvent.click(container.querySelector('.ant-input-password-icon')!);
expect(document.activeElement).toBe(container.querySelector('input')); expect(document.activeElement).toBe(container.querySelector('input'));
expect(document.activeElement.selectionStart).toBe(2); expect((document?.activeElement as any).selectionStart).toBe(2);
unmount(); unmount();
}); });
// https://github.com/ant-design/ant-design/issues/20541 // https://github.com/ant-design/ant-design/issues/20541
it('should not show value attribute in input element', async () => { it('should not show value attribute in input element', async () => {
const { container } = render(<Input.Password />); const { container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } }); fireEvent.change(container.querySelector('input')!, { target: { value: 'value' } });
await sleep(); 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 // https://github.com/ant-design/ant-design/issues/24526
it('should not show value attribute in input element after blur it', async () => { it('should not show value attribute in input element after blur it', async () => {
const { container } = render(<Input.Password />); const { container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } }); fireEvent.change(container.querySelector('input')!, { target: { value: 'value' } });
await sleep(); await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy(); expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
fireEvent.blur(container.querySelector('input')); fireEvent.blur(container.querySelector('input')!);
await sleep(); await sleep();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy(); expect(container.querySelector('input')?.getAttribute('value')).toBeFalsy();
fireEvent.focus(container.querySelector('input')); fireEvent.focus(container.querySelector('input')!);
await sleep(); 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 // https://github.com/ant-design/ant-design/issues/20541
it('could be unmount without errors', () => { it('could be unmount without errors', () => {
expect(() => { expect(() => {
const { container, unmount } = render(<Input.Password />); const { container, unmount } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } }); fireEvent.change(container.querySelector('input')!, { target: { value: 'value' } });
unmount(); unmount();
}).not.toThrow(); }).not.toThrow();
}); });
@ -104,6 +104,6 @@ describe('Input.Password', () => {
it('should not contain value attribute in input element with defaultValue', async () => { it('should not contain value attribute in input element with defaultValue', async () => {
const { container } = render(<Input.Password defaultValue="value" />); const { container } = render(<Input.Password defaultValue="value" />);
await sleep(); 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 mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
import Button from '../../button'; import Button from '../../button';
import type { InputRef } from '../Input';
import Search from '../Search'; import Search from '../Search';
describe('Input.Search', () => { describe('Input.Search', () => {
@ -42,24 +43,16 @@ describe('Input.Search', () => {
const { container } = render( const { container } = render(
<Search defaultValue="search text" onSearch={onSearch} disabled />, <Search defaultValue="search text" onSearch={onSearch} disabled />,
); );
fireEvent.click(container.querySelector('button')); fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(0); expect(onSearch).toHaveBeenCalledTimes(0);
}); });
it('should trigger onSearch when click search icon', () => { it('should trigger onSearch when click search icon', () => {
const onSearch = jest.fn(); const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />); 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).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
}); });
it('should trigger onSearch when click search button', () => { it('should trigger onSearch when click search button', () => {
@ -67,17 +60,9 @@ describe('Input.Search', () => {
const { container } = render( const { container } = render(
<Search defaultValue="search text" enterButton onSearch={onSearch} />, <Search defaultValue="search text" enterButton onSearch={onSearch} />,
); );
fireEvent.click(container.querySelector('button')); fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1); expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
}); });
it('should trigger onSearch when click search button with text', () => { it('should trigger onSearch when click search button with text', () => {
@ -85,17 +70,9 @@ describe('Input.Search', () => {
const { container } = render( const { container } = render(
<Search defaultValue="search text" enterButton="button text" onSearch={onSearch} />, <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).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
}); });
it('should trigger onSearch when click search button with customize button', () => { it('should trigger onSearch when click search button with customize button', () => {
@ -107,17 +84,9 @@ describe('Input.Search', () => {
onSearch={onSearch} onSearch={onSearch}
/>, />,
); );
fireEvent.click(container.querySelector('button')); fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1); expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
}); });
it('should trigger onSearch when click search button of native', () => { it('should trigger onSearch when click search button of native', () => {
@ -134,56 +103,32 @@ describe('Input.Search', () => {
onSearch={onSearch} onSearch={onSearch}
/>, />,
); );
fireEvent.click(container.querySelector('button')); fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1); expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onButtonClick).toHaveBeenCalledTimes(1); expect(onButtonClick).toHaveBeenCalledTimes(1);
}); });
it('should trigger onSearch when press enter', () => { it('should trigger onSearch when press enter', () => {
const onSearch = jest.fn(); const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />); 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).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'keydown',
// preventDefault: expect.any(Function),
// }),
);
}); });
// https://github.com/ant-design/ant-design/issues/34844 // https://github.com/ant-design/ant-design/issues/34844
it('should not trigger onSearch when press enter using chinese inputting method', () => { it('should not trigger onSearch when press enter using chinese inputting method', () => {
const onSearch = jest.fn(); const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />); const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.compositionStart(container.querySelector('input')); fireEvent.compositionStart(container.querySelector('input')!);
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 }); fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled(); expect(onSearch).not.toHaveBeenCalled();
fireEvent.compositionEnd(container.querySelector('input')); fireEvent.compositionEnd(container.querySelector('input')!);
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 }); fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).toHaveBeenCalledTimes(1); expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith( expect(onSearch).toHaveBeenCalledWith('search text', expect.anything());
'search text',
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'keydown',
// preventDefault: expect.any(Function),
// }),
);
}); });
// https://github.com/ant-design/ant-design/issues/14785 // https://github.com/ant-design/ant-design/issues/14785
@ -204,7 +149,7 @@ describe('Input.Search', () => {
const { container } = render( const { container } = render(
<Search allowClear defaultValue="value" onSearch={onSearch} onChange={onChange} />, <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(onSearch).toHaveBeenLastCalledWith('', expect.anything());
expect(onChange).toHaveBeenCalled(); expect(onChange).toHaveBeenCalled();
}); });
@ -236,13 +181,13 @@ describe('Input.Search', () => {
}); });
it('should prevent search button mousedown event', () => { 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" />, { const { container } = render(<Search ref={ref} enterButton="button text" />, {
container: document.body, container: document.body,
}); });
ref.current.focus(); ref.current?.focus();
expect(document.activeElement).toBe(container.querySelector('input')); expect(document.activeElement).toBe(container.querySelector('input'));
fireEvent.mouseDown(container.querySelector('button')); fireEvent.mouseDown(container.querySelector('button')!);
expect(document.activeElement).toBe(container.querySelector('input')); expect(document.activeElement).toBe(container.querySelector('input'));
}); });
@ -250,7 +195,7 @@ describe('Input.Search', () => {
const ref = jest.fn(); const ref = jest.fn();
const { container } = render(<Search ref={ref} enterButton />); const { container } = render(<Search ref={ref} enterButton />);
expect(() => { expect(() => {
fireEvent.mouseDown(container.querySelector('button')); fireEvent.mouseDown(container.querySelector('button')!);
}).not.toThrow(); }).not.toThrow();
}); });
@ -258,10 +203,10 @@ describe('Input.Search', () => {
it('Search with allowClear should have one className only', () => { it('Search with allowClear should have one className only', () => {
const { container } = render(<Search allowClear className="className" />); const { container } = render(<Search allowClear className="className" />);
expect( expect(
container.querySelector('.ant-input-group-wrapper').classList.contains('className'), container.querySelector('.ant-input-group-wrapper')?.classList.contains('className'),
).toBe(true); ).toBe(true);
expect( expect(
container.querySelector('.ant-input-affix-wrapper').classList.contains('className'), container.querySelector('.ant-input-affix-wrapper')?.classList.contains('className'),
).toBe(false); ).toBe(false);
}); });
}); });

View File

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

View File

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

View File

@ -49,7 +49,7 @@ describe('List Item Layout', () => {
/>, />,
); );
expect( 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); ).toBe(false);
}); });
@ -188,17 +188,17 @@ describe('List Item Layout', () => {
}); });
it('should ref', () => { it('should ref', () => {
const ref = React.createRef(); const ref = React.createRef<HTMLElement>();
render(<List.Item ref={ref}>Item</List.Item>); render(<List.Item ref={ref}>Item</List.Item>);
expect(ref.current).toHaveClass('ant-list-item'); expect(ref.current).toHaveClass('ant-list-item');
}); });
it('should grid ref', () => { it('should grid ref', () => {
const ref = React.createRef(); const ref = React.createRef<HTMLElement>();
render( render(
<List grid> <List grid={{}}>
<List.Item ref={ref}>Item</List.Item>, <List.Item ref={ref}>Item</List.Item>,
</List>, </List>,
); );

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import type { ListProps } from '..';
import List from '..'; import List from '..';
import mountTest from '../../../tests/shared/mountTest'; import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest'; import rtlTest from '../../../tests/shared/rtlTest';
@ -13,12 +14,12 @@ describe('List', () => {
it('locale not passed to internal div', async () => { it('locale not passed to internal div', async () => {
const locale = { emptyText: 'Custom text' }; const locale = { emptyText: 'Custom text' };
const renderItem = item => <List.Item>{item}</List.Item>; const renderItem: ListProps<any>['renderItem'] = item => <List.Item>{item}</List.Item>;
const dataSource = []; const dataSource: ListProps<any>['dataSource'] = [];
const { container } = render( const { container } = render(
<List renderItem={renderItem} dataSource={dataSource} locale={locale} />, <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', () => { describe('List', () => {
it('renders empty loading', () => { it('renders empty loading', () => {
const loading = { const loading = { spinning: true };
spinning: true,
};
const { container: wrapper } = render( const { container: wrapper } = render(
<List loading={loading} dataSource={[]} renderItem={() => <List.Item />} />, <List loading={loading} dataSource={[]} renderItem={() => <List.Item />} />,
); );

View File

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

View File

@ -35871,7 +35871,7 @@ exports[`Locale Provider should display the text as cs 1`] = `
type="button" type="button"
> >
<span> <span>
Storno Zrušit
</span> </span>
</button> </button>
<button <button
@ -37444,7 +37444,7 @@ exports[`Locale Provider should display the text as cs 1`] = `
type="button" type="button"
> >
<span> <span>
Storno Zrušit
</span> </span>
</button> </button>
<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 Pagination from 'rc-pagination/lib/locale/cs_CZ';
import Calendar from '../calendar/locale/cs_CZ'; import Calendar from '../calendar/locale/cs_CZ';
import DatePicker from '../date-picker/locale/cs_CZ'; import DatePicker from '../date-picker/locale/cs_CZ';
import type { Locale } from '../locale-provider'; import type { Locale } from '../locale-provider';
import TimePicker from '../time-picker/locale/cs_CZ'; import TimePicker from '../time-picker/locale/cs_CZ';
const typeTemplate = '${label} není platný ${type}';
const localeValues: Locale = { const localeValues: Locale = {
locale: 'cs', locale: 'cs',
Pagination, Pagination,
@ -18,8 +21,12 @@ const localeValues: Locale = {
filterConfirm: 'Potvrdit', filterConfirm: 'Potvrdit',
filterReset: 'Obnovit', filterReset: 'Obnovit',
filterEmptyText: 'Žádné filtry', 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', selectAll: 'Vybrat všechny řádky na současné stránce',
selectInvert: 'Invertovat výběr 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', selectionAll: 'Vybrat všechny řádky',
sortTitle: 'Řadit', sortTitle: 'Řadit',
expand: 'Rozbalit řádek', expand: 'Rozbalit řádek',
@ -30,17 +37,24 @@ const localeValues: Locale = {
}, },
Modal: { Modal: {
okText: 'OK', okText: 'OK',
cancelText: 'Storno', cancelText: 'Zrušit',
justOkText: 'OK', justOkText: 'OK',
}, },
Popconfirm: { Popconfirm: {
okText: 'OK', okText: 'OK',
cancelText: 'Storno', cancelText: 'Zrušit',
}, },
Transfer: { Transfer: {
titles: ['', ''],
searchPlaceholder: 'Vyhledávání', searchPlaceholder: 'Vyhledávání',
itemUnit: 'položka', itemUnit: 'položka',
itemsUnit: 'položek', 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: { Upload: {
uploading: 'Nahrávání...', uploading: 'Nahrávání...',
@ -52,6 +66,71 @@ const localeValues: Locale = {
Empty: { Empty: {
description: 'Žádná data', 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; export default localeValues;

View File

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

View File

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

View File

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

View File

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

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