chore: auto merge branches (#38111)

chore: sync master to feature
This commit is contained in:
github-actions[bot] 2022-10-19 09:28:49 +00:00 committed by GitHub
commit f71ee5384f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 442 additions and 310 deletions

View File

@ -7,7 +7,7 @@ version: 2.1
jobs:
test-argos-ci:
docker:
- image: circleci/node:16-browsers
- image: cimg/node:lts-browsers
steps:
- checkout
- run:
@ -16,9 +16,6 @@ jobs:
- run:
name: Install argos cli
command: npm i fast-glob lodash @argos-ci/core
- run:
name: Install puppeteer
command: node node_modules/puppeteer/install.js
- run:
name: Build dist file
command: npm run dist

View File

@ -31,7 +31,7 @@ module.exports = {
'@typescript-eslint/no-unused-vars': [2, { args: 'none' }],
'no-unused-expressions': 'off',
'@typescript-eslint/no-unused-expressions': 2,
'@typescript-eslint/consistent-type-imports': 2,
'@typescript-eslint/consistent-type-imports': [2, { disallowTypeAnnotations: false }],
},
},
{

View File

@ -3,7 +3,6 @@ import { easeInOutCubic } from '../easings';
describe('Test easings', () => {
it('easeInOutCubic return value', () => {
const nums: number[] = [];
// eslint-disable-next-line no-plusplus
for (let index = 0; index < 5; index++) {
nums.push(easeInOutCubic(index, 1, 5, 4));
}

View File

@ -2,17 +2,14 @@ import { waitFakeTimer } from '../../../tests/utils';
import scrollTo from '../scrollTo';
describe('Test ScrollTo function', () => {
let dateNowMock: jest.SpyInstance;
const dateNowMock = jest.spyOn(Date, 'now');
beforeAll(() => {
jest.useFakeTimers();
});
beforeEach(() => {
dateNowMock = jest
.spyOn(Date, 'now')
.mockImplementationOnce(() => 0)
.mockImplementationOnce(() => 1000);
dateNowMock.mockReturnValueOnce(0).mockReturnValueOnce(1000);
});
afterAll(() => {
@ -21,7 +18,7 @@ describe('Test ScrollTo function', () => {
afterEach(() => {
jest.clearAllTimers();
dateNowMock.mockRestore();
dateNowMock.mockClear();
});
it('test scrollTo', async () => {

View File

@ -4,7 +4,7 @@ import { render, fireEvent } from '../../../tests/utils';
describe('Table', () => {
it('useSyncState', () => {
const Test: React.FC = () => {
const Test = () => {
const [getVal, setVal] = useSyncState('light');
return <span onClick={() => setVal('bamboo')}>{getVal()}</span>;
};

View File

@ -236,15 +236,19 @@ describe('Wave component', () => {
fakeDoc.appendChild(document.createElement('span'));
expect(fakeDoc.childNodes).toHaveLength(2);
(container.querySelector('.bamboo') as any).getRootNode = () => fakeDoc;
const elem = container.querySelector('.bamboo');
// Click should not throw
fireEvent.click(container.querySelector('.bamboo')!);
act(() => {
jest.runAllTimers();
});
if (elem) {
elem.getRootNode = () => fakeDoc;
expect(fakeDoc.querySelector('style')).toBeTruthy();
// Click should not throw
fireEvent.click(elem);
act(() => {
jest.runAllTimers();
});
expect(fakeDoc.querySelector('style')).toBeTruthy();
}
jest.useRealTimers();
});

View File

@ -1,7 +1,6 @@
import { updateCSS } from 'rc-util/lib/Dom/dynamicCSS';
import { composeRef, supportRef } from 'rc-util/lib/ref';
import * as React from 'react';
import { forwardRef } from 'react';
import type { ConfigConsumerProps, CSPConfig } from '../config-provider';
import { ConfigConsumer, ConfigContext } from '../config-provider';
import raf from './raf';
@ -42,7 +41,7 @@ export interface WaveProps {
children?: React.ReactNode;
}
class InternalWave extends React.Component<WaveProps> {
class Wave extends React.Component<WaveProps> {
static contextType = ConfigContext;
private instance?: {
@ -237,8 +236,4 @@ class InternalWave extends React.Component<WaveProps> {
}
}
const Wave = forwardRef<InternalWave, WaveProps>((props, ref) => (
<InternalWave ref={ref} {...props} />
));
export default Wave;

View File

@ -29,17 +29,17 @@ const Content = () => {
};
it('Delay loading timer in Button component', () => {
const otherTimer: any = 9528;
jest.spyOn(window, 'setTimeout').mockReturnValue(otherTimer);
const otherTimer = 9528;
jest.spyOn<Window, 'setTimeout'>(window, 'setTimeout').mockReturnValue(otherTimer);
jest.restoreAllMocks();
const wrapper = render(<Content />);
const btnTimer: any = 9527;
jest.spyOn(window, 'setTimeout').mockReturnValue(btnTimer);
jest.spyOn(window, 'clearTimeout');
const setTimeoutMock = window.setTimeout as any as jest.Mock;
const clearTimeoutMock = window.clearTimeout as any as jest.Mock;
const btnTimer = 9527;
const setTimeoutMock = jest
.spyOn<Window, 'setTimeout'>(window, 'setTimeout')
.mockReturnValue(btnTimer);
const clearTimeoutMock = jest.spyOn<Window, 'clearTimeout'>(window, 'clearTimeout');
// other component may call setTimeout or clearTimeout
const setTimeoutCount = () => {
@ -58,7 +58,11 @@ it('Delay loading timer in Button component', () => {
// trigger timer handler
act(() => {
setTimeoutMock.mock.calls[0][0]();
const timerHandler = setTimeoutMock.mock.calls[0][0];
if (typeof timerHandler === 'function') {
timerHandler();
}
});
expect(setTimeoutCount()).toBe(1);
expect(clearTimeoutCount()).toBe(0);

View File

@ -7,7 +7,6 @@ import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, sleep } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import type { SizeType } from '../../config-provider/SizeContext';
describe('Button', () => {
mountTest(Button);
@ -38,7 +37,8 @@ describe('Button', () => {
it('warns if size is wrong', () => {
resetWarned();
const mockWarn = jest.spyOn(console, 'error').mockImplementation(() => {});
const size = 'who am I' as any as SizeType;
const size = 'who am I';
// @ts-expect-error: Type '"who am I"' is not assignable to type 'SizeType'.ts(2322)
render(<Button.Group size={size} />);
expect(mockWarn).toHaveBeenCalledWith('Warning: [antd: Button.Group] Invalid prop `size`.');

View File

@ -1,18 +1,19 @@
import React from 'react';
import Button from '..';
import { fireEvent, render, sleep } from '../../../tests/utils';
import { fireEvent, render, sleep, assertsExist } from '../../../tests/utils';
// Mock Wave ref
let waveInstanceMock: any;
let waveInstanceMock: InstanceType<typeof import('../../_util/wave').default> | null;
jest.mock('../../_util/wave', () => {
const Wave = jest.requireActual('../../_util/wave');
const Wave: typeof import('../../_util/wave') = jest.requireActual('../../_util/wave');
const WaveComponent = Wave.default;
return {
...Wave,
__esModule: true,
default: (props: any) => (
default: (props: import('../../_util/wave').WaveProps) => (
<WaveComponent
ref={(node: any) => {
ref={node => {
waveInstanceMock = node;
}}
{...props}
@ -77,12 +78,14 @@ describe('click wave effect', () => {
it('should run resetEffect in transitionstart', async () => {
const wrapper = render(<Button type="primary">button</Button>);
assertsExist(waveInstanceMock);
const resetEffect = jest.spyOn(waveInstanceMock, 'resetEffect');
await clickButton(wrapper);
expect(resetEffect).toHaveBeenCalledTimes(1);
fireEvent.click(wrapper.container.querySelector('.ant-btn')!);
await sleep(10);
expect(resetEffect).toHaveBeenCalledTimes(2);
// @ts-expect-error: Property 'animationStart' is private and only accessible within class 'Wave'.ts(2341)
waveInstanceMock.animationStart = false;
fireEvent(wrapper.container.querySelector('.ant-btn')!, new Event('transitionstart'));
expect(resetEffect).toHaveBeenCalledTimes(3);
@ -91,6 +94,7 @@ describe('click wave effect', () => {
it('should handle transitionend', async () => {
const wrapper = render(<Button type="primary">button</Button>);
assertsExist(waveInstanceMock);
const resetEffect = jest.spyOn(waveInstanceMock, 'resetEffect');
await clickButton(wrapper);
expect(resetEffect).toHaveBeenCalledTimes(1);

View File

@ -13,19 +13,16 @@ import Button from '../../radio/radioButton';
import Select from '../../select';
import Header, { type CalendarHeaderProps } from '../Header';
function calendarProps(): PickerPanelProps<any> {
return (global as any).calendarProps;
}
function calendarHeaderProps(): CalendarHeaderProps<any> {
return (global as any).calendarHeaderProps;
}
const ref: {
calendarProps?: PickerPanelProps<unknown>;
calendarHeaderProps?: CalendarHeaderProps<unknown>;
} = {};
jest.mock('../Header', () => {
const HeaderModule = jest.requireActual('../Header');
const HeaderComponent = HeaderModule.default;
return (props: CalendarHeaderProps<any>) => {
(global as any).calendarHeaderProps = props;
ref.calendarHeaderProps = props;
return <HeaderComponent {...props} />;
};
});
@ -35,8 +32,8 @@ jest.mock('rc-picker', () => {
const PickerPanelComponent = RcPicker.PickerPanel;
return {
...RcPicker,
PickerPanel: (props: PickerPanelProps<any>) => {
(global as any).calendarProps = props;
PickerPanel: (props: PickerPanelProps<unknown>) => {
ref.calendarProps = props;
return <PickerPanelComponent {...props} />;
},
};
@ -152,8 +149,8 @@ describe('Calendar', () => {
it('getDateRange should returns a disabledDate function', () => {
const validRange: [Moment.Moment, Moment.Moment] = [Moment('2018-02-02'), Moment('2018-05-18')];
render(<Calendar validRange={validRange} defaultValue={Moment('2018-02-02')} />);
expect(calendarProps().disabledDate?.(Moment('2018-06-02'))).toBe(true);
expect(calendarProps().disabledDate?.(Moment('2018-04-02'))).toBe(false);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-06-02'))).toBe(true);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-04-02'))).toBe(false);
});
it('validRange should work with disabledDate function', () => {
@ -162,11 +159,11 @@ describe('Calendar', () => {
<Calendar validRange={validRange} disabledDate={data => data.isSame(Moment('2018-02-03'))} />,
);
expect(calendarProps().disabledDate?.(Moment('2018-02-01'))).toBe(true);
expect(calendarProps().disabledDate?.(Moment('2018-02-02'))).toBe(false);
expect(calendarProps().disabledDate?.(Moment('2018-02-03'))).toBe(true);
expect(calendarProps().disabledDate?.(Moment('2018-02-04'))).toBe(false);
expect(calendarProps().disabledDate?.(Moment('2018-06-01'))).toBe(true);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-02-01'))).toBe(true);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-02-02'))).toBe(false);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-02-03'))).toBe(true);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-02-04'))).toBe(false);
expect(ref.calendarProps?.disabledDate?.(Moment('2018-06-01'))).toBe(true);
});
it('Calendar MonthSelect should display correct label', () => {
@ -181,9 +178,9 @@ describe('Calendar', () => {
const monthMode = 'month';
const yearMode = 'year';
const wrapper = render(<Calendar />);
expect(calendarHeaderProps().mode).toEqual(monthMode);
expect(ref.calendarHeaderProps?.mode).toEqual(monthMode);
wrapper.rerender(<Calendar mode={yearMode} />);
expect(calendarHeaderProps().mode).toEqual(yearMode);
expect(ref.calendarHeaderProps?.mode).toEqual(yearMode);
});
it('Calendar should switch mode', () => {
@ -191,9 +188,9 @@ describe('Calendar', () => {
const yearMode = 'year';
const onPanelChangeStub = jest.fn();
const wrapper = render(<Calendar mode={yearMode} onPanelChange={onPanelChangeStub} />);
expect(calendarHeaderProps().mode).toEqual(yearMode);
expect(ref.calendarHeaderProps?.mode).toEqual(yearMode);
wrapper.rerender(<Calendar mode={monthMode} onPanelChange={onPanelChangeStub} />);
expect(calendarHeaderProps().mode).toEqual(monthMode);
expect(ref.calendarHeaderProps?.mode).toEqual(monthMode);
expect(onPanelChangeStub).toHaveBeenCalledTimes(0);
});
@ -234,7 +231,7 @@ describe('Calendar', () => {
const date = Moment(new Date(Date.UTC(2017, 7, 9, 8)));
const wrapper = render(<Calendar onPanelChange={onPanelChange} value={date} />);
expect(calendarHeaderProps().mode).toBe('month');
expect(ref.calendarHeaderProps?.mode).toBe('month');
expect(wrapper.container.querySelectorAll('.ant-picker-date-panel').length).toBe(1);
expect(wrapper.container.querySelectorAll('.ant-picker-month-panel').length).toBe(0);
fireEvent.click(wrapper.container.querySelector('.ant-radio-button-input[value="year"]')!);

View File

@ -262,7 +262,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
4
</h3>
@ -282,7 +282,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
1
</h3>
@ -302,7 +302,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
2
</h3>
@ -322,7 +322,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
3
</h3>
@ -342,7 +342,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
4
</h3>
@ -362,7 +362,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
1
</h3>
@ -382,7 +382,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
2
</h3>
@ -402,7 +402,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
3
</h3>
@ -422,7 +422,7 @@ exports[`renders ./components/carousel/demo/basic.md extend context correctly 1`
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
4
</h3>

View File

@ -262,7 +262,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
4
</h3>
@ -282,7 +282,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
1
</h3>
@ -302,7 +302,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
2
</h3>
@ -322,7 +322,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
3
</h3>
@ -342,7 +342,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
4
</h3>
@ -362,7 +362,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
1
</h3>
@ -382,7 +382,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
2
</h3>
@ -402,7 +402,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
3
</h3>
@ -422,7 +422,7 @@ exports[`renders ./components/carousel/demo/basic.md correctly 1`] = `
tabindex="-1"
>
<h3
style="height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
style="margin:0;height:160px;color:#fff;line-height:160px;text-align:center;background:#364d79"
>
4
</h3>

View File

@ -18,6 +18,7 @@ import { Carousel } from 'antd';
import React from 'react';
const contentStyle: React.CSSProperties = {
margin: 0,
height: '160px',
color: '#fff',
lineHeight: '160px',

View File

@ -180,6 +180,7 @@
display: flex !important;
justify-content: center;
margin-right: 15%;
margin-bottom: 0;
margin-left: 15%;
padding-left: 0;
list-style: none;

View File

@ -242,10 +242,10 @@ describe('CheckboxGroup', () => {
const onChange = jest.fn();
const Demo: React.FC = () => {
const [v, setV] = useState<string>('');
const [v, setV] = useState('');
React.useEffect(() => {
setTimeout(setV('1') as unknown as TimerHandler, 1000);
setV('1');
}, []);
return (

View File

@ -1,7 +1,6 @@
import type { ChangeEventHandler } from 'react';
import React, { useState } from 'react';
import scrollIntoView from 'scroll-into-view-if-needed';
import userEvent from '@testing-library/user-event';
import classNames from 'classnames';
import type { ColProps } from 'antd/es/grid';
import type { FormInstance } from '..';
@ -20,15 +19,7 @@ import Switch from '../../switch';
import TreeSelect from '../../tree-select';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import {
fireEvent,
render,
sleep,
act,
screen,
pureRender,
waitFakeTimer,
} from '../../../tests/utils';
import { fireEvent, render, screen, pureRender, waitFakeTimer } from '../../../tests/utils';
import ConfigProvider from '../../config-provider';
import Drawer from '../../drawer';
import zhCN from '../../locale/zh_CN';
@ -51,27 +42,52 @@ describe('Form', () => {
const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const change = async (
container: ReturnType<typeof render>['container'],
index: number,
value: string,
executeMockTimer: boolean,
) => {
fireEvent.change(container.querySelectorAll('input')?.[index], { target: { value } });
await sleep(200);
// const change = async (
// container: ReturnType<typeof render>['container'],
// index: number,
// value: string,
// executeMockTimer: boolean,
// ) => {
// fireEvent.change(container.querySelectorAll('input')?.[index], { target: { value } });
// await sleep(200);
if (executeMockTimer) {
for (let i = 0; i < 10; i += 1) {
act(() => {
jest.runAllTimers();
});
}
await sleep(1);
// if (executeMockTimer) {
// for (let i = 0; i < 10; i += 1) {
// act(() => {
// jest.runAllTimers();
// });
// }
// await sleep(1);
// }
// };
const changeValue = async (
input: HTMLElement | null | number,
value: string,
advTimer = 1000,
) => {
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, 20);
}
};
beforeEach(() => {
jest.useRealTimers();
document.body.innerHTML = '';
jest.useFakeTimers();
(scrollIntoView as any).mockReset();
});
@ -80,6 +96,8 @@ describe('Form', () => {
});
afterAll(() => {
jest.clearAllTimers();
jest.useRealTimers();
errorSpy.mockRestore();
warnSpy.mockRestore();
(scrollIntoView as any).mockRestore();
@ -100,34 +118,38 @@ describe('Form', () => {
);
// user type something and clear
await userEvent.type(screen.getByLabelText('test'), 'test');
await userEvent.clear(screen.getByLabelText('test'));
await changeValue(0, 'test');
await changeValue(0, '');
// should show alert with correct message and show correct styles
await expect(screen.findByRole('alert')).resolves.toHaveTextContent("'test' is required");
expect(screen.getByLabelText('test')).toHaveClass('ant-input-status-error');
expect(container.querySelectorAll('.ant-form-item-has-error').length).toBeTruthy();
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
"'test' is required",
);
expect(container.querySelector('.ant-input-status-error')).toBeTruthy();
expect(container.querySelector('.ant-form-item-has-error')).toBeTruthy();
expect(onChange).toHaveBeenCalled();
});
it('should clean up', async () => {
jest.useFakeTimers();
const Demo: React.FC = () => {
const [form] = Form.useForm();
const onChange = async () => {
// Wait a while and then some logic to validate
await waitFakeTimer();
try {
await form.validateFields();
} catch (err) {
// do nothing
}
};
return (
<Form form={form} initialValues={{ aaa: '2' }}>
<Form.Item name="aaa">
<Input
onChange={async () => {
await sleep(0);
try {
await form.validateFields();
} catch {
// do nothing
}
}}
/>
<Input onChange={onChange} />
</Form.Item>
<Form.Item shouldUpdate noStyle>
{() => {
@ -155,14 +177,18 @@ describe('Form', () => {
};
const { container } = render(<Demo />);
await change(container, 0, '1', true);
expect(screen.getByRole('alert')).toHaveTextContent('aaa');
await change(container, 0, '2', true);
expect(screen.getByRole('alert')).toHaveTextContent('ccc');
await change(container, 0, '1', true);
expect(screen.getByRole('alert')).toHaveTextContent('aaa');
jest.useRealTimers();
await changeValue(0, '1');
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent('aaa');
await changeValue(0, '2');
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent('ccc');
await changeValue(0, '1');
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent('aaa');
});
});
@ -176,6 +202,7 @@ describe('Form', () => {
'Warning: [antd: Form.Item] `children` of render props only work with `shouldUpdate` or `dependencies`.',
);
});
it("`shouldUpdate` shouldn't work with `dependencies`", () => {
render(
<Form>
@ -255,7 +282,6 @@ describe('Form', () => {
});
it('input element should have the prop aria-describedby pointing to the help id when there are errors', async () => {
jest.useFakeTimers();
const { container } = pureRender(
<Form>
<Form.Item name="test" rules={[{ len: 3 }, { type: 'number' }]}>
@ -263,15 +289,11 @@ describe('Form', () => {
</Form.Item>
</Form>,
);
fireEvent.change(container.querySelector('input')!, { target: { value: 'Invalid number' } });
await waitFakeTimer();
await changeValue(0, 'Invalid number');
expect(container.querySelector('input')?.getAttribute('aria-describedby')).toBe('test_help');
expect(container.querySelector('.ant-form-item-explain')?.id).toBe('test_help');
jest.clearAllTimers();
jest.useRealTimers();
});
it('input element should have the prop aria-invalid when there are errors', async () => {
@ -283,8 +305,7 @@ describe('Form', () => {
</Form>,
);
fireEvent.change(container.querySelector('input')!, { target: { value: 'Invalid number' } });
await sleep(800);
await changeValue(0, 'Invalid number');
expect(container.querySelector('input')?.getAttribute('aria-invalid')).toBe('true');
});
@ -407,7 +428,7 @@ describe('Form', () => {
it('scrollToFirstError', async () => {
const onFinishFailed = jest.fn();
render(
const { container } = render(
<Form scrollToFirstError={{ block: 'center' }} onFinishFailed={onFinishFailed}>
<Form.Item name="test" rules={[{ required: true }]}>
<input />
@ -419,7 +440,9 @@ describe('Form', () => {
);
expect(scrollIntoView).not.toHaveBeenCalled();
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
const inputNode = document.getElementById('test');
expect(scrollIntoView).toHaveBeenCalledWith(inputNode, {
block: 'center',
@ -452,7 +475,7 @@ describe('Form', () => {
});
it('dynamic change required', async () => {
render(
const { container } = render(
<Form>
<Form.Item label="light" name="light" valuePropName="checked">
<input type="checkbox" />
@ -469,45 +492,24 @@ describe('Form', () => {
);
// should not show alert by default
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
expect(container.querySelector('.ant-form-item-explain')).toBeFalsy();
// click to change the light field value to true
await userEvent.click(screen.getByLabelText('light'));
fireEvent.click(container.querySelector('input')!);
await waitFakeTimer();
// user input something and clear
await userEvent.type(screen.getByLabelText('bamboo'), '1');
await userEvent.clear(screen.getByLabelText('bamboo'));
await changeValue(1, '1');
await changeValue(1, '');
// should show alert says that the field is required
await expect(screen.findByRole('alert')).resolves.toHaveTextContent("'bamboo' is required");
});
it('should show alert with string when help is non-empty string', async () => {
render(
<Form>
<Form.Item help="good">
<input />
</Form.Item>
</Form>,
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
"'bamboo' is required",
);
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('good');
});
it('should show alert with empty string when help is empty string', async () => {
render(
<Form>
<Form.Item help="">
<input />
</Form.Item>
</Form>,
);
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('');
});
describe('should show related className when customize help', () => {
it('normal', () => {
it('normal', async () => {
const { container } = render(
<Form>
<Form.Item help="good">
@ -515,10 +517,14 @@ describe('Form', () => {
</Form.Item>
</Form>,
);
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('good');
expect(container.querySelector('.ant-form-item-with-help')).toBeTruthy();
});
it('empty string', () => {
it('empty string', async () => {
const { container } = render(
<Form>
<Form.Item help="">
@ -526,6 +532,10 @@ describe('Form', () => {
</Form.Item>
</Form>,
);
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('');
expect(container.querySelector('.ant-form-item-with-help')).toBeTruthy();
});
});
@ -539,8 +549,6 @@ describe('Form', () => {
// https://github.com/ant-design/ant-design/issues/20706
it('Error change should work', async () => {
jest.useFakeTimers();
const { container } = render(
<Form>
<Form.Item
@ -565,17 +573,16 @@ describe('Form', () => {
/* eslint-disable no-await-in-loop */
for (let i = 0; i < 3; i += 1) {
await change(container, 0, 'bamboo', true);
await change(container, 0, '', true);
await changeValue(0, 'bamboo');
await changeValue(0, '');
expect(container.querySelector('.ant-form-item-explain')?.textContent).toEqual(
"'name' is required",
);
await change(container, 0, 'p', true);
await sleep(100);
await changeValue(0, 'p');
expect(container.querySelector('.ant-form-item-explain')?.textContent).toEqual('not a p');
}
/* eslint-enable */
jest.useRealTimers();
});
// https://github.com/ant-design/ant-design/issues/20813
@ -592,15 +599,17 @@ describe('Form', () => {
);
};
render(<App />);
const { container } = render(<App />);
// should show initial text
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('');
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('');
await userEvent.click(screen.getByRole('button'));
fireEvent.click(container.querySelector('button')!);
// should show bamboo alert without opacity and hide first alert with opacity: 0
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('bamboo');
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain')).toHaveTextContent('bamboo');
});
it('warning when use `dependencies` but `name` is empty & children is not a render props', () => {
@ -616,8 +625,6 @@ describe('Form', () => {
// https://github.com/ant-design/ant-design/issues/20948
it('not repeat render when Form.Item is not a real Field', async () => {
jest.useFakeTimers();
const shouldNotRender = jest.fn();
const StaticInput: React.FC<React.InputHTMLAttributes<HTMLInputElement>> = ({
id,
@ -663,9 +670,6 @@ describe('Form', () => {
expect(container.querySelector<HTMLInputElement>('#changed')!.value).toEqual('bamboo');
expect(shouldNotRender).toHaveBeenCalledTimes(1);
expect(shouldRender).toHaveBeenCalledTimes(2);
jest.clearAllTimers();
jest.useRealTimers();
});
it('empty help should also render', () => {
@ -692,14 +696,12 @@ describe('Form', () => {
</Form>,
);
await change(container, 0, '', true);
await changeValue(0, '');
expect(container.querySelector('.ant-form-item')).toHaveClass('ant-form-item-has-error');
expect(container.querySelector('.ant-form-item-explain')!.textContent).toEqual('help');
jest.useRealTimers();
});
it('clear validation message when', async () => {
jest.useFakeTimers();
const { container } = render(
<Form>
<Form.Item name="test" label="test" rules={[{ required: true, message: 'message' }]}>
@ -708,27 +710,26 @@ describe('Form', () => {
</Form>,
);
await change(container, 0, '1', true);
await changeValue(0, '1');
expect(container.querySelectorAll('.ant-form-item-explain').length).toBeFalsy();
await change(container, 0, '', true);
await changeValue(0, '');
expect(container.querySelectorAll('.ant-form-item-explain').length).toBeTruthy();
await change(container, 0, '123', true);
await sleep(800);
await changeValue(0, '123');
expect(container.querySelectorAll('.ant-form-item-explain').length).toBeFalsy();
jest.useRealTimers();
});
// https://github.com/ant-design/ant-design/issues/21167
it('`require` without `name`', () => {
render(
const { container } = render(
<Form.Item label="test" name="test" required>
<input />
</Form.Item>,
);
expect(screen.getByTitle('test')).toHaveClass('ant-form-item-required');
// expect(screen.getByTitle('test')).toHaveClass('ant-form-item-required');
expect(container.querySelector('.ant-form-item-required')).toBeTruthy();
});
it('0 is a validate Field', () => {
@ -757,7 +758,7 @@ describe('Form', () => {
});
// https://github.com/ant-design/ant-design/issues/21415
it('should not throw error when Component.props.onChange is null', () => {
it('should not throw error when Component.props.onChange is null', async () => {
const CustomComponent: React.FC = () => (
<input onChange={null as unknown as ChangeEventHandler<HTMLInputElement>} />
);
@ -768,10 +769,8 @@ describe('Form', () => {
</Form.Item>
</Form>,
);
const handle = async () => {
await userEvent.type(screen.getByRole('textbox'), 'aaa');
};
expect(handle).not.toThrow();
await changeValue(0, 'aaa');
});
it('change `help` should not warning', async () => {
@ -797,91 +796,91 @@ describe('Form', () => {
);
};
render(<Demo />);
await userEvent.click(screen.getByRole('button'));
const { container } = render(<Demo />);
fireEvent.click(container.querySelector('button')!);
expect(errorSpy).not.toHaveBeenCalled();
});
it('`label` support template', async () => {
render(
const { container } = render(
// eslint-disable-next-line no-template-curly-in-string
<Form validateMessages={{ required: '${label} is good!' }}>
<Form.Item name="test" label="Bamboo" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>,
);
await userEvent.click(screen.getByRole('button'));
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('Bamboo is good!');
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
'Bamboo is good!',
);
});
// https://github.com/ant-design/ant-design/issues/33691
it('should keep upper locale in nested ConfigProvider', async () => {
render(
const { container } = render(
<ConfigProvider locale={zhCN}>
<ConfigProvider>
<Form>
<Form.Item name="test" label="Bamboo" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>
</ConfigProvider>
</ConfigProvider>,
);
await userEvent.click(screen.getByRole('button'));
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('请输入Bamboo');
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
'请输入Bamboo',
);
});
it('`name` support template when label is not provided', async () => {
render(
const { container } = render(
// eslint-disable-next-line no-template-curly-in-string
<Form validateMessages={{ required: '${label} is good!' }}>
<Form.Item name="Bamboo" rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>,
);
await userEvent.click(screen.getByRole('button'));
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('Bamboo is good!');
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
'Bamboo is good!',
);
});
it('`messageVariables` support validate', async () => {
render(
const { container } = render(
// eslint-disable-next-line no-template-curly-in-string
<Form validateMessages={{ required: '${label} is good!' }}>
<Form.Item name="test" messageVariables={{ label: 'Bamboo' }} rules={[{ required: true }]}>
<input />
</Form.Item>
<Form.Item>
<Button htmlType="submit">Submit</Button>
</Form.Item>
</Form>,
);
await userEvent.click(screen.getByRole('button'));
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('Bamboo is good!');
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
'Bamboo is good!',
);
});
it('validation message should has alert role', async () => {
// https://github.com/ant-design/ant-design/issues/25711
render(
const { container } = render(
// eslint-disable-next-line no-template-curly-in-string
<Form validateMessages={{ required: 'name is good!' }}>
<Form.Item name="test" rules={[{ required: true }]}>
@ -893,9 +892,12 @@ describe('Form', () => {
</Form>,
);
await userEvent.click(screen.getByRole('button'));
fireEvent.submit(container.querySelector('form')!);
await waitFakeTimer();
await expect(screen.findByRole('alert')).resolves.toHaveTextContent('name is good!');
expect(container.querySelector('.ant-form-item-explain-error')).toHaveTextContent(
'name is good!',
);
});
it('return same form instance', async () => {
@ -917,11 +919,12 @@ describe('Form', () => {
);
};
pureRender(<App />);
const { container } = pureRender(<App />);
for (let i = 0; i < 5; i += 1) {
fireEvent.click(container.querySelector('button')!);
// eslint-disable-next-line no-await-in-loop
await userEvent.click(screen.getByRole('button'));
await waitFakeTimer();
}
expect(instances.size).toBe(1);
@ -942,12 +945,13 @@ describe('Form', () => {
</Form.Item>
</Form>
);
pureRender(<Demo />);
const { container } = pureRender(<Demo />);
renderTimes = 0;
jest.clearAllMocks();
fireEvent.change(screen.getByLabelText('username'), { target: { value: 'a' } });
await changeValue(0, 'a');
expect(renderTimes).toEqual(1);
expect(screen.getByLabelText('username')).toHaveValue('a');
expect(container.querySelector('input')).toHaveValue('a');
});
it('should warning with `defaultValue`', () => {
@ -979,13 +983,15 @@ describe('Form', () => {
</Form>
);
const { rerender } = render(<Demo showA />);
const { container, rerender } = render(<Demo showA />);
await expect(screen.findByRole('alert')).resolves.toBeInTheDocument();
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain')).toBeTruthy();
rerender(<Demo showA={false} />);
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
await waitFakeTimer();
expect(container.querySelector('.ant-form-item-explain')).toBeFalsy();
});
it('no warning of initialValue & getValueProps & preserve', () => {
@ -1000,7 +1006,7 @@ describe('Form', () => {
});
it('should customize id when pass with id', () => {
render(
const { container } = render(
<Form>
<Form.Item name="light">
<Input id="bamboo" />
@ -1008,11 +1014,11 @@ describe('Form', () => {
</Form>,
);
expect(screen.getByRole('textbox')).toHaveAttribute('id', 'bamboo');
expect(container.querySelector('input')!.id).toEqual('bamboo');
});
it('should trigger validate when onBlur when pass validateTrigger onBlur', async () => {
render(
const { container } = render(
<Form validateTrigger="onBlur">
<Form.Item name="light" label="light" rules={[{ len: 3 }]}>
<Input />
@ -1021,14 +1027,14 @@ describe('Form', () => {
);
// type a invalidate value, not trigger validation
await userEvent.type(screen.getByRole('textbox'), '7777');
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
await changeValue(0, '7777');
expect(container.querySelector('.ant-form-item-explain')).toBeFalsy();
// tab(onBlur) the input field, trigger and see the alert
fireEvent.blur(screen.getByRole('textbox'));
fireEvent.blur(container.querySelector('input')!);
await waitFakeTimer();
await expect(screen.findByRole('alert')).resolves.toBeInTheDocument();
expect(container.querySelector('.ant-form-item-explain')).toBeTruthy();
});
describe('Form item hidden', () => {
@ -1056,7 +1062,7 @@ describe('Form', () => {
});
it('legacy hideRequiredMark', () => {
render(
const { container } = render(
<Form hideRequiredMark role="form">
<Form.Item name="light" label="light" required>
<Input />
@ -1064,7 +1070,7 @@ describe('Form', () => {
</Form>,
);
expect(screen.getByRole('form')).toHaveClass('ant-form-hide-required-mark');
expect(container.querySelector('form')!).toHaveClass('ant-form-hide-required-mark');
});
it('form should support disabled', () => {
@ -1137,7 +1143,7 @@ describe('Form', () => {
});
it('_internalItemRender api test', () => {
render(
const { container } = render(
<Form>
<Form.Item
name="light"
@ -1159,7 +1165,7 @@ describe('Form', () => {
</Form>,
);
expect(screen.getByRole('heading')).toHaveTextContent(/warning title/i);
expect(container.querySelector('h1')!).toHaveTextContent(/warning title/i);
});
it('Form Item element id will auto add form_item prefix if form name is empty and item name is in the black list', async () => {
@ -1198,16 +1204,17 @@ describe('Form', () => {
);
};
const { rerender } = render(<Demo />);
const { container, rerender } = render(<Demo />);
expect(mockFn).toHaveBeenCalled();
expect((Util.getFieldId as () => string)()).toBe(itemName);
// make sure input id is parentNode
expect(screen.getByLabelText(itemName)).toHaveAccessibleName(itemName);
await userEvent.click(screen.getByRole('button'));
fireEvent.click(container.querySelector('button')!);
await waitFakeTimer();
expect(screen.getByRole('button')).toHaveTextContent('show');
expect(container.querySelector('button')!).toHaveTextContent('show');
mockFn.mockRestore();
@ -1217,7 +1224,7 @@ describe('Form', () => {
describe('tooltip', () => {
it('ReactNode', async () => {
render(
const { container } = render(
<Form>
<Form.Item label="light" tooltip={<span>Bamboo</span>}>
<Input />
@ -1225,21 +1232,14 @@ describe('Form', () => {
</Form>,
);
await userEvent.hover(screen.getByRole('img', { name: 'question-circle' }));
await expect(screen.findByRole('tooltip')).resolves.toMatchInlineSnapshot(`
<div
class="ant-tooltip-inner"
role="tooltip"
>
<span>
Bamboo
</span>
</div>
`);
fireEvent.mouseEnter(container.querySelector('.anticon-question-circle')!);
await waitFakeTimer();
expect(container.querySelector('.ant-tooltip-inner')).toHaveTextContent('Bamboo');
});
it('config tooltip should show when hover on icon', async () => {
render(
const { container } = render(
<Form>
<Form.Item label="light" tooltip={{ title: 'Bamboo' }}>
<Input />
@ -1247,9 +1247,10 @@ describe('Form', () => {
</Form>,
);
await userEvent.hover(screen.getByRole('img', { name: 'question-circle' }));
fireEvent.mouseEnter(container.querySelector('.anticon-question-circle')!);
await waitFakeTimer();
await expect(screen.findByRole('tooltip')).resolves.toHaveTextContent('Bamboo');
expect(container.querySelector('.ant-tooltip-inner')).toHaveTextContent('Bamboo');
});
});
@ -1269,20 +1270,17 @@ describe('Form', () => {
</Form>,
);
await userEvent.type(screen.getByLabelText('test'), 'test');
await userEvent.clear(screen.getByLabelText('test'));
await changeValue(0, 'test');
await changeValue(0, '');
await sleep(1000);
expect(container.querySelectorAll('.ant-form-item-with-help').length).toBeTruthy();
expect(container.querySelectorAll('.ant-form-item-has-warning').length).toBeTruthy();
expect(container.querySelector('.ant-form-item-with-help')).toBeTruthy();
expect(container.querySelector('.ant-form-item-has-warning')).toBeTruthy();
});
it('not warning when remove on validate', async () => {
jest.useFakeTimers();
let rejectFn: (reason?: any) => void = jest.fn();
const { container, unmount } = render(
const { unmount } = render(
<Form>
<Form.Item>
<Form.Item
@ -1304,7 +1302,7 @@ describe('Form', () => {
</Form>,
);
await change(container, 0, '', true);
await changeValue(0, '');
unmount();
@ -1312,8 +1310,6 @@ describe('Form', () => {
rejectFn(new Error('delay failed'));
expect(errorSpy).not.toHaveBeenCalled();
jest.useRealTimers();
});
describe('form colon', () => {
@ -1437,8 +1433,6 @@ describe('Form', () => {
});
it('Form.Item.useStatus should work', async () => {
jest.useFakeTimers();
const {
Item: { useStatus },
} = Form;
@ -1495,9 +1489,6 @@ describe('Form', () => {
expect(container.querySelector('.custom-input-required')?.classList).toContain(
'custom-input-status-error',
);
jest.clearAllTimers();
jest.useRealTimers();
});
it('item customize margin', async () => {
@ -1513,9 +1504,8 @@ describe('Form', () => {
</Form>,
);
fireEvent.change(container.querySelector('input')!, { target: { value: '' } });
await changeValue(0, '');
await sleep(0);
computeSpy.mockRestore();
expect(container.querySelector('.ant-form-item-margin-offset')).toHaveStyle({

View File

@ -5083,6 +5083,34 @@ exports[`renders ./components/input/demo/borderless-debug.md extend context corr
RMB
</span>
</span>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
style="border:2px solid #000"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close-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 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</div>
`;

View File

@ -1300,6 +1300,34 @@ exports[`renders ./components/input/demo/borderless-debug.md correctly 1`] = `
RMB
</span>
</span>
<span
class="ant-input-affix-wrapper ant-input-affix-wrapper-textarea-with-clear-btn"
style="border:2px solid #000"
>
<textarea
class="ant-input"
/>
<span
aria-label="close-circle"
class="anticon anticon-close-circle ant-input-clear-icon-hidden ant-input-clear-icon"
role="button"
tabindex="-1"
>
<svg
aria-hidden="true"
data-icon="close-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 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
/>
</svg>
</span>
</span>
</div>
`;

View File

@ -1,14 +1,14 @@
---
order: 98
title:
zh-CN: Borderless Debug
en-US: Borderless Debug
zh-CN: Style Debug
en-US: Style Debug
debug: true
---
## zh-CN
Buggy!
Buggy! 测试一些踩过的样式坑。
## en-US
@ -29,6 +29,7 @@ const App: React.FC = () => (
<Input placeholder="Unbordered" bordered={false} allowClear />
<Input prefix="¥" suffix="RMB" bordered={false} />
<Input prefix="¥" suffix="RMB" disabled bordered={false} />
<TextArea allowClear style={{ border: '2px solid #000' }} />
</div>
);

View File

@ -28,8 +28,7 @@
}
}
> input.@{ant-prefix}-input {
padding: 0;
> .@{ant-prefix}-input {
font-size: inherit;
border: none;
outline: none;
@ -37,6 +36,10 @@
&:focus {
box-shadow: none !important;
}
&:not(textarea) {
padding: 0;
}
}
&::before {

View File

@ -2,8 +2,8 @@
@input-prefix-cls: ~'@{ant-prefix}-input';
// ========================= Input =========================
.@{iconfont-css-prefix}.@{ant-prefix}-input-clear-icon,
.@{ant-prefix}-input-clear-icon {
.@{iconfont-css-prefix}.@{input-prefix-cls}-clear-icon,
.@{input-prefix-cls}-clear-icon {
margin: 0;
color: @disabled-color;
font-size: @font-size-sm;
@ -31,11 +31,10 @@
}
// ======================= TextArea ========================
.@{ant-prefix}-input-affix-wrapper-textarea-with-clear-btn {
padding: 0 !important;
border: 0 !important;
.@{input-prefix-cls}-affix-wrapper.@{input-prefix-cls}-affix-wrapper-textarea-with-clear-btn {
padding: 0;
.@{ant-prefix}-input-clear-icon {
.@{input-prefix-cls}-clear-icon {
position: absolute;
top: 8px;
right: 8px;

View File

@ -1,9 +1,12 @@
/* eslint-disable no-template-curly-in-string */
import Pagination from 'rc-pagination/lib/locale/it_IT';
import Calendar from '../calendar/locale/it_IT';
import DatePicker from '../date-picker/locale/it_IT';
import type { Locale } from '../locale-provider';
import TimePicker from '../time-picker/locale/it_IT';
const typeTemplate = ' ${label} non è un ${type} valido';
const localeValues: Locale = {
locale: 'it',
Pagination,
@ -17,11 +20,17 @@ const localeValues: Locale = {
filterTitle: 'Menù Filtro',
filterConfirm: 'OK',
filterReset: 'Reset',
selectNone: 'Deseleziona tutto',
selectionAll: 'Seleziona tutto',
filterEmptyText: 'Senza filtri',
filterCheckall: 'Seleziona tutti',
filterSearchPlaceholder: 'Cerca nei filtri',
emptyText: 'Senza dati',
selectAll: 'Seleziona pagina corrente',
selectInvert: 'Inverti selezione nella pagina corrente',
selectNone: 'Deseleziona tutto',
selectionAll: 'Seleziona tutto',
sortTitle: 'Ordina',
expand: 'Espandi riga',
collapse: 'Comprimi riga ',
triggerDesc: 'Clicca per ordinare in modo discendente',
triggerAsc: 'Clicca per ordinare in modo ascendente',
cancelSort: "Clicca per eliminare l'ordinamento",
@ -36,16 +45,23 @@ const localeValues: Locale = {
cancelText: 'Annulla',
},
Transfer: {
titles: ['', ''],
searchPlaceholder: 'Cerca qui',
itemUnit: 'elemento',
itemsUnit: 'elementi',
remove: 'Elimina',
selectCurrent: 'Seleziona la pagina corrente',
removeCurrent: 'Rimuovi la pagina corrente',
selectAll: 'Seleziona tutti i dati',
removeAll: 'Rimuovi tutti i dati',
selectInvert: 'Inverti la pagina corrente',
},
Upload: {
uploading: 'Caricamento...',
removeFile: 'Rimuovi il file',
uploadError: 'Errore di caricamento',
previewFile: 'Anteprima file',
downloadFile: 'Download file',
downloadFile: 'Scarica file',
},
Empty: {
description: 'Nessun dato',
@ -59,6 +75,62 @@ const localeValues: Locale = {
copied: 'copia effettuata',
expand: 'espandi',
},
PageHeader: {
back: 'Torna',
},
Form: {
optional: '(opzionale)',
defaultValidateMessages: {
default: 'Errore di convalida del campo ${label}',
required: 'Si prega di inserire ${label}',
enum: '${label} deve essere uno di [${enum}]',
whitespace: '${label} non può essere un carattere vuoto',
date: {
format: 'Il formato della data ${label} non è valido',
parse: '${label} non può essere convertito in una data',
invalid: '${label} non è una data valida',
},
types: {
string: typeTemplate,
method: typeTemplate,
array: typeTemplate,
object: typeTemplate,
number: typeTemplate,
date: typeTemplate,
boolean: typeTemplate,
integer: typeTemplate,
float: typeTemplate,
regexp: typeTemplate,
email: typeTemplate,
url: typeTemplate,
hex: typeTemplate,
},
string: {
len: '${label} deve avere ${len} caratteri',
min: '${label} deve contenere almeno ${min} caratteri',
max: '${label} deve contenere fino a ${max} caratteri',
range: '${label} deve contenere tra ${min}-${max} caratteri',
},
number: {
len: '${label} deve essere uguale a ${len}',
min: '${label} valore minimo è ${min}',
max: '${label} valor e massimo è ${max}',
range: '${label} deve essere compreso tra ${min}-${max}',
},
array: {
len: 'Deve essere ${len} ${label}',
min: 'Almeno ${min} ${label}',
max: 'Massimo ${max} ${label}',
range: 'Il totale di ${label} deve essere compreso tra ${min}-${max}',
},
pattern: {
mismatch: '${label} non corrisponde al modello ${pattern}',
},
},
},
Image: {
preview: 'Anteprima',
},
};
export default localeValues;

View File

@ -1,6 +1,9 @@
import classNames from 'classnames';
import RcMentions from 'rc-mentions';
import type { MentionsProps as RcMentionsProps } from 'rc-mentions/lib/Mentions';
import type {
MentionsProps as RcMentionsProps,
MentionsRef as RcMentionsRef,
} from 'rc-mentions/lib/Mentions';
import { composeRef } from 'rc-util/lib/ref';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
@ -29,6 +32,8 @@ export interface MentionProps extends RcMentionsProps {
status?: InputStatus;
}
export interface MentionsRef extends RcMentionsRef {}
export interface MentionState {
focused: boolean;
}
@ -44,12 +49,12 @@ interface MentionsEntity {
}
interface CompoundedComponent
extends React.ForwardRefExoticComponent<MentionProps & React.RefAttributes<HTMLElement>> {
extends React.ForwardRefExoticComponent<MentionProps & React.RefAttributes<MentionsRef>> {
Option: typeof Option;
getMentions: (value: string, config?: MentionsConfig) => MentionsEntity[];
}
const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> = (
const InternalMentions: React.ForwardRefRenderFunction<MentionsRef, MentionProps> = (
{
prefixCls: customizePrefixCls,
className,
@ -64,7 +69,7 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
ref,
) => {
const [focused, setFocused] = React.useState(false);
const innerRef = React.useRef<HTMLElement>();
const innerRef = React.useRef<MentionsRef>();
const mergedRef = composeRef(ref, innerRef);
const { getPrefixCls, renderEmpty, direction } = React.useContext(ConfigContext);
const {
@ -163,7 +168,9 @@ const InternalMentions: React.ForwardRefRenderFunction<unknown, MentionProps> =
return mentions;
};
const Mentions = React.forwardRef<unknown, MentionProps>(InternalMentions) as CompoundedComponent;
const Mentions = React.forwardRef<MentionsRef, MentionProps>(
InternalMentions,
) as CompoundedComponent;
if (process.env.NODE_ENV !== 'production') {
Mentions.displayName = 'Mentions';
}

View File

@ -98,7 +98,7 @@
"test-node": "jest --config .jest.node.js --cache=false",
"tsc": "tsc --noEmit",
"site:test": "jest --config .jest.site.js --cache=false --force-exit",
"test-image": "npm run dist && jest --config .jest.image.js --no-cache -i -u",
"test-image": "npm run dist && jest --config .jest.image.js -i -u",
"argos": "node ./scripts/argos-upload.js",
"version": "node ./scripts/generate-version",
"install-react-16": "npm i --no-save --legacy-peer-deps react@16 react-dom@16",

View File

@ -6,6 +6,11 @@ import { render, act } from '@testing-library/react';
import { _rs as onLibResize } from 'rc-resize-observer/lib/utils/observerUtil';
import { _rs as onEsResize } from 'rc-resize-observer/es/utils/observerUtil';
export function assertsExist<T>(item: T | null | undefined): asserts item is T {
expect(item).not.toBeUndefined();
expect(item).not.toBeNull();
}
export function setMockDate(dateString = '2017-09-18T03:30:07.795') {
MockDate.set(dateString);
}