Files
ant-design/components/input/__tests__/Search.test.tsx
EmilyyyLiu bbef6f8752 feat[Input.Search]: Abandoning the use of addon * in the Search component (#55705)
* refactor: Search component refactoring using compact

* test: update snap

* test: update snap

* test: update snap

* chore: fix style

* chore: fix search style

* test: update snapshot

* test: update snapshot

* test: update snapshot

---------

Co-authored-by: 刘欢 <lh01217311@antgroup.com>
Co-authored-by: 二货机器人 <smith3816@gmail.com>
2025-11-14 16:16:23 +08:00

362 lines
14 KiB
TypeScript

import React from 'react';
import { EditOutlined, UserOutlined } from '@ant-design/icons';
import { fireEvent, render } from '@testing-library/react';
import focusTest from '../../../tests/shared/focusTest';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import Button from '../../button';
import type { InputRef } from '../Input';
import Search from '../Search';
import type { SearchProps } from '../Search';
describe('Input.Search', () => {
focusTest(Search, { refFocus: true });
mountTest(Search);
rtlTest(Search);
it('should support custom button', () => {
const { asFragment } = render(<Search enterButton={<button type="button">ok</button>} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support custom Button', () => {
const { asFragment } = render(<Search enterButton={<Button>ok</Button>} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support enterButton null', () => {
expect(() => {
render(<Search enterButton={null} />);
}).not.toThrow();
});
it('should support ReactNode suffix without error', () => {
const { asFragment } = render(<Search suffix={<div>ok</div>} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should disable enter button when disabled prop is true', () => {
const { container } = render(<Search placeholder="input search text" enterButton disabled />);
expect(container.querySelectorAll('.ant-btn[disabled]')).toHaveLength(1);
});
it('should disable search icon when disabled prop is true', () => {
const onSearch = jest.fn();
const { container } = render(
<Search defaultValue="search text" onSearch={onSearch} disabled />,
);
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(0);
});
it('should trigger onSearch when click search icon', () => {
const onSearch = jest.fn();
const { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything(), { source: 'input' });
});
it('should trigger onSearch when click search button', () => {
const onSearch = jest.fn();
const { container } = render(
<Search defaultValue="search text" enterButton onSearch={onSearch} />,
);
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything(), { source: 'input' });
});
it('should trigger onSearch when click search button with text', () => {
const onSearch = jest.fn();
const { container } = render(
<Search defaultValue="search text" enterButton="button text" onSearch={onSearch} />,
);
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything(), { source: 'input' });
});
it('should trigger onSearch when click search button with customize button', () => {
const onSearch = jest.fn();
const { container } = render(
<Search
defaultValue="search text"
enterButton={<Button>antd button</Button>}
onSearch={onSearch}
/>,
);
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything(), { source: 'input' });
});
it('should trigger onSearch when click search button of native', () => {
const onSearch = jest.fn();
const onButtonClick = jest.fn();
const { container } = render(
<Search
defaultValue="search text"
enterButton={
<button type="button" onClick={onButtonClick}>
antd button
</button>
}
onSearch={onSearch}
/>,
);
fireEvent.click(container.querySelector('button')!);
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything(), { source: 'input' });
expect(onButtonClick).toHaveBeenCalledTimes(1);
});
it('should trigger onSearch when press enter', () => {
const onSearch = jest.fn();
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.anything(), { source: 'input' });
});
// 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 { container } = render(<Search defaultValue="search text" onSearch={onSearch} />);
fireEvent.compositionStart(container.querySelector('input')!);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
fireEvent.keyUp(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled();
fireEvent.compositionEnd(container.querySelector('input')!);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
fireEvent.keyUp(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).toHaveBeenCalledTimes(1);
expect(onSearch).toHaveBeenCalledWith('search text', expect.anything(), { source: 'input' });
});
// https://github.com/ant-design/ant-design/issues/14785
it('should support addonAfter', () => {
const addonAfter = <span>Addon After</span>;
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 { container } = render(
<Search allowClear defaultValue="value" onSearch={onSearch} onChange={onChange} />,
);
fireEvent.click(container.querySelector('.ant-input-clear-icon')!);
expect(onSearch).toHaveBeenLastCalledWith('', expect.anything(), { source: 'clear' });
expect(onChange).toHaveBeenCalled();
});
it('should support loading', () => {
const { asFragment } = render(<Search loading />);
const { asFragment: asFragmentWithEnterButton } = render(<Search loading enterButton />);
expect(asFragment().firstChild).toMatchSnapshot();
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
it('should not trigger onSearch when press enter while loading', () => {
const onSearch = jest.fn();
const { container } = render(<Search loading onSearch={onSearch} />);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onSearch).not.toHaveBeenCalled();
});
it('should support addonAfter and suffix for loading', () => {
const { asFragment } = render(<Search loading suffix="suffix" addonAfter="addonAfter" />);
const { asFragment: asFragmentWithEnterButton } = render(
<Search loading enterButton suffix="suffix" addonAfter="addonAfter" />,
);
expect(asFragment().firstChild).toMatchSnapshot();
expect(asFragmentWithEnterButton().firstChild).toMatchSnapshot();
});
it('should support invalid suffix', () => {
const { asFragment } = render(<Search suffix={[]} />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should support invalid addonAfter', () => {
const { asFragment } = render(<Search addonAfter={[]} enterButton />);
expect(asFragment().firstChild).toMatchSnapshot();
});
it('should prevent search button mousedown event', () => {
const ref = React.createRef<InputRef>();
const { container } = render(<Search ref={ref} enterButton="button text" />, {
container: document.body,
});
ref.current?.focus();
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 { container } = render(<Search ref={ref} enterButton />);
expect(() => {
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 { container } = render(<Search allowClear className="bamboo" />);
expect(container.querySelectorAll('.bamboo')).toHaveLength(1);
expect(container.querySelector('.ant-input-search')).toHaveClass('bamboo');
expect(container.querySelector('.ant-input-affix-wrapper')).not.toHaveClass('bamboo');
});
// https://github.com/ant-design/ant-design/issues/53897
it('should trigger onPressEnter when press enter', () => {
const onPressEnter = jest.fn();
const { container } = render(<Search onPressEnter={onPressEnter} />);
fireEvent.keyDown(container.querySelector('input')!, { key: 'Enter', keyCode: 13 });
expect(onPressEnter).toHaveBeenCalledTimes(1);
});
it('support function classNames and styles', () => {
const functionClassNames = (info: { props: SearchProps }) => {
const { props } = info;
const { enterButton, disabled } = props;
return {
root: 'dynamic-root',
input: enterButton ? 'dynamic-input-with-button' : 'dynamic-input-without-button',
prefix: 'dynamic-prefix',
suffix: 'dynamic-suffix',
count: 'dynamic-count',
button: {
root: 'dynamic-button-root',
icon: disabled ? 'dynamic-button-icon-disabled' : 'dynamic-button-icon',
},
};
};
const functionStyles = (info: { props: SearchProps }) => {
const { props } = info;
const { enterButton, disabled } = props;
return {
root: { color: 'rgb(255, 0, 0)' },
input: { color: enterButton ? 'rgb(0, 255, 0)' : 'rgb(255, 0, 0)' },
prefix: { color: 'rgb(0, 0, 255)' },
suffix: { color: 'rgb(255, 0, 0)' },
count: { color: 'rgb(255, 0, 0)' },
button: {
root: { color: 'rgb(0, 255, 0)' },
icon: { color: disabled ? 'rgb(0, 0, 255)' : 'rgb(255, 0, 0)' },
},
};
};
const { container, rerender } = render(
<Search
showCount
prefix={<UserOutlined />}
suffix={<EditOutlined />}
defaultValue="Hello, Ant Design"
classNames={functionClassNames}
styles={functionStyles}
disabled
/>,
);
const root = container.querySelector('.ant-input-search');
const input = container.querySelector('.ant-input');
const prefix = container.querySelector('.ant-input-prefix');
const suffix = container.querySelector('.ant-input-suffix');
const count = container.querySelector('.ant-input-show-count-suffix');
const button = container.querySelector('.ant-btn');
const buttonIcon = container.querySelector('.ant-btn-icon');
expect(root).toHaveClass('dynamic-root');
expect(input).toHaveClass('dynamic-input-without-button');
expect(prefix).toHaveClass('dynamic-prefix');
expect(suffix).toHaveClass('dynamic-suffix');
expect(count).toHaveClass('dynamic-count');
expect(button).toHaveClass('dynamic-button-root');
expect(buttonIcon).toHaveClass('dynamic-button-icon-disabled');
expect(root).toHaveStyle('color: rgb(255, 0, 0)');
expect(input).toHaveStyle('color: rgb(255, 0, 0)');
expect(prefix).toHaveStyle('color: rgb(0, 0, 255)');
expect(suffix).toHaveStyle('color: rgb(255, 0, 0)');
expect(count).toHaveStyle('color: rgb(255, 0, 0)');
expect(button).toHaveStyle('color: rgb(0, 255, 0)');
expect(buttonIcon).toHaveStyle('color: rgb(0, 0, 255)');
const objectClassNames = {
root: 'dynamic-root-default',
input: 'dynamic-input-default',
prefix: 'dynamic-prefix-default',
suffix: 'dynamic-suffix-default',
count: 'dynamic-count-default',
};
const objectStyles = {
root: { color: 'rgb(255, 0, 0)' },
input: { color: 'rgb(0, 255, 0)' },
prefix: { color: 'rgb(0, 0, 255)' },
suffix: { color: 'rgb(0, 255, 0)' },
count: { color: 'rgb(0, 255, 0)' },
};
const objectButtonClassNames = {
root: 'dynamic-custom-button-root',
icon: 'dynamic-custom-button-icon',
content: 'dynamic-custom-button-content',
};
const objectButtonStyles = {
root: { color: 'rgb(0, 255, 0)' },
icon: { color: 'rgb(255, 0, 0)' },
content: { color: 'rgb(0, 255, 0)' },
};
rerender(
<Search
showCount
prefix={<UserOutlined />}
suffix={<EditOutlined />}
defaultValue="Hello, Ant Design"
classNames={objectClassNames}
styles={objectStyles}
disabled
enterButton={
<Button
classNames={objectButtonClassNames}
styles={objectButtonStyles}
icon={<UserOutlined />}
>
button text
</Button>
}
/>,
);
const buttonContent = container.querySelector('.ant-btn > .ant-btn-icon + span');
expect(root).toHaveClass('dynamic-root-default');
expect(input).toHaveClass('dynamic-input-default');
expect(prefix).toHaveClass('dynamic-prefix-default');
expect(suffix).toHaveClass('dynamic-suffix-default');
expect(count).toHaveClass('dynamic-count-default');
expect(button).toHaveClass('dynamic-custom-button-root');
expect(buttonIcon).toHaveClass('dynamic-custom-button-icon');
expect(buttonContent).toHaveClass('dynamic-custom-button-content');
expect(root).toHaveStyle('color: rgb(255, 0, 0)');
expect(input).toHaveStyle('color: rgb(0, 255, 0)');
expect(prefix).toHaveStyle('color: rgb(0, 0, 255)');
expect(suffix).toHaveStyle('color: rgb(0, 255, 0)');
expect(count).toHaveStyle('color: rgb(0, 255, 0)');
expect(button).toHaveStyle('color: rgb(0, 255, 0)');
expect(buttonIcon).toHaveStyle('color: rgb(255, 0, 0)');
expect(buttonContent).toHaveStyle('color: rgb(0, 255, 0)');
});
});