import React, { Component } from 'react'; import { mount } from 'enzyme'; import { act } from 'react-dom/test-utils'; import scrollIntoView from 'scroll-into-view-if-needed'; import Form from '..'; import Input from '../../input'; import Button from '../../button'; import mountTest from '../../../tests/shared/mountTest'; import rtlTest from '../../../tests/shared/rtlTest'; import { sleep } from '../../../tests/utils'; jest.mock('scroll-into-view-if-needed'); describe('Form', () => { mountTest(Form); mountTest(Form.Item); rtlTest(Form); rtlTest(Form.Item); scrollIntoView.mockImplementation(() => {}); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); async function change(wrapper, index, value, executeMockTimer) { wrapper.find(Input).at(index).simulate('change', { target: { value } }); await sleep(200); if (executeMockTimer) { act(() => { jest.runAllTimers(); wrapper.update(); }); await sleep(1); } } beforeEach(() => { jest.useRealTimers(); scrollIntoView.mockReset(); }); afterEach(() => { errorSpy.mockReset(); }); afterAll(() => { errorSpy.mockRestore(); scrollIntoView.mockRestore(); }); describe('noStyle Form.Item', () => { it('work', async () => { jest.useFakeTimers(); const onChange = jest.fn(); const wrapper = mount(
, ); await change(wrapper, 0, '', true); expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy(); expect(wrapper.find('.ant-form-item-has-error').length).toBeTruthy(); expect(onChange).toHaveBeenCalled(); jest.useRealTimers(); }); it('should clean up', async () => { jest.useFakeTimers(); const Demo = () => { const [form] = Form.useForm(); return (
{ await sleep(0); try { await form.validateFields(); } catch (e) { // do nothing } }} /> {() => { const aaa = form.getFieldValue('aaa'); if (aaa === '1') { return ( ); } return ( ); }}
); }; const wrapper = mount(); await change(wrapper, 0, '1', true); expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa'); await change(wrapper, 0, '2', true); expect(wrapper.find('.ant-form-item-explain').text()).toEqual('ccc'); await change(wrapper, 0, '1', true); expect(wrapper.find('.ant-form-item-explain').text()).toEqual('aaa'); jest.useRealTimers(); }); }); it('`shouldUpdate` should work with render props', () => { mount(
{() => null}
, ); expect(errorSpy).toHaveBeenCalledWith( 'Warning: [antd: Form.Item] `children` of render props only work with `shouldUpdate` or `dependencies`.', ); }); it("`shouldUpdate` shouldn't work with `dependencies`", () => { mount(
{() => null}
, ); expect(errorSpy).toHaveBeenCalledWith( "Warning: [antd: Form.Item] `shouldUpdate` and `dependencies` shouldn't be used together. See https://ant.design/components/form/#dependencies.", ); }); it('`name` should not work with render props', () => { mount(
{() => null}
, ); expect(errorSpy).toHaveBeenCalledWith( "Warning: [antd: Form.Item] Do not use `name` with `children` of render props since it's not a field.", ); }); it('children is array has name props', () => { mount(
one
two
, ); expect(errorSpy).toHaveBeenCalledWith( 'Warning: [antd: Form.Item] `children` is array of render props cannot have `name`.', ); }); describe('scrollToField', () => { function test(name, genForm) { it(name, () => { let callGetForm; const Demo = () => { const { props, getForm } = genForm(); callGetForm = getForm; return (
); }; const wrapper = mount(, { attachTo: document.body }); expect(scrollIntoView).not.toHaveBeenCalled(); const form = callGetForm(); form.scrollToField('test', { block: 'start', }); const inputNode = document.getElementById('scroll_test'); expect(scrollIntoView).toHaveBeenCalledWith(inputNode, { block: 'start', scrollMode: 'if-needed', }); wrapper.unmount(); }); } // hooks test('useForm', () => { const [form] = Form.useForm(); return { props: { form }, getForm: () => form, }; }); // ref test('ref', () => { let form; return { props: { ref: instance => { form = instance; }, }, getForm: () => form, }; }); }); it('scrollToFirstError', async () => { const onFinishFailed = jest.fn(); const wrapper = mount(
, { attachTo: document.body }, ); expect(scrollIntoView).not.toHaveBeenCalled(); wrapper.find('form').simulate('submit'); await sleep(50); const inputNode = document.getElementById('test'); expect(scrollIntoView).toHaveBeenCalledWith(inputNode, { block: 'center', scrollMode: 'if-needed', }); expect(onFinishFailed).toHaveBeenCalled(); wrapper.unmount(); }); it('Form.Item should support data-*、aria-* and custom attribute', () => { const wrapper = mount(
, ); expect(wrapper.render()).toMatchSnapshot(); }); it('warning when use `name` but children is not validate element', () => { mount(
text
, ); expect(errorSpy).toHaveBeenCalledWith( 'Warning: [antd: Form.Item] `name` is only used for validate React element. If you are using Form.Item as layout display, please remove `name` instead.', ); }); it('dynamic change required', () => { const wrapper = mount(
({ required: getFieldValue('light') })]} >
, ); expect(wrapper.find('.ant-form-item-required')).toHaveLength(0); wrapper.find('input[type="checkbox"]').simulate('change', { target: { checked: true } }); wrapper.update(); expect(wrapper.find('.ant-form-item-required')).toHaveLength(1); }); it('should show related className when customize help', () => { const wrapper = mount(
, ); expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy(); }); it('warning when use v3 function', () => { Form.create(); expect(errorSpy).toHaveBeenCalledWith( 'Warning: [antd: Form] antd v4 removed `Form.create`. Please remove or use `@ant-design/compatible` instead.', ); }); // https://github.com/ant-design/ant-design/issues/20706 it('Error change should work', async () => { jest.useFakeTimers(); const wrapper = mount(
{ if (value === 'p') { return Promise.reject(new Error('not a p')); } return Promise.resolve(); }, }, ]} >
, ); /* eslint-disable no-await-in-loop */ for (let i = 0; i < 3; i += 1) { await change(wrapper, 0, '', true); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual("'name' is required"); await change(wrapper, 0, 'p', true); await sleep(100); wrapper.update(); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('not a p'); } /* eslint-enable */ jest.useRealTimers(); }); // https://github.com/ant-design/ant-design/issues/20813 it('should update help directly when provided', () => { function App() { const [message, updateMessage] = React.useState(''); return (
); }; const wrapper = mount(); wrapper.find('button').simulate('click'); expect(errorSpy).not.toHaveBeenCalled(); }); it('`label` support template', async () => { const wrapper = mount( // eslint-disable-next-line no-template-curly-in-string
, ); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); await sleep(100); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('Bamboo is good!'); }); it('`name` support template when label is not provided', async () => { const wrapper = mount( // eslint-disable-next-line no-template-curly-in-string
, ); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); await sleep(100); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('Bamboo is good!'); }); it('`messageVariables` support validate', async () => { const wrapper = mount( // eslint-disable-next-line no-template-curly-in-string
, ); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); await sleep(100); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('Bamboo is good!'); }); it('validation message should has alert role', async () => { // https://github.com/ant-design/ant-design/issues/25711 const wrapper = mount( // eslint-disable-next-line no-template-curly-in-string
, ); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); await sleep(100); expect(wrapper.find('.ant-form-item-explain div').getDOMNode().getAttribute('role')).toBe( 'alert', ); }); it('return same form instance', () => { const instances = new Set(); const App = () => { const [form] = Form.useForm(); instances.add(form); const [, forceUpdate] = React.useState({}); return ( ); }; const wrapper = mount(); for (let i = 0; i < 5; i += 1) { wrapper.find('button').simulate('click'); } expect(instances.size).toEqual(1); }); it('avoid re-render', async () => { let renderTimes = 0; const MyInput = ({ value = '', ...props }) => { renderTimes += 1; return ; }; const Demo = () => (
); const wrapper = mount(); renderTimes = 0; wrapper.find('input').simulate('change', { target: { value: 'a', }, }); await sleep(); expect(renderTimes).toEqual(1); expect(wrapper.find('input').props().value).toEqual('a'); }); it('warning with `defaultValue`', () => { mount(
, ); expect(errorSpy).toHaveBeenCalledWith( 'Warning: [antd: Form.Item] `defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.', ); }); it('Remove Field should also reset error', async () => { class Demo extends React.Component { state = { showA: true, }; render() { return (
{this.state.showA ? ( ) : ( )}
); } } const wrapper = mount(); await Promise.resolve(); expect(wrapper.find('.ant-form-item').last().hasClass('ant-form-item-with-help')).toBeTruthy(); wrapper.setState({ showA: false }); await Promise.resolve(); wrapper.update(); expect(wrapper.find('.ant-form-item').last().hasClass('ant-form-item-with-help')).toBeFalsy(); }); it('no warning of initialValue & getValueProps & preserve', () => { mount(
null} preserve={false}>
, ); expect(errorSpy).not.toHaveBeenCalled(); }); it('should customize id work', () => { const wrapper = mount(
, ); expect(wrapper.find('input').prop('id')).toEqual('bamboo'); }); it('Form validateTrigger', () => { const wrapper = mount(
, ); expect(wrapper.find('input').prop('onBlur')).toBeTruthy(); }); describe('Form item hidden', () => { it('should work', () => { const wrapper = mount(
, ); expect(wrapper).toMatchRenderedSnapshot(); }); it('noStyle should not work when hidden', () => { const wrapper = mount(
, ); expect(wrapper).toMatchRenderedSnapshot(); }); }); it('legacy hideRequiredMark', () => { const wrapper = mount(
, ); expect(wrapper.find('form').hasClass('ant-form-hide-required-mark')).toBeTruthy(); }); it('_internalItemRender api test', () => { const wrapper = mount(
(
{doms.input} {doms.errorList} {doms.extra}
), }} >
, ); expect(wrapper.find('#test').exists()).toBeTruthy(); }); describe('tooltip', () => { it('ReactNode', () => { const wrapper = mount(
Bamboo}>
, ); const tooltipProps = wrapper.find('Tooltip').props(); expect(tooltipProps.title).toEqual(Bamboo); }); it('config', () => { const wrapper = mount(
, ); const tooltipProps = wrapper.find('Tooltip').props(); expect(tooltipProps.title).toEqual('Bamboo'); }); }); it('warningOnly validate', async () => { jest.useFakeTimers(); const wrapper = mount(
, ); await change(wrapper, 0, '', true); expect(wrapper.find('.ant-form-item-with-help').length).toBeTruthy(); expect(wrapper.find('.ant-form-item-has-warning').length).toBeTruthy(); jest.useRealTimers(); }); it('not warning when remove on validate', async () => { jest.useFakeTimers(); let rejectFn = null; const wrapper = mount(
new Promise((_, reject) => { rejectFn = reject; }), }, ]} >
, ); await change(wrapper, 0, '', true); wrapper.unmount(); // Delay validate failed rejectFn(new Error('delay failed')); expect(errorSpy).not.toHaveBeenCalled(); jest.useRealTimers(); }); });