mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 03:59:01 +08:00
commit
f5b16a2e1d
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
|
||||
export const { isValidElement } = React;
|
||||
|
||||
export function isFragment(child: React.ReactElement): boolean {
|
||||
export function isFragment(child: any): boolean {
|
||||
return child && isValidElement(child) && child.type === React.Fragment;
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ class Wave extends React.Component<WaveProps> {
|
||||
onClick = (node: HTMLElement, waveColor: string) => {
|
||||
const { insertExtraNode, disabled } = this.props;
|
||||
|
||||
if (disabled || !node || isHidden(node) || node.className.indexOf('-leave') >= 0) {
|
||||
if (disabled || !node || isHidden(node) || node.className.includes('-leave')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ class Wave extends React.Component<WaveProps> {
|
||||
!node ||
|
||||
!node.getAttribute ||
|
||||
node.getAttribute('disabled') ||
|
||||
node.className.indexOf('disabled') >= 0
|
||||
node.className.includes('disabled')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ Alert component for feedback.
|
||||
| banner | Whether to show as banner | boolean | false | |
|
||||
| closable | Whether Alert can be closed | boolean | - | |
|
||||
| closeText | Close text to show | ReactNode | - | |
|
||||
| closeIcon | Custom close icon | ReactNode | `<CloseOutlined />` | 4.17.0 |
|
||||
| closeIcon | Custom close icon | ReactNode | `<CloseOutlined />` | 4.18.0 |
|
||||
| description | Additional content of Alert | ReactNode | - | |
|
||||
| icon | Custom icon, effective when `showIcon` is true | ReactNode | - | |
|
||||
| message | Content of Alert | ReactNode | - | |
|
||||
|
@ -22,7 +22,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/8emPa3fjl/Alert.svg
|
||||
| banner | 是否用作顶部公告 | boolean | false | |
|
||||
| closable | 默认不显示关闭按钮 | boolean | - | |
|
||||
| closeText | 自定义关闭按钮 | ReactNode | - | |
|
||||
| closeIcon | 自定义关闭 Icon | ReactNode | `<CloseOutlined />` | 4.17.0 |
|
||||
| closeIcon | 自定义关闭 Icon | ReactNode | `<CloseOutlined />` | 4.18.0 |
|
||||
| description | 警告提示的辅助性文字介绍 | ReactNode | - | |
|
||||
| icon | 自定义图标,`showIcon` 为 true 时有效 | ReactNode | - | |
|
||||
| message | 警告提示内容 | ReactNode | - | |
|
||||
|
@ -2,13 +2,15 @@ import React from 'react';
|
||||
import BackTop from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render, sleep } from '../../../tests/utils';
|
||||
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
|
||||
describe('BackTop', () => {
|
||||
mountTest(BackTop);
|
||||
rtlTest(BackTop);
|
||||
|
||||
it('should scroll to top after click it', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const { container } = render(<BackTop visibilityHeight={-1} />);
|
||||
const scrollToSpy = jest.spyOn(window, 'scrollTo').mockImplementation((_, y) => {
|
||||
window.scrollY = y;
|
||||
@ -18,9 +20,12 @@ describe('BackTop', () => {
|
||||
window.scrollTo(0, 400);
|
||||
expect(document.documentElement.scrollTop).toBe(400);
|
||||
fireEvent.click(container.querySelector('.ant-back-top')!);
|
||||
await sleep(500);
|
||||
await waitFakeTimer();
|
||||
expect(document.documentElement.scrollTop).toBe(0);
|
||||
scrollToSpy.mockRestore();
|
||||
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('support onClick', async () => {
|
||||
|
@ -2,5 +2,5 @@ import { PresetColorTypes } from '../_util/colors';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export function isPresetColor(color?: string): boolean {
|
||||
return (PresetColorTypes as any[]).indexOf(color) !== -1;
|
||||
return (PresetColorTypes as any[]).includes(color);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import DisabledContext from '../config-provider/DisabledContext';
|
||||
import type { SizeType } from '../config-provider/SizeContext';
|
||||
import SizeContext from '../config-provider/SizeContext';
|
||||
import { useCompactItemContext } from '../space/Compact';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import { cloneElement, isFragment } from '../_util/reactNode';
|
||||
import { tuple } from '../_util/type';
|
||||
import warning from '../_util/warning';
|
||||
import Wave from '../_util/wave';
|
||||
@ -25,10 +25,6 @@ function isUnBorderedButtonType(type: ButtonType | undefined) {
|
||||
return type === 'text' || type === 'link';
|
||||
}
|
||||
|
||||
function isReactFragment(node: React.ReactNode) {
|
||||
return React.isValidElement(node) && node.type === React.Fragment;
|
||||
}
|
||||
|
||||
// Insert one space between two chinese characters automatically.
|
||||
function insertSpace(child: React.ReactElement | string | number, needInserted: boolean) {
|
||||
// Check the child if is undefined or null.
|
||||
@ -50,7 +46,7 @@ function insertSpace(child: React.ReactElement | string | number, needInserted:
|
||||
if (typeof child === 'string') {
|
||||
return isTwoCNChar(child) ? <span>{child.split('').join(SPACE)}</span> : <span>{child}</span>;
|
||||
}
|
||||
if (isReactFragment(child)) {
|
||||
if (isFragment(child)) {
|
||||
return <span>{child}</span>;
|
||||
}
|
||||
return child;
|
||||
|
@ -3,7 +3,7 @@ import type { CarouselRef } from '..';
|
||||
import Carousel from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { sleep, render, act } from '../../../tests/utils';
|
||||
import { waitFakeTimer, render } from '../../../tests/utils';
|
||||
|
||||
describe('Carousel', () => {
|
||||
mountTest(Carousel);
|
||||
@ -17,14 +17,6 @@ describe('Carousel', () => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
function runAllTimersWithAct(times = 1) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
it('should has innerSlider', () => {
|
||||
const ref = React.createRef<CarouselRef>();
|
||||
render(
|
||||
@ -51,16 +43,16 @@ describe('Carousel', () => {
|
||||
expect(typeof goTo).toBe('function');
|
||||
expect(ref.current?.innerSlider.state.currentSlide).toBe(0);
|
||||
ref.current?.goTo(2);
|
||||
runAllTimersWithAct(1);
|
||||
await waitFakeTimer();
|
||||
expect(ref.current?.innerSlider.state.currentSlide).toBe(2);
|
||||
// wait for animation to be finished
|
||||
runAllTimersWithAct(2);
|
||||
await waitFakeTimer();
|
||||
ref.current?.prev();
|
||||
runAllTimersWithAct(1);
|
||||
await waitFakeTimer();
|
||||
expect(ref.current?.innerSlider.state.currentSlide).toBe(1);
|
||||
runAllTimersWithAct(2);
|
||||
await waitFakeTimer();
|
||||
ref.current?.next();
|
||||
runAllTimersWithAct(1);
|
||||
await waitFakeTimer();
|
||||
expect(ref.current?.innerSlider.state.currentSlide).toBe(2);
|
||||
});
|
||||
|
||||
@ -77,7 +69,7 @@ describe('Carousel', () => {
|
||||
const spy = jest.spyOn(ref.current?.innerSlider, 'autoPlay');
|
||||
window.resizeTo(1000, window.outerHeight);
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
await sleep(500);
|
||||
await waitFakeTimer();
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
@ -102,7 +102,7 @@ const InternalCheckbox: React.ForwardRefRenderFunction<HTMLInputElement, Checkbo
|
||||
}
|
||||
};
|
||||
checkboxProps.name = checkboxGroup.name;
|
||||
checkboxProps.checked = checkboxGroup.value.indexOf(restProps.value) !== -1;
|
||||
checkboxProps.checked = checkboxGroup.value.includes(restProps.value);
|
||||
}
|
||||
const classString = classNames(
|
||||
{
|
||||
|
@ -101,7 +101,7 @@ const InternalCheckboxGroup: React.ForwardRefRenderFunction<HTMLDivElement, Chec
|
||||
const opts = getOptions();
|
||||
onChange?.(
|
||||
newValue
|
||||
.filter(val => registeredValues.indexOf(val) !== -1)
|
||||
.filter(val => registeredValues.includes(val))
|
||||
.sort((a, b) => {
|
||||
const indexA = opts.findIndex(opt => opt.value === a);
|
||||
const indexB = opts.findIndex(opt => opt.value === b);
|
||||
@ -122,7 +122,7 @@ const InternalCheckboxGroup: React.ForwardRefRenderFunction<HTMLDivElement, Chec
|
||||
key={option.value.toString()}
|
||||
disabled={'disabled' in option ? option.disabled : restProps.disabled}
|
||||
value={option.value}
|
||||
checked={value.indexOf(option.value) !== -1}
|
||||
checked={value.includes(option.value)}
|
||||
onChange={option.onChange}
|
||||
className={`${groupPrefixCls}-item`}
|
||||
style={option.style}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { sleep, render, fireEvent } from '../../../tests/utils';
|
||||
import { waitFakeTimer, render, fireEvent } from '../../../tests/utils';
|
||||
import { resetWarned } from '../../_util/warning';
|
||||
|
||||
describe('Collapse', () => {
|
||||
@ -66,6 +66,7 @@ describe('Collapse', () => {
|
||||
});
|
||||
|
||||
it('could be expand and collapse', async () => {
|
||||
jest.useFakeTimers();
|
||||
const { container } = render(
|
||||
<Collapse>
|
||||
<Collapse.Panel header="This is panel header 1" key="1">
|
||||
@ -77,10 +78,11 @@ describe('Collapse', () => {
|
||||
container.querySelector('.ant-collapse-item')?.classList.contains('ant-collapse-item-active'),
|
||||
).toBe(false);
|
||||
fireEvent.click(container.querySelector('.ant-collapse-header')!);
|
||||
await sleep(400);
|
||||
await waitFakeTimer();
|
||||
expect(
|
||||
container.querySelector('.ant-collapse-item')?.classList.contains('ant-collapse-item-active'),
|
||||
).toBe(true);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('could override default openMotion', () => {
|
||||
|
@ -4,7 +4,7 @@ import Dropdown from '..';
|
||||
import type { DropDownProps } from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, fireEvent, render, sleep } from '../../../tests/utils';
|
||||
import { act, fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import Menu from '../../menu';
|
||||
|
||||
let triggerProps: TriggerProps;
|
||||
@ -55,6 +55,7 @@ describe('Dropdown', () => {
|
||||
});
|
||||
|
||||
it('support Menu expandIcon', async () => {
|
||||
jest.useFakeTimers();
|
||||
const props: DropDownProps = {
|
||||
overlay: (
|
||||
<Menu expandIcon={<span id="customExpandIcon" />}>
|
||||
@ -73,8 +74,9 @@ describe('Dropdown', () => {
|
||||
<button type="button">button</button>
|
||||
</Dropdown>,
|
||||
);
|
||||
await sleep(500);
|
||||
await waitFakeTimer();
|
||||
expect(container.querySelectorAll('#customExpandIcon').length).toBe(1);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should warn if use topCenter or bottomCenter', () => {
|
||||
|
@ -1,27 +1,59 @@
|
||||
import React from 'react';
|
||||
import type { FormListFieldData, FormListOperation } from '..';
|
||||
import Form from '..';
|
||||
import { fireEvent, render, sleep, act } from '../../../tests/utils';
|
||||
import { fireEvent, render, waitFakeTimer } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import Input from '../../input';
|
||||
|
||||
describe('Form.List', () => {
|
||||
const change = async (
|
||||
wrapper: ReturnType<typeof render>['container'],
|
||||
index: number,
|
||||
// const change = async (
|
||||
// wrapper: ReturnType<typeof render>['container'],
|
||||
// index: number,
|
||||
// value: string,
|
||||
// ) => {
|
||||
// fireEvent.change(wrapper.getElementsByClassName('ant-input')?.[index], { target: { value } });
|
||||
// await sleep();
|
||||
// };
|
||||
|
||||
const changeValue = async (
|
||||
input: HTMLElement | null | number,
|
||||
value: string,
|
||||
advTimer = 1000,
|
||||
) => {
|
||||
fireEvent.change(wrapper.getElementsByClassName('ant-input')?.[index], { target: { value } });
|
||||
await sleep();
|
||||
let element: HTMLElement;
|
||||
|
||||
if (typeof input === 'number') {
|
||||
element = document.querySelectorAll('input')[input];
|
||||
}
|
||||
|
||||
expect(element!).toBeTruthy();
|
||||
|
||||
fireEvent.change(element!, {
|
||||
target: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
if (advTimer) {
|
||||
await waitFakeTimer(advTimer / 20);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
document.body.innerHTML = '';
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllTimers();
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
const testList = (
|
||||
name: string,
|
||||
renderField: (value: FormListFieldData) => React.ReactNode,
|
||||
): void => {
|
||||
it(name, async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const { container } = render(
|
||||
<Form>
|
||||
<Form.List name="list">
|
||||
@ -39,39 +71,31 @@ describe('Form.List', () => {
|
||||
</Form>,
|
||||
);
|
||||
|
||||
function operate(className: string) {
|
||||
async function operate(className: string) {
|
||||
fireEvent.click(container.querySelector(className)!);
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
await waitFakeTimer();
|
||||
}
|
||||
|
||||
operate('.add');
|
||||
expect(container.getElementsByClassName('ant-input').length).toBe(1);
|
||||
await operate('.add');
|
||||
expect(container.querySelectorAll('.ant-input').length).toBe(1);
|
||||
|
||||
operate('.add');
|
||||
expect(container.getElementsByClassName('ant-input').length).toBe(2);
|
||||
await operate('.add');
|
||||
expect(container.querySelectorAll('.ant-input').length).toBe(2);
|
||||
|
||||
operate('.add');
|
||||
expect(container.getElementsByClassName('ant-input').length).toBe(3);
|
||||
await operate('.add');
|
||||
expect(container.querySelectorAll('.ant-input').length).toBe(3);
|
||||
|
||||
await change(container, 2, '');
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
}
|
||||
expect(container.getElementsByClassName('ant-form-item-explain').length).toBe(1);
|
||||
await changeValue(2, '');
|
||||
|
||||
operate('.remove-0');
|
||||
expect(container.getElementsByClassName('ant-input').length).toBe(2);
|
||||
expect(container.getElementsByClassName('ant-form-item-explain').length).toBe(1);
|
||||
expect(container.querySelectorAll('.ant-form-item-explain').length).toBe(1);
|
||||
|
||||
operate('.remove-1');
|
||||
expect(container.getElementsByClassName('ant-input').length).toBe(1);
|
||||
expect(container.getElementsByClassName('ant-form-item-explain').length).toBe(0);
|
||||
await operate('.remove-0');
|
||||
expect(container.querySelectorAll('.ant-input').length).toBe(2);
|
||||
expect(container.querySelectorAll('.ant-form-item-explain').length).toBe(1);
|
||||
|
||||
jest.useRealTimers();
|
||||
await operate('.remove-1');
|
||||
expect(container.querySelectorAll('.ant-input').length).toBe(1);
|
||||
expect(container.querySelectorAll('.ant-form-item-explain').length).toBe(0);
|
||||
});
|
||||
};
|
||||
|
||||
@ -131,28 +155,26 @@ describe('Form.List', () => {
|
||||
);
|
||||
|
||||
await click(container, '.add');
|
||||
await change(container, 0, 'input1');
|
||||
await changeValue(0, 'input1');
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await sleep();
|
||||
await waitFakeTimer();
|
||||
expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1'] });
|
||||
|
||||
await click(container, '.add');
|
||||
await change(container, 1, 'input2');
|
||||
await changeValue(1, 'input2');
|
||||
await click(container, '.add');
|
||||
await change(container, 2, 'input3');
|
||||
await changeValue(2, 'input3');
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await sleep();
|
||||
await waitFakeTimer();
|
||||
expect(onFinish).toHaveBeenLastCalledWith({ list: ['input1', 'input2', 'input3'] });
|
||||
|
||||
await click(container, '.remove'); // will remove first input
|
||||
fireEvent.submit(container.querySelector('form')!);
|
||||
await sleep();
|
||||
await waitFakeTimer();
|
||||
expect(onFinish).toHaveBeenLastCalledWith({ list: ['input2', 'input3'] });
|
||||
});
|
||||
|
||||
it('list errors', async () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
let operation: FormListOperation;
|
||||
const { container } = render(
|
||||
<Form>
|
||||
@ -177,15 +199,8 @@ describe('Form.List', () => {
|
||||
);
|
||||
|
||||
async function addItem() {
|
||||
await act(async () => {
|
||||
operation.add();
|
||||
await sleep(100);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
operation.add();
|
||||
await waitFakeTimer();
|
||||
}
|
||||
|
||||
await addItem();
|
||||
@ -193,8 +208,6 @@ describe('Form.List', () => {
|
||||
|
||||
await addItem();
|
||||
expect(container.getElementsByClassName('ant-form-item-explain div')).toHaveLength(0);
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should render empty without errors', () => {
|
||||
@ -243,7 +256,7 @@ describe('Form.List', () => {
|
||||
const { container } = render(<Demo />);
|
||||
fireEvent.click(container.querySelector('button')!);
|
||||
|
||||
await sleep();
|
||||
await waitFakeTimer();
|
||||
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
|
||||
|
@ -22,7 +22,7 @@ export function getFieldId(namePath: InternalNamePath, formName?: string): strin
|
||||
return `${formName}_${mergedId}`;
|
||||
}
|
||||
|
||||
const isIllegalName = formItemNameBlackList.indexOf(mergedId) >= 0;
|
||||
const isIllegalName = formItemNameBlackList.includes(mergedId);
|
||||
|
||||
return isIllegalName ? `${defaultItemNamePrefixCls}_${mergedId}` : mergedId;
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ const Progress: React.FC<ProgressProps> = (props: ProgressProps) => {
|
||||
|
||||
function getProgressStatus() {
|
||||
const { status } = props;
|
||||
if (ProgressStatuses.indexOf(status!) < 0 && getPercentNumber() >= 100) {
|
||||
if (!ProgressStatuses.includes(status!) && getPercentNumber() >= 100) {
|
||||
return 'success';
|
||||
}
|
||||
return status || 'normal';
|
||||
@ -119,7 +119,7 @@ const Progress: React.FC<ProgressProps> = (props: ProgressProps) => {
|
||||
const strokeColorNotArray = Array.isArray(strokeColor) ? strokeColor[0] : strokeColor;
|
||||
const strokeColorNotGradient =
|
||||
typeof strokeColor === 'string' || Array.isArray(strokeColor) ? strokeColor : undefined;
|
||||
let progress;
|
||||
let progress: React.ReactNode;
|
||||
// Render progress shape
|
||||
if (type === 'line') {
|
||||
progress = steps ? (
|
||||
|
@ -41,7 +41,7 @@ export function formatTimeStr(duration: number, format: string) {
|
||||
const templateText = format.replace(escapeRegex, '[]');
|
||||
|
||||
const replacedText = timeUnits.reduce((current, [name, unit]) => {
|
||||
if (current.indexOf(name) !== -1) {
|
||||
if (current.includes(name)) {
|
||||
const value = Math.floor(leftDuration / unit);
|
||||
leftDuration -= value * unit;
|
||||
return current.replace(new RegExp(`${name}+`, 'g'), (match: string) => {
|
||||
|
@ -482,8 +482,8 @@ function InternalTable<RecordType extends object = any>(
|
||||
const defaultPosition = direction === 'rtl' ? 'left' : 'right';
|
||||
const { position } = mergedPagination;
|
||||
if (position !== null && Array.isArray(position)) {
|
||||
const topPos = position.find(p => p.indexOf('top') !== -1);
|
||||
const bottomPos = position.find(p => p.indexOf('bottom') !== -1);
|
||||
const topPos = position.find(p => p.includes('top'));
|
||||
const bottomPos = position.find(p => p.includes('bottom'));
|
||||
const isDisable = position.every(p => `${p}` === 'none');
|
||||
if (!topPos && !bottomPos && !isDisable) {
|
||||
bottomPaginationNode = renderPagination(defaultPosition);
|
||||
|
@ -237,14 +237,14 @@ const Tooltip = React.forwardRef<unknown, TooltipProps>((props, ref) => {
|
||||
|
||||
const transformOrigin = { top: '50%', left: '50%' };
|
||||
|
||||
if (['top', 'Bottom'].includes(placement)) {
|
||||
if (/top|Bottom/.test(placement)) {
|
||||
transformOrigin.top = `${rect.height - align.offset![1]}px`;
|
||||
} else if (['Top', 'bottom'].includes(placement)) {
|
||||
} else if (/Top|bottom/.test(placement)) {
|
||||
transformOrigin.top = `${-align.offset![1]}px`;
|
||||
}
|
||||
if (['left', 'Right'].includes(placement)) {
|
||||
if (/left|Right/.test(placement)) {
|
||||
transformOrigin.left = `${rect.width - align.offset![0]}px`;
|
||||
} else if (['right', 'Left'].includes(placement)) {
|
||||
} else if (/right|Left/.test(placement)) {
|
||||
transformOrigin.left = `${-align.offset![0]}px`;
|
||||
}
|
||||
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
|
||||
|
@ -71,7 +71,7 @@ class ListBody<RecordType extends KeyWiseTransferItem> extends React.Component<
|
||||
|
||||
onItemSelect = (item: RecordType) => {
|
||||
const { onItemSelect, selectedKeys } = this.props;
|
||||
const checked = selectedKeys.indexOf(item.key) >= 0;
|
||||
const checked = selectedKeys.includes(item.key);
|
||||
onItemSelect(item.key, !checked);
|
||||
};
|
||||
|
||||
@ -144,7 +144,7 @@ class ListBody<RecordType extends KeyWiseTransferItem> extends React.Component<
|
||||
>
|
||||
{this.getItems().map(({ renderedEl, renderedText, item }: RenderedItem<RecordType>) => {
|
||||
const { disabled } = item;
|
||||
const checked = selectedKeys.indexOf(item.key) >= 0;
|
||||
const checked = selectedKeys.includes(item.key);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { render } from '../../../tests/utils';
|
||||
import type { TransferProps } from '../index';
|
||||
import Transfer from '../index';
|
||||
|
||||
describe('Transfer.Customize', () => {
|
||||
@ -15,7 +16,7 @@ describe('Transfer.Customize', () => {
|
||||
|
||||
it('props#body does not work anymore', () => {
|
||||
const body = jest.fn();
|
||||
const props = { body };
|
||||
const props = { body } as TransferProps<any>;
|
||||
render(<Transfer {...props} />);
|
||||
expect(errorSpy).not.toHaveBeenCalled();
|
||||
expect(body).not.toHaveBeenCalled();
|
||||
|
@ -14,6 +14,11 @@ const listCommonProps: TransferListProps<any> = {
|
||||
notFoundContent: 'Not Found',
|
||||
} as TransferListProps<any>;
|
||||
|
||||
const listProps: TransferListProps<any> = {
|
||||
...listCommonProps,
|
||||
dataSource: undefined as unknown as any[],
|
||||
};
|
||||
|
||||
describe('Transfer.List', () => {
|
||||
it('should render correctly', () => {
|
||||
const { container } = render(<List {...listCommonProps} />);
|
||||
@ -43,4 +48,9 @@ describe('Transfer.List', () => {
|
||||
expect(instance.current?.handleFilter({ target: 'test' })).toBe(undefined);
|
||||
expect(handleFilter).toHaveBeenCalled();
|
||||
});
|
||||
it('should render correctly when dataSource is not exists', () => {
|
||||
expect(() => {
|
||||
render(<List {...listProps} />);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
@ -29,9 +29,11 @@ interface RecordType {
|
||||
}
|
||||
|
||||
interface DataType {
|
||||
key: string;
|
||||
title: string;
|
||||
tag: string;
|
||||
description: string;
|
||||
disabled: boolean;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
interface TableTransferProps extends TransferProps<TransferItem> {
|
||||
|
@ -69,7 +69,7 @@ export interface TransferProps<RecordType> {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
dataSource: RecordType[];
|
||||
dataSource?: RecordType[];
|
||||
targetKeys?: string[];
|
||||
selectedKeys?: string[];
|
||||
render?: TransferRender<RecordType>;
|
||||
@ -116,13 +116,6 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
|
||||
static Search = Search;
|
||||
|
||||
static defaultProps = {
|
||||
dataSource: [],
|
||||
locale: {},
|
||||
showSearch: false,
|
||||
listStyle: () => {},
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps<T>({
|
||||
selectedKeys,
|
||||
targetKeys,
|
||||
@ -156,8 +149,8 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
|
||||
const { selectedKeys = [], targetKeys = [] } = props;
|
||||
this.state = {
|
||||
sourceSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) === -1),
|
||||
targetSelectedKeys: selectedKeys.filter(key => targetKeys.indexOf(key) > -1),
|
||||
sourceSelectedKeys: selectedKeys.filter(key => !targetKeys.includes(key)),
|
||||
targetSelectedKeys: selectedKeys.filter(key => targetKeys.includes(key)),
|
||||
};
|
||||
}
|
||||
|
||||
@ -180,11 +173,10 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
return this.props.titles ?? transferLocale.titles ?? [];
|
||||
}
|
||||
|
||||
getLocale = (transferLocale: TransferLocale, renderEmpty: RenderEmptyHandler) => ({
|
||||
...transferLocale,
|
||||
notFoundContent: renderEmpty('Transfer'),
|
||||
...this.props.locale,
|
||||
});
|
||||
getLocale = (transferLocale: TransferLocale, renderEmpty: RenderEmptyHandler) => {
|
||||
const { locale = {} } = this.props;
|
||||
return { ...transferLocale, notFoundContent: renderEmpty('Transfer'), ...locale };
|
||||
};
|
||||
|
||||
moveTo = (direction: TransferDirection) => {
|
||||
const { targetKeys = [], dataSource = [], onChange } = this.props;
|
||||
@ -192,13 +184,13 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
const moveKeys = direction === 'right' ? sourceSelectedKeys : targetSelectedKeys;
|
||||
// filter the disabled options
|
||||
const newMoveKeys = moveKeys.filter(
|
||||
(key: string) => !dataSource.some(data => !!(key === data.key && data.disabled)),
|
||||
key => !dataSource.some(data => !!(key === data.key && data.disabled)),
|
||||
);
|
||||
// move items to target box
|
||||
const newTargetKeys =
|
||||
direction === 'right'
|
||||
? newMoveKeys.concat(targetKeys)
|
||||
: targetKeys.filter(targetKey => newMoveKeys.indexOf(targetKey) === -1);
|
||||
: targetKeys.filter(targetKey => !newMoveKeys.includes(targetKey));
|
||||
|
||||
// empty checked keys
|
||||
const oppositeDirection = direction === 'right' ? 'left' : 'right';
|
||||
@ -214,13 +206,13 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
|
||||
onItemSelectAll = (direction: TransferDirection, selectedKeys: string[], checkAll: boolean) => {
|
||||
this.setStateKeys(direction, prevKeys => {
|
||||
let mergedCheckedKeys = [];
|
||||
let mergedCheckedKeys: string[] = [];
|
||||
if (checkAll) {
|
||||
// Merge current keys with origin key
|
||||
mergedCheckedKeys = Array.from(new Set([...prevKeys, ...selectedKeys]));
|
||||
mergedCheckedKeys = Array.from(new Set<string>([...prevKeys, ...selectedKeys]));
|
||||
} else {
|
||||
// Remove current keys from origin keys
|
||||
mergedCheckedKeys = prevKeys.filter((key: string) => selectedKeys.indexOf(key) === -1);
|
||||
mergedCheckedKeys = prevKeys.filter(key => !selectedKeys.includes(key));
|
||||
}
|
||||
|
||||
this.handleSelectChange(direction, mergedCheckedKeys);
|
||||
@ -324,7 +316,7 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
};
|
||||
|
||||
separateDataSource() {
|
||||
const { dataSource, rowKey, targetKeys = [] } = this.props;
|
||||
const { dataSource = [], rowKey, targetKeys = [] } = this.props;
|
||||
|
||||
const leftDataSource: KeyWise<RecordType>[] = [];
|
||||
const rightDataSource: KeyWise<RecordType>[] = new Array(targetKeys.length);
|
||||
@ -365,10 +357,10 @@ class Transfer<RecordType extends TransferItem = TransferItem> extends React.Com
|
||||
className,
|
||||
disabled,
|
||||
operations = [],
|
||||
showSearch,
|
||||
showSearch = false,
|
||||
footer,
|
||||
style,
|
||||
listStyle,
|
||||
listStyle = {},
|
||||
operationStyle,
|
||||
filterOption,
|
||||
render,
|
||||
|
@ -81,12 +81,6 @@ interface TransferListState {
|
||||
export default class TransferList<
|
||||
RecordType extends KeyWiseTransferItem,
|
||||
> extends React.PureComponent<TransferListProps<RecordType>, TransferListState> {
|
||||
static defaultProps = {
|
||||
dataSource: [],
|
||||
titleText: '',
|
||||
showSearch: false,
|
||||
};
|
||||
|
||||
timer: number;
|
||||
|
||||
triggerScrollTimer: number;
|
||||
@ -109,7 +103,7 @@ export default class TransferList<
|
||||
if (checkedKeys.length === 0) {
|
||||
return 'none';
|
||||
}
|
||||
if (filteredItems.every(item => checkedKeys.indexOf(item.key) >= 0 || !!item.disabled)) {
|
||||
if (filteredItems.every(item => checkedKeys.includes(item.key) || !!item.disabled)) {
|
||||
return 'all';
|
||||
}
|
||||
return 'part';
|
||||
@ -161,7 +155,7 @@ export default class TransferList<
|
||||
if (filterOption) {
|
||||
return filterOption(filterValue, item);
|
||||
}
|
||||
return text.indexOf(filterValue) >= 0;
|
||||
return text.includes(filterValue);
|
||||
};
|
||||
|
||||
// =============================== Render ===============================
|
||||
@ -305,12 +299,12 @@ export default class TransferList<
|
||||
const { filterValue } = this.state;
|
||||
const {
|
||||
prefixCls,
|
||||
dataSource,
|
||||
titleText,
|
||||
dataSource = [],
|
||||
titleText = '',
|
||||
checkedKeys,
|
||||
disabled,
|
||||
footer,
|
||||
showSearch,
|
||||
showSearch = false,
|
||||
style,
|
||||
searchPlaceholder,
|
||||
notFoundContent,
|
||||
|
@ -65,12 +65,7 @@ export function calcRangeKeys({
|
||||
// Append selection
|
||||
keys.push(key);
|
||||
}
|
||||
|
||||
if (expandedKeys.indexOf(key) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return expandedKeys.includes(key);
|
||||
});
|
||||
|
||||
return keys;
|
||||
|
@ -19,7 +19,7 @@ const Title = React.forwardRef<HTMLElement, TitleProps>((props, ref) => {
|
||||
const { level = 1, ...restProps } = props;
|
||||
let component: keyof JSX.IntrinsicElements;
|
||||
|
||||
if (TITLE_ELE_LIST.indexOf(level) !== -1) {
|
||||
if (TITLE_ELE_LIST.includes(level)) {
|
||||
component = `h${level}`;
|
||||
} else {
|
||||
warning(
|
||||
|
@ -262,14 +262,14 @@ describe('Typography', () => {
|
||||
</Paragraph>,
|
||||
);
|
||||
|
||||
if (triggerType === undefined || triggerType.indexOf('icon') !== -1) {
|
||||
if (triggerType === undefined || triggerType.includes('icon')) {
|
||||
if (icon) {
|
||||
expect(wrapper.querySelectorAll('.anticon-highlight').length).toBeGreaterThan(0);
|
||||
} else {
|
||||
expect(wrapper.querySelectorAll('.anticon-edit').length).toBeGreaterThan(0);
|
||||
}
|
||||
|
||||
if (triggerType === undefined || triggerType.indexOf('text') === -1) {
|
||||
if (triggerType === undefined || !triggerType.includes('text')) {
|
||||
fireEvent.click(wrapper.firstChild!);
|
||||
expect(onStart).not.toHaveBeenCalled();
|
||||
}
|
||||
@ -295,15 +295,15 @@ describe('Typography', () => {
|
||||
fireEvent.click(wrapper.querySelectorAll('.ant-typography-edit')[0]);
|
||||
|
||||
expect(onStart).toHaveBeenCalled();
|
||||
if (triggerType !== undefined && triggerType.indexOf('text') !== -1) {
|
||||
if (triggerType !== undefined && triggerType.includes('text')) {
|
||||
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) {
|
||||
if (triggerType !== undefined && triggerType.includes('text')) {
|
||||
if (!triggerType.includes('icon')) {
|
||||
expect(wrapper.querySelectorAll('.anticon-highlight').length).toBe(0);
|
||||
expect(wrapper.querySelectorAll('.anticon-edit').length).toBe(0);
|
||||
}
|
||||
|
@ -49,6 +49,18 @@ See: https://ant.design/docs/react/customize-theme .
|
||||
|
||||
While you can override a component's style, we don't recommend doing so. antd is not only a set of React components, but also a design specification as well.
|
||||
|
||||
## How to avoid breaking change when update version?
|
||||
|
||||
antd will avoid breaking change in minor & patch version. You can safe do follow things:
|
||||
|
||||
- Official demo usage
|
||||
- FAQ suggestion. Including codesandbox sample, marked as FAQ issue
|
||||
|
||||
And which you should avoid to do:
|
||||
|
||||
- Bug as feature. It will break in any other case (e.g. Use div as Tabs children)
|
||||
- Use magic code to realize requirement but which can be realized with normal API
|
||||
|
||||
## How do I replace Moment.js with Day.js to reduce bundle size?
|
||||
|
||||
Please refer to [Replace Moment.js](/docs/react/replace-moment).
|
||||
|
@ -49,6 +49,18 @@ title: FAQ
|
||||
|
||||
你可以覆盖它们的样式,但是我们不推荐这么做。antd 是一系列 React 组件,但同样是一套设计规范。
|
||||
|
||||
## 如何避免升级导致的破坏性变更?
|
||||
|
||||
antd 在 minor 和 patch 版本迭代中会避免引入破坏性变更,遵从以下原则会确保不会破坏你的代码:
|
||||
|
||||
- 使用出现在官方 Demo 中的写法
|
||||
- FAQ 中出现的解法,包含代码片段以及 codesandbox 示例、issue 中当前版本标记 FAQ label 的
|
||||
|
||||
而下述变更则需要开发者自行校验:
|
||||
|
||||
- 特定场景的错误用法,BUG as Feature(例如 Tabs 下直接包 div 的用法)
|
||||
- 可以通过正常用法实现功能需求却魔改的
|
||||
|
||||
## 如何使用 Day.js 替换 Moment.js 来减小打包大小?
|
||||
|
||||
可以参考[替换 Moment.js](/docs/react/replace-moment)。
|
||||
|
@ -68,10 +68,13 @@ Let's create a `ProductList` component that we can use in multiple places to sho
|
||||
|
||||
Create `src/components/ProductList.tsx` by typing:
|
||||
|
||||
```js
|
||||
```tsx
|
||||
import { Table, Popconfirm, Button } from 'antd';
|
||||
|
||||
const ProductList = ({ onDelete, products }) => {
|
||||
const ProductList: React.FC<{ products: { name: string }[]; onDelete: (id: string) => void }> = ({
|
||||
onDelete,
|
||||
products,
|
||||
}) => {
|
||||
const columns = [
|
||||
{
|
||||
title: 'Name',
|
||||
@ -112,16 +115,12 @@ export function queryProductList() {
|
||||
export function queryProductList() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 1,
|
||||
name: 'dva',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'antd',
|
||||
},
|
||||
]);
|
||||
resolve({
|
||||
data: [
|
||||
{ id: 1, name: 'dva' },
|
||||
{ id: 2, name: 'antd' },
|
||||
],
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
@ -134,7 +133,7 @@ import { useRequest } from 'umi';
|
||||
import { queryProductList } from '@/services/product';
|
||||
|
||||
export default function useProductList(params: { pageSize: number; current: number }) {
|
||||
const msg = useRequest(() => queryUserList(params));
|
||||
const msg = useRequest(() => queryProductList(params));
|
||||
|
||||
const deleteProducts = async (id: string) => {
|
||||
try {
|
||||
|
@ -109,16 +109,12 @@ export function queryProductList() {
|
||||
export function queryProductList() {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
resolve([
|
||||
{
|
||||
id: 1,
|
||||
name: 'dva',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'antd',
|
||||
},
|
||||
]);
|
||||
resolve({
|
||||
data: [
|
||||
{ id: 1, name: 'dva' },
|
||||
{ id: 2, name: 'antd' },
|
||||
],
|
||||
});
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ async function execute() {
|
||||
logs = _.remove(logs, ({ author_email: email }) => {
|
||||
for (let i = 0; i < excludes.length; i++) {
|
||||
const item = excludes[i];
|
||||
if (email.indexOf(item) !== -1) {
|
||||
if (email.includes(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export default class Palette extends React.Component {
|
||||
this.hexColors = {};
|
||||
Object.keys(this.colorNodes).forEach(key => {
|
||||
const computedColor = getComputedStyle(this.colorNodes[key])['background-color'];
|
||||
if (computedColor.indexOf('rgba') >= 0) {
|
||||
if (computedColor.includes('rgba')) {
|
||||
this.hexColors[key] = computedColor;
|
||||
} else {
|
||||
this.hexColors[key] = rgbToHex(computedColor);
|
||||
|
@ -3,7 +3,7 @@ import MainContent from './MainContent';
|
||||
import * as utils from '../utils';
|
||||
|
||||
function isChangelog(pathname) {
|
||||
return pathname.indexOf('changelog') >= 0;
|
||||
return pathname.includes('changelog');
|
||||
}
|
||||
|
||||
export default collect(async nextProps => {
|
||||
|
Loading…
Reference in New Issue
Block a user