mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
chore: merge master
This commit is contained in:
commit
1cad665d15
@ -30,7 +30,7 @@ timeline: true
|
||||
- 🛠 Table changes `filterDropdownVisible` to `filterDropdownOpen`. [#37026](https://github.com/ant-design/ant-design/pull/37026) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Slider add `tooltip` prop for all props related with Tooltip. [#37000](https://github.com/ant-design/ant-design/pull/37000) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Tooltip Popover and Popconfirm change `visible` to `open`. [#37241](https://github.com/ant-design/ant-design/pull/37241) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Remove `visible` prop of Tag. [#36934](https://github.com/ant-design/ant-design/pull/36934) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Deprecate `visible` prop of Tag. [#36934](https://github.com/ant-design/ant-design/pull/36934) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Deprecate `dropdownClassName` prop of all components and change to `popupClassName`. [#36880](https://github.com/ant-design/ant-design/pull/36880) [@heiyu4585](https://github.com/heiyu4585)
|
||||
- 🛠 Tabs support `items` props and origin jsx usage will be depreacted. [#36889](https://github.com/ant-design/ant-design/pull/36889)
|
||||
- 🐞 Fix that some css variables are not consistent with less variables.
|
||||
|
@ -30,7 +30,7 @@ timeline: true
|
||||
- 🛠 Table 组件 `columns` 中的 `filterDropdownVisible` 改为 `filterDropdownOpen`。[#37026](https://github.com/ant-design/ant-design/pull/37026) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Tooltip, Popover 和 Popconfirm 中的 `visible` 改为 `open`。[#37241](https://github.com/ant-design/ant-design/pull/37241) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 Slider 的 `tooltip` 相关属性合并到 `tooltip` 属性中。[#37000](https://github.com/ant-design/ant-design/pull/37000) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 移除 Tag 组件的 `visible` 属性。[#36934](https://github.com/ant-design/ant-design/pull/36934) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 废弃 Tag 组件的 `visible` 属性。[#36934](https://github.com/ant-design/ant-design/pull/36934) [@yykoypj](https://github.com/yykoypj)
|
||||
- 🛠 废弃所有组件的 `dropdownClassName`,统一为 `popupClassName`。[#36880](https://github.com/ant-design/ant-design/pull/36880) [@heiyu4585](https://github.com/heiyu4585)
|
||||
- 🛠 Tabs 支持 `items` 属性,并且废弃原 jsx 语法糖用法。[#36889](https://github.com/ant-design/ant-design/pull/36889)
|
||||
- 🐞 修复 css 变量与 less 变量不一致的问题。
|
||||
|
@ -141,7 +141,7 @@ describe('Affix Render', () => {
|
||||
describe('updatePosition when target changed', () => {
|
||||
it('function change', async () => {
|
||||
document.body.innerHTML = '<div id="mounter" />';
|
||||
const container = document.querySelector('#id') as HTMLDivElement;
|
||||
const container = document.getElementById('mounter');
|
||||
const getTarget = () => container;
|
||||
let affixInstance: InternalAffixClass;
|
||||
const { rerender } = render(
|
||||
|
@ -25,6 +25,33 @@ export interface BackTopProps {
|
||||
visible?: boolean; // Only for test. Don't use it.
|
||||
}
|
||||
|
||||
interface ChildrenProps {
|
||||
prefixCls: string;
|
||||
rootPrefixCls: string;
|
||||
children?: React.ReactNode;
|
||||
visible?: boolean; // Only for test. Don't use it.
|
||||
}
|
||||
|
||||
const BackTopContent: React.FC<ChildrenProps> = props => {
|
||||
const { prefixCls, rootPrefixCls, children, visible } = props;
|
||||
const defaultElement = (
|
||||
<div className={`${prefixCls}-content`}>
|
||||
<div className={`${prefixCls}-icon`}>
|
||||
<VerticalAlignTopOutlined />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<CSSMotion visible={visible} motionName={`${rootPrefixCls}-fade`}>
|
||||
{({ className: motionClassName }) =>
|
||||
cloneElement(children || defaultElement, ({ className }) => ({
|
||||
className: classNames(motionClassName, className),
|
||||
}))
|
||||
}
|
||||
</CSSMotion>
|
||||
);
|
||||
};
|
||||
|
||||
const BackTop: React.FC<BackTopProps> = props => {
|
||||
const [visible, setVisible] = useMergedState(false, {
|
||||
value: props.visible,
|
||||
@ -51,9 +78,7 @@ const BackTop: React.FC<BackTopProps> = props => {
|
||||
scrollEvent.current = addEventListener(container, 'scroll', (e: React.UIEvent<HTMLElement>) => {
|
||||
handleScroll(e);
|
||||
});
|
||||
handleScroll({
|
||||
target: container,
|
||||
});
|
||||
handleScroll({ target: container });
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -62,7 +87,7 @@ const BackTop: React.FC<BackTopProps> = props => {
|
||||
if (scrollEvent.current) {
|
||||
scrollEvent.current.remove();
|
||||
}
|
||||
(handleScroll as any).cancel();
|
||||
handleScroll.cancel();
|
||||
};
|
||||
}, [props.target]);
|
||||
|
||||
@ -77,32 +102,6 @@ const BackTop: React.FC<BackTopProps> = props => {
|
||||
}
|
||||
};
|
||||
|
||||
const renderChildren = ({
|
||||
prefixCls,
|
||||
rootPrefixCls,
|
||||
}: {
|
||||
prefixCls: string;
|
||||
rootPrefixCls: string;
|
||||
}) => {
|
||||
const { children } = props;
|
||||
const defaultElement = (
|
||||
<div className={`${prefixCls}-content`}>
|
||||
<div className={`${prefixCls}-icon`}>
|
||||
<VerticalAlignTopOutlined />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<CSSMotion visible={visible} motionName={`${rootPrefixCls}-fade`}>
|
||||
{({ className: motionClassName }) =>
|
||||
cloneElement(children || defaultElement, ({ className }) => ({
|
||||
className: classNames(motionClassName, className),
|
||||
}))
|
||||
}
|
||||
</CSSMotion>
|
||||
);
|
||||
};
|
||||
|
||||
const { getPrefixCls, direction } = React.useContext(ConfigContext);
|
||||
const { prefixCls: customizePrefixCls, className = '' } = props;
|
||||
const prefixCls = getPrefixCls('back-top', customizePrefixCls);
|
||||
@ -130,7 +129,9 @@ const BackTop: React.FC<BackTopProps> = props => {
|
||||
|
||||
return wrapSSR(
|
||||
<div {...divProps} className={classString} onClick={scrollToTop} ref={ref}>
|
||||
{renderChildren({ prefixCls, rootPrefixCls })}
|
||||
<BackTopContent prefixCls={prefixCls} rootPrefixCls={rootPrefixCls} visible={visible}>
|
||||
{props.children}
|
||||
</BackTopContent>
|
||||
</div>,
|
||||
);
|
||||
};
|
||||
|
@ -48,7 +48,7 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
|
||||
| style | Style of Drawer panel. Use `bodyStyle` if want to config body only | CSSProperties | - | |
|
||||
| size | presetted size of drawer, default `378px` and large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
|
||||
| title | The title for Drawer | ReactNode | - | |
|
||||
|
||||
<<<<<<< HEAD | open | Whether the Drawer dialog is visible or not | boolean | false | | ======= | open | Whether the Drawer dialog is visible or not | boolean | false | 4.23.0 |
|
||||
|
||||
> > > > > > > feature | width | Width of the Drawer dialog | string \| number | 378 | | | zIndex | The `z-index` of the Drawer | number | 1000 | | | onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button | function(e) | - | |
|
||||
| open | Whether the Drawer dialog is visible or not | boolean | false | |
|
||||
| width | Width of the Drawer dialog | string \| number | 378 | |
|
||||
| zIndex | The `z-index` of the Drawer | number | 1000 | |
|
||||
| onClose | Specify a callback that will be called when a user clicks mask, close button or Cancel button | function(e) | - | |
|
||||
|
@ -47,7 +47,7 @@ cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!600000000168
|
||||
| size | 预设抽屉宽度(或高度),default `378px` 和 large `736px` | 'default' \| 'large' | 'default' | 4.17.0 |
|
||||
| style | 设计 Drawer 容器样式,如果你只需要设置内容部分请使用 `bodyStyle` | CSSProperties | - | |
|
||||
| title | 标题 | ReactNode | - | |
|
||||
|
||||
<<<<<<< HEAD | open | Drawer 是否可见 | boolean | - | | ======= | open | Drawer 是否可见 | boolean | - | 4.23.0 |
|
||||
|
||||
> > > > > > > feature | width | 宽度 | string \| number | 378 | | | zIndex | 设置 Drawer 的 `z-index` | number | 1000 | | | onClose | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | - | |
|
||||
| open | Drawer 是否可见 | boolean | - |
|
||||
| width | 宽度 | string \| number | 378 | |
|
||||
| zIndex | 设置 Drawer 的 `z-index` | number | 1000 | |
|
||||
| onClose | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | - | |
|
||||
|
@ -27,8 +27,8 @@ When there are more than a few options to choose from, you can wrap them in a `D
|
||||
| overlayStyle | The style of the dropdown root element | CSSProperties | - | |
|
||||
| placement | Placement of popup menu: `bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
|
||||
| trigger | The trigger mode which executes the dropdown action. Note that hover can't be used on touchscreens | Array<`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
|
||||
| open | Whether the dropdown menu is currently open | boolean | - | 4.23.0 |
|
||||
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item | (open: boolean) => void | - | 4.23.0 |
|
||||
| open | Whether the dropdown menu is currently open. Use `visible` under 4.23.0 ([why?](/docs/react/faq#why-open)) | boolean | - | 4.23.0 |
|
||||
| onOpenChange | Called when the open state is changed. Not trigger when hidden by click item. Use `onVisibleChange` under 4.23.0 ([why?](/docs/react/faq#why-open)) | (open: boolean) => void | - | 4.23.0 |
|
||||
|
||||
You should use [Menu](/components/menu/) as `overlay`. The menu items and dividers are also available by using `Menu.Item` and `Menu.Divider`.
|
||||
|
||||
|
@ -31,8 +31,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
|
||||
| overlayStyle | 下拉根元素的样式 | CSSProperties | - | |
|
||||
| placement | 菜单弹出位置:`bottom` `bottomLeft` `bottomRight` `top` `topLeft` `topRight` | string | `bottomLeft` | |
|
||||
| trigger | 触发下拉的行为, 移动端不支持 hover | Array<`click`\|`hover`\|`contextMenu`> | \[`hover`] | |
|
||||
| open | 菜单是否显示 | boolean | - | 4.23.0 |
|
||||
| onOpenChange | 菜单显示状态改变时调用,参数为 `visible`。点击菜单按钮导致的消失不会触发 | (open: boolean) => void | - | 4.23.0 |
|
||||
| open | 菜单是否显示,小于 4.23.0 使用 `visible`([为什么?](/docs/react/faq#why-open)) | boolean | - | 4.23.0 |
|
||||
| onOpenChange | 菜单显示状态改变时调用,点击菜单按钮导致的消失不会触发。小于 4.23.0 使用 `onVisibleChange`([为什么?](/docs/react/faq#why-open)) | (open: boolean) => void | - | 4.23.0 |
|
||||
|
||||
`overlay` 菜单使用 [Menu](/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`。
|
||||
|
||||
@ -56,4 +56,4 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
|
||||
| type | 按钮类型,和 [Button](/components/button/#API) 一致 | string | `default` | |
|
||||
| open | 菜单是否显示 | boolean | - | 4.23.0 |
|
||||
| onClick | 点击左侧按钮的回调,和 [Button](/components/button/#API) 一致 | (event) => void | - | |
|
||||
| onOpenChange | 菜单显示状态改变时调用,参数为 `visible` | (open: boolean) => void | - | 4.23.0 |
|
||||
| onOpenChange | 菜单显示状态改变时调用 | (open: boolean) => void | - | 4.23.0 |
|
||||
|
@ -18,7 +18,7 @@ import { Button, InputNumber, Space } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [value, setValue] = useState<string | number>('99');
|
||||
const [value, setValue] = useState<string | number | null>('99');
|
||||
|
||||
return (
|
||||
<Space>
|
||||
|
@ -3,6 +3,7 @@ import UpOutlined from '@ant-design/icons/UpOutlined';
|
||||
import classNames from 'classnames';
|
||||
import type { InputNumberProps as RcInputNumberProps } from 'rc-input-number';
|
||||
import RcInputNumber from 'rc-input-number';
|
||||
import type { ValueType } from 'rc-input-number/lib/utils/MiniDecimal';
|
||||
import * as React from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
@ -15,8 +16,6 @@ import type { InputStatus } from '../_util/statusUtils';
|
||||
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
|
||||
import useStyle from './style';
|
||||
|
||||
type ValueType = string | number;
|
||||
|
||||
export interface InputNumberProps<T extends ValueType = ValueType>
|
||||
extends Omit<RcInputNumberProps<T>, 'prefix' | 'size' | 'controls'> {
|
||||
prefixCls?: string;
|
||||
|
@ -10,7 +10,7 @@ describe('Result', () => {
|
||||
rtlTest(Result);
|
||||
|
||||
it('🙂 successPercent should decide the progress status when it exists', () => {
|
||||
const { container: wrapper } = render(
|
||||
const { container } = render(
|
||||
<Result
|
||||
status="success"
|
||||
title="Successfully Purchased Cloud Server ECS!"
|
||||
@ -23,37 +23,35 @@ describe('Result', () => {
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
expect(wrapper.querySelectorAll('.anticon-check-circle')).toHaveLength(1);
|
||||
expect(container.querySelectorAll('.anticon-check-circle')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('🙂 different status, different class', () => {
|
||||
const { container: wrapper, rerender } = render(<Result status="warning" />);
|
||||
expect(wrapper.querySelectorAll('.ant-result-warning')).toHaveLength(1);
|
||||
const { container, rerender } = render(<Result status="warning" />);
|
||||
expect(container.querySelectorAll('.ant-result-warning')).toHaveLength(1);
|
||||
|
||||
rerender(<Result status="error" />);
|
||||
|
||||
expect(wrapper.querySelectorAll('.ant-result-error')).toHaveLength(1);
|
||||
expect(container.querySelectorAll('.ant-result-error')).toHaveLength(1);
|
||||
|
||||
rerender(<Result status="500" />);
|
||||
|
||||
expect(wrapper.querySelectorAll('.ant-result-500')).toHaveLength(1);
|
||||
expect(container.querySelectorAll('.ant-result-500')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('🙂 When status = 404, the icon is an image', () => {
|
||||
const { container: wrapper } = render(<Result status="404" />);
|
||||
expect(wrapper.querySelectorAll('.ant-result-404 .ant-result-image')).toHaveLength(1);
|
||||
const { container } = render(<Result status="404" />);
|
||||
expect(container.querySelectorAll('.ant-result-404 .ant-result-image')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('🙂 When extra is undefined, the extra dom is undefined', () => {
|
||||
const { container: wrapper } = render(<Result status="404" />);
|
||||
expect(wrapper.querySelectorAll('.ant-result-extra')).toHaveLength(0);
|
||||
const { container } = render(<Result status="404" />);
|
||||
expect(container.querySelectorAll('.ant-result-extra')).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('🙂 result should support className', () => {
|
||||
const { container: wrapper } = render(
|
||||
<Result status="404" title="404" className="my-result" />,
|
||||
);
|
||||
expect(wrapper.querySelectorAll('.ant-result.my-result')).toHaveLength(1);
|
||||
const { container } = render(<Result status="404" title="404" className="my-result" />);
|
||||
expect(container.querySelectorAll('.ant-result.my-result')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should warning when pass a string as icon props', () => {
|
||||
|
@ -44,12 +44,11 @@ interface DataType {
|
||||
};
|
||||
}
|
||||
|
||||
interface Params {
|
||||
interface TableParams {
|
||||
pagination?: TablePaginationConfig;
|
||||
sorter?: SorterResult<any> | SorterResult<any>[];
|
||||
total?: number;
|
||||
sortField?: string;
|
||||
sortOrder?: string;
|
||||
filters?: Record<string, FilterValue>;
|
||||
}
|
||||
|
||||
const columns: ColumnsType<DataType> = [
|
||||
@ -75,7 +74,7 @@ const columns: ColumnsType<DataType> = [
|
||||
},
|
||||
];
|
||||
|
||||
const getRandomuserParams = (params: Params) => ({
|
||||
const getRandomuserParams = (params: TableParams) => ({
|
||||
results: params.pagination?.pageSize,
|
||||
page: params.pagination?.current,
|
||||
...params,
|
||||
@ -84,41 +83,45 @@ const getRandomuserParams = (params: Params) => ({
|
||||
const App: React.FC = () => {
|
||||
const [data, setData] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [pagination, setPagination] = useState<TablePaginationConfig>({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
const [tableParams, setTableParams] = useState<TableParams>({
|
||||
pagination: {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchData = (params: Params = {}) => {
|
||||
const fetchData = () => {
|
||||
setLoading(true);
|
||||
fetch(`https://randomuser.me/api?${qs.stringify(getRandomuserParams(params))}`)
|
||||
fetch(`https://randomuser.me/api?${qs.stringify(getRandomuserParams(tableParams))}`)
|
||||
.then(res => res.json())
|
||||
.then(({ results }) => {
|
||||
setData(results);
|
||||
setLoading(false);
|
||||
setPagination({
|
||||
...params.pagination,
|
||||
total: 200,
|
||||
// 200 is mock data, you should read it from server
|
||||
// total: data.totalCount,
|
||||
setTableParams({
|
||||
...tableParams,
|
||||
pagination: {
|
||||
...tableParams.pagination,
|
||||
total: 200,
|
||||
// 200 is mock data, you should read it from server
|
||||
// total: data.totalCount,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData({ pagination });
|
||||
}, []);
|
||||
fetchData();
|
||||
}, [JSON.stringify(tableParams)]);
|
||||
|
||||
const handleTableChange = (
|
||||
newPagination: TablePaginationConfig,
|
||||
pagination: TablePaginationConfig,
|
||||
filters: Record<string, FilterValue>,
|
||||
sorter: SorterResult<DataType>,
|
||||
) => {
|
||||
fetchData({
|
||||
sortField: sorter.field as string,
|
||||
sortOrder: sorter.order as string,
|
||||
pagination: newPagination,
|
||||
...filters,
|
||||
setTableParams({
|
||||
pagination,
|
||||
filters,
|
||||
...sorter,
|
||||
});
|
||||
};
|
||||
|
||||
@ -127,7 +130,7 @@ const App: React.FC = () => {
|
||||
columns={columns}
|
||||
rowKey={record => record.login.uuid}
|
||||
dataSource={data}
|
||||
pagination={pagination}
|
||||
pagination={tableParams.pagination}
|
||||
loading={loading}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
|
@ -1,106 +0,0 @@
|
||||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
import Tag from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import { render, act } from '../../../tests/utils';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
|
||||
describe('Tag', () => {
|
||||
mountTest(Tag);
|
||||
mountTest(Tag.CheckableTag);
|
||||
rtlTest(Tag);
|
||||
rtlTest(Tag.CheckableTag);
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be closable', () => {
|
||||
const onClose = jest.fn();
|
||||
const wrapper = mount(<Tag closable onClose={onClose} />);
|
||||
expect(wrapper.find('.anticon-close').length).toBe(1);
|
||||
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
|
||||
wrapper.find('.anticon-close').simulate('click');
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
wrapper.update();
|
||||
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not be closed when prevent default', () => {
|
||||
const onClose = e => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const wrapper = mount(<Tag closable onClose={onClose} />);
|
||||
expect(wrapper.find('.anticon-close').length).toBe(1);
|
||||
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
|
||||
wrapper.find('.anticon-close').simulate('click');
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(wrapper.find('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should trigger onClick', () => {
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(<Tag onClick={onClick} />);
|
||||
wrapper.find('.ant-tag').simulate('click');
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'click',
|
||||
preventDefault: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger onClick on CheckableTag', () => {
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(<Tag.CheckableTag onClick={onClick} />);
|
||||
wrapper.find('.ant-tag').simulate('click');
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'click',
|
||||
preventDefault: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/20344
|
||||
it('should not trigger onClick when click close icon', () => {
|
||||
const onClose = jest.fn();
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(<Tag closable onClose={onClose} onClick={onClick} />);
|
||||
wrapper.find('.anticon-close').simulate('click');
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
expect(onClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('deprecated warning', () => {
|
||||
resetWarned();
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const { container } = render(<Tag visible={false} />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Tag] `visible` is deprecated, please use `visible && <Tag />` instead.',
|
||||
);
|
||||
expect(container.querySelector('.ant-tag-hidden')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('CheckableTag', () => {
|
||||
it('support onChange', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(<Tag.CheckableTag onChange={onChange} />);
|
||||
wrapper.find('.ant-tag').simulate('click');
|
||||
expect(onChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
151
components/tag/__tests__/index.test.tsx
Normal file
151
components/tag/__tests__/index.test.tsx
Normal file
@ -0,0 +1,151 @@
|
||||
import React from 'react';
|
||||
import { Simulate } from 'react-dom/test-utils';
|
||||
|
||||
import Tag from '..';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, render, fireEvent } from '../../../tests/utils';
|
||||
|
||||
describe('Tag', () => {
|
||||
mountTest(Tag);
|
||||
mountTest(Tag.CheckableTag);
|
||||
rtlTest(Tag);
|
||||
rtlTest(Tag.CheckableTag);
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should be closable', () => {
|
||||
const onClose = jest.fn();
|
||||
const { container } = render(<Tag closable onClose={onClose} />);
|
||||
expect(container.querySelectorAll('.anticon-close').length).toBe(1);
|
||||
expect(container.querySelectorAll('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
|
||||
fireEvent.click(container.querySelectorAll('.anticon-close')[0]);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(container.querySelectorAll('.ant-tag:not(.ant-tag-hidden)').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not be closed when prevent default', () => {
|
||||
const onClose = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
};
|
||||
const { container } = render(<Tag closable onClose={onClose} />);
|
||||
expect(container.querySelectorAll('.anticon-close').length).toBe(1);
|
||||
expect(container.querySelectorAll('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
|
||||
fireEvent.click(container.querySelectorAll('.anticon-close')[0]);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(container.querySelectorAll('.ant-tag:not(.ant-tag-hidden)').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should trigger onClick', () => {
|
||||
const onClick = jest.fn();
|
||||
const { container } = render(<Tag onClick={onClick} />);
|
||||
const target = container.querySelectorAll('.ant-tag')[0];
|
||||
Simulate.click(target);
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'click',
|
||||
target,
|
||||
preventDefault: expect.any(Function),
|
||||
nativeEvent: {
|
||||
type: 'click',
|
||||
target,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should trigger onClick on CheckableTag', () => {
|
||||
const onClick = jest.fn();
|
||||
const { container } = render(<Tag.CheckableTag checked={false} onClick={onClick} />);
|
||||
const target = container.querySelectorAll('.ant-tag')[0];
|
||||
Simulate.click(target);
|
||||
expect(onClick).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: 'click',
|
||||
target,
|
||||
preventDefault: expect.any(Function),
|
||||
nativeEvent: {
|
||||
type: 'click',
|
||||
target,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/20344
|
||||
it('should not trigger onClick when click close icon', () => {
|
||||
const onClose = jest.fn();
|
||||
const onClick = jest.fn();
|
||||
const { container } = render(<Tag closable onClose={onClose} onClick={onClick} />);
|
||||
fireEvent.click(container.querySelectorAll('.anticon-close')[0]);
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
expect(onClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('deprecated warning', () => {
|
||||
resetWarned();
|
||||
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const { container } = render(<Tag visible={false} />);
|
||||
expect(errSpy).toHaveBeenCalledWith(
|
||||
'Warning: [antd: Tag] `visible` is deprecated, please use `visible && <Tag />` instead.',
|
||||
);
|
||||
expect(container.querySelector('.ant-tag-hidden')).toBeTruthy();
|
||||
|
||||
errSpy.mockRestore();
|
||||
});
|
||||
|
||||
describe('visibility', () => {
|
||||
it('can be controlled by visible with visible as initial value', () => {
|
||||
const { asFragment, rerender } = render(<Tag visible />);
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
rerender(<Tag visible={false} />);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
rerender(<Tag visible />);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('can be controlled by visible with hidden as initial value', () => {
|
||||
const { asFragment, rerender } = render(<Tag visible={false} />);
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
rerender(<Tag visible />);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
rerender(<Tag visible={false} />);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
expect(asFragment().firstChild).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('CheckableTag', () => {
|
||||
it('support onChange', () => {
|
||||
const onChange = jest.fn();
|
||||
const { container } = render(<Tag.CheckableTag checked={false} onChange={onChange} />);
|
||||
fireEvent.click(container.querySelectorAll('.ant-tag')[0]);
|
||||
expect(onChange).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
@ -38,7 +38,7 @@ The following APIs are shared by Tooltip, Popconfirm, Popover.
|
||||
| overlayInnerStyle | Style of the tooltip inner content | object | - | |
|
||||
| placement | The position of the tooltip relative to the target, which can be one of `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | `top` | |
|
||||
| trigger | Tooltip trigger mode. Could be multiple by passing an array | `hover` \| `focus` \| `click` \| `contextMenu` \| Array<string> | `hover` | |
|
||||
| open | Whether the floating tooltip card is open or not | boolean | false | 4.23.0 |
|
||||
| open | Whether the floating tooltip card is open or not. Use `visible` under 4.23.0 ([why?](/docs/react/faq#why-open)) | boolean | false | 4.23.0 |
|
||||
| zIndex | Config `z-index` of Tooltip | number | - | |
|
||||
| onOpenChange | Callback executed when visibility of the tooltip card is changed | (open) => void | - | 4.23.0 |
|
||||
|
||||
|
@ -40,7 +40,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Vyyeu8jq2/Tooltp.svg
|
||||
| overlayInnerStyle | 卡片内容区域的样式对象 | object | - | |
|
||||
| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | `top` | |
|
||||
| trigger | 触发行为,可选 `hover` \| `focus` \| `click` \| `contextMenu`,可使用数组设置多个触发行为 | string \| string\[] | `hover` | |
|
||||
| open | 用于手动控制浮层显隐 | boolean | false | 4.23.0 |
|
||||
| open | 用于手动控制浮层显隐,小于 4.23.0 使用 `visible`([为什么?](/docs/react/faq#why-open)) | boolean | false | 4.23.0 |
|
||||
| zIndex | 设置 Tooltip 的 `z-index` | number | - | |
|
||||
| onOpenChange | 显示隐藏的回调 | (open) => void | - | 4.23.0 |
|
||||
|
||||
|
@ -108,7 +108,7 @@ import { Table } from 'antd';
|
||||
|
||||
type Props<T extends (...args: any) => any> = Parameters<T>[0];
|
||||
|
||||
type TableProps = Props<typeof Table<{ key: string, name: string, age: number }>>;
|
||||
type TableProps = Props<typeof Table<{ key: string; name: string; age: number }>>;
|
||||
type DataSource = TableProps['dataSource'];
|
||||
```
|
||||
|
||||
@ -169,6 +169,12 @@ ConfigProvider.config({
|
||||
|
||||
You should only access the API by official doc with ref. Directly access internal `props` or `state` is not recommended which will make your code strong coupling with current version. Any refactor will break your code like refactor with [Hooks](https://reactjs.org/docs/hooks-intro.html) version, delete or rename internal `props` or `state`, adjust internal node constructor, etc.
|
||||
|
||||
<div id="why-open"></div>
|
||||
|
||||
## Why we need align pop component with `open` prop?
|
||||
|
||||
For historical reasons, the display names of the pop components are not uniform, and both `open` and `visible` are used. This makes the memory cost that non-tsx users encounter when developing. It also leads to ambiguity about what name to choose when adding a feature. So we want to unify the attribute name, you can still use the original `visible` and it will still be backward compatible, but we will remove this attribute from the documentation as of v5.
|
||||
|
||||
## How to spell Ant Design correctly?
|
||||
|
||||
- ✅ **Ant Design**: Capitalized with space, for the design language.
|
||||
|
@ -122,7 +122,7 @@ import { Table } from 'antd';
|
||||
|
||||
type Props<T extends (...args: any) => any> = Parameters<T>[0];
|
||||
|
||||
type TableProps = Props<typeof Table<{ key: string, name: string, age: number }>>;
|
||||
type TableProps = Props<typeof Table<{ key: string; name: string; age: number }>>;
|
||||
type DataSource = TableProps['dataSource'];
|
||||
```
|
||||
|
||||
@ -189,6 +189,12 @@ ConfigProvider.config({
|
||||
|
||||
你通过 ref 获得引用时只应该使用文档提供的方法。直接读取组件内部的 `props` 和 `state` 不是一个好的设计,这会使你的代码与组件版本强耦合。任何重构都可能会使你的代码无法工作,其中重构包括且不仅限于改造成 [Hooks](https://reactjs.org/docs/hooks-intro.html) 版本、移除 / 更名内部 `props` 与 `state`、调整内部 React 节点结构等等。
|
||||
|
||||
<div id="why-open"></div>
|
||||
|
||||
## 弹层类组件为什么要统一至 `open` 属性?
|
||||
|
||||
因为历史原因,弹层类组件展示命名并不统一,出现了 `open` 与 `visible` 都在使用的情况。这使得非 tsx 用户在开发时遭遇的记忆成本。同样导致新增 feature 时选择何种命名的模棱两可。因而我们希望统一该属性命名,你仍然可以使用原本的 `visible` 它仍然会向下兼容,但是从 v5 起我们将从文档中移除该属性。
|
||||
|
||||
## 如何正确的拼写 Ant Design?
|
||||
|
||||
- ✅ **Ant Design**:用空格分隔的首字母大写单词,指代设计语言。
|
||||
|
@ -195,6 +195,14 @@ class MainContent extends Component {
|
||||
handleLoad = () => {
|
||||
if (window.location.hash) {
|
||||
updateActiveToc(window.location.hash.replace(/^#/, ''));
|
||||
|
||||
// 有时候不滚动,强制触发一次滚动逻辑
|
||||
setTimeout(() => {
|
||||
const target = document.querySelector(window.location.hash);
|
||||
if (target) {
|
||||
target.scrollIntoView();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
this.bindScroller();
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user