chore: auto merge branches (#36042)

chore: merge master into feature
This commit is contained in:
github-actions[bot] 2022-06-14 03:41:15 +00:00 committed by GitHub
commit 0b2b27341c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 1607 additions and 2293 deletions

View File

@ -30,7 +30,7 @@ jobs:
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts --legacy-peer-deps
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |

View File

@ -22,7 +22,7 @@ jobs:
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts --legacy-peer-deps
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |

View File

@ -26,7 +26,7 @@ jobs:
key: lock-${{ github.sha }}
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts --legacy-peer-deps
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |

View File

@ -28,7 +28,7 @@ jobs:
node-version: '16'
- name: create package-lock.json
run: npm i --package-lock-only --ignore-scripts --legacy-peer-deps
run: npm i --package-lock-only --ignore-scripts
- name: hack for single file
run: |

View File

@ -37,7 +37,8 @@
"alpha-value-notation": "number",
"color-function-notation": "legacy",
"selector-class-pattern": null,
"selector-id-pattern": null
"selector-id-pattern": null,
"selector-not-notation": null
},
"ignoreFiles": ["components/style/color/{bezierEasing,colorPalette,tinyColor}.less"]
}

View File

@ -15,6 +15,22 @@ timeline: true
---
## 4.21.1
`2022-06-13`
- 🐞 Fixed Image the `getContainer` property not reading from ConfigProvider. [#36002](https://github.com/ant-design/ant-design/pull/36002) [@robothot](https://github.com/robothot)
- 🐞 Fixed Button issue [#35952](https://github.com/ant-design/ant-design/issues/35952) where the `disabled` attribute does not take effect with `href`. [#35975](https://github.com/ant-design/ant-design/pull/35975) [@MuxinFeng](https://github.com/MuxinFeng)
- 🐞 Fix less color palette algorithm according to `@ant-design/colors`. [#35954](https://github.com/ant-design/ant-design/pull/35954) [@christian-lechner](https://github.com/christian-lechner)
- 🐞 Fix Upload image flickering. [#35943](https://github.com/ant-design/ant-design/pull/35943)
- 💄 Remove styles from Form such as `status` for children of Modal and Drawer. [#35849](https://github.com/ant-design/ant-design/pull/35849)
- TypeScript
- 🤖 Fix type definition for `autoFocus` in Dropdown. [#35990](https://github.com/ant-design/ant-design/pull/35990) [@robothot](https://github.com/robothot)
- 🤖 Fix type definition for `MenuItemGroupType` in Menu. [#35790](https://github.com/ant-design/ant-design/pull/35790) [@MasaoBlue](https://github.com/MasaoBlue)
- 🤖 Fix Carousel type definition in React 18. [#35959](https://github.com/ant-design/ant-design/pull/35959)
- 🌐 Localization
- 🇮🇹 Fix italian translation for `Table.cancelSort` key. [#35970](https://github.com/ant-design/ant-design/pull/35970) [@gariggio](https://github.com/gariggio)
## 4.21.0
`2022-06-06`

View File

@ -15,6 +15,22 @@ timeline: true
---
## 4.21.1
`2022-06-13`
- 🐞 修复 Image `getContainer` 属性没有从 ConfigProvider 中读取的问题。[#36002](https://github.com/ant-design/ant-design/pull/36002) [@robothot](https://github.com/robothot)
- 🐞 修复 Button 有 `href``disabled` 属性不生效的问题。[#35952](https://github.com/ant-design/ant-design/issues/35952)。[#35975](https://github.com/ant-design/ant-design/pull/35975) [@MuxinFeng](https://github.com/MuxinFeng)
- 🐞 修复 Upload 组件动画闪烁的问题。[#35943](https://github.com/ant-design/ant-design/pull/35943)
- 🐞 修复 less 色彩算法,使其和 `@ant-design/colors` 保持一致。[#35954](https://github.com/ant-design/ant-design/pull/35954) [@christian-lechner](https://github.com/christian-lechner)
- 💄 Form.Item 中的 Modal 或 Drawer 组件包含的控件去除 `status` 等受 Form 影响的样式。[#35849](https://github.com/ant-design/ant-design/pull/35849)
- TypeScript
- 🤖 修复 Dropdown `autoFocus` 属性定义。[#35990](https://github.com/ant-design/ant-design/pull/35990) [@robothot](https://github.com/robothot)
- 🤖 修复 Menu 中 `MenuItemGroupType` 的类型定义。[#35790](https://github.com/ant-design/ant-design/pull/35790) [@MasaoBlue](https://github.com/MasaoBlue)
- 🤖 修复 Carousel 在 React 18 下的 TS 定义问题。[#35959](https://github.com/ant-design/ant-design/pull/35959)
- 🌐 国际化
- 🇮🇹 修复 `Table.cancelSort` 的意大利语翻译。[#35970](https://github.com/ant-design/ant-design/pull/35970) [@gariggio](https://github.com/gariggio)
## 4.21.0
`2022-06-06`

View File

@ -1323,89 +1323,38 @@ Array [
`;
exports[`renders ./components/alert/demo/loop-banner.md extend context correctly 1`] = `
Array [
<div
class="ant-alert ant-alert-warning ant-alert-banner"
data-show="true"
role="alert"
<div
class="ant-alert ant-alert-warning ant-alert-banner"
data-show="true"
role="alert"
>
<span
aria-label="exclamation-circle"
class="anticon anticon-exclamation-circle ant-alert-icon"
role="img"
>
<span
aria-label="exclamation-circle"
class="anticon anticon-exclamation-circle ant-alert-icon"
role="img"
<svg
aria-hidden="true"
data-icon="exclamation-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
data-icon="exclamation-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
<div
class="x0 x1 x2 x3 "
>
<div
style="transition:width 150ms linear;height:auto;width:auto"
>
<div
class="x1 x4 x5 x6"
style="opacity:1;transform:translateY(0px);position:relative"
>
<div>
Notice message one
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
<div
class="ant-alert ant-alert-warning ant-alert-banner"
data-show="true"
role="alert"
>
<span
aria-label="exclamation-circle"
class="anticon anticon-exclamation-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="exclamation-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</div>
</div>,
]
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
/>
</div>
</div>
`;
exports[`renders ./components/alert/demo/smooth-closed.md extend context correctly 1`] = `

View File

@ -1323,89 +1323,38 @@ Array [
`;
exports[`renders ./components/alert/demo/loop-banner.md correctly 1`] = `
Array [
<div
class="ant-alert ant-alert-warning ant-alert-banner"
data-show="true"
role="alert"
<div
class="ant-alert ant-alert-warning ant-alert-banner"
data-show="true"
role="alert"
>
<span
aria-label="exclamation-circle"
class="anticon anticon-exclamation-circle ant-alert-icon"
role="img"
>
<span
aria-label="exclamation-circle"
class="anticon anticon-exclamation-circle ant-alert-icon"
role="img"
<svg
aria-hidden="true"
data-icon="exclamation-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<svg
aria-hidden="true"
data-icon="exclamation-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
<div
class="x0 x1 x2 x3 "
>
<div
style="transition:width 150ms linear;height:auto;width:auto"
>
<div
class="x1 x4 x5 x6"
style="opacity:1;transform:translateY(0px);position:relative"
>
<div>
Notice message one
</div>
</div>
</div>
</div>
</div>
</div>
</div>,
<div
class="ant-alert ant-alert-warning ant-alert-banner"
data-show="true"
role="alert"
>
<span
aria-label="exclamation-circle"
class="anticon anticon-exclamation-circle ant-alert-icon"
role="img"
>
<svg
aria-hidden="true"
data-icon="exclamation-circle"
fill="currentColor"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
<path
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
/>
</div>
</div>,
]
</svg>
</span>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
/>
</div>
</div>
`;
exports[`renders ./components/alert/demo/smooth-closed.md correctly 1`] = `

View File

@ -17,30 +17,16 @@ Show a loop banner by using with [react-text-loop-next](https://npmjs.com/packag
import { Alert } from 'antd';
import React from 'react';
import Marquee from 'react-fast-marquee';
import { TextLoop } from 'react-text-loop-next';
const App: React.FC = () => (
<>
<Alert
banner
message={
<TextLoop mask>
<div>Notice message one</div>
<div>Notice message two</div>
<div>Notice message three</div>
<div>Notice message four</div>
</TextLoop>
}
/>
<Alert
banner
message={
<Marquee pauseOnHover gradient={false}>
I can be a React component, multiple React components, or just some text.
</Marquee>
}
/>
</>
<Alert
banner
message={
<Marquee pauseOnHover gradient={false}>
I can be a React component, multiple React components, or just some text.
</Marquee>
}
/>
);
export default App;

View File

@ -1,6 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { act } from 'react-dom/test-utils';
import AutoComplete from '..';
import { render } from '../../../tests/utils';
describe('AutoComplete children could be focus', () => {
beforeAll(() => {
@ -23,25 +24,35 @@ describe('AutoComplete children could be focus', () => {
it('focus() and onFocus', () => {
const handleFocus = jest.fn();
const wrapper = mount(<AutoComplete onFocus={handleFocus} />, { attachTo: container });
wrapper.find('input').instance().focus();
jest.runAllTimers();
const { container: wrapper } = render(<AutoComplete onFocus={handleFocus} />, {
attachTo: container,
});
wrapper.querySelector('input').focus();
act(() => {
jest.runAllTimers();
});
expect(handleFocus).toHaveBeenCalled();
});
it('blur() and onBlur', () => {
const handleBlur = jest.fn();
const wrapper = mount(<AutoComplete onBlur={handleBlur} />, { attachTo: container });
wrapper.find('input').instance().focus();
jest.runAllTimers();
wrapper.find('input').instance().blur();
jest.runAllTimers();
const { container: wrapper } = render(<AutoComplete onBlur={handleBlur} />, {
attachTo: container,
});
wrapper.querySelector('input').focus();
act(() => {
jest.runAllTimers();
});
wrapper.querySelector('input').blur();
act(() => {
jest.runAllTimers();
});
expect(handleBlur).toHaveBeenCalled();
});
it('child.ref should work', () => {
const mockRef = jest.fn();
mount(
render(
<AutoComplete dataSource={[]}>
<input ref={mockRef} />
</AutoComplete>,
@ -51,7 +62,7 @@ describe('AutoComplete children could be focus', () => {
it('child.ref instance should support be focused and blured', () => {
let inputRef;
mount(
render(
<AutoComplete dataSource={[]}>
<input
ref={node => {

View File

@ -1,30 +1,30 @@
import React from 'react';
import { mount } from 'enzyme';
import AutoComplete from '..';
import Input from '../../input';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import Input from '../../input';
describe('AutoComplete', () => {
mountTest(AutoComplete);
rtlTest(AutoComplete);
it('AutoComplete with custom Input render perfectly', () => {
const wrapper = mount(
const { container } = render(
<AutoComplete dataSource={['12345', '23456', '34567']}>
<textarea />
</AutoComplete>,
);
expect(wrapper.find('textarea').length).toBe(1);
wrapper.find('textarea').simulate('change', { target: { value: '123' } });
expect(container.querySelectorAll('textarea').length).toBe(1);
fireEvent.change(container.querySelector('textarea'), { target: { value: '123' } });
// should not filter data source defaultly
expect(wrapper.find('.ant-select-item-option').length).toBe(3);
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(3);
});
it('AutoComplete should work when dataSource is object array', () => {
const wrapper = mount(
const { container } = render(
<AutoComplete
dataSource={[
{ text: 'text', value: 'value' },
@ -34,17 +34,17 @@ describe('AutoComplete', () => {
<input />
</AutoComplete>,
);
expect(wrapper.find('input').length).toBe(1);
wrapper.find('input').simulate('change', { target: { value: 'a' } });
expect(container.querySelectorAll('input').length).toBe(1);
fireEvent.change(container.querySelector('input'), { target: { value: 'a' } });
// should not filter data source defaultly
expect(wrapper.find('.ant-select-item-option').length).toBe(2);
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(2);
});
it('AutoComplete throws error when contains invalid dataSource', () => {
const spy = jest.spyOn(console, 'error').mockImplementation(() => undefined);
mount(
render(
<AutoComplete dataSource={[() => {}]}>
<textarea />
</AutoComplete>,
@ -54,25 +54,27 @@ describe('AutoComplete', () => {
});
it('legacy dataSource should accept react element option', () => {
const wrapper = mount(<AutoComplete open dataSource={[<span key="key">ReactNode</span>]} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(
<AutoComplete open dataSource={[<span key="key">ReactNode</span>]} />,
);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('legacy AutoComplete.Option should be compatiable', () => {
const wrapper = mount(
const { container } = render(
<AutoComplete>
<AutoComplete.Option value="111">111</AutoComplete.Option>
<AutoComplete.Option value="222">222</AutoComplete.Option>
</AutoComplete>,
);
expect(wrapper.find('input').length).toBe(1);
wrapper.find('input').simulate('change', { target: { value: '1' } });
expect(wrapper.find('.ant-select-item-option').length).toBe(2);
expect(container.querySelectorAll('input').length).toBe(1);
fireEvent.change(container.querySelector('input'), { target: { value: '1' } });
expect(container.querySelectorAll('.ant-select-item-option').length).toBe(2);
});
it('should not warning when getInputElement is null', () => {
jest.spyOn(console, 'warn').mockImplementation(() => undefined);
mount(<AutoComplete placeholder="input here" allowClear />);
render(<AutoComplete placeholder="input here" allowClear />);
// eslint-disable-next-line no-console
expect(console.warn).not.toBeCalled();
// eslint-disable-next-line no-console
@ -80,11 +82,11 @@ describe('AutoComplete', () => {
});
it('should not override custom input className', () => {
const wrapper = mount(
const { container } = render(
<AutoComplete>
<Input className="custom" />
</AutoComplete>,
);
expect(wrapper.find('input').hasClass('custom')).toBe(true);
expect(container.querySelector('input').classList.contains('custom')).toBeTruthy();
});
});

View File

@ -41,7 +41,7 @@ The differences with Select are:
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| value | Selected option | string | - | |
| onBlur | Called when leaving the component | function() | - | |
| onChange | Called when select an option or input value change, or value of input is changed | function(value) | - | |
| onChange | Called when selecting an option or changing an input value | function(value) | - | |
| onDropdownVisibleChange | Call when dropdown open | function(open) | - | |
| onFocus | Called when entering the component | function() | - | |
| onSearch | Called when searching items | function(value) | - | |

View File

@ -1,6 +1,5 @@
import React from 'react';
import { mount } from 'enzyme';
import { sleep } from '../../../tests/utils';
import { sleep, render, fireEvent } from '../../../tests/utils';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import BackTop from '..';
@ -10,7 +9,7 @@ describe('BackTop', () => {
rtlTest(BackTop);
it('should scroll to top after click it', async () => {
const wrapper = mount(<BackTop visibilityHeight={-1} />);
const { container } = render(<BackTop visibilityHeight={-1} />);
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
window.scrollY = y;
window.pageYOffset = y;
@ -18,7 +17,7 @@ describe('BackTop', () => {
});
window.scrollTo(0, 400);
expect(document.documentElement.scrollTop).toBe(400);
wrapper.find('.ant-back-top').simulate('click');
fireEvent.click(container.querySelector('.ant-back-top'));
await sleep(500);
expect(document.documentElement.scrollTop).toBe(0);
scrollToSpy.mockRestore();
@ -26,24 +25,24 @@ describe('BackTop', () => {
it('support onClick', async () => {
const onClick = jest.fn();
const wrapper = mount(<BackTop onClick={onClick} visibilityHeight={-1} />);
const { container } = render(<BackTop onClick={onClick} visibilityHeight={-1} />);
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((x, y) => {
window.scrollY = y;
window.pageYOffset = y;
});
document.dispatchEvent(new Event('scroll'));
window.scrollTo(0, 400);
wrapper.find('.ant-back-top').simulate('click');
fireEvent.click(container.querySelector('.ant-back-top'));
expect(onClick).toHaveBeenCalled();
scrollToSpy.mockRestore();
});
it('invalid target', async () => {
const onClick = jest.fn();
const wrapper = mount(
const { container } = render(
<BackTop onClick={onClick} visible target={() => ({ documentElement: {} })} />,
);
wrapper.find('.ant-back-top').simulate('click');
fireEvent.click(container.querySelector('.ant-back-top'));
expect(onClick).toHaveBeenCalled();
});
});

View File

@ -328,6 +328,15 @@ describe('Button', () => {
expect(onClick).not.toHaveBeenCalled();
});
it('should match class .ant-btn-disabled when button is disabled and href is not undefined', () => {
const wrapper = render(
<Button href="https://ant.design" disabled>
click me
</Button>,
);
expect(wrapper.container.querySelector('.ant-btn')).toHaveClass('ant-btn-disabled');
});
// https://github.com/ant-design/ant-design/issues/30953
it('should handle fragment as children', () => {
const wrapper = render(

View File

@ -247,6 +247,8 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
const iconType = innerLoading ? 'loading' : icon;
const linkButtonRestProps = omit(rest as AnchorButtonProps & { navigate: any }, ['navigate']);
const classes = classNames(
prefixCls,
{
@ -260,6 +262,7 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
[`${prefixCls}-block`]: block,
[`${prefixCls}-dangerous`]: !!danger,
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-disabled`]: linkButtonRestProps.href !== undefined && mergedDisabled,
},
className,
);
@ -276,7 +279,6 @@ const InternalButton: React.ForwardRefRenderFunction<unknown, ButtonProps> = (pr
? spaceChildren(children, isNeedInserted() && autoInsertSpace)
: null;
const linkButtonRestProps = omit(rest as AnchorButtonProps & { navigate: any }, ['navigate']);
if (linkButtonRestProps.href !== undefined) {
return (
<a {...linkButtonRestProps} className={classes} onClick={handleClick} ref={buttonRef}>

View File

@ -278,6 +278,10 @@ a.@{btn-prefix-cls} {
padding-top: 0.01px !important;
line-height: @btn-height-base - 2px;
&-disabled {
.btn-href-disabled();
}
&-lg {
line-height: @btn-height-lg - 2px;
}

View File

@ -385,6 +385,24 @@
}
.button-disabled(@disabled-color; transparent; transparent);
}
// link button disabled style
.btn-href-disabled() {
cursor: not-allowed;
> * {
pointer-events: none;
}
&,
&:hover,
&:focus,
&:active {
.button-color(@btn-disable-color,transparent, transparent);
text-shadow: none;
box-shadow: none;
}
}
// text button style
.btn-text() {
.button-variant-other(@text-color, transparent, transparent);

View File

@ -1,15 +1,15 @@
import * as React from 'react';
import RcDropdown from 'rc-dropdown';
import classNames from 'classnames';
import RightOutlined from '@ant-design/icons/RightOutlined';
import DropdownButton from './dropdown-button';
import classNames from 'classnames';
import RcDropdown from 'rc-dropdown';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import warning from '../_util/warning';
import { tuple } from '../_util/type';
import { cloneElement } from '../_util/reactNode';
import getPlacements from '../_util/placements';
import OverrideContext from '../menu/OverrideContext';
import type { OverrideContextProps } from '../menu/OverrideContext';
import OverrideContext from '../menu/OverrideContext';
import getPlacements from '../_util/placements';
import { cloneElement } from '../_util/reactNode';
import { tuple } from '../_util/type';
import warning from '../_util/warning';
import DropdownButton from './dropdown-button';
const Placements = tuple(
'topLeft',
@ -44,6 +44,7 @@ export type DropdownArrowOptions = {
};
export interface DropdownProps {
autoFocus?: boolean;
arrow?: boolean | DropdownArrowOptions;
trigger?: ('click' | 'hover' | 'contextMenu')[];
overlay: React.ReactElement | OverlayFunc;

View File

@ -9,7 +9,7 @@ export interface FormListFieldData {
name: number;
key: number;
/** @deprecated No need anymore Use key instead */
fieldKey?:number
fieldKey?: number;
}
export interface FormListOperation {

View File

@ -1,8 +1,9 @@
import React from 'react';
import { render, fireEvent } from '../../../tests/utils';
import Image from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
const src = 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png';
@ -76,4 +77,17 @@ describe('Image', () => {
expect(baseElement.querySelector('.ant-image-preview')).toHaveClass('abc');
expect(baseElement.querySelector('.ant-image-preview-mask')).toHaveClass('def');
});
it('ConfigProvider getPopupContainer', () => {
const { container: wrapper, baseElement } = render(
<>
<div className="container" />
<ConfigProvider getPopupContainer={() => document.querySelector('.container')}>
<Image src={src} />
</ConfigProvider>
</>,
);
fireEvent.click(wrapper.querySelector('.ant-image'));
const containerElement = baseElement.querySelector('.container');
expect(containerElement.children.length).not.toBe(0);
});
});

View File

@ -1,11 +1,11 @@
import * as React from 'react';
import { useContext } from 'react';
import EyeOutlined from '@ant-design/icons/EyeOutlined';
import RcImage, { ImageProps } from 'rc-image';
import defaultLocale from '../locale/en_US';
import PreviewGroup, { icons } from './PreviewGroup';
import * as React from 'react';
import { useContext } from 'react';
import { ConfigContext } from '../config-provider';
import defaultLocale from '../locale/en_US';
import { getTransitionName } from '../_util/motion';
import PreviewGroup, { icons } from './PreviewGroup';
export interface CompositionImage<P> extends React.FC<P> {
PreviewGroup: typeof PreviewGroup;
@ -16,11 +16,15 @@ const Image: CompositionImage<ImageProps> = ({
preview,
...otherProps
}) => {
const { getPrefixCls } = useContext(ConfigContext);
const {
getPrefixCls,
locale: contextLocale = defaultLocale,
getPopupContainer: getContextPopupContainer,
} = useContext(ConfigContext);
const prefixCls = getPrefixCls('image', customizePrefixCls);
const rootPrefixCls = getPrefixCls();
const { locale: contextLocale = defaultLocale } = useContext(ConfigContext);
const imageLocale = contextLocale.Image || defaultLocale.Image;
const mergedPreview = React.useMemo(() => {
@ -28,7 +32,7 @@ const Image: CompositionImage<ImageProps> = ({
return preview;
}
const _preview = typeof preview === 'object' ? preview : {};
const { getContainer, ...restPreviewProps } = _preview;
return {
mask: (
<div className={`${prefixCls}-mask-info`}>
@ -37,7 +41,8 @@ const Image: CompositionImage<ImageProps> = ({
</div>
),
icons,
..._preview,
...restPreviewProps,
getContainer: getContainer || getContextPopupContainer,
transitionName: getTransitionName(rootPrefixCls, 'zoom', _preview.transitionName),
maskTransitionName: getTransitionName(rootPrefixCls, 'fade', _preview.maskTransitionName),
};

View File

@ -23,8 +23,8 @@ const localeValues: Locale = {
selectInvert: 'Inverti selezione nella pagina corrente',
sortTitle: 'Ordina',
triggerDesc: 'Clicca per ordinare in modo discendente',
triggerAsc: 'Clicca per ordinare in modo ascendente',
cancelSort: 'Clicca per eliminare i filtri',
triggerAsc: 'Clicca per ordinare in modo ascendente',
cancelSort: "Clicca per eliminare l'ordinamento",
},
Modal: {
okText: 'OK',

View File

@ -162,6 +162,167 @@ Array [
]
`;
exports[`Menu all types must be available in the "items" syntax 1`] = `
Array [
<ul
class="ant-menu ant-menu-root ant-menu-inline ant-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-submenu-popup"
aria-expanded="true"
aria-haspopup="true"
class="ant-menu-submenu-title"
data-menu-id="rc-menu-uuid-test-submenu"
role="menuitem"
style="padding-left: 24px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
Submenu
</span>
<i
class="ant-menu-submenu-arrow"
/>
</div>
<ul
class="ant-menu ant-menu-sub ant-menu-inline"
data-menu-list="true"
id="rc-menu-uuid-test-submenu-popup"
>
<li
class="ant-menu-item ant-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-submenu-item1"
role="menuitem"
style="padding-left: 48px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
SubmenuItem 1
</span>
</li>
<li
class="ant-menu-item ant-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-submenu-item2"
role="menuitem"
style="padding-left: 48px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
SubmenuItem 2
</span>
</li>
</ul>
</li>
<li
class="ant-menu-item-divider"
/>
<li
class="ant-menu-item-group"
>
<div
class="ant-menu-item-group-title"
title="Group"
>
Group
</div>
<ul
class="ant-menu-item-group-list"
>
<li
class="ant-menu-item ant-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-group-item"
role="menuitem"
style="padding-left: 24px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
GroupItem
</span>
</li>
<li
class="ant-menu-item-divider"
/>
<li
class="ant-menu-submenu ant-menu-submenu-inline ant-menu-submenu-open"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-group-submenu-popup"
aria-expanded="true"
aria-haspopup="true"
class="ant-menu-submenu-title"
data-menu-id="rc-menu-uuid-test-group-submenu"
role="menuitem"
style="padding-left: 24px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
GroupSubmenu
</span>
<i
class="ant-menu-submenu-arrow"
/>
</div>
<ul
class="ant-menu ant-menu-sub ant-menu-inline"
data-menu-list="true"
id="rc-menu-uuid-test-group-submenu-popup"
>
<li
class="ant-menu-item ant-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-group-submenu-item1"
role="menuitem"
style="padding-left: 48px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
GroupSubmenuItem 1
</span>
</li>
<li
class="ant-menu-item ant-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-group-submenu-item2"
role="menuitem"
style="padding-left: 48px;"
tabindex="-1"
>
<span
class="ant-menu-title-content"
>
GroupSubmenuItem 2
</span>
</li>
</ul>
</li>
</ul>
</li>
</ul>,
<div
aria-hidden="true"
style="display: none;"
/>,
]
`;
exports[`Menu rtl render component should be rendered correctly in RTL direction 1`] = `
Array [
<ul

View File

@ -960,4 +960,46 @@ describe('Menu', () => {
expect(wrapper.exists('.bamboo')).toBeTruthy();
});
it('all types must be available in the "items" syntax', () => {
const wrapper = mount(
<Menu
mode="inline"
defaultOpenKeys={['submenu', 'group-submenu']}
items={[
{
key: 'submenu',
label: 'Submenu',
children: [
{ key: 'submenu-item1', label: 'SubmenuItem 1' },
{ key: 'submenu-item2', label: 'SubmenuItem 2' },
],
},
{ key: 'divider', type: 'divider' },
{
key: 'group',
type: 'group',
label: 'Group',
children: [
{
key: 'group-item',
label: 'GroupItem',
},
{ key: 'group-divider', type: 'divider' },
{
key: 'group-submenu',
label: 'GroupSubmenu',
children: [
{ key: 'group-submenu-item1', label: 'GroupSubmenuItem 1' },
{ key: 'group-submenu-item2', label: 'GroupSubmenuItem 2' },
],
},
],
},
]}
/>,
);
expect(wrapper.render()).toMatchSnapshot();
});
});

View File

@ -0,0 +1,40 @@
import * as React from 'react';
import Menu from '..';
describe('Menu.typescript', () => {
it('Menu.items', () => {
const menu = (
<Menu
items={[
{ key: 'item', title: 'Item' },
{
key: 'submenu',
theme: 'light',
children: [
{ key: 'submenu-item', title: 'SubmenuItem' },
{ key: 'submenu-submenu', theme: 'light', children: [] },
{ key: 'submenu-divider', type: 'divider' },
{ key: 'submenu-group', type: 'group' },
null,
],
},
{
key: 'group',
type: 'group',
children: [
{ key: 'group-item', label: 'GroupItem' },
{ key: 'group-submenu', theme: 'light', children: [] },
{ key: 'group-divider', type: 'divider' },
{ key: 'group-group', type: 'group' },
null,
],
},
{ key: 'divider', type: 'divider' },
null,
]}
/>
);
expect(menu).toBeTruthy();
});
});

View File

@ -23,7 +23,7 @@ interface SubMenuType extends Omit<RcSubMenuType, 'children'> {
}
interface MenuItemGroupType extends Omit<RcMenuItemGroupType, 'children'> {
children?: MenuItemType[];
children?: ItemType[];
key?: React.Key;
}

View File

@ -119,7 +119,7 @@ The legacy demo code for version `<4.20.0` could be found at [https://github.com
#### SubMenuType
| Param | Description | Type | Default value | Version |
| --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| children | Sub-menus or sub-menu items | [ItemType\[\]](#ItemType) | - | |
| disabled | Whether sub-menu is disabled | boolean | false | |
| icon | Icon of sub menu | ReactNode | - | |
@ -127,7 +127,6 @@ The legacy demo code for version `<4.20.0` could be found at [https://github.com
| label | Menu label | ReactNode | - | |
| popupClassName | Sub-menu class name, not working when `mode="inline"` | string | - | |
| popupOffset | Sub-menu offset, not working when `mode="inline"` | \[number, number] | - | |
| title | Title of sub menu | ReactNode | - | |
| theme | Color theme of the SubMenu (inherits from Menu by default) | | `light` \| `dark` | - | |
| onTitleClick | Callback executed when the sub-menu title is clicked | function({ key, domEvent }) | - | |

View File

@ -120,7 +120,7 @@ return <Menu items={items} />;
#### SubMenuType
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| children | 子菜单的菜单项 | [ItemType\[\]](#ItemType) | - | |
| disabled | 是否禁用 | boolean | false | |
| icon | 菜单图标 | ReactNode | - | |

View File

@ -22,6 +22,6 @@ Segmented Controls. This component is available since `antd@4.20.0`.
| defaultValue | Default selected value | string \| number | | |
| disabled | Disable all segments | boolean | false | |
| onChange | The callback function that is triggered when the state changes | function(value: string \| number) | | |
| options | Set children optional | string\[] \| number\[] \| Array<{ label: string value: string icon? ReactNode disabled?: boolean className?: string }> | [] | |
| options | Set children optional | string\[] \| number\[] \| Array<{ label: ReactNode value: string icon? ReactNode disabled?: boolean className?: string }> | [] | |
| size | The size of the Segmented. | `large` \| `middle` \| `small` | - | |
| value | Currently selected value | string \| number | | |

View File

@ -25,6 +25,6 @@ cover: https://gw.alipayobjects.com/zos/bmw-prod/a3ff040f-24ba-43e0-92e9-c845df1
| defaultValue | 默认选中的值 | string \| number | | |
| disabled | 是否禁用 | boolean | false | |
| onChange | 选项变化时的回调函数 | function(value: string \| number) | | |
| options | 数据化配置选项内容 | string\[] \| number\[] \| Array<{ label: string value: string icon? ReactNode disabled?: boolean className?: string }> | [] | |
| options | 数据化配置选项内容 | string\[] \| number\[] \| Array<{ label: ReactNode value: string icon? ReactNode disabled?: boolean className?: string }> | [] | |
| size | 控件尺寸 | `large` \| `middle` \| `small` | - | |
| value | 当前选中的值 | string \| number | | |

View File

@ -1,11 +1,10 @@
import React from 'react';
import MockDate from 'mockdate';
import moment from 'moment';
import { mount } from 'enzyme';
import { fireEvent, render } from '@testing-library/react';
import Statistic from '..';
import type Countdown from '../Countdown';
import { formatTimeStr } from '../utils';
import { sleep } from '../../../tests/utils';
import { sleep, render, fireEvent } from '../../../tests/utils';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
@ -23,63 +22,69 @@ describe('Statistic', () => {
});
it('`-` is not a number', () => {
const wrapper = mount(<Statistic value="-" />);
expect(wrapper.find('.ant-statistic-content').text()).toEqual('-');
const { container } = render(<Statistic value="-" />);
expect(container.querySelector('.ant-statistic-content')!.textContent).toEqual('-');
});
it('customize formatter', () => {
const formatter = jest.fn(() => 93);
const wrapper = mount(<Statistic value={1128} formatter={formatter} />);
const { container } = render(<Statistic value={1128} formatter={formatter} />);
expect(formatter).toHaveBeenCalledWith(1128);
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual('93');
expect(container.querySelector('.ant-statistic-content-value')!.textContent).toEqual('93');
});
it('groupSeparator', () => {
const wrapper = mount(<Statistic value={1128} groupSeparator="__TEST__" />);
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual('1__TEST__128');
const { container } = render(<Statistic value={1128} groupSeparator="__TEST__" />);
expect(container.querySelector('.ant-statistic-content-value')!.textContent).toEqual(
'1__TEST__128',
);
});
it('not a number', () => {
const wrapper = mount(<Statistic value="bamboo" />);
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual('bamboo');
const { container } = render(<Statistic value="bamboo" />);
expect(container.querySelector('.ant-statistic-content-value')!.textContent).toEqual('bamboo');
});
it('support negetive number', () => {
const wrapper = mount(
const { asFragment } = render(
<Statistic title="Account Balance (CNY)" value={-112893.12345} precision={2} />,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('allow negetive precision', () => {
[
[-1, -1112893.1212, '-1,112,893'],
[-2, -1112893.1212, '-1,112,893'],
[-3, -1112893.1212, '-1,112,893'],
[-1, -1112893, '-1,112,893'],
[-1, 1112893, '1,112,893'],
].forEach(([precision, value, expectValue]) => {
const wrapper = mount(<Statistic precision={precision} value={value} />);
expect(wrapper.find('.ant-statistic-content-value-int').text()).toEqual(expectValue);
expect(wrapper.find('.ant-statistic-content-value-decimal').length).toBe(0);
})
[-3, -1112893.1212, '-1,112,893'],
[-1, -1112893, '-1,112,893'],
[-1, 1112893, '1,112,893'],
].forEach(([precision, value, expectValue]: [number, number, string]) => {
const { container } = render(<Statistic precision={precision} value={value} />);
expect(container.querySelector('.ant-statistic-content-value-int')!.textContent).toEqual(
expectValue,
);
expect(container.querySelectorAll('.ant-statistic-content-value-decimal').length).toBe(0);
});
});
it('loading with skeleton', async () => {
let loading = false;
const wrapper = mount(<Statistic title="Active Users" value={112112} loading={loading} />);
expect(wrapper.find('.ant-skeleton')).toHaveLength(0);
expect(wrapper.find('.ant-statistic-content')).toHaveLength(1);
const { container, rerender } = render(
<Statistic title="Active Users" value={112112} loading={loading} />,
);
expect(container.querySelectorAll('.ant-skeleton')).toHaveLength(0);
expect(container.querySelectorAll('.ant-statistic-content')).toHaveLength(1);
loading = true;
wrapper.setProps({ loading });
expect(wrapper.find('.ant-skeleton')).toHaveLength(1);
expect(wrapper.find('.ant-statistic-content')).toHaveLength(0);
rerender(<Statistic title="Active Users" value={112112} loading={loading} />);
expect(container.querySelectorAll('.ant-skeleton')).toHaveLength(1);
expect(container.querySelectorAll('.ant-statistic-content')).toHaveLength(0);
});
describe('Countdown', () => {
it('render correctly', () => {
const now = moment().add(2, 'd').add(11, 'h').add(28, 'm').add(9, 's').add(3, 'ms');
const now = moment().add(2, 'd').add(11, 'h').add(28, 'm').add(9, 's').add(3, 'ms').valueOf();
[
['H:m:s', '59:28:9'],
@ -87,25 +92,31 @@ describe('Statistic', () => {
['HH:mm:ss:SSS', '59:28:09:003'],
['DD-HH:mm:ss', '02-11:28:09'],
].forEach(([format, value]) => {
const wrapper = mount(<Statistic.Countdown format={format} value={now} />);
expect(wrapper.find('.ant-statistic-content-value').text()).toEqual(value);
const { container } = render(<Statistic.Countdown format={format} value={now} />);
expect(container.querySelector('.ant-statistic-content-value')!.textContent).toEqual(value);
});
});
it('time going', async () => {
const now = Date.now() + 1000;
const onFinish = jest.fn();
const wrapper = mount(<Statistic.Countdown value={now} onFinish={onFinish} />);
wrapper.update();
let instance: Countdown | null;
const { unmount } = render(
<Statistic.Countdown
ref={n => {
instance = n;
}}
value={now}
onFinish={onFinish}
/>,
);
// setInterval should work
const instance = wrapper.find('Countdown').instance();
expect(instance.countdownId).not.toBe(undefined);
expect(instance!.countdownId).not.toBe(undefined);
await sleep(10);
wrapper.unmount();
expect(instance.countdownId).toBe(undefined);
unmount();
expect(onFinish).not.toHaveBeenCalled();
});
@ -115,21 +126,21 @@ describe('Statistic', () => {
const { container } = render(
<Statistic onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />,
);
fireEvent.mouseEnter(container.firstChild);
fireEvent.mouseEnter(container.firstChild!);
expect(onMouseEnter).toHaveBeenCalled();
fireEvent.mouseLeave(container.firstChild);
fireEvent.mouseLeave(container.firstChild!);
expect(onMouseLeave).toHaveBeenCalled();
});
it('responses hover events for Countdown', () => {
const onMouseEnter = jest.fn();
const onMouseLeave = jest.fn();
const wrapper = mount(
const { container } = render(
<Statistic.Countdown onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} />,
);
wrapper.simulate('mouseenter');
fireEvent.mouseEnter(container.firstChild!);
expect(onMouseEnter).toHaveBeenCalled();
wrapper.simulate('mouseleave');
fireEvent.mouseLeave(container.firstChild!);
expect(onMouseLeave).toHaveBeenCalled();
});
@ -138,11 +149,11 @@ describe('Statistic', () => {
const deadline = Date.now() + 10 * 1000;
let remainingTime;
const onChange = value => {
const onChange = (value: number) => {
remainingTime = value;
};
const wrapper = mount(<Statistic.Countdown value={deadline} onChange={onChange} />);
wrapper.update();
render(<Statistic.Countdown value={deadline} onChange={onChange} />);
// container.update();
await sleep(100);
expect(remainingTime).toBeGreaterThan(0);
});
@ -151,20 +162,26 @@ describe('Statistic', () => {
describe('time finished', () => {
it('not call if time already passed', () => {
const now = Date.now() - 1000;
let instance: Countdown | null;
const onFinish = jest.fn();
const wrapper = mount(<Statistic.Countdown value={now} onFinish={onFinish} />);
wrapper.update();
render(
<Statistic.Countdown
ref={n => {
instance = n;
}}
value={now}
onFinish={onFinish}
/>,
);
expect(wrapper.find('Countdown').instance().countdownId).toBe(undefined);
expect(instance!.countdownId).toBe(undefined);
expect(onFinish).not.toHaveBeenCalled();
});
it('called if finished', async () => {
const now = Date.now() + 10;
const onFinish = jest.fn();
const wrapper = mount(<Statistic.Countdown value={now} onFinish={onFinish} />);
wrapper.update();
render(<Statistic.Countdown value={now} onFinish={onFinish} />);
MockDate.set(moment('2019-11-28 00:00:00').valueOf());
await sleep(100);
expect(onFinish).toHaveBeenCalled();

View File

@ -31,6 +31,10 @@
return Math.round(hue);
};
var getSaturation = function(hsv, i, isLight) {
// grey color don't change saturation
if (hsv.h === 0 && hsv.s === 0) {
return hsv.s;
}
var saturation;
if (isLight) {
saturation = hsv.s - saturationStep * i;

File diff suppressed because it is too large Load Diff

View File

@ -976,7 +976,60 @@ exports[`Table.rowSelection fix selection column on the left when any other colu
</div>
`;
exports[`Table.rowSelection render with default selection correctly 1`] = `<div />`;
exports[`Table.rowSelection render with default selection correctly 1`] = `
<div
class="ant-dropdown ant-slide-up-appear ant-slide-up-appear-prepare ant-slide-up"
style="opacity: 0;"
>
<ul
class="ant-dropdown-menu ant-dropdown-menu-root ant-dropdown-menu-vertical ant-dropdown-menu-light"
data-menu-list="true"
role="menu"
tabindex="0"
>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-all"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
Select all data
</span>
</li>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-invert"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
Invert current page
</span>
</li>
<li
class="ant-dropdown-menu-item ant-dropdown-menu-item-only-child"
data-menu-id="rc-menu-uuid-test-none"
role="menuitem"
tabindex="-1"
>
<span
class="ant-dropdown-menu-title-content"
>
Clear all data
</span>
</li>
</ul>
<div
aria-hidden="true"
style="display: none;"
/>
</div>
`;
exports[`Table.rowSelection should support getPopupContainer 1`] = `
<div

View File

@ -1,9 +1,7 @@
import * as React from 'react';
import classNames from 'classnames';
import type RcTree from 'rc-tree';
import debounce from 'lodash/debounce';
import { conductExpandParent } from 'rc-tree/lib/util';
import omit from 'rc-util/lib/omit';
import type { EventDataNode, DataNode, Key } from 'rc-tree/lib/interface';
import { convertDataToEntities, convertTreeToData } from 'rc-tree/lib/utils/treeUtil';
import FileOutlined from '@ant-design/icons/FileOutlined';
@ -87,21 +85,6 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
}
}, [props.expandedKeys]);
const expandFolderNode = (event: React.MouseEvent<HTMLElement>, node: any) => {
const { isLeaf } = node;
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
return;
}
// Call internal rc-tree expand function
// https://github.com/ant-design/ant-design/issues/12567
treeRef.current!.onNodeExpand(event as any, node);
};
const onDebounceExpand = debounce(expandFolderNode, 200, {
leading: true,
});
const onExpand = (
keys: Key[],
info: {
@ -117,28 +100,6 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
return props.onExpand?.(keys, info);
};
const onClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode<any>) => {
const { expandAction } = props;
// Expand the tree
if (expandAction === 'click') {
onDebounceExpand(event, node);
}
props.onClick?.(event, node);
};
const onDoubleClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode<any>) => {
const { expandAction } = props;
// Expand the tree
if (expandAction === 'doubleClick') {
onDebounceExpand(event, node);
}
props.onDoubleClick?.(event, node);
};
const onSelect = (
keys: Key[],
event: {
@ -219,14 +180,12 @@ const DirectoryTree: React.ForwardRefRenderFunction<RcTree, DirectoryTreeProps>
icon={getIcon}
ref={treeRef}
blockNode
{...omit(otherProps, ['expandAction'])}
{...otherProps}
prefixCls={prefixCls}
className={connectClassName}
expandedKeys={expandedKeys}
selectedKeys={selectedKeys}
onSelect={onSelect}
onClick={onClick}
onDoubleClick={onDoubleClick}
onExpand={onExpand}
/>
);

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
import React from 'react';
import { mount, render } from 'enzyme';
import debounce from 'lodash/debounce';
import Tree from '../index';
import React from 'react';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { act, fireEvent, render } from '../../../tests/utils';
import Tree from '../index';
const { DirectoryTree, TreeNode } = Tree;
@ -44,23 +44,45 @@ describe('Directory Tree', () => {
describe('expand', () => {
it('click', () => {
const wrapper = mount(createTree());
const onExpand = jest.fn();
const { container } = render(createTree({ onExpand }));
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
jest.runAllTimers();
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-tree-node-content-wrapper'));
act(() => {
jest.runAllTimers();
});
expect(onExpand).toHaveBeenCalledWith(['0-0'], expect.anything());
onExpand.mockReset();
act(() => {
jest.runAllTimers();
});
fireEvent.click(container.querySelector('.ant-tree-node-content-wrapper'));
act(() => {
jest.runAllTimers();
});
expect(onExpand).toHaveBeenCalledWith([], expect.anything());
});
it('double click', () => {
const wrapper = mount(createTree({ expandAction: 'doubleClick' }));
const onExpand = jest.fn();
const { container } = render(createTree({ expandAction: 'doubleClick', onExpand }));
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('doubleClick');
expect(wrapper.render()).toMatchSnapshot();
jest.runAllTimers();
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('doubleClick');
expect(wrapper.render()).toMatchSnapshot();
fireEvent.doubleClick(container.querySelector('.ant-tree-node-content-wrapper'));
act(() => {
jest.runAllTimers();
});
expect(onExpand).toHaveBeenCalledWith(['0-0'], expect.anything());
onExpand.mockReset();
act(() => {
jest.runAllTimers();
});
fireEvent.doubleClick(container.querySelector('.ant-tree-node-content-wrapper'));
act(() => {
jest.runAllTimers();
});
expect(onExpand).toHaveBeenCalledWith([], expect.anything());
});
describe('with state control', () => {
@ -86,21 +108,26 @@ describe('Directory Tree', () => {
}
}
['click', 'doubleClick'].forEach(action => {
it(action, () => {
const wrapper = mount(<StateDirTree expandAction={action} />);
it('click', () => {
const { container, asFragment } = render(<StateDirTree expandAction="click" />);
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate(action);
jest.runAllTimers();
expect(wrapper.render()).toMatchSnapshot();
});
fireEvent.click(container.querySelector('.ant-tree-node-content-wrapper'));
jest.runAllTimers();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('doubleClick', () => {
const { container, asFragment } = render(<StateDirTree expandAction="doubleClick" />);
fireEvent.doubleClick(container.querySelector('.ant-tree-node-content-wrapper'));
jest.runAllTimers();
expect(asFragment().firstChild).toMatchSnapshot();
});
});
});
it('defaultExpandAll', () => {
const wrapper = render(createTree({ defaultExpandAll: true }));
expect(wrapper).toMatchSnapshot();
const { asFragment } = render(createTree({ defaultExpandAll: true }));
expect(asFragment().firstChild).toMatchSnapshot();
});
it('DirectoryTree should expend all when use treeData and defaultExpandAll is true', () => {
@ -123,90 +150,78 @@ describe('Directory Tree', () => {
],
},
];
const wrapper = render(createTree({ defaultExpandAll: true, treeData }));
expect(wrapper).toMatchSnapshot();
const { asFragment } = render(createTree({ defaultExpandAll: true, treeData }));
expect(asFragment().firstChild).toMatchSnapshot();
});
it('defaultExpandParent', () => {
const wrapper = render(createTree({ defaultExpandParent: true }));
expect(wrapper).toMatchSnapshot();
const { asFragment } = render(createTree({ defaultExpandParent: true }));
expect(asFragment().firstChild).toMatchSnapshot();
});
it('expandedKeys update', () => {
const wrapper = mount(createTree());
wrapper.setProps({ expandedKeys: ['0-1'] });
expect(wrapper.render()).toMatchSnapshot();
const { rerender, asFragment } = render(createTree());
rerender(createTree({ expandedKeys: ['0-1'] }));
jest.runAllTimers();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('selectedKeys update', () => {
const wrapper = mount(createTree({ defaultExpandAll: true }));
wrapper.setProps({ selectedKeys: ['0-1-0'] });
expect(wrapper.render()).toMatchSnapshot();
const { rerender, asFragment } = render(createTree({ defaultExpandAll: true }));
rerender(createTree({ selectedKeys: ['0-1-0'] }));
expect(asFragment().firstChild).toMatchSnapshot();
});
it('group select', () => {
let nativeEventProto = null;
const onSelect = jest.fn();
const wrapper = mount(
const { container, asFragment } = render(
createTree({
defaultExpandAll: true,
expandAction: 'doubleClick',
multiple: true,
onClick: e => {
nativeEventProto = Object.getPrototypeOf(e.nativeEvent);
},
onSelect,
}),
);
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[0]);
expect(onSelect.mock.calls[0][1].selected).toBeTruthy();
expect(onSelect.mock.calls[0][1].selectedNodes.length).toBe(1);
// Click twice should keep selected
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('click');
fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[0]);
expect(onSelect.mock.calls[1][1].selected).toBeTruthy();
expect(onSelect.mock.calls[0][0]).toEqual(onSelect.mock.calls[1][0]);
expect(onSelect.mock.calls[1][1].selectedNodes.length).toBe(1);
// React not simulate full of NativeEvent. Hook it.
// Ref: https://github.com/facebook/react/blob/master/packages/react-dom/src/test-utils/ReactTestUtils.js#L360
nativeEventProto.ctrlKey = true;
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(1).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[1], {
ctrlKey: true,
});
expect(asFragment().firstChild).toMatchSnapshot();
expect(onSelect.mock.calls[2][0].length).toBe(2);
expect(onSelect.mock.calls[2][1].selected).toBeTruthy();
expect(onSelect.mock.calls[2][1].selectedNodes.length).toBe(2);
delete nativeEventProto.ctrlKey;
nativeEventProto.shiftKey = true;
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(4).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
fireEvent.click(container.querySelectorAll('.ant-tree-node-content-wrapper')[4], {
shiftKey: true,
});
expect(asFragment().firstChild).toMatchSnapshot();
expect(onSelect.mock.calls[3][0].length).toBe(5);
expect(onSelect.mock.calls[3][1].selected).toBeTruthy();
expect(onSelect.mock.calls[3][1].selectedNodes.length).toBe(5);
delete nativeEventProto.shiftKey;
});
it('onDoubleClick', () => {
const onDoubleClick = jest.fn();
const wrapper = mount(createTree({ onDoubleClick }));
wrapper.find(TreeNode).find('.ant-tree-node-content-wrapper').at(0).simulate('doubleclick');
const { container } = render(createTree({ onDoubleClick }));
fireEvent.doubleClick(container.querySelector('.ant-tree-node-content-wrapper'));
expect(onDoubleClick).toBeCalled();
});
it('should not expand tree now when pressing ctrl', () => {
const onExpand = jest.fn();
const onSelect = jest.fn();
const wrapper = mount(createTree({ onExpand, onSelect }));
wrapper
.find(TreeNode)
.find('.ant-tree-node-content-wrapper')
.at(0)
.simulate('click', { ctrlKey: true });
const { container } = render(createTree({ onExpand, onSelect }));
fireEvent.click(container.querySelector('.ant-tree-node-content-wrapper'), { ctrlKey: true });
expect(onExpand).not.toHaveBeenCalled();
expect(onSelect).toHaveBeenCalledWith(
['0-0'],
@ -217,7 +232,7 @@ describe('Directory Tree', () => {
it('should not expand tree now when click leaf node', () => {
const onExpand = jest.fn();
const onSelect = jest.fn();
const wrapper = mount(
const { container } = render(
createTree({
onExpand,
onSelect,
@ -243,7 +258,8 @@ describe('Directory Tree', () => {
],
}),
);
wrapper.find(TreeNode).last().find('.ant-tree-node-content-wrapper').at(0).simulate('click');
const nodeList = container.querySelectorAll('.ant-tree-node-content-wrapper');
fireEvent.click(nodeList[nodeList.length - 1]);
expect(onExpand).not.toHaveBeenCalled();
expect(onSelect).toHaveBeenCalledWith(
['0-0-2'],
@ -253,7 +269,7 @@ describe('Directory Tree', () => {
it('ref support', () => {
const treeRef = React.createRef();
mount(createTree({ ref: treeRef }));
render(createTree({ ref: treeRef }));
expect('scrollTo' in treeRef.current).toBeTruthy();
});

View File

@ -1,4 +1,4 @@
import { mount } from 'enzyme';
import { render } from '../../../tests/utils';
import dropIndicatorRender, { offset } from '../utils/dropIndicator';
describe('dropIndicatorRender', () => {
@ -10,8 +10,8 @@ describe('dropIndicatorRender', () => {
prefixCls: 'ant',
direction: 'ltr',
});
const wrapper = mount(indicator);
expect(wrapper.find('div').props().style!.bottom).toEqual(-3);
const { container } = render(indicator);
expect(container.querySelector('div')?.style.bottom).toEqual('-3px');
});
it('work with dropPosition inner (-0)', () => {
const indicator = dropIndicatorRender({
@ -21,9 +21,9 @@ describe('dropIndicatorRender', () => {
prefixCls: 'ant',
direction: 'ltr',
});
const wrapper = mount(indicator);
expect(wrapper.find('div').props().style!.bottom).toEqual(-3);
expect(wrapper.find('div').props().style!.left).toEqual(24 + offset);
const { container } = render(indicator);
expect(container.querySelector('div')?.style.bottom).toEqual('-3px');
expect(container.querySelector('div')?.style.left).toEqual(`${24 + offset}px`);
});
it('work with dropPosition after (-1)', () => {
const indicator = dropIndicatorRender({
@ -33,8 +33,8 @@ describe('dropIndicatorRender', () => {
prefixCls: 'ant',
direction: 'ltr',
});
const wrapper = mount(indicator);
expect(wrapper.find('div').props().style!.top).toEqual(-3);
const { container } = render(indicator);
expect(container.querySelector('div')?.style.top).toEqual('-3px');
});
it('work with drop level', () => {
const indicator = dropIndicatorRender({
@ -44,8 +44,8 @@ describe('dropIndicatorRender', () => {
prefixCls: 'ant',
direction: 'ltr',
});
const wrapper = mount(indicator);
expect(wrapper.find('div').props().style!.left).toEqual(-2 * 24 + offset);
const { container } = render(indicator);
expect(container.querySelector('div')?.style.left).toEqual(`${-2 * 24 + offset}px`);
});
it('work with drop level (rtl)', () => {
const indicator = dropIndicatorRender({
@ -55,7 +55,7 @@ describe('dropIndicatorRender', () => {
prefixCls: 'ant',
direction: 'rtl',
});
const wrapper = mount(indicator);
expect(wrapper.find('div').props().style!.right).toEqual(-2 * 24 + offset);
const { container } = render(indicator);
expect(container.querySelector('div')?.style.right).toEqual(`${-2 * 24 + offset}px`);
});
});

View File

@ -1,12 +1,12 @@
import React from 'react';
import { mount } from 'enzyme';
import { render } from '../../../tests/utils';
import Tree from '../index';
const { TreeNode } = Tree;
describe('Tree', () => {
it('icon and switcherIcon of Tree with showLine should render correctly', () => {
const wrapper = mount(
const { asFragment } = render(
<Tree showLine showIcon>
<TreeNode icon="icon" switcherIcon="switcherIcon" key="0-0">
<TreeNode icon="icon" switcherIcon="switcherIcon" key="0-0-0" />
@ -28,11 +28,11 @@ describe('Tree', () => {
</TreeNode>
</Tree>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('switcherIcon in Tree should not render at leaf nodes', () => {
const wrapper = mount(
const { container } = render(
<Tree switcherIcon={<i className="switcherIcon" />} defaultExpandAll>
<TreeNode icon="icon">
<TreeNode id="node1" title="node1" icon="icon" key="0-0-2" />
@ -40,11 +40,11 @@ describe('Tree', () => {
</TreeNode>
</Tree>,
);
expect(wrapper.find('.switcherIcon').length).toBe(1);
expect(container.querySelectorAll('.switcherIcon').length).toBe(1);
});
it('switcherIcon in Tree could be string', () => {
const wrapper = mount(
const { asFragment } = render(
<Tree switcherIcon="switcherIcon" defaultExpandAll>
<TreeNode icon="icon">
<TreeNode id="node1" title="node1" icon="icon" key="0-0-2" />
@ -52,7 +52,7 @@ describe('Tree', () => {
</TreeNode>
</Tree>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('switcherIcon should be loading icon when loadData', () => {
@ -62,7 +62,7 @@ describe('Tree', () => {
resolve();
}, 1000);
});
const wrapper = mount(
const { asFragment } = render(
<Tree switcherIcon="switcherIcon" defaultExpandAll loadData={onLoadData}>
<TreeNode icon="icon">
<TreeNode id="node1" title="node1" icon="icon" key="0-0-2" />
@ -70,11 +70,11 @@ describe('Tree', () => {
</TreeNode>
</Tree>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('switcherIcon in Tree could be render prop function', () => {
const wrapper = mount(
const { container } = render(
<Tree
switcherIcon={expanded =>
expanded ? <span className="open" /> : <span className="close" />
@ -87,12 +87,12 @@ describe('Tree', () => {
</TreeNode>
</Tree>,
);
expect(wrapper.find('.open').length).toBe(1);
expect(container.querySelectorAll('.open').length).toBe(1);
});
// https://github.com/ant-design/ant-design/issues/23261
it('showLine is object type should render correctly', () => {
const wrapper = mount(
const { asFragment } = render(
<Tree showLine={{ showLeafIcon: false }} defaultExpandedKeys={['0-0-0']}>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0">
@ -110,7 +110,7 @@ describe('Tree', () => {
</TreeNode>
</Tree>,
);
expect(wrapper.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
});
describe('draggable', () => {
@ -122,26 +122,26 @@ describe('Tree', () => {
];
it('hide icon', () => {
const wrapper = mount(<Tree treeData={dragTreeData} draggable={{ icon: false }} />);
expect(wrapper.exists('.anticon-holder')).toBeFalsy();
const { container } = render(<Tree treeData={dragTreeData} draggable={{ icon: false }} />);
expect(container.querySelector('.anticon-holder')).toBeFalsy();
});
it('customize icon', () => {
const wrapper = mount(
const { container } = render(
<Tree treeData={dragTreeData} draggable={{ icon: <span className="little" /> }} />,
);
expect(wrapper.exists('.little')).toBeTruthy();
expect(container.querySelector('.little')).toBeTruthy();
});
it('nodeDraggable', () => {
const nodeDraggable = jest.fn(() => false);
mount(<Tree treeData={dragTreeData} draggable={{ nodeDraggable }} />);
render(<Tree treeData={dragTreeData} draggable={{ nodeDraggable }} />);
expect(nodeDraggable).toHaveBeenCalledWith(dragTreeData[0]);
});
it('nodeDraggable func', () => {
const nodeDraggable = jest.fn(() => false);
mount(<Tree treeData={dragTreeData} draggable={nodeDraggable} />);
render(<Tree treeData={dragTreeData} draggable={nodeDraggable} />);
expect(nodeDraggable).toHaveBeenCalledWith(dragTreeData[0]);
});
});

View File

@ -1,11 +1,11 @@
import * as React from 'react';
import { mount } from 'enzyme';
import type { BasicDataNode } from 'rc-tree';
import * as React from 'react';
import { render } from '../../../tests/utils';
import Tree from '../index';
describe('Tree.TypeScript', () => {
it('without generic', () => {
const wrapper = mount(
const { container } = render(
<Tree
treeData={[
{
@ -22,7 +22,7 @@ describe('Tree.TypeScript', () => {
/>,
);
expect(wrapper).toBeTruthy();
expect(container).toBeTruthy();
});
it('support generic', () => {
@ -31,7 +31,7 @@ describe('Tree.TypeScript', () => {
list?: MyDataNode[];
}
const wrapper = mount(
const { container } = render(
<Tree<MyDataNode>
treeData={[
{
@ -46,6 +46,6 @@ describe('Tree.TypeScript', () => {
/>,
);
expect(wrapper).toBeTruthy();
expect(container).toBeTruthy();
});
});

View File

@ -1,7 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { SmileOutlined, LikeOutlined } from '@ant-design/icons';
import { LikeOutlined, SmileOutlined } from '@ant-design/icons';
import * as copyObj from 'copy-to-clipboard';
import { fireEvent, render, waitFor } from '../../../tests/utils';
import Base from '../Base';
@ -33,57 +33,69 @@ describe('Typography copy', () => {
}) {
it(name, async () => {
jest.useFakeTimers();
const wrapper = mount(
const { container: wrapper, unmount } = render(
<Base component="p" copyable={{ icon, tooltips }}>
test copy
</Base>,
);
if (iconClassNames[0] !== undefined) {
expect(wrapper.exists(iconClassNames[0])).toBeTruthy();
expect(wrapper.querySelector(iconClassNames[0])).not.toBeNull();
}
if (iconTexts[0] !== undefined) {
expect(wrapper.find('.ant-typography-copy').at(0).text()).toBe(iconTexts[0]);
expect(wrapper.querySelectorAll('.ant-typography-copy')[0].textContent).toBe(
iconTexts[0],
);
}
wrapper.find('.ant-typography-copy').first().simulate('mouseenter');
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-copy')[0]);
jest.runAllTimers();
wrapper.update();
if (tooltipTexts[0] !== undefined) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltipTexts[0]);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner')?.textContent).toBe(
tooltipTexts[0],
);
});
}
if (tooltipLength !== undefined) {
expect(wrapper.find('.ant-tooltip-inner').length).toBe(tooltipLength);
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(tooltipLength);
});
}
wrapper.find('.ant-typography-copy').first().simulate('click');
fireEvent.click(wrapper.querySelectorAll('.ant-typography-copy')[0]);
jest.useRealTimers();
if (iconClassNames[1] !== undefined) {
expect(wrapper.exists(iconClassNames[1])).toBeTruthy();
expect(wrapper.querySelector(iconClassNames[1])).not.toBeNull();
}
wrapper.find('.ant-typography-copy').first().simulate('mouseenter');
wrapper.update();
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-copy')[0]);
wrapper.find('.ant-typography-copy').first().simulate('mouseenter');
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-copy')[0]);
if (tooltipTexts[1] !== undefined) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltipTexts[1]);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner')?.textContent).toBe(
tooltipTexts[1],
);
});
}
if (iconTexts[1] !== undefined) {
expect(wrapper.find('.ant-typography-copy').at(0).text()).toBe(iconTexts[1]);
expect(wrapper.querySelectorAll('.ant-typography-copy')[0].textContent).toBe(
iconTexts[1],
);
}
jest.useFakeTimers();
wrapper.find('.ant-typography-copy').first().simulate('click');
fireEvent.click(wrapper.querySelectorAll('.ant-typography-copy')[0]);
jest.runAllTimers();
wrapper.update();
wrapper.unmount();
unmount();
jest.useRealTimers();
});
}
const dom = (
<>
<span>1</span>2
@ -197,14 +209,14 @@ describe('Typography copy', () => {
it('copy click event stopPropagation', () => {
const onDivClick = jest.fn();
const wrapper = mount(
const { container: wrapper } = render(
<div onClick={onDivClick}>
<Base component="p" copyable>
test copy
</Base>
</div>,
);
wrapper.find('.ant-typography-copy').first().simulate('click');
fireEvent.click(wrapper.querySelectorAll('.ant-typography-copy')[0]);
expect(onDivClick).not.toBeCalled();
});
@ -212,12 +224,13 @@ describe('Typography copy', () => {
function onCopy(e: React.MouseEvent<HTMLDivElement>) {
expect(e).not.toBeUndefined();
}
const wrapper = mount(
const { container: wrapper } = render(
<Base component="p" copyable={{ onCopy }}>
test copy
</Base>,
);
wrapper.find('.ant-typography-copy').first().simulate('click');
fireEvent.click(wrapper.querySelectorAll('.ant-typography-copy')[0]);
});
it('copy to clipboard', done => {
@ -237,13 +250,13 @@ describe('Typography copy', () => {
</Base>
);
};
const wrapper = mount(<Test />);
const copyBtn = wrapper.find('.ant-typography-copy').first();
copyBtn.simulate('click');
const { container: wrapper } = render(<Test />);
const copyBtn = wrapper.querySelectorAll('.ant-typography-copy')[0];
fireEvent.click(copyBtn);
expect(spy.mock.calls[0][0]).toEqual(originText);
setTimeout(() => {
spy.mockReset();
copyBtn.simulate('click');
fireEvent.click(copyBtn);
expect(spy.mock.calls[0][0]).toEqual(nextText);
done();
}, 500);

View File

@ -1,9 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import Base from '../Base';
import Typography from '../Typography';
import { sleep } from '../../../tests/utils';
import { fireEvent, render, sleep, triggerResize, waitFor } from '../../../tests/utils';
// eslint-disable-next-line no-unused-vars
import * as styleChecker from '../../_util/styleChecker';
@ -53,47 +51,59 @@ describe('Typography.Ellipsis', () => {
'Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light Bamboo is Little Light';
it('should trigger update', async () => {
const ref = React.createRef();
const onEllipsis = jest.fn();
const wrapper = mount(
<Base ellipsis={{ onEllipsis }} component="p" editable>
const {
container: wrapper,
rerender,
unmount,
} = render(
<Base ellipsis={{ onEllipsis }} component="p" editable ref={ref}>
{fullStr}
</Base>,
);
// First resize
wrapper.triggerResize();
triggerResize(ref.current);
await sleep(20);
wrapper.update();
expect(wrapper.text()).toEqual('Bamboo is Little ...');
expect(wrapper.firstChild.textContent).toEqual('Bamboo is Little ...');
expect(onEllipsis).toHaveBeenCalledWith(true);
onEllipsis.mockReset();
// Second resize
wrapper.setProps({ ellipsis: { rows: 2, onEllipsis } });
await sleep(20);
wrapper.update();
expect(wrapper.text()).toEqual('Bamboo is Little Light Bamboo is Litt...');
rerender(
<Base ellipsis={{ rows: 2, onEllipsis }} component="p" editable>
{fullStr}
</Base>,
);
expect(wrapper.textContent).toEqual('Bamboo is Little Light Bamboo is Litt...');
expect(onEllipsis).not.toHaveBeenCalled();
// Third resize
wrapper.setProps({ ellipsis: { rows: 99, onEllipsis } });
await sleep(20);
wrapper.update();
expect(wrapper.find('p').text()).toEqual(fullStr);
rerender(
<Base ellipsis={{ rows: 99, onEllipsis }} component="p" editable>
{fullStr}
</Base>,
);
expect(wrapper.querySelector('p').textContent).toEqual(fullStr);
expect(onEllipsis).toHaveBeenCalledWith(false);
wrapper.unmount();
unmount();
});
it('support css multiple lines', async () => {
const wrapper = mount(
const { container: wrapper } = render(
<Base ellipsis={{ rows: 2 }} component="p">
{fullStr}
</Base>,
);
expect(wrapper.exists('.ant-typography-ellipsis-multiple-line')).toBeTruthy();
expect(wrapper.find(Typography).prop('style').WebkitLineClamp).toEqual(2);
expect(
wrapper.querySelectorAll('.ant-typography-ellipsis-multiple-line').length,
).toBeGreaterThan(0);
expect(
wrapper.querySelector('.ant-typography-ellipsis-multiple-line').style.WebkitLineClamp,
).toEqual('2');
});
it('string with parentheses', async () => {
@ -105,71 +115,87 @@ describe('Typography.Ellipsis', () => {
design language for background applications, is refined by Ant UED Team.
Ant Design, a design language for background applications, is refined by
Ant UED Team.`;
const ref = React.createRef();
const onEllipsis = jest.fn();
const wrapper = mount(
<Base ellipsis={{ onEllipsis }} component="p" editable>
const { container: wrapper, unmount } = render(
<Base ellipsis={{ onEllipsis }} component="p" editable ref={ref}>
{parenthesesStr}
</Base>,
);
wrapper.triggerResize();
triggerResize(ref.current);
await sleep(20);
wrapper.update();
expect(wrapper.text()).toEqual('Ant Design, a des...');
const ellipsisSpan = wrapper.find('span[aria-hidden]').last();
expect(ellipsisSpan.text()).toEqual('...');
expect(wrapper.firstChild.textContent).toEqual('Ant Design, a des...');
const ellipsisSpans = wrapper.querySelectorAll('span[aria-hidden]');
expect(ellipsisSpans[ellipsisSpans.length - 1].textContent).toEqual('...');
onEllipsis.mockReset();
wrapper.unmount();
unmount();
});
it('should middle ellipsis', async () => {
const suffix = '--suffix';
const wrapper = mount(
<Base ellipsis={{ rows: 1, suffix }} component="p">
const ref = React.createRef();
const { container: wrapper, unmount } = render(
<Base ellipsis={{ rows: 1, suffix }} component="p" ref={ref}>
{fullStr}
</Base>,
);
wrapper.triggerResize();
triggerResize(ref.current);
await sleep(20);
wrapper.update();
expect(wrapper.find('p').text()).toEqual('Bamboo is...--suffix');
wrapper.unmount();
expect(wrapper.querySelector('p').textContent).toEqual('Bamboo is...--suffix');
unmount();
});
it('should front or middle ellipsis', async () => {
const suffix = '--The information is very important';
const wrapper = mount(
<Base ellipsis={{ rows: 1, suffix }} component="p">
const ref = React.createRef();
const {
container: wrapper,
rerender,
unmount,
} = render(
<Base ellipsis={{ rows: 1, suffix }} component="p" ref={ref}>
{fullStr}
</Base>,
);
wrapper.triggerResize();
triggerResize(ref.current);
await sleep(20);
wrapper.update();
expect(wrapper.find('p').text()).toEqual('...--The information is very important');
wrapper.setProps({ ellipsis: { rows: 2, suffix } });
await sleep(20);
wrapper.update();
expect(wrapper.find('p').text()).toEqual('Ba...--The information is very important');
expect(wrapper.querySelector('p').textContent).toEqual(
'...--The information is very important',
);
wrapper.setProps({ ellipsis: { rows: 99, suffix } });
await sleep(20);
wrapper.update();
expect(wrapper.find('p').text()).toEqual(fullStr + suffix);
rerender(
<Base ellipsis={{ rows: 2, suffix }} component="p">
{fullStr}
</Base>,
);
expect(wrapper.querySelector('p').textContent).toEqual(
'Ba...--The information is very important',
);
wrapper.unmount();
rerender(
<Base ellipsis={{ rows: 99, suffix }} component="p">
{fullStr}
</Base>,
);
expect(wrapper.querySelector('p').textContent).toEqual(fullStr + suffix);
unmount();
});
it('connect children', async () => {
const bamboo = 'Bamboo';
const is = ' is ';
const wrapper = mount(
<Base ellipsis component="p" editable>
const ref = React.createRef();
const { container: wrapper } = render(
<Base ellipsis component="p" editable ref={ref}>
{bamboo}
{is}
<code>Little</code>
@ -177,54 +203,49 @@ describe('Typography.Ellipsis', () => {
</Base>,
);
wrapper.triggerResize();
triggerResize(ref.current);
await sleep(20);
wrapper.update();
expect(wrapper.text()).toEqual('Bamboo is Little...');
expect(wrapper.textContent).toEqual('Bamboo is Little...');
});
it('should expandable work', async () => {
const onExpand = jest.fn();
const wrapper = mount(
const { container: wrapper } = render(
<Base ellipsis={{ expandable: true, onExpand }} component="p" copyable editable>
{fullStr}
</Base>,
);
await sleep(20);
wrapper.update();
wrapper.find('.ant-typography-expand').simulate('click');
fireEvent.click(wrapper.querySelector('.ant-typography-expand'));
expect(onExpand).toHaveBeenCalled();
await sleep(20);
wrapper.update();
expect(wrapper.find('p').text()).toEqual(fullStr);
expect(wrapper.querySelector('p').textContent).toEqual(fullStr);
});
it('should have custom expand style', async () => {
const symbol = 'more';
const wrapper = mount(
const { container: wrapper } = render(
<Base ellipsis={{ expandable: true, symbol }} component="p">
{fullStr}
</Base>,
);
await sleep(20);
wrapper.update();
expect(wrapper.find('.ant-typography-expand').text()).toEqual('more');
expect(wrapper.querySelector('.ant-typography-expand').textContent).toEqual('more');
});
it('can use css ellipsis', () => {
const wrapper = mount(<Base ellipsis component="p" />);
expect(wrapper.find('.ant-typography-ellipsis-single-line').length).toBeTruthy();
const { container: wrapper } = render(<Base ellipsis component="p" />);
expect(wrapper.querySelectorAll('.ant-typography-ellipsis-single-line').length).toBeGreaterThan(
0,
);
});
it('should calculate padding', () => {
const wrapper = mount(
const { container: wrapper } = render(
<Base ellipsis component="p" style={{ paddingTop: '12px', paddingBottom: '12px' }} />,
);
expect(wrapper.find('.ant-typography-ellipsis-single-line').length).toBeTruthy();
expect(wrapper.querySelectorAll('.ant-typography-ellipsis-single-line').length).toBeGreaterThan(
0,
);
});
describe('should tooltip support', () => {
@ -245,40 +266,52 @@ describe('Typography.Ellipsis', () => {
domSpy.mockRestore();
});
function getWrapper(tooltip) {
return mount(
<Base ellipsis={{ tooltip }} component="p">
async function getWrapper(tooltip) {
const ref = React.createRef();
const wrapper = render(
<Base ellipsis={{ tooltip }} component="p" ref={ref}>
{fullStr}
</Base>,
);
triggerResize(ref.current);
await sleep(20);
return wrapper;
}
it('boolean', async () => {
const wrapper = getWrapper(true);
await sleep(20);
wrapper.update();
expect(wrapper.find('Tooltip').prop('title')).toEqual(fullStr);
const { container, baseElement } = await getWrapper(true);
fireEvent.mouseEnter(container.firstChild);
await waitFor(() => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
it('customize', async () => {
const wrapper = getWrapper('Bamboo is Light');
await sleep(20);
wrapper.update();
expect(wrapper.find('Tooltip').prop('title')).toEqual('Bamboo is Light');
const { container, baseElement } = await getWrapper('Bamboo is Light');
fireEvent.mouseEnter(container.firstChild);
await waitFor(() => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
});
});
it('js ellipsis should show aria-label', () => {
const titleWrapper = mount(<Base title="bamboo" ellipsis={{ expandable: true }} />);
expect(titleWrapper.find('.ant-typography').prop('aria-label')).toEqual('bamboo');
const { container: titleWrapper } = render(
<Base title="bamboo" ellipsis={{ expandable: true }} />,
);
expect(titleWrapper.querySelector('.ant-typography').getAttribute('aria-label')).toEqual(
'bamboo',
);
const tooltipWrapper = mount(<Base ellipsis={{ expandable: true, tooltip: 'little' }} />);
expect(tooltipWrapper.find('.ant-typography').prop('aria-label')).toEqual('little');
const { container: tooltipWrapper } = render(
<Base ellipsis={{ expandable: true, tooltip: 'little' }} />,
);
expect(tooltipWrapper.querySelector('.ant-typography').getAttribute('aria-label')).toEqual(
'little',
);
});
it('should display tooltip if line clamp', () => {
it('should display tooltip if line clamp', async () => {
mockRectSpy = spyElementPrototypes(HTMLElement, {
scrollHeight: {
get() {
@ -299,12 +332,19 @@ describe('Typography.Ellipsis', () => {
},
});
const wrapper = mount(
<Base ellipsis={{ tooltip: 'This is tooltip', rows: 2 }}>
const ref = React.createRef();
const { container: wrapper, baseElement } = render(
<Base ellipsis={{ tooltip: 'This is tooltip', rows: 2 }} ref={ref}>
Ant Design, a design language for background applications, is refined by Ant UED Team.
</Base>,
);
expect(wrapper.find('EllipsisTooltip').prop('isEllipsis')).toBeTruthy();
triggerResize(ref.current);
await sleep(20);
fireEvent.mouseEnter(wrapper.firstChild);
await waitFor(() => {
expect(baseElement.querySelector('.ant-tooltip-open')).not.toBeNull();
});
mockRectSpy.mockRestore();
});
});

View File

@ -1,13 +1,13 @@
import React from 'react';
import { mount } from 'enzyme';
import KeyCode from 'rc-util/lib/KeyCode';
import Paragraph from '../Paragraph';
import { fireEvent, render } from '../../../tests/utils';
test('Callback on enter key is triggered', () => {
const onEditStart = jest.fn();
const onCopy = jest.fn();
const wrapper = mount(
const { container: wrapper } = render(
<Paragraph
copyable={{
onCopy,
@ -23,8 +23,8 @@ test('Callback on enter key is triggered', () => {
jest.spyOn(window, 'setTimeout').mockReturnValue(timer);
jest.spyOn(window, 'clearTimeout');
// must copy first, because editing button will hide copy button
wrapper.find('.ant-typography-copy').at(0).simulate('keyup', { keyCode: KeyCode.ENTER });
wrapper.find('.anticon-edit').at(0).simulate('keyup', { keyCode: KeyCode.ENTER });
fireEvent.keyUp(wrapper.querySelectorAll('.ant-typography-copy')[0], { keyCode: KeyCode.ENTER });
fireEvent.keyUp(wrapper.querySelectorAll('.anticon-edit')[0], { keyCode: KeyCode.ENTER });
expect(onEditStart.mock.calls.length).toBe(1);
expect(onCopy.mock.calls.length).toBe(1);

View File

@ -1,9 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import { SmileOutlined, LikeOutlined, HighlightOutlined, CheckOutlined } from '@ant-design/icons';
import { CheckOutlined, HighlightOutlined, LikeOutlined, SmileOutlined } from '@ant-design/icons';
import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning';
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
import copy from 'copy-to-clipboard';
import Title from '../Title';
import Link from '../Link';
@ -13,7 +11,7 @@ import Base from '../Base';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import Typography from '../Typography';
import { sleep, render } from '../../../tests/utils';
import { fireEvent, render, sleep, waitFor } from '../../../tests/utils';
jest.mock('copy-to-clipboard');
@ -79,7 +77,7 @@ describe('Typography', () => {
describe('Title', () => {
it('warning if `level` not correct', () => {
mount(<Title level={false} />);
render(<Title level={false} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Typography.Title] Title only accept `1 | 2 | 3 | 4 | 5` as `level` value. And `5` need 4.6.0+ version.',
@ -93,47 +91,57 @@ describe('Typography', () => {
it(name, async () => {
jest.useFakeTimers();
const onCopy = jest.fn();
const wrapper = mount(
const { container: wrapper, unmount } = render(
<Base component="p" copyable={{ text, onCopy, icon, tooltips, format }}>
test copy
</Base>,
);
if (icon) {
expect(wrapper.find('.anticon-smile').length).toBeTruthy();
expect(wrapper.querySelectorAll('.anticon-smile').length).toBeGreaterThan(0);
} else {
expect(wrapper.find('.anticon-copy').length).toBeTruthy();
expect(wrapper.querySelectorAll('.anticon-copy').length).toBeGreaterThan(0);
}
wrapper.find('.ant-typography-copy').first().simulate('mouseenter');
fireEvent.mouseEnter(wrapper.querySelector('.ant-typography-copy'));
jest.runAllTimers();
wrapper.update();
if (tooltips === undefined || tooltips === true) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe('Copy');
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe('Copy');
});
} else if (tooltips === false) {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(0);
});
} else if (tooltips[0] === '' && tooltips[1] === '') {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(0);
});
} else if (tooltips[0] === '' && tooltips[1]) {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(0);
});
} else if (tooltips[1] === '' && tooltips[0]) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltips[0]);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe(tooltips[0]);
});
} else {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltips[0]);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe(tooltips[0]);
});
}
wrapper.find('.ant-typography-copy').first().simulate('click');
fireEvent.click(wrapper.querySelector('.ant-typography-copy'));
jest.useRealTimers();
wrapper.find('.ant-typography-copy').first().simulate('mouseenter');
// tooltips 为 ['', 'xxx'] 时,切换时需要延时 mousenEnterDelay 的时长
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-copy')[0]);
// tooltips 为 ['', 'xxx'] 时,切换时需要延时 mouseEnterDelay 的时长
if (tooltips && tooltips[0] === '' && tooltips[1]) {
await sleep(150);
}
expect(copy.lastStr).toEqual(target);
expect(copy.lastOptions.format).toEqual(format);
wrapper.update();
expect(onCopy).toHaveBeenCalled();
let copiedIcon = '.anticon-check';
@ -143,31 +151,43 @@ describe('Typography', () => {
copiedIcon = '.anticon-check';
}
expect(wrapper.find(copiedIcon).length).toBeTruthy();
wrapper.find('.ant-typography-copy').first().simulate('mouseenter');
expect(wrapper.querySelectorAll(copiedIcon).length).toBeGreaterThan(0);
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-copy')[0]);
if (tooltips === undefined || tooltips === true) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe('Copied');
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe('Copied');
});
} else if (tooltips === false) {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(0);
});
} else if (tooltips[0] === '' && tooltips[1] === '') {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(0);
});
} else if (tooltips[0] === '' && tooltips[1]) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltips[1]);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe(tooltips[1]);
});
} else if (tooltips[1] === '' && tooltips[0]) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe('');
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe('');
});
} else {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltips[1]);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe(tooltips[1]);
});
}
jest.useFakeTimers();
wrapper.find('.ant-typography-copy').first().simulate('click');
fireEvent.click(wrapper.querySelectorAll('.ant-typography-copy')[0]);
jest.runAllTimers();
wrapper.update();
// Will set back when 3 seconds pass
expect(wrapper.find(copiedIcon).length).toBeFalsy();
wrapper.unmount();
await sleep(3000);
expect(wrapper.querySelectorAll(copiedIcon).length).toBe(0);
unmount();
jest.useRealTimers();
});
}
@ -221,15 +241,15 @@ describe('Typography', () => {
submitFunc,
expectFunc,
) {
it(name, () => {
it(name, async () => {
jest.useFakeTimers();
const onStart = jest.fn();
const onChange = jest.fn();
const className = 'test';
const style = {};
const style = { padding: 'unset' };
const wrapper = mount(
const { container: wrapper } = render(
<Paragraph
editable={{ onChange, onStart, icon, tooltip, triggerType, enterIcon }}
className={className}
@ -241,64 +261,71 @@ describe('Typography', () => {
if (triggerType === undefined || triggerType.indexOf('icon') !== -1) {
if (icon) {
expect(wrapper.find('.anticon-highlight').length).toBeTruthy();
expect(wrapper.querySelectorAll('.anticon-highlight').length).toBeGreaterThan(0);
} else {
expect(wrapper.find('.anticon-edit').length).toBeTruthy();
expect(wrapper.querySelectorAll('.anticon-edit').length).toBeGreaterThan(0);
}
if (triggerType === undefined || triggerType.indexOf('text') === -1) {
wrapper.simulate('click');
fireEvent.click(wrapper.firstChild);
expect(onStart).not.toHaveBeenCalled();
}
wrapper.find('.ant-typography-edit').first().simulate('mouseenter');
fireEvent.mouseEnter(wrapper.querySelectorAll('.ant-typography-edit')[0]);
jest.runAllTimers();
wrapper.update();
if (tooltip === undefined || tooltip === true) {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe('Edit');
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe('Edit');
});
} else if (tooltip === false) {
expect(wrapper.find('.ant-tooltip-inner').length).toBeFalsy();
await waitFor(() => {
expect(wrapper.querySelectorAll('.ant-tooltip-inner').length).toBe(0);
});
} else {
expect(wrapper.find('.ant-tooltip-inner').text()).toBe(tooltip);
await waitFor(() => {
expect(wrapper.querySelector('.ant-tooltip-inner').textContent).toBe(tooltip);
});
}
wrapper.find('.ant-typography-edit').first().simulate('click');
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
expect(onStart).toHaveBeenCalled();
if (triggerType !== undefined && triggerType.indexOf('text') !== -1) {
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ESC });
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ESC });
fireEvent.keyDown(wrapper.querySelector('textarea'), { keyCode: KeyCode.ESC });
fireEvent.keyUp(wrapper.querySelector('textarea'), { keyCode: KeyCode.ESC });
expect(onChange).not.toHaveBeenCalled();
}
}
if (triggerType !== undefined && triggerType.indexOf('text') !== -1) {
if (triggerType.indexOf('icon') === -1) {
expect(wrapper.find('.anticon-highlight').length).toBeFalsy();
expect(wrapper.find('.anticon-edit').length).toBeFalsy();
expect(wrapper.querySelectorAll('.anticon-highlight').length).toBe(0);
expect(wrapper.querySelectorAll('.anticon-edit').length).toBe(0);
}
wrapper.simulate('click');
fireEvent.click(wrapper.firstChild);
expect(onStart).toHaveBeenCalled();
}
// Should have className
const props = wrapper.find('div').first().props();
expect(props.style).toEqual(style);
const props = wrapper.querySelectorAll('div')[0];
expect(props.getAttribute('style')).toContain('padding: unset');
expect(props.className.includes(className)).toBeTruthy();
wrapper.find('textarea').simulate('change', {
fireEvent.change(wrapper.querySelector('textarea'), {
target: { value: 'Bamboo' },
});
if (enterIcon === undefined) {
expect(
wrapper.find('span.ant-typography-edit-content-confirm').first().props().className,
wrapper.querySelectorAll('span.ant-typography-edit-content-confirm')[0].className,
).toContain('anticon-enter');
} else if (enterIcon === null) {
expect(wrapper.find('span.ant-typography-edit-content-confirm').length).toBe(0);
expect(
wrapper.querySelectorAll('span.ant-typography-edit-content-confirm').length,
).toBe(0);
} else {
expect(
wrapper.find('span.ant-typography-edit-content-confirm').first().props().className,
wrapper.querySelectorAll('span.ant-typography-edit-content-confirm')[0].className,
).not.toContain('anticon-enter');
}
@ -319,21 +346,21 @@ describe('Typography', () => {
testStep({ name: 'by key up' }, wrapper => {
// Not trigger when inComposition
wrapper.find('textarea').simulate('compositionStart');
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ENTER });
wrapper.find('textarea').simulate('compositionEnd');
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ENTER });
fireEvent.compositionStart(wrapper.querySelector('textarea'));
fireEvent.keyDown(wrapper.querySelector('textarea'), { keyCode: KeyCode.ENTER });
fireEvent.compositionEnd(wrapper.querySelector('textarea'));
fireEvent.keyUp(wrapper.querySelector('textarea'), { keyCode: KeyCode.ENTER });
// Now trigger
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ENTER });
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ENTER });
fireEvent.keyDown(wrapper.querySelector('textarea'), { keyCode: KeyCode.ENTER });
fireEvent.keyUp(wrapper.querySelector('textarea'), { keyCode: KeyCode.ENTER });
});
testStep(
{ name: 'by esc key' },
wrapper => {
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ESC });
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ESC });
fireEvent.keyDown(wrapper.querySelector('textarea'), { keyCode: KeyCode.ESC });
fireEvent.keyUp(wrapper.querySelector('textarea'), { keyCode: KeyCode.ESC });
},
onChange => {
// eslint-disable-next-line jest/no-standalone-expect
@ -342,7 +369,7 @@ describe('Typography', () => {
);
testStep({ name: 'by blur' }, wrapper => {
wrapper.find('textarea').simulate('blur');
fireEvent.blur(wrapper.querySelector('textarea'));
});
testStep({ name: 'customize edit icon', icon: <HighlightOutlined /> });
@ -359,47 +386,51 @@ describe('Typography', () => {
it('should trigger onEnd when type Enter', () => {
const onEnd = jest.fn();
const wrapper = mount(<Paragraph editable={{ onEnd }}>Bamboo</Paragraph>);
wrapper.find('.ant-typography-edit').first().simulate('click');
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ENTER });
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ENTER });
const { container: wrapper } = render(<Paragraph editable={{ onEnd }}>Bamboo</Paragraph>);
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
fireEvent.keyDown(wrapper.querySelector('textarea'), { keyCode: KeyCode.ENTER });
fireEvent.keyUp(wrapper.querySelector('textarea'), { keyCode: KeyCode.ENTER });
expect(onEnd).toHaveBeenCalledTimes(1);
});
it('should trigger onCancel when type ESC', () => {
const onCancel = jest.fn();
const wrapper = mount(<Paragraph editable={{ onCancel }}>Bamboo</Paragraph>);
wrapper.find('.ant-typography-edit').first().simulate('click');
wrapper.find('textarea').simulate('keyDown', { keyCode: KeyCode.ESC });
wrapper.find('textarea').simulate('keyUp', { keyCode: KeyCode.ESC });
const { container: wrapper } = render(
<Paragraph editable={{ onCancel }}>Bamboo</Paragraph>,
);
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
fireEvent.keyDown(wrapper.querySelector('textarea'), { keyCode: KeyCode.ESC });
fireEvent.keyUp(wrapper.querySelector('textarea'), { keyCode: KeyCode.ESC });
expect(onCancel).toHaveBeenCalledTimes(1);
});
it('should only trigger focus on the first time', () => {
let triggerTimes = 0;
const mockFocus = spyElementPrototype(HTMLElement, 'focus', () => {
const { container: wrapper } = render(<Paragraph editable>Bamboo</Paragraph>);
const editIcon = wrapper.querySelectorAll('.ant-typography-edit')[0];
editIcon.addEventListener('focus', () => {
triggerTimes += 1;
});
const wrapper = mount(<Paragraph editable>Bamboo</Paragraph>);
wrapper.find('.ant-typography-edit').first().simulate('click');
fireEvent.focus(editIcon);
expect(triggerTimes).toEqual(1);
wrapper.find('textarea').simulate('change', {
fireEvent.click(editIcon);
expect(triggerTimes).toEqual(1);
fireEvent.change(wrapper.querySelector('textarea'), {
target: { value: 'good' },
});
expect(triggerTimes).toEqual(1);
mockFocus.mockRestore();
});
});
it('should focus at the end of textarea', () => {
const wrapper = mount(<Paragraph editable>content</Paragraph>);
wrapper.find('.ant-typography-edit').first().simulate('click');
const textareaNode = wrapper.find('textarea').getDOMNode();
const { container: wrapper } = render(<Paragraph editable>content</Paragraph>);
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
const textareaNode = wrapper.querySelector('textarea');
expect(textareaNode.selectionStart).toBe(7);
expect(textareaNode.selectionEnd).toBe(7);
});
@ -407,7 +438,7 @@ describe('Typography', () => {
it('warning if use setContentRef', () => {
const refFunc = () => {};
mount(<Typography setContentRef={refFunc} />);
render(<Typography setContentRef={refFunc} />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Typography] `setContentRef` is deprecated. Please use `ref` instead.',
);
@ -423,21 +454,21 @@ describe('Typography', () => {
it('should get HTMLHeadingElement ref from Title', () => {
const ref = React.createRef();
mount(<Title level={1} ref={ref} />);
render(<Title level={1} ref={ref} />);
expect(ref.current instanceof HTMLHeadingElement).toBe(true);
});
it('should get HTMLDivElement ref from Paragraph', () => {
const ref = React.createRef();
mount(<Paragraph ref={ref} />);
render(<Paragraph ref={ref} />);
expect(ref.current instanceof HTMLDivElement).toBe(true);
});
it('should get HTMLSpanElement ref from Text', () => {
const ref = React.createRef();
mount(<Text ref={ref} />);
render(<Text ref={ref} />);
expect(ref.current instanceof HTMLSpanElement).toBe(true);
});
});

View File

@ -527,7 +527,7 @@
.@{upload-prefix-cls}-animate-inline-enter,
.@{upload-prefix-cls}-animate-inline-leave {
animation-duration: @animation-duration-slow;
animation-fill-mode: @ease-in-out-circ;
animation-timing-function: @ease-in-out-circ;
}
.@{upload-prefix-cls}-animate-inline-appear,

View File

@ -1,6 +1,6 @@
{
"name": "antd",
"version": "4.21.0",
"version": "4.21.1",
"description": "An enterprise-class UI design language and React components implementation",
"title": "Ant Design",
"keywords": [
@ -115,7 +115,7 @@
"dependencies": {
"@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.7.0",
"@ant-design/react-slick": "~0.28.1",
"@ant-design/react-slick": "~0.29.1",
"@babel/runtime": "^7.12.5",
"@ctrl/tinycolor": "^3.4.0",
"classnames": "^2.2.6",
@ -126,11 +126,11 @@
"rc-cascader": "~3.6.0",
"rc-checkbox": "~2.3.0",
"rc-collapse": "~3.3.0",
"rc-dialog": "~8.8.2",
"rc-dialog": "~8.9.0",
"rc-drawer": "~4.4.2",
"rc-dropdown": "~4.0.0",
"rc-field-form": "~1.26.1",
"rc-image": "~5.6.0",
"rc-image": "~5.7.0",
"rc-input": "~0.0.1-alpha.5",
"rc-input-number": "~7.3.0",
"rc-mentions": "~1.8.0",
@ -151,7 +151,7 @@
"rc-tabs": "~11.16.0",
"rc-textarea": "~0.3.0",
"rc-tooltip": "~5.1.1",
"rc-tree": "~5.6.4",
"rc-tree": "~5.6.5",
"rc-tree-select": "~5.4.0",
"rc-trigger": "^5.2.10",
"rc-upload": "~4.3.0",
@ -159,9 +159,9 @@
"scroll-into-view-if-needed": "^2.2.25"
},
"devDependencies": {
"@ant-design/bisheng-plugin": "^3.2.0",
"@ant-design/bisheng-plugin": "^3.3.0-alpha.4",
"@ant-design/hitu": "^0.0.0-alpha.13",
"@ant-design/tools": "^15.0.2",
"@ant-design/tools": "^15.0.3",
"@docsearch/css": "^3.0.0",
"@qixian.cs/github-contributors-list": "^1.0.3",
"@stackblitz/sdk": "^1.3.0",
@ -191,7 +191,7 @@
"antd-img-crop": "^4.0.0",
"array-move": "^4.0.0",
"babel-plugin-add-react-displayname": "^0.0.5",
"bisheng": "^3.5.0",
"bisheng": "^3.7.0-alpha.4",
"bisheng-plugin-description": "^0.1.4",
"bisheng-plugin-react": "^1.2.0",
"bisheng-plugin-toc": "^0.4.4",
@ -272,7 +272,6 @@
"react-router-dom": "^6.0.2",
"react-sortable-hoc": "^2.0.0",
"react-sticky": "^6.0.3",
"react-text-loop-next": "0.0.3",
"react-window": "^1.8.5",
"remark": "^14.0.1",
"remark-cli": "^10.0.0",
@ -283,10 +282,10 @@
"scrollama": "^3.0.0",
"semver": "^7.3.5",
"simple-git": "^3.0.0",
"stylelint": "14.8.3",
"stylelint": "^14.9.0",
"stylelint-config-prettier": "^9.0.2",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^25.0.0",
"stylelint-config-standard": "^26.0.0",
"stylelint-declaration-block-no-ignored-properties": "^2.1.0",
"stylelint-order": "^5.0.0",
"theme-switcher": "^1.0.2",

View File

@ -10,7 +10,9 @@
"moduleResolution": "node",
"esModuleInterop": true,
"experimentalDecorators": true,
"jsx": "preserve",
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"noUnusedParameters": true,
"noUnusedLocals": true,
"noImplicitAny": true,