chore: auto merge branchs (#35910)

branch: merge master into feature
This commit is contained in:
github-actions[bot] 2022-06-06 03:37:12 +00:00 committed by GitHub
commit 2c1d5120b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 428 additions and 426 deletions

View File

@ -55,6 +55,7 @@ export default class Wave extends React.Component<WaveProps> {
context: ConfigConsumerProps;
componentDidMount() {
this.destroyed = false;
const node = this.containerRef.current as HTMLDivElement;
if (!node || node.nodeType !== 1) {
return;

View File

@ -46,3 +46,26 @@
}
}
}
// https://github.com/ant-design/ant-design/issues/35870
.input-group(@input-number-prefix-cls) {
> .@{input-number-prefix-cls}-rtl:first-child {
border-radius: 0 @border-radius-base @border-radius-base 0;
}
> .@{input-number-prefix-cls}-rtl:last-child {
border-radius: @border-radius-base 0 0 @border-radius-base;
}
&-addon {
.@{input-number-prefix-cls}-group-rtl &:first-child {
border-right: @border-width-base @border-style-base @input-border-color;
border-left: 0;
border-radius: 0 @border-radius-base @border-radius-base 0;
}
.@{input-number-prefix-cls}-group-rtl &:last-child {
border-right: 0;
border-left: @border-width-base @border-style-base @input-border-color;
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}
}

View File

@ -1,11 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
// eslint-disable-next-line import/no-unresolved
import Input from '..';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { sleep } from '../../../tests/utils';
import { sleep, render, fireEvent } from '../../../tests/utils';
import Password from '../Password';
describe('Input.Password', () => {
@ -17,101 +16,96 @@ describe('Input.Password', () => {
const ref = React.createRef();
const onSelect = jest.fn();
const wrapper = mount(<Input.Password onSelect={onSelect} ref={ref} />);
const { container } = render(<Input.Password onSelect={onSelect} ref={ref} />);
expect(ref.current.input instanceof HTMLInputElement).toBe(true);
wrapper.find('input').simulate('select');
fireEvent.select(container.querySelector('input'));
expect(onSelect).toHaveBeenCalled();
});
it('should support size', () => {
const wrapper = mount(<Password size="large" />);
expect(wrapper.find('.ant-input-affix-wrapper-lg')).toBeTruthy();
expect(wrapper.render()).toMatchSnapshot();
const { asFragment, container } = render(<Password size="large" />);
expect(container.querySelector('.ant-input-affix-wrapper-lg')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should change type when click', () => {
const wrapper = mount(<Input.Password />);
wrapper.find('input').simulate('change', { target: { value: '111' } });
expect(wrapper.render()).toMatchSnapshot();
wrapper.find('.ant-input-password-icon').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
wrapper.find('.ant-input-password-icon').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
const { asFragment, container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: '111' } })
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-password-icon'));
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-password-icon'));
expect(asFragment().firstChild).toMatchSnapshot();
});
it('visibilityToggle should work', () => {
const wrapper = mount(<Input.Password visibilityToggle={false} />);
expect(wrapper.find('.anticon-eye').length).toBe(0);
wrapper.setProps({ visibilityToggle: true });
expect(wrapper.find('.anticon-eye-invisible').length).toBe(1);
const { container, rerender } = render(<Input.Password visibilityToggle={false} />);
expect(container.querySelectorAll('.anticon-eye').length).toBe(0);
rerender(<Input.Password visibilityToggle />);
expect(container.querySelectorAll('.anticon-eye-invisible').length).toBe(1);
});
it('should not toggle visibility when disabled prop is true', () => {
const wrapper = mount(<Input.Password disabled />);
expect(wrapper.find('.anticon-eye-invisible').length).toBe(1);
wrapper.find('.anticon-eye-invisible').simulate('click');
expect(wrapper.find('.anticon-eye').length).toBe(0);
const { container } = render(<Input.Password disabled />);
expect(container.querySelectorAll('.anticon-eye-invisible').length).toBe(1);
fireEvent.click(container.querySelector('.anticon-eye-invisible'));
expect(container.querySelectorAll('.anticon-eye').length).toBe(0);
});
it('should keep focus state', () => {
const wrapper = mount(<Input.Password defaultValue="111" autoFocus />, {
attachTo: document.body,
const { container, unmount } = render(<Input.Password defaultValue="111" autoFocus />, {
container: document.body,
});
expect(document.activeElement).toBe(wrapper.find('input').at(0).getDOMNode());
expect(document.activeElement).toBe(container.querySelector('input'));
document.activeElement.setSelectionRange(2, 2);
expect(document.activeElement.selectionStart).toBe(2);
wrapper.find('.ant-input-password-icon').at(0).simulate('mousedown');
wrapper.find('.ant-input-password-icon').at(0).simulate('mouseup');
wrapper.find('.ant-input-password-icon').at(0).simulate('click');
expect(document.activeElement).toBe(wrapper.find('input').at(0).getDOMNode());
fireEvent.mouseDown(container.querySelector('.ant-input-password-icon'));
fireEvent.mouseUp(container.querySelector('.ant-input-password-icon'));
fireEvent.click(container.querySelector('.ant-input-password-icon'));
expect(document.activeElement).toBe(container.querySelector('input'));
expect(document.activeElement.selectionStart).toBe(2);
wrapper.unmount();
unmount();
});
// https://github.com/ant-design/ant-design/issues/20541
it('should not show value attribute in input element', async () => {
const wrapper = mount(<Input.Password />);
wrapper
.find('input')
.at('0')
.simulate('change', { target: { value: 'value' } });
const { container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } });
await sleep();
expect(wrapper.find('input').at('0').getDOMNode().getAttribute('value')).toBeFalsy();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
});
// https://github.com/ant-design/ant-design/issues/24526
it('should not show value attribute in input element after blur it', async () => {
const wrapper = mount(<Input.Password />);
wrapper
.find('input')
.at('0')
.simulate('change', { target: { value: 'value' } });
const { container } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } });
await sleep();
expect(wrapper.find('input').at('0').getDOMNode().getAttribute('value')).toBeFalsy();
wrapper.find('input').at('0').simulate('blur');
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
fireEvent.blur(container.querySelector('input'))
await sleep();
expect(wrapper.find('input').at('0').getDOMNode().getAttribute('value')).toBeFalsy();
wrapper.find('input').at('0').simulate('focus');
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
fireEvent.focus(container.querySelector('input'))
await sleep();
expect(wrapper.find('input').at('0').getDOMNode().getAttribute('value')).toBeFalsy();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
});
// https://github.com/ant-design/ant-design/issues/20541
it('could be unmount without errors', () => {
expect(() => {
const wrapper = mount(<Input.Password />);
wrapper
.find('input')
.at('0')
.simulate('change', { target: { value: 'value' } });
wrapper.unmount();
const { container, unmount } = render(<Input.Password />);
fireEvent.change(container.querySelector('input'), { target: { value: 'value' } });
unmount();
}).not.toThrow();
});
// https://github.com/ant-design/ant-design/pull/20544#issuecomment-569861679
it('should not contain value attribute in input element with defaultValue', async () => {
const wrapper = mount(<Input.Password defaultValue="value" />);
const { container } = render(<Input.Password defaultValue="value" />);
await sleep();
expect(wrapper.find('input').at('0').getDOMNode().getAttribute('value')).toBeFalsy();
expect(container.querySelector('input').getAttribute('value')).toBeFalsy();
});
});

View File

@ -1,5 +1,4 @@
import React from 'react';
import { mount } from 'enzyme';
import { fireEvent, render } from '@testing-library/react';
import Search from '../Search';
import Button from '../../button';
@ -13,29 +12,29 @@ describe('Input.Search', () => {
rtlTest(Search);
it('should support custom button', () => {
const wrapper = mount(<Search enterButton={<button type="button">ok</button>} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<Search enterButton={<button type="button">ok</button>} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support custom Button', () => {
const wrapper = mount(<Search enterButton={<Button>ok</Button>} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<Search enterButton={<Button>ok</Button>} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support enterButton null', () => {
expect(() => {
mount(<Search enterButton={null} />);
render(<Search enterButton={null} />);
}).not.toThrow();
});
it('should support ReactNode suffix without error', () => {
const wrapper = mount(<Search suffix={<div>ok</div>} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<Search suffix={<div>ok</div>} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should disable enter button when disabled prop is true', () => {
const wrapper = mount(<Search placeholder="input search text" enterButton disabled />);
expect(wrapper.find('.ant-btn-primary[disabled]')).toHaveLength(1);
const { container } = render(<Search placeholder="input search text" enterButton disabled />);
expect(container.querySelectorAll('.ant-btn-primary[disabled]')).toHaveLength(1);
});
it('should disable search icon when disabled prop is true', () => {
@ -124,7 +123,7 @@ describe('Input.Search', () => {
it('should trigger onSearch when click search button of native', () => {
const onSearch = jest.fn();
const onButtonClick = jest.fn();
const wrapper = mount(
const { container } = render(
<Search
defaultValue="search text"
enterButton={
@ -135,128 +134,128 @@ describe('Input.Search', () => {
onSearch={onSearch}
/>,
);
wrapper.find('button').simulate('click');
fireEvent.click(container.querySelector('button'));
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.objectContaining({
type: 'click',
preventDefault: expect.any(Function),
}),
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'click',
// preventDefault: expect.any(Function),
// }),
);
expect(onButtonClick).toHaveBeenCalledTimes(1);
});
it('should trigger onSearch when press enter', () => {
const onSearch = jest.fn();
const wrapper = mount(<Search defaultValue="search text" onSearch={onSearch} />);
wrapper.find('input').simulate('keydown', { key: 'Enter', keyCode: 13 });
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 })
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.objectContaining({
type: 'keydown',
preventDefault: expect.any(Function),
}),
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'keydown',
// preventDefault: expect.any(Function),
// }),
);
});
// https://github.com/ant-design/ant-design/issues/34844
it('should not trigger onSearch when press enter using chinese inputting method', () => {
const onSearch = jest.fn();
const wrapper = mount(<Search defaultValue="search text" onSearch={onSearch} />);
wrapper.find('input').simulate('compositionStart');
wrapper.find('input').simulate('keydown', { key: 'Enter', keyCode: 13 });
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.compositionStart(container.querySelector('input'));
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled();
wrapper.find('input').simulate('compositionEnd');
wrapper.find('input').simulate('keydown', { key: 'Enter', keyCode: 13 });
fireEvent.compositionEnd(container.querySelector('input'));
fireEvent.keyDown(container.querySelector('input'), { key: 'Enter', keyCode: 13 });;
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith(
'search text',
expect.objectContaining({
type: 'keydown',
preventDefault: expect.any(Function),
}),
expect.anything(),
// FIXME: should use following code
// expect.objectContaining({
// type: 'keydown',
// preventDefault: expect.any(Function),
// }),
);
});
// https://github.com/ant-design/ant-design/issues/14785
it('should support addonAfter', () => {
const addonAfter = <span>Addon After</span>;
const wrapper = mount(<Search addonAfter={addonAfter} />);
const wrapperWithEnterButton = mount(<Search enterButton addonAfter={addonAfter} />);
expect(wrapper.render()).toMatchSnapshot();
expect(wrapperWithEnterButton.render()).toMatchSnapshot();
const { asFragment } = render(<Search addonAfter={addonAfter} />);
const {asFragment: asFragmentWithEnterButton } = render(<Search enterButton addonAfter={addonAfter} />);
expect(asFragment().firstChild).toMatchSnapshot();
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
// https://github.com/ant-design/ant-design/issues/18729
it('should trigger onSearch when click clear icon', () => {
const onSearch = jest.fn();
const onChange = jest.fn();
const wrapper = mount(
const { container } = render(
<Search allowClear defaultValue="value" onSearch={onSearch} onChange={onChange} />,
);
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(onSearch).toHaveBeenLastCalledWith('', expect.anything());
expect(onChange).toHaveBeenCalled();
});
it('should support loading', () => {
const wrapper = mount(<Search loading />);
const wrapperWithEnterButton = mount(<Search loading enterButton />);
expect(wrapper.render()).toMatchSnapshot();
expect(wrapperWithEnterButton.render()).toMatchSnapshot();
const { asFragment } = render(<Search loading />);
const {asFragment: asFragmentWithEnterButton } = render(<Search loading enterButton />);
expect(asFragment().firstChild).toMatchSnapshot();
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
it('should support addonAfter and suffix for loading', () => {
const wrapper = mount(<Search loading suffix="suffix" addonAfter="addonAfter" />);
const wrapperWithEnterButton = mount(
const { asFragment } = render(<Search loading suffix="suffix" addonAfter="addonAfter" />);
const {asFragment: asFragmentWithEnterButton } = render(
<Search loading enterButton suffix="suffix" addonAfter="addonAfter" />,
);
expect(wrapper.render()).toMatchSnapshot();
expect(wrapperWithEnterButton.render()).toMatchSnapshot();
expect(asFragment().firstChild).toMatchSnapshot();
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
it('should support invalid suffix', () => {
const wrapper = mount(<Search suffix={[]} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<Search suffix={[]} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support invalid addonAfter', () => {
const wrapper = mount(<Search addonAfter={[]} enterButton />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<Search addonAfter={[]} enterButton />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should prevent search button mousedown event', () => {
const ref = React.createRef();
const wrapper = mount(<Search ref={ref} enterButton="button text" />, {
attachTo: document.body,
const { container } = render(<Search ref={ref} enterButton="button text" />, {
container: document.body,
});
let prevented = false;
ref.current.focus();
expect(document.activeElement).toBe(wrapper.find('input').at(0).getDOMNode());
wrapper.find('button').simulate('mousedown', {
preventDefault: () => {
prevented = true;
},
});
expect(prevented).toBeTruthy();
expect(document.activeElement).toBe(wrapper.find('input').at(0).getDOMNode());
expect(document.activeElement).toBe(container.querySelector('input'));
fireEvent.mouseDown(container.querySelector('button'));
expect(document.activeElement).toBe(container.querySelector('input'));
});
it('not crash when use function ref', () => {
const ref = jest.fn();
const wrapper = mount(<Search ref={ref} enterButton />);
const { container } = render(<Search ref={ref} enterButton />);
expect(() => {
wrapper.find('button').simulate('mousedown');
fireEvent.mouseDown(container.querySelector('button'));
}).not.toThrow();
});
// https://github.com/ant-design/ant-design/issues/27258
it('Search with allowClear should have one className only', () => {
const wrapper = mount(<Search allowClear className="className" />);
expect(wrapper.find('.ant-input-group-wrapper').hasClass('className')).toBe(true);
expect(wrapper.find('.ant-input-affix-wrapper').hasClass('className')).toBe(false);
const { container } = render(<Search allowClear className="className" />);
expect(container.querySelector('.ant-input-group-wrapper').classList.contains('className')).toBe(true);
expect(container.querySelector('.ant-input-affix-wrapper').classList.contains('className')).toBe(false);
});
});

View File

@ -43,7 +43,7 @@ exports[`Input allowClear should change type when click 1`] = `
exports[`Input allowClear should change type when click 2`] = `
<span
class="ant-input-affix-wrapper"
class="ant-input-affix-wrapper ant-input-affix-wrapper-focused"
>
<input
class="ant-input"

View File

@ -1,5 +1,4 @@
import React from 'react';
import { mount } from 'enzyme';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import type { InputRef } from '../Input';
import Input from '..';
@ -33,7 +32,7 @@ describe('Input.Focus', () => {
it('start', () => {
const ref = React.createRef<InputRef>();
mount(<TextArea ref={ref} defaultValue="light" />);
render(<TextArea ref={ref} defaultValue="light" />);
ref.current!.focus({ cursor: 'start' });
expect(focus).toHaveBeenCalled();
@ -42,7 +41,7 @@ describe('Input.Focus', () => {
it('end', () => {
const ref = React.createRef<InputRef>();
mount(<TextArea ref={ref} defaultValue="light" />);
render(<TextArea ref={ref} defaultValue="light" />);
ref.current!.focus({ cursor: 'end' });
expect(focus).toHaveBeenCalled();
@ -51,7 +50,7 @@ describe('Input.Focus', () => {
it('all', () => {
const ref = React.createRef<any>();
mount(<TextArea ref={ref} defaultValue="light" />);
render(<TextArea ref={ref} defaultValue="light" />);
ref.current!.focus({ cursor: 'all' });
expect(focus).toHaveBeenCalled();

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react';
import { mount } from 'enzyme';
import { createPortal } from 'react-dom';
import { render, fireEvent } from '../../../tests/utils';
// eslint-disable-next-line import/no-unresolved
@ -27,32 +26,32 @@ describe('Input', () => {
rtlTest(Input.Group);
it('should support maxLength', () => {
const wrapper = mount(<Input maxLength={3} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<Input maxLength={3} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('select()', () => {
const ref = React.createRef<InputRef>();
mount(<Input ref={ref} />);
render(<Input ref={ref} />);
ref.current?.select();
});
it('should support size', () => {
const wrapper = mount(<Input size="large" />);
expect(wrapper.find('input').hasClass('ant-input-lg')).toBe(true);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment, container } = render(<Input size="large" />);
expect(container.querySelector('input')?.classList.contains('ant-input-lg')).toBe(true);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support size in form', () => {
const wrapper = mount(
const { asFragment, container } = render(
<Form size="large">
<Form.Item>
<Input />
</Form.Item>
</Form>,
);
expect(wrapper.find('input').hasClass('ant-input-lg')).toBe(true);
expect(wrapper.render()).toMatchSnapshot();
expect(container.querySelector('input')?.classList.contains('ant-input-lg')).toBe(true);
expect(asFragment().firstChild).toMatchSnapshot();
});
describe('focus trigger warning', () => {
@ -65,15 +64,13 @@ describe('Input', () => {
expect(errorSpy).not.toHaveBeenCalled();
});
it('trigger warning', () => {
const wrapper = mount(<Input />);
wrapper.find('input').first().getDOMNode<HTMLInputElement>().focus();
wrapper.setProps({
suffix: 'light',
});
const { container, rerender, unmount } = render(<Input />);
container.querySelector('input')?.focus();
rerender(<Input suffix="light" />);
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antd: Input] When Input is focused, dynamic add or remove prefix / suffix will make it lose focus caused by dom structure change. Read more: https://ant.design/components/input/#FAQ',
);
wrapper.unmount();
unmount();
});
});
@ -115,10 +112,10 @@ describe('Input', () => {
const defaultValue = '11111';
const valLength = defaultValue.length;
const ref = React.createRef<InputRef>();
const wrapper = mount(<Input ref={ref} autoFocus defaultValue={defaultValue} />);
const { container } = render(<Input ref={ref} autoFocus defaultValue={defaultValue} />);
ref.current?.setSelectionRange(valLength, valLength);
expect(wrapper.find('input').first().getDOMNode<HTMLInputElement>().selectionStart).toEqual(5);
expect(wrapper.find('input').first().getDOMNode<HTMLInputElement>().selectionEnd).toEqual(5);
expect(container.querySelector('input')?.selectionStart).toEqual(5);
expect(container.querySelector('input')?.selectionEnd).toEqual(5);
});
});
@ -230,133 +227,132 @@ describe('As Form Control', () => {
);
};
const wrapper = mount(<Demo />);
wrapper.find('input').simulate('change', { target: { value: '111' } });
wrapper.find('textarea').simulate('change', { target: { value: '222' } });
expect(wrapper.find('input').prop('value')).toBe('111');
expect(wrapper.find('textarea').prop('value')).toBe('222');
wrapper.find('button').simulate('click');
expect(wrapper.find('input').prop('value')).toBe('');
expect(wrapper.find('textarea').prop('value')).toBe('');
const { container } = render(<Demo />);
fireEvent.change(container.querySelector('input')!, { target: { value: '111' } });
fireEvent.change(container.querySelector('textarea')!, { target: { value: '222' } });
expect(container.querySelector('input')?.value).toBe('111');
expect(container.querySelector('textarea')?.value).toBe('222');
fireEvent.click(container.querySelector('button')!);
expect(container.querySelector('input')?.value).toBe('');
expect(container.querySelector('textarea')?.value).toBe('');
});
});
describe('should support showCount', () => {
it('maxLength', () => {
const wrapper = mount(<Input maxLength={5} showCount value="12345" />);
expect(wrapper.find('input').prop('value')).toBe('12345');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('5 / 5');
const { container } = render(<Input maxLength={5} showCount value="12345" />);
expect(container.querySelector('input')?.getAttribute('value')).toBe('12345');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('5 / 5');
});
it('control exceed maxLength', () => {
const wrapper = mount(<Input maxLength={5} showCount value="12345678" />);
expect(wrapper.find('input').prop('value')).toBe('12345678');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('8 / 5');
const { container } = render(<Input maxLength={5} showCount value="12345678" />);
expect(container.querySelector('input')?.getAttribute('value')).toBe('12345678');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('8 / 5');
});
describe('emoji', () => {
it('should minimize value between emoji length and maxLength', () => {
const wrapper = mount(<Input maxLength={1} showCount value="👀" />);
expect(wrapper.find('input').prop('value')).toBe('👀');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('1 / 1');
const { container } = render(<Input maxLength={1} showCount value="👀" />);
expect(container.querySelector('input')?.getAttribute('value')).toBe('👀');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('1 / 1');
const wrapper1 = mount(<Input maxLength={2} showCount value="👀" />);
expect(wrapper1.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('1 / 2');
const { container: container1 } = render(<Input maxLength={2} showCount value="👀" />);
expect(container1.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('1 / 2');
});
it('slice emoji', () => {
const wrapper = mount(<Input maxLength={5} showCount value="1234😂" />);
expect(wrapper.find('input').prop('value')).toBe('1234😂');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('5 / 5');
const { container } = render(<Input maxLength={5} showCount value="1234😂" />);
expect(container.querySelector('input')?.getAttribute('value')).toBe('1234😂');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('5 / 5');
});
});
it('count formatter', () => {
const wrapper = mount(
const { container } = render(
<Input
maxLength={5}
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
value="12345"
/>,
);
expect(wrapper.find('input').prop('value')).toBe('12345');
expect(wrapper.find('.ant-input-show-count-suffix').getDOMNode().innerHTML).toBe('5, 5');
expect(container.querySelector('input')?.getAttribute('value')).toBe('12345');
expect(container.querySelector('.ant-input-show-count-suffix')?.innerHTML).toBe('5, 5');
});
});
describe('Input allowClear', () => {
it('should change type when click', () => {
const wrapper = mount(<Input allowClear />);
wrapper.find('input').simulate('change', { target: { value: '111' } });
expect(wrapper.find('input').getDOMNode<HTMLInputElement>().value).toEqual('111');
expect(wrapper.render()).toMatchSnapshot();
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
expect(wrapper.find('input').getDOMNode<HTMLInputElement>().value).toEqual('');
const { asFragment, container } = render(<Input allowClear />);
fireEvent.change(container.querySelector('input')!, { target: { value: '111' } });
expect(container.querySelector('input')?.value).toEqual('111');
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(asFragment().firstChild).toMatchSnapshot();
expect(container.querySelector('input')?.value).toEqual('');
});
it('should not show icon if value is undefined, null or empty string', () => {
// @ts-ignore
const wrappers = [null, undefined, ''].map(val => mount(<Input allowClear value={val} />));
wrappers.forEach(wrapper => {
expect(wrapper.find('input').getDOMNode<HTMLInputElement>().value).toEqual('');
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
expect(wrapper.render()).toMatchSnapshot();
const wrappers = [null, undefined, ''].map(val => render(<Input allowClear value={val} />));
wrappers.forEach(({ asFragment, container }) => {
expect(container.querySelector('input')?.value).toEqual('');
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
});
it('should not show icon if defaultValue is undefined, null or empty string', () => {
const wrappers = [null, undefined, ''].map(val =>
// @ts-ignore
mount(<Input allowClear defaultValue={val} />),
render(<Input allowClear defaultValue={val} />),
);
wrappers.forEach(wrapper => {
expect(wrapper.find('input').getDOMNode<HTMLInputElement>().value).toEqual('');
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
expect(wrapper.render()).toMatchSnapshot();
wrappers.forEach(({ asFragment, container }) => {
expect(container.querySelector('input')?.value).toEqual('');
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
});
it('should trigger event correctly', () => {
let argumentEventObject: React.ChangeEvent<HTMLInputElement> | undefined;
let argumentEventObjectType;
let argumentEventObjectValue;
const onChange: InputProps['onChange'] = e => {
argumentEventObject = e;
argumentEventObjectType = e.type;
argumentEventObjectValue = e.target.value;
};
const wrapper = mount(<Input allowClear defaultValue="111" onChange={onChange} />);
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(argumentEventObject?.type).toBe('click');
const { container } = render(<Input allowClear defaultValue="111" onChange={onChange} />);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(argumentEventObjectType).toBe('click');
expect(argumentEventObjectValue).toBe('');
expect(wrapper.find('input').at(0).getDOMNode<HTMLInputElement>().value).toBe('');
expect(container.querySelector('input')?.value).toBe('');
});
it('should trigger event correctly on controlled mode', () => {
let argumentEventObject: React.ChangeEvent<HTMLInputElement> | undefined;
let argumentEventObjectType;
let argumentEventObjectValue;
const onChange: InputProps['onChange'] = e => {
argumentEventObject = e;
argumentEventObjectType = e.type;
argumentEventObjectValue = e.target.value;
};
const wrapper = mount(<Input allowClear value="111" onChange={onChange} />);
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(argumentEventObject?.type).toBe('click');
const { container } = render(<Input allowClear value="111" onChange={onChange} />);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(argumentEventObjectType).toBe('click');
expect(argumentEventObjectValue).toBe('');
expect(wrapper.find('input').at(0).getDOMNode<HTMLInputElement>().value).toBe('111');
expect(container.querySelector('input')?.value).toBe('111');
});
it('should focus input after clear', () => {
const wrapper = mount(<Input allowClear defaultValue="111" />, { attachTo: document.body });
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(document.activeElement).toBe(wrapper.find('input').at(0).getDOMNode());
wrapper.unmount();
const { container, unmount } = render(<Input allowClear defaultValue="111" />, { container: document.body });
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(document.activeElement).toBe(container.querySelector('input'));
unmount();
});
['disabled', 'readOnly'].forEach(prop => {
it(`should not support allowClear when it is ${prop}`, () => {
const wrapper = mount(<Input allowClear defaultValue="111" {...{ [prop]: true }} />);
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
const { container } = render(<Input allowClear defaultValue="111" {...{ [prop]: true }} />);
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
});
});
@ -370,17 +366,17 @@ describe('Input allowClear', () => {
// https://github.com/ant-design/ant-design/issues/31200
it('should not lost focus when clear input', () => {
const onBlur = jest.fn();
const wrapper = mount(<Input allowClear defaultValue="value" onBlur={onBlur} />, {
attachTo: document.body,
const { container, unmount } = render(<Input allowClear defaultValue="value" onBlur={onBlur} />, {
container: document.body,
});
wrapper.find('input').getDOMNode<HTMLInputElement>().focus();
wrapper.find('.ant-input-clear-icon').at(0).simulate('mouseDown');
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
wrapper.find('.ant-input-clear-icon').at(0).simulate('mouseUp');
wrapper.find('.ant-input-clear-icon').at(0).simulate('focus');
wrapper.find('.ant-input-clear-icon').at(0).getDOMNode<HTMLInputElement>().click();
container.querySelector('input')?.focus();
fireEvent.mouseDown(container.querySelector('.ant-input-clear-icon')!);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
fireEvent.mouseUp(container.querySelector('.ant-input-clear-icon')!);
fireEvent.focus(container.querySelector('.ant-input-clear-icon')!);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(onBlur).not.toBeCalled();
wrapper.unmount();
unmount();
});
// https://github.com/ant-design/ant-design/issues/31927
@ -398,34 +394,35 @@ describe('Input allowClear', () => {
);
};
const wrapper = mount(<App />);
const { container, unmount } = render(<App />);
wrapper.find('input').getDOMNode<HTMLInputElement>().focus();
wrapper.find('input').simulate('change', { target: { value: '111' } });
expect(wrapper.find('input').getDOMNode<HTMLInputElement>().value).toEqual('111');
container.querySelector('input')?.focus();
fireEvent.change(container.querySelector('input')!, { target: { value: '111' } });
expect(container.querySelector('input')?.value).toEqual('111');
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(wrapper.find('input').getDOMNode<HTMLInputElement>().value).toEqual('');
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(container.querySelector('input')?.value).toEqual('');
wrapper.unmount();
unmount();
});
it('not crash when value is number', () => {
const wrapper = mount(<Input suffix="Bamboo" value={1} />);
expect(wrapper).toBeTruthy();
const { container } = render(<Input suffix="Bamboo" value={1} />);
expect(container).toBeTruthy();
});
it('should display boolean value as string', () => {
// @ts-ignore
const wrapper = mount(<Input value />);
expect(wrapper.find('input').first().getDOMNode<HTMLInputElement>().value).toBe('true');
wrapper.setProps({ value: false });
expect(wrapper.find('input').first().getDOMNode<HTMLInputElement>().value).toBe('false');
const { container, rerender } = render(<Input value />);
expect(container.querySelector('input')?.value).toBe('true');
// @ts-ignore
rerender(<Input value={false} />);
expect(container.querySelector('input')?.value).toBe('false');
});
it('should support custom clearIcon', () => {
const wrapper = mount(<Input allowClear={{ clearIcon: 'clear' }} />);
expect(wrapper.find('.ant-input-clear-icon').text()).toBe('clear');
const { container } = render(<Input allowClear={{ clearIcon: 'clear' }} />);
expect(container.querySelector('.ant-input-clear-icon')?.textContent).toBe('clear');
});
});
@ -438,9 +435,9 @@ describe('typescript types ', () => {
'data-testid': 'test-id',
'data-id': '12345',
};
const wrapper = mount(<Input {...props} />);
const input = wrapper.find('input').first().getDOMNode();
expect(input.getAttribute('data-testid')).toBe('test-id');
expect(input.getAttribute('data-id')).toBe('12345');
const { container } = render(<Input {...props} />);
const input = container.querySelector('input');
expect(input?.getAttribute('data-testid')).toBe('test-id');
expect(input?.getAttribute('data-id')).toBe('12345');
});
});

View File

@ -1,9 +1,7 @@
import React, { useState } from 'react';
import { mount } from 'enzyme';
import RcTextArea from 'rc-textarea';
import Input from '..';
import focusTest from '../../../tests/shared/focusTest';
import { sleep, render } from '../../../tests/utils';
import { sleep, render, fireEvent, triggerResize } from '../../../tests/utils';
const { TextArea } = Input;
@ -69,34 +67,34 @@ describe('TextArea', () => {
it('should support onPressEnter and onKeyDown', () => {
const fakeHandleKeyDown = jest.fn();
const fakeHandlePressEnter = jest.fn();
const wrapper = mount(
const { container } = render(
<TextArea onKeyDown={fakeHandleKeyDown} onPressEnter={fakeHandlePressEnter} />,
);
/** KeyCode 65 is A */
wrapper.find('textarea').simulate('keydown', { keyCode: 65 });
fireEvent.keyDown(container.querySelector('textarea'), { keyCode: 65 });
expect(fakeHandleKeyDown).toHaveBeenCalledTimes(1);
expect(fakeHandlePressEnter).toHaveBeenCalledTimes(0);
/** KeyCode 13 is Enter */
wrapper.find('textarea').simulate('keydown', { keyCode: 13 });
fireEvent.keyDown(container.querySelector('textarea'), { keyCode: 13 });
expect(fakeHandleKeyDown).toHaveBeenCalledTimes(2);
expect(fakeHandlePressEnter).toHaveBeenCalledTimes(1);
});
it('should support disabled', () => {
const wrapper = mount(<TextArea disabled />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<TextArea disabled />);
expect(asFragment().firstChild).toMatchSnapshot();
});
describe('maxLength', () => {
it('should support maxLength', () => {
const wrapper = mount(<TextArea maxLength={10} />);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment } = render(<TextArea maxLength={10} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('maxLength should not block control', () => {
const wrapper = mount(<TextArea maxLength={1} value="light" />);
expect(wrapper.find('textarea').props().value).toEqual('light');
const { container } = render(<TextArea maxLength={1} value="light" />);
expect(container.querySelector('textarea').value).toEqual('light');
});
it('should limit correctly when in control', () => {
@ -105,20 +103,20 @@ describe('TextArea', () => {
return <TextArea maxLength={1} value={val} onChange={e => setVal(e.target.value)} />;
};
const wrapper = mount(<Demo />);
wrapper.find('textarea').simulate('change', { target: { value: 'light' } });
const { container } = render(<Demo />);
fireEvent.change(container.querySelector('textarea'), { target: { value: 'light' } });
expect(wrapper.find('textarea').props().value).toEqual('l');
expect(container.querySelector('textarea').value).toEqual('l');
});
it('should exceed maxLength when use IME', () => {
const onChange = jest.fn();
const wrapper = mount(<TextArea maxLength={1} onChange={onChange} />);
wrapper.find('textarea').simulate('compositionStart');
wrapper.find('textarea').simulate('change', { target: { value: 'zhu' } });
wrapper.find('textarea').simulate('compositionEnd', { currentTarget: { value: '竹' } });
wrapper.find('textarea').simulate('change', { target: { value: '竹' } });
const { container } = render(<TextArea maxLength={1} onChange={onChange} />);
fireEvent.compositionStart(container.querySelector('textarea'));
fireEvent.change(container.querySelector('textarea'), { target: { value: 'zhu' } });
fireEvent.compositionEnd(container.querySelector('textarea'), { currentTarget: { value: '竹' } });
fireEvent.change(container.querySelector('textarea'), { target: { value: '竹' } });
expect(onChange).toHaveBeenLastCalledWith(
expect.objectContaining({ target: expect.objectContaining({ value: '竹' }) }),
@ -128,90 +126,72 @@ describe('TextArea', () => {
// 字符输入
it('should not cut off string when cursor position is not at the end', () => {
const onChange = jest.fn();
const wrapper = mount(<TextArea maxLength={6} defaultValue="123456" onChange={onChange} />);
wrapper
.find('textarea')
.simulate('change', { target: { selectionStart: 1, value: 'w123456' } });
wrapper
.find('textarea')
.simulate('change', { target: { selectionStart: 3, value: '123w456' } });
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('123456');
const { container } = render(<TextArea maxLength={6} defaultValue="123456" onChange={onChange} />);
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 1, value: 'w123456' } });
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 3, value: 'w123456' } });
expect(container.querySelector('textarea').value).toBe('123456');
});
// 拼音输入
// 1. 光标位于最后且当前字符数未达到6个若选中的字符 + 原字符的长度超过6个则将最终的字符按照maxlength截断
it('when the input method is pinyin and the cursor is at the end, should use maxLength to crop', () => {
const onChange = jest.fn();
const wrapper = mount(<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />);
wrapper.find('textarea').instance().value = '1234'; // enzyme not support change `currentTarget`
wrapper.find('textarea').instance().selectionStart = 4;
wrapper.find('textarea').simulate('compositionStart');
const { container } = render(<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />);
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 4, value: '1234' } });
fireEvent.compositionStart(container.querySelector('textarea'));
wrapper
.find('textarea')
.simulate('change', { target: { selectionStart: 9, value: '1234z z z' } });
wrapper
.find('textarea')
.simulate('change', { target: { selectionStart: 7, value: '1234组织者' } });
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 9, value: '1234z z z' } });
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 7, value: '1234组织者' } });
wrapper.find('textarea').instance().value = '1234组织者';
wrapper.find('textarea').instance().selectionStart = 7;
wrapper.find('textarea').simulate('compositionEnd');
fireEvent.compositionEnd(container.querySelector('textarea'));
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('1234组织');
expect(container.querySelector('textarea').value).toBe('1234组织');
});
// 2. 光标位于中间或开头且当前字符数未达到6个若选中的字符 + 原字符的长度超过6个则显示原有字符
it('when the input method is Pinyin and the cursor is in the middle, should display the original string', () => {
const onChange = jest.fn();
const wrapper = mount(<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />);
wrapper.find('textarea').instance().value = '1234'; // enzyme not support change `currentTarget`
wrapper.find('textarea').instance().selectionStart = 2;
wrapper.find('textarea').simulate('compositionStart');
const { container } = render(<TextArea maxLength={6} defaultValue="1234" onChange={onChange} />);
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 2, value: '1234' } });
fireEvent.compositionStart(container.querySelector('textarea'));
wrapper
.find('textarea')
.simulate('change', { target: { selectionStart: 2, value: '12z z z34' } });
wrapper
.find('textarea')
.simulate('change', { target: { selectionStart: 5, value: '12组织者34' } });
wrapper.find('textarea').instance().value = '12组织者34';
wrapper.find('textarea').instance().selectionStart = 5;
wrapper.find('textarea').simulate('compositionEnd');
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 2, value: '12z z z34' } });
fireEvent.change(container.querySelector('textarea'), { target: { selectionStart: 5, value: '12组织者34' } });
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('1234');
fireEvent.compositionEnd(container.querySelector('textarea'));
expect(container.querySelector('textarea').value).toBe('1234');
});
});
it('when prop value not in this.props, resizeTextarea should be called', async () => {
const ref = React.createRef();
const wrapper = mount(<TextArea aria-label="textarea" ref={ref} />);
const { container } = render(<TextArea aria-label="textarea" ref={ref} />);
const resizeTextarea = jest.spyOn(ref.current.resizableTextArea, 'resizeTextarea');
wrapper.find('textarea').simulate('change', {
target: {
value: 'test',
},
});
fireEvent.change(container.querySelector('textarea'), { target: { value: 'test' } });
expect(resizeTextarea).toHaveBeenCalled();
});
it('handleKeyDown', () => {
const onPressEnter = jest.fn();
const onKeyDown = jest.fn();
const wrapper = mount(
const { container } = render(
<TextArea onPressEnter={onPressEnter} onKeyDown={onKeyDown} aria-label="textarea" />,
);
wrapper.find(RcTextArea).instance().handleKeyDown({ keyCode: 13 });
fireEvent.keyDown(container.querySelector('textarea'), { keyCode: 13 });
expect(onPressEnter).toHaveBeenCalled();
expect(onKeyDown).toHaveBeenCalled();
});
it('should trigger onResize', async () => {
const onResize = jest.fn();
const wrapper = mount(<TextArea onResize={onResize} autoSize />);
const ref = React.createRef();
render(<TextArea ref={ref} onResize={onResize} autoSize />);
await sleep(100);
wrapper.triggerResize();
const target = ref.current.resizableTextArea.textArea;
triggerResize(target);
await Promise.resolve();
expect(onResize).toHaveBeenCalledWith(
@ -236,86 +216,80 @@ describe('TextArea', () => {
describe('should support showCount', () => {
it('maxLength', () => {
const wrapper = mount(<TextArea maxLength={5} showCount value="12345" />);
const textarea = wrapper.find('.ant-input-textarea');
expect(wrapper.find('textarea').prop('value')).toBe('12345');
expect(textarea.prop('data-count')).toBe('5 / 5');
const { container } = render(<TextArea maxLength={5} showCount value="12345" />);
expect(container.querySelector('textarea').value).toBe('12345');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe('5 / 5');
});
it('control exceed maxLength', () => {
const wrapper = mount(<TextArea maxLength={5} showCount value="12345678" />);
const textarea = wrapper.find('.ant-input-textarea');
expect(wrapper.find('textarea').prop('value')).toBe('12345678');
expect(textarea.prop('data-count')).toBe('8 / 5');
const { container } = render(<TextArea maxLength={5} showCount value="12345678" />);
expect(container.querySelector('textarea').value).toBe('12345678');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe('8 / 5');
});
describe('emoji', () => {
it('should minimize value between emoji length and maxLength', () => {
const wrapper = mount(<TextArea maxLength={1} showCount value="👀" />);
const textarea = wrapper.find('.ant-input-textarea');
expect(wrapper.find('textarea').prop('value')).toBe('👀');
expect(textarea.prop('data-count')).toBe('1 / 1');
const { container } = render(<TextArea maxLength={1} showCount value="👀" />);
expect(container.querySelector('textarea').value).toBe('👀');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe('1 / 1');
// fix: 当 maxLength 长度为 2 的时候,输入 emoji 之后 showCount 会显示 1/2但是不能再输入了
// zombieJ: 逻辑统一了emoji 现在也可以正确计数了
const wrapper1 = mount(<TextArea maxLength={2} showCount value="👀" />);
const textarea1 = wrapper1.find('.ant-input-textarea');
expect(textarea1.prop('data-count')).toBe('1 / 2');
const { container: container1 } = render(<TextArea maxLength={2} showCount value="👀" />);
expect(container1.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe('1 / 2');
});
it('defaultValue should slice', () => {
const wrapper = mount(<TextArea maxLength={1} defaultValue="🧐cut" />);
expect(wrapper.find('textarea').prop('value')).toBe('🧐');
const { container } = render(<TextArea maxLength={1} defaultValue="🧐cut" />);
expect(container.querySelector('textarea').value).toBe('🧐');
});
// 修改TextArea value截取规则后新增单测
it('slice emoji', () => {
const wrapper = mount(<TextArea maxLength={5} showCount value="1234😂" />);
const textarea = wrapper.find('.ant-input-textarea');
expect(wrapper.find('textarea').prop('value')).toBe('1234😂');
expect(textarea.prop('data-count')).toBe('5 / 5');
const { container } = render(<TextArea maxLength={5} showCount value="1234😂" />);
expect(container.querySelector('textarea').value).toBe('1234😂');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe('5 / 5');
});
});
it('className & style patch to outer', () => {
const wrapper = mount(
const { container } = render(
<TextArea className="bamboo" style={{ background: 'red' }} showCount />,
);
// Outer
expect(wrapper.find('div').first().hasClass('bamboo')).toBeTruthy();
expect(wrapper.find('div').first().props().style.background).toEqual('red');
expect(container.querySelector('div').classList.contains('bamboo')).toBeTruthy();
expect(container.querySelector('div').style.background).toEqual('red');
// Inner
expect(wrapper.find('.ant-input').hasClass('bamboo')).toBeFalsy();
expect(wrapper.find('.ant-input').props().style.background).toBeFalsy();
expect(container.querySelector('.ant-input').classList.contains('bamboo')).toBeFalsy();
expect(container.querySelector('.ant-input').style.background).toBeFalsy();
});
it('count formatter', () => {
const wrapper = mount(
const { container } = render(
<TextArea
maxLength={5}
showCount={{ formatter: ({ count, maxLength }) => `${count}, ${maxLength}` }}
value="12345"
/>,
);
const textarea = wrapper.find('.ant-input-textarea');
expect(wrapper.find('textarea').prop('value')).toBe('12345');
expect(textarea.prop('data-count')).toBe('5, 5');
expect(container.querySelector('textarea').value).toBe('12345');
expect(container.querySelector('.ant-input-textarea').getAttribute('data-count')).toBe('5, 5');
});
});
it('should support size', async () => {
const wrapper = mount(<TextArea size="large" />);
expect(wrapper.find('textarea').hasClass('ant-input-lg')).toBe(true);
expect(wrapper.render()).toMatchSnapshot();
const { asFragment, container } = render(<TextArea size="large" />);
expect(container.querySelector('textarea').classList.contains('ant-input-lg')).toBe(true);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('set mouse cursor position', () => {
const defaultValue = '11111';
const valLength = defaultValue.length;
const ref = React.createRef();
mount(<TextArea autoFocus ref={ref} defaultValue={defaultValue} />);
render(<TextArea autoFocus ref={ref} defaultValue={defaultValue} />);
ref.current.resizableTextArea.textArea.setSelectionRange(valLength, valLength);
expect(ref.current.resizableTextArea.textArea.selectionStart).toEqual(5);
expect(ref.current.resizableTextArea.textArea.selectionEnd).toEqual(5);
@ -324,110 +298,113 @@ describe('TextArea', () => {
describe('TextArea allowClear', () => {
it('should change type when click', () => {
const wrapper = mount(<TextArea allowClear />);
wrapper.find('textarea').simulate('change', { target: { value: '111' } });
expect(wrapper.find('textarea').getDOMNode().value).toEqual('111');
expect(wrapper.render()).toMatchSnapshot();
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(wrapper.render()).toMatchSnapshot();
expect(wrapper.find('textarea').getDOMNode().value).toEqual('');
const { asFragment, container } = render(<TextArea allowClear />);
fireEvent.change(container.querySelector('textarea'), { target: { value: '111' } });
expect(container.querySelector('textarea').value).toEqual('111');
expect(asFragment().firstChild).toMatchSnapshot();
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(asFragment().firstChild).toMatchSnapshot();
expect(container.querySelector('textarea').value).toEqual('');
});
it('should not show icon if value is undefined, null or empty string', () => {
const wrappers = [null, undefined, ''].map(val => mount(<TextArea allowClear value={val} />));
wrappers.forEach(wrapper => {
expect(wrapper.find('textarea').getDOMNode().value).toEqual('');
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
expect(wrapper.render()).toMatchSnapshot();
const wrappers = [null, undefined, ''].map(val => render(<TextArea allowClear value={val} />));
wrappers.forEach(({ asFragment, container }) => {
expect(container.querySelector('textarea').value).toEqual('');
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
});
it('should not show icon if defaultValue is undefined, null or empty string', () => {
const wrappers = [null, undefined, ''].map(val =>
mount(<TextArea allowClear defaultValue={val} />),
render(<TextArea allowClear defaultValue={val} />),
);
wrappers.forEach(wrapper => {
expect(wrapper.find('textarea').getDOMNode().value).toEqual('');
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
expect(wrapper.render()).toMatchSnapshot();
wrappers.forEach(({ asFragment, container }) => {
expect(container.querySelector('textarea').value).toEqual('');
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
expect(asFragment().firstChild).toMatchSnapshot();
});
});
it('should trigger event correctly', () => {
let argumentEventObject;
let argumentEventObjectType;
let argumentEventObjectValue;
const onChange = e => {
argumentEventObject = e;
argumentEventObjectType = e.type;
argumentEventObjectValue = e.target.value;
};
const wrapper = mount(<TextArea allowClear defaultValue="111" onChange={onChange} />);
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(argumentEventObject.type).toBe('click');
const { container } = render(<TextArea allowClear defaultValue="111" onChange={onChange} />);
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(argumentEventObjectType).toBe('click');
expect(argumentEventObjectValue).toBe('');
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('');
expect(container.querySelector('textarea').value).toBe('');
});
it('should trigger event correctly on controlled mode', () => {
let argumentEventObject;
let argumentEventObjectType;
let argumentEventObjectValue;
const onChange = e => {
argumentEventObject = e;
argumentEventObjectType = e.type;
argumentEventObjectValue = e.target.value;
};
const wrapper = mount(<TextArea allowClear value="111" onChange={onChange} />);
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(argumentEventObject.type).toBe('click');
const { container } = render(<TextArea allowClear value="111" onChange={onChange} />);
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(argumentEventObjectType).toBe('click');
expect(argumentEventObjectValue).toBe('');
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('111');
expect(container.querySelector('textarea').value).toBe('111');
});
it('should focus textarea after clear', () => {
const wrapper = mount(<TextArea allowClear defaultValue="111" />, { attachTo: document.body });
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(document.activeElement).toBe(wrapper.find('textarea').at(0).getDOMNode());
wrapper.unmount();
const { container, unmount } = render(<TextArea allowClear defaultValue="111" />, { container: document.body });
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(document.activeElement).toBe(container.querySelector('textarea'));
unmount();
});
it('should not support allowClear when it is disabled', () => {
const wrapper = mount(<TextArea allowClear defaultValue="111" disabled />);
expect(wrapper.find('.ant-input-clear-icon-hidden').exists()).toBeTruthy();
const { container } = render(<TextArea allowClear defaultValue="111" disabled />);
expect(container.querySelector('.ant-input-clear-icon-hidden')).toBeTruthy();
});
it('not block input when `value` is undefined', () => {
const wrapper = mount(<Input value={undefined} />);
wrapper.find('input').simulate('change', { target: { value: 'Bamboo' } });
expect(wrapper.find('input').props().value).toEqual('Bamboo');
const { container, rerender } = render(<Input value={undefined} />);
fireEvent.change(container.querySelector('input'), { target: { value: 'Bamboo' } });
expect(container.querySelector('input').value).toEqual('Bamboo');
// Controlled
wrapper.setProps({ value: 'Light' });
wrapper.find('input').simulate('change', { target: { value: 'Bamboo' } });
expect(wrapper.find('input').props().value).toEqual('Light');
rerender(<Input value="Light" />);
fireEvent.change(container.querySelector('input'), { target: { value: 'Bamboo' } });
expect(container.querySelector('input').value).toEqual('Light');
});
it('scroll to bottom when autoSize', async () => {
const wrapper = mount(<Input.TextArea autoSize />, { attachTo: document.body });
wrapper.find('textarea').simulate('focus');
wrapper.find('textarea').getDOMNode().focus();
const ref = React.createRef();
const { container, unmount } = render(<Input.TextArea ref={ref} autoSize />, { container: document.body, legacyRoot: true });
fireEvent.focus(container.querySelector('textarea'));
container.querySelector('textarea').focus();
const setSelectionRangeFn = jest.spyOn(
wrapper.find('textarea').getDOMNode(),
container.querySelector('textarea'),
'setSelectionRange',
);
wrapper.find('textarea').simulate('input', { target: { value: '\n1' } });
wrapper.triggerResize();
fireEvent.input(container.querySelector('textarea'), { target: { value: '\n1' } });
const target = ref.current.resizableTextArea.textArea;
triggerResize(target);
await sleep(100);
expect(setSelectionRangeFn).toHaveBeenCalled();
wrapper.unmount();
unmount();
});
// https://github.com/ant-design/ant-design/issues/26308
it('should display defaultValue when value is undefined', () => {
const wrapper = mount(<Input.TextArea defaultValue="Light" value={undefined} />);
expect(wrapper.find('textarea').at(0).getDOMNode().value).toBe('Light');
const { container } = render(<Input.TextArea defaultValue="Light" value={undefined} />);
expect(container.querySelector('textarea').value).toBe('Light');
});
it('onChange event should return HTMLTextAreaElement', () => {
const onChange = jest.fn();
const wrapper = mount(<Input.TextArea onChange={onChange} allowClear />);
const { container } = render(<Input.TextArea onChange={onChange} allowClear />);
function isNativeElement() {
expect(onChange).toHaveBeenCalledWith(
@ -440,20 +417,16 @@ describe('TextArea allowClear', () => {
}
// Change
wrapper.find('textarea').simulate('change', {
target: {
value: 'bamboo',
},
});
fireEvent.change(container.querySelector('textarea'), { target: { value: 'bamboo' } });
isNativeElement();
// Composition End
wrapper.find('textarea').instance().value = 'light'; // enzyme not support change `currentTarget`
wrapper.find('textarea').simulate('compositionEnd');
fireEvent.change(container.querySelector('textarea'), { target: { value: 'light' } });
fireEvent.compositionEnd(container.querySelector('textarea'));
isNativeElement();
// Reset
wrapper.find('.ant-input-clear-icon').first().simulate('click');
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
isNativeElement();
});
@ -472,45 +445,44 @@ describe('TextArea allowClear', () => {
);
};
const wrapper = mount(<App />);
const { container, unmount } = render(<App />);
container.querySelector('textarea').focus();
fireEvent.change(container.querySelector('textarea'), { target: { value: '111' } });
expect(container.querySelector('textarea').value).toEqual('111');
wrapper.find('textarea').getDOMNode().focus();
wrapper.find('textarea').simulate('change', { target: { value: '111' } });
expect(wrapper.find('textarea').getDOMNode().value).toEqual('111');
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(container.querySelector('textarea').value).toEqual('');
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(wrapper.find('textarea').getDOMNode().value).toEqual('');
wrapper.unmount();
unmount();
});
// https://github.com/ant-design/ant-design/issues/31200
it('should not lost focus when clear input', () => {
const onBlur = jest.fn();
const wrapper = mount(<TextArea allowClear defaultValue="value" onBlur={onBlur} />, {
attachTo: document.body,
const { container, unmount } = render(<TextArea allowClear defaultValue="value" onBlur={onBlur} />, {
container: document.body,
});
wrapper.find('textarea').getDOMNode().focus();
wrapper.find('.ant-input-clear-icon').at(0).simulate('mouseDown');
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
wrapper.find('.ant-input-clear-icon').at(0).simulate('mouseUp');
wrapper.find('.ant-input-clear-icon').at(0).simulate('focus');
wrapper.find('.ant-input-clear-icon').at(0).getDOMNode().click();
container.querySelector('textarea').focus();
fireEvent.mouseDown(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
fireEvent.mouseUp(container.querySelector('.ant-input-clear-icon'));
fireEvent.focus(container.querySelector('.ant-input-clear-icon'));
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(onBlur).not.toBeCalled();
wrapper.unmount();
unmount();
});
it('should focus text area after clear', () => {
const wrapper = mount(<TextArea allowClear defaultValue="111" />, { attachTo: document.body });
wrapper.find('.ant-input-clear-icon').at(0).simulate('click');
expect(document.activeElement).toBe(wrapper.find('textarea').at(0).getDOMNode());
wrapper.unmount();
const { container, unmount } = render(<TextArea allowClear defaultValue="111" />, { container: document.body });
fireEvent.click(container.querySelector('.ant-input-clear-icon'));
expect(document.activeElement).toBe(container.querySelector('textarea'));
unmount();
});
it('should display boolean value as string', () => {
const wrapper = mount(<TextArea value />);
expect(wrapper.find('textarea').first().getDOMNode().value).toBe('true');
wrapper.setProps({ value: false });
expect(wrapper.find('textarea').first().getDOMNode().value).toBe('false');
const { container, rerender } = render(<TextArea value />);
expect(container.querySelector('textarea').value).toBe('true');
rerender(<TextArea value={false} />);
expect(container.querySelector('textarea').value).toBe('false');
});
});

View File

@ -1,7 +1,7 @@
import React from 'react';
import { mount } from 'enzyme';
import Input from '..';
import type { InputProps } from '../Input';
import { render } from '../../../tests/utils';
describe('Input types', () => {
it('should support data-attributes', () => {
@ -9,9 +9,9 @@ describe('Input types', () => {
'data-test': 'test',
size: 'large',
};
const wrapper = mount(<Input {...dataProps} />);
expect(wrapper.find('input').prop('data-test')).toBe('test');
const wrapper2 = mount(<Input data-test="test" size="large" />);
expect(wrapper2.find('input').prop('data-test')).toBe('test');
const { container } = render(<Input {...dataProps} />);
expect(container.querySelector('input')?.getAttribute('data-test')).toBe('test');
const { container: container2 } = render(<Input data-test="test" size="large" />);
expect(container2.querySelector('input')?.getAttribute('data-test')).toBe('test');
});
});

View File

@ -102,6 +102,7 @@
.@{inputClass}-group-rtl & {
border-right: 0;
border-left: @border-width-base @border-style-base @input-border-color;
border-radius: @border-radius-base 0 0 @border-radius-base;
}
}

View File

@ -54,7 +54,7 @@ const Statistic: React.FC<StatisticProps & ConfigConsumerProps> = props => {
return (
<div className={cls} style={style} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
{title && <div className={`${prefixCls}-title`}>{title}</div>}
<Skeleton paragraph={false} loading={loading}>
<Skeleton paragraph={false} loading={loading} className={`${prefixCls}-skeleton`}>
<div style={valueStyle} className={`${prefixCls}-content`}>
{prefix && <span className={`${prefixCls}-content-prefix`}>{prefix}</span>}
{valueRender ? valueRender(valueNode) : valueNode}

View File

@ -86,7 +86,7 @@ exports[`renders ./components/statistic/demo/basic.md extend context correctly 1
Active Users
</div>
<div
class="ant-skeleton"
class="ant-skeleton ant-statistic-skeleton"
>
<div
class="ant-skeleton-content"

View File

@ -86,7 +86,7 @@ exports[`renders ./components/statistic/demo/basic.md correctly 1`] = `
Active Users
</div>
<div
class="ant-skeleton"
class="ant-skeleton ant-statistic-skeleton"
>
<div
class="ant-skeleton-content"

View File

@ -12,6 +12,10 @@
font-size: @statistic-title-font-size;
}
&-skeleton {
padding-top: @padding-md;
}
&-content {
color: @heading-color;
font-size: @statistic-content-font-size;

View File

@ -142,7 +142,7 @@
"rc-progress": "~3.3.2",
"rc-rate": "~2.9.0",
"rc-resize-observer": "^1.2.0",
"rc-segmented": "~2.1.0 ",
"rc-segmented": "~2.1.0",
"rc-select": "~14.1.1",
"rc-slider": "~10.0.0",
"rc-steps": "~4.1.0",
@ -169,7 +169,7 @@
"@testing-library/react": "^12.0.0",
"@types/enzyme": "^3.10.5",
"@types/gtag.js": "^0.0.10",
"@types/jest": "^27.0.0",
"@types/jest": "^28.0.0",
"@types/jest-axe": "^3.5.3",
"@types/jest-environment-puppeteer": "^5.0.0",
"@types/jest-image-snapshot": "^4.1.0",

View File

@ -4,6 +4,8 @@ import { StrictMode } from 'react';
import { act } from 'react-dom/test-utils';
import type { RenderOptions } from '@testing-library/react';
import { render } 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 setMockDate(dateString = '2017-09-18T03:30:07.795') {
MockDate.set(dateString);
@ -28,4 +30,14 @@ const customRender = (ui: ReactElement, options?: Omit<RenderOptions, 'wrapper'>
export { customRender as render };
export const triggerResize = (target: Element) => {
const originGetBoundingClientRect = target.getBoundingClientRect;
target.getBoundingClientRect = () => ({ width: 510, height: 903 }) as DOMRect;
onLibResize([{ target } as ResizeObserverEntry]);
onEsResize([{ target } as ResizeObserverEntry]);
target.getBoundingClientRect = originGetBoundingClientRect;
}
export * from '@testing-library/react';