import { mount } from 'enzyme'; import React, { Component, useState } from 'react'; import { act } from 'react-dom/test-utils'; import scrollIntoView from 'scroll-into-view-if-needed'; import classNames from 'classnames'; import Form from '..'; import * as Util from '../util'; import Button from '../../button'; import Input from '../../input'; import Select from '../../select'; import Upload from '../../upload'; import Cascader from '../../cascader'; import Checkbox from '../../checkbox'; import DatePicker from '../../date-picker'; import InputNumber from '../../input-number'; import Radio from '../../radio'; 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 } from '../../../tests/utils'; import ConfigProvider from '../../config-provider'; import Drawer from '../../drawer'; import zhCN from '../../locale/zh_CN'; import Modal from '../../modal'; const { RangePicker } = DatePicker; const { TextArea } = Input; 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(container, index, value, executeMockTimer) { 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); } } 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 { container } = render(
, ); await change(container, 0, '', true); expect(container.querySelectorAll('.ant-form-item-with-help').length).toBeTruthy(); expect(container.querySelectorAll('.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 { container } = render(); await change(container, 0, '1', true); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('aaa'); await change(container, 0, '2', true); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual('ccc'); await change(container, 0, '1', true); expect(container.querySelector('.ant-form-item-explain').textContent).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); }); describe('should show related className when customize help', () => { it('normal', () => { const wrapper = mount(
, ); expect(wrapper.exists('.ant-form-item-with-help')).toBeTruthy(); }); it('empty string', () => { const wrapper = mount(
, ); expect(wrapper.exists('.ant-form-item-with-help')).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 { container } = render(
{ 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(container, 0, 'bamboo', true); await change(container, 0, '', true); expect(container.querySelector('.ant-form-item-explain').textContent).toEqual( "'name' is required", ); await change(container, 0, 'p', true); await sleep(100); 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 it('should update help directly when provided', () => { function App() { const [message, updateMessage] = React.useState(''); return (
); }; const { container } = render(); fireEvent.click(container.querySelector('button')); 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!'); }); // https://github.com/ant-design/ant-design/issues/33691 it('should keep upper locale in nested ConfigProvider', async () => { const wrapper = mount(
, ); wrapper.find('form').simulate('submit'); await sleep(100); wrapper.update(); await sleep(100); expect(wrapper.find('.ant-form-item-explain').first().text()).toEqual('请输入Bamboo'); }); 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(, { strictMode: false, }); 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(, { strictMode: false, }); 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 () => { const Demo = ({ showA }) => (
{showA ? ( ) : ( )}
); const wrapper = mount(); await Promise.resolve(); expect(wrapper.find('.ant-form-item').last().hasClass('ant-form-item-with-help')).toBeTruthy(); wrapper.setProps({ 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', () => { render(
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.render()).toMatchSnapshot(); }); it('noStyle should not work when hidden', () => { const wrapper = mount(
, ); expect(wrapper.render()).toMatchSnapshot(); }); }); it('legacy hideRequiredMark', () => { const wrapper = mount(
, ); expect(wrapper.find('form').hasClass('ant-form-hide-required-mark')).toBeTruthy(); }); it('form should support disabled', () => { const App = () => (
disabled Apple Pear