2022-09-28 12:01:20 +08:00
import type { ChangeEventHandler } from 'react' ;
2023-03-23 16:40:53 +08:00
import React , { version as ReactVersion , useEffect , useRef , useState } from 'react' ;
2023-11-21 14:28:50 +08:00
import { AlertFilled } from '@ant-design/icons' ;
2023-07-28 16:17:43 +08:00
import type { ColProps } from 'antd/es/grid' ;
2023-09-04 10:03:12 +08:00
import classNames from 'classnames' ;
2019-12-23 18:33:08 +08:00
import scrollIntoView from 'scroll-into-view-if-needed' ;
2023-09-04 10:03:12 +08:00
2023-03-09 21:28:54 +08:00
import type { FormInstance } from '..' ;
2019-07-03 20:14:39 +08:00
import Form from '..' ;
2023-09-04 10:03:12 +08:00
import { resetWarned } from '../../_util/warning' ;
2023-03-23 16:40:53 +08:00
import mountTest from '../../../tests/shared/mountTest' ;
import rtlTest from '../../../tests/shared/rtlTest' ;
2024-06-10 10:48:21 +08:00
import { fireEvent , pureRender , render , screen , waitFakeTimer } from '../../../tests/utils' ;
2019-07-03 20:14:39 +08:00
import Button from '../../button' ;
2022-04-29 20:48:10 +08:00
import Cascader from '../../cascader' ;
2022-06-06 23:39:00 +08:00
import Checkbox from '../../checkbox' ;
2023-11-21 14:28:50 +08:00
import ColorPicker from '../../color-picker' ;
2023-03-23 16:40:53 +08:00
import ConfigProvider from '../../config-provider' ;
2022-04-29 20:48:10 +08:00
import DatePicker from '../../date-picker' ;
2023-03-23 16:40:53 +08:00
import Drawer from '../../drawer' ;
import Input from '../../input' ;
2022-04-29 20:48:10 +08:00
import InputNumber from '../../input-number' ;
2023-03-23 16:40:53 +08:00
import zhCN from '../../locale/zh_CN' ;
import Modal from '../../modal' ;
2022-06-06 23:39:00 +08:00
import Radio from '../../radio' ;
2023-03-23 16:40:53 +08:00
import Select from '../../select' ;
2023-06-24 14:20:56 +08:00
import Slider from '../../slider' ;
2022-04-29 20:48:10 +08:00
import Switch from '../../switch' ;
2022-06-06 23:39:00 +08:00
import TreeSelect from '../../tree-select' ;
2023-03-23 16:40:53 +08:00
import Upload from '../../upload' ;
2022-09-28 12:01:20 +08:00
import type { NamePath } from '../interface' ;
2023-03-23 16:40:53 +08:00
import * as Util from '../util' ;
2019-07-03 20:14:39 +08:00
2022-04-29 20:48:10 +08:00
const { RangePicker } = DatePicker ;
const { TextArea } = Input ;
2023-06-07 21:59:21 +08:00
jest . mock ( 'scroll-into-view-if-needed' ) ;
2019-07-04 15:00:11 +08:00
2019-07-03 20:14:39 +08:00
describe ( 'Form' , ( ) = > {
2019-08-26 22:53:20 +08:00
mountTest ( Form ) ;
mountTest ( Form . Item ) ;
2020-01-02 19:10:16 +08:00
rtlTest ( Form ) ;
rtlTest ( Form . Item ) ;
2022-09-28 12:01:20 +08:00
( scrollIntoView as any ) . mockImplementation ( ( ) = > { } ) ;
2023-06-07 21:59:21 +08:00
const errorSpy = jest . spyOn ( console , 'error' ) . mockImplementation ( ( ) = > { } ) ;
const warnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ( ) = > { } ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
const changeValue = async (
input : HTMLElement | null | number ,
2022-09-28 12:01:20 +08:00
value : string ,
2022-10-19 16:34:24 +08:00
advTimer = 1000 ,
2022-09-28 12:01:20 +08:00
) = > {
2022-10-19 16:34:24 +08:00
let element : HTMLElement ;
2021-06-04 14:44:41 +08:00
2022-10-19 16:34:24 +08:00
if ( typeof input === 'number' ) {
element = document . querySelectorAll ( 'input' ) [ input ] ;
}
expect ( element ! ) . toBeTruthy ( ) ;
fireEvent . change ( element ! , {
target : {
value ,
} ,
} ) ;
if ( advTimer ) {
await waitFakeTimer ( advTimer / 20 , 20 ) ;
2021-06-04 14:44:41 +08:00
}
2022-09-28 12:01:20 +08:00
} ;
2019-07-03 20:14:39 +08:00
2019-07-04 15:00:11 +08:00
beforeEach ( ( ) = > {
2022-10-19 16:34:24 +08:00
document . body . innerHTML = '' ;
2023-06-07 21:59:21 +08:00
jest . useFakeTimers ( ) ;
2022-09-28 12:01:20 +08:00
( scrollIntoView as any ) . mockReset ( ) ;
2019-07-04 15:00:11 +08:00
} ) ;
2019-07-03 20:14:39 +08:00
afterEach ( ( ) = > {
errorSpy . mockReset ( ) ;
} ) ;
afterAll ( ( ) = > {
2023-06-07 21:59:21 +08:00
jest . clearAllTimers ( ) ;
jest . useRealTimers ( ) ;
2019-07-03 20:14:39 +08:00
errorSpy . mockRestore ( ) ;
2022-09-28 12:01:20 +08:00
warnSpy . mockRestore ( ) ;
( scrollIntoView as any ) . mockRestore ( ) ;
2019-07-03 20:14:39 +08:00
} ) ;
2020-07-21 20:51:36 +08:00
describe ( 'noStyle Form.Item' , ( ) = > {
2022-09-28 12:01:20 +08:00
it ( 'should show alert when form field is required but empty' , async ( ) = > {
2023-06-07 21:59:21 +08:00
const onChange = jest . fn ( ) ;
2019-07-03 20:14:39 +08:00
2022-04-06 11:07:15 +08:00
const { container } = render (
2020-07-21 20:51:36 +08:00
< Form >
< Form.Item >
2022-09-28 12:01:20 +08:00
< Form.Item name = "test" label = "test" initialValue = "bamboo" rules = { [ { required : true } ] } >
2020-07-21 20:51:36 +08:00
< Input onChange = { onChange } / >
< / Form.Item >
2019-07-03 20:14:39 +08:00
< / Form.Item >
2020-07-21 20:51:36 +08:00
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
// user type something and clear
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'test' ) ;
await changeValue ( 0 , '' ) ;
2022-09-28 12:01:20 +08:00
// should show alert with correct message and show correct styles
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
"'test' is required" ,
) ;
expect ( container . querySelector ( '.ant-input-status-error' ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( '.ant-form-item-has-error' ) ) . toBeTruthy ( ) ;
2020-07-21 20:51:36 +08:00
expect ( onChange ) . toHaveBeenCalled ( ) ;
} ) ;
it ( 'should clean up' , async ( ) = > {
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > {
2020-07-21 20:51:36 +08:00
const [ form ] = Form . useForm ( ) ;
2022-10-19 16:34:24 +08:00
const onChange = async ( ) = > {
// Wait a while and then some logic to validate
await waitFakeTimer ( ) ;
try {
await form . validateFields ( ) ;
2024-02-23 11:01:56 +08:00
} catch {
2022-10-19 16:34:24 +08:00
// do nothing
}
} ;
2020-07-21 20:51:36 +08:00
return (
< Form form = { form } initialValues = { { aaa : '2' } } >
< Form.Item name = "aaa" >
2022-10-19 16:34:24 +08:00
< Input onChange = { onChange } / >
2020-07-21 20:51:36 +08:00
< / Form.Item >
< Form.Item shouldUpdate noStyle >
{ ( ) = > {
const aaa = form . getFieldValue ( 'aaa' ) ;
if ( aaa === '1' ) {
return (
< Form.Item name = "bbb" rules = { [ { required : true , message : 'aaa' } ] } >
< Input / >
< / Form.Item >
) ;
}
2019-07-03 20:14:39 +08:00
2020-07-21 20:51:36 +08:00
return (
< Form.Item >
< Form.Item name = "ccc" rules = { [ { required : true , message : 'ccc' } ] } noStyle >
< Input / >
< / Form.Item >
< / Form.Item >
) ;
} }
< / Form.Item >
< / Form >
) ;
} ;
2019-07-03 20:14:39 +08:00
2022-04-19 20:06:32 +08:00
const { container } = render ( < Demo / > ) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '1' ) ;
2022-10-31 21:23:59 +08:00
await waitFakeTimer ( 2000 , 2000 ) ;
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent ( 'aaa' ) ;
await changeValue ( 0 , '2' ) ;
2022-10-31 21:23:59 +08:00
await waitFakeTimer ( 2000 , 2000 ) ;
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent ( 'ccc' ) ;
await changeValue ( 0 , '1' ) ;
2022-10-31 21:23:59 +08:00
await waitFakeTimer ( 2000 , 2000 ) ;
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent ( 'aaa' ) ;
2020-07-21 20:51:36 +08:00
} ) ;
2023-04-10 12:03:11 +08:00
// https://github.com/ant-design/ant-design/issues/41620
it ( 'should not throw error when `help=false` and `noStyle=true`' , async ( ) = > {
2024-01-11 15:11:55 +08:00
const App : React.FC < { help? : React.ReactNode } > = ( props ) = > {
2023-04-10 12:03:11 +08:00
const { help = false } = props || { } ;
return (
< Form >
< Form.Item name = "list" label = "List" rules = { [ { required : true } ] } >
< Form.Item name = { [ 'list' , 0 ] } noStyle help = { help } rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< Form.Item name = { [ 'list' , 1 ] } noStyle help = { help } rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< / Form.Item >
< Form.Item >
< button type = "submit" > submit < / button >
< / Form.Item >
< / Form >
) ;
} ;
const { container , getByRole , rerender } = render ( < App / > ) ;
// click submit to trigger validate
fireEvent . click ( getByRole ( 'button' ) ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelectorAll ( '.ant-form-item-explain-error' ) ) . toHaveLength ( 1 ) ;
// When noStyle=true but help is not false, help will be displayed
rerender ( < App help = "help" / > ) ;
await waitFakeTimer ( ) ;
fireEvent . click ( getByRole ( 'button' ) ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelectorAll ( '.ant-form-item-explain-error' ) ) . toHaveLength ( 3 ) ;
} ) ;
2019-07-03 20:14:39 +08:00
} ) ;
2022-11-07 17:32:58 +08:00
it ( 'render functions require either `shouldUpdate` or `dependencies`' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2019-07-03 20:14:39 +08:00
< Form >
< Form.Item > { ( ) = > null } < / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
2022-11-07 17:32:58 +08:00
'Warning: [antd: Form.Item] A `Form.Item` with a render function must have either `shouldUpdate` or `dependencies`.' ,
2020-07-03 16:25:29 +08:00
) ;
} ) ;
2022-10-19 16:34:24 +08:00
2020-07-03 16:25:29 +08:00
it ( "`shouldUpdate` shouldn't work with `dependencies`" , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2020-07-03 16:25:29 +08:00
< Form >
< Form.Item shouldUpdate dependencies = { [ ] } >
{ ( ) = > null }
< / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
2022-11-14 16:32:34 +08:00
"Warning: [antd: Form.Item] `shouldUpdate` and `dependencies` shouldn't be used together. See https://u.ant.design/form-deps." ,
2019-07-03 20:14:39 +08:00
) ;
} ) ;
2020-09-18 16:53:18 +08:00
2020-01-07 19:17:51 +08:00
it ( '`name` should not work with render props' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2020-01-07 19:17:51 +08:00
< Form >
< Form.Item name = "test" shouldUpdate >
{ ( ) = > null }
< / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
2022-11-07 23:32:46 +08:00
'Warning: [antd: Form.Item] A `Form.Item` with a render function cannot be a field, and thus cannot have a `name` prop.' ,
2020-01-07 19:17:51 +08:00
) ;
} ) ;
2020-09-18 16:53:18 +08:00
2022-11-07 17:32:58 +08:00
it ( 'multiple children with a name prop' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2020-01-01 20:07:10 +08:00
< Form >
< Form.Item name = "test" >
< div > one < / div >
< div > two < / div >
< / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
2022-11-14 16:32:34 +08:00
'Warning: [antd: Form.Item] A `Form.Item` with a `name` prop must have a single child element. For information on how to render more complex form items, see https://u.ant.design/complex-form-item.' ,
2020-01-01 20:07:10 +08:00
) ;
} ) ;
2019-07-04 15:00:11 +08:00
2022-08-24 17:07:50 +08:00
it ( 'input element should have the prop aria-describedby pointing to the help id when there is a help message' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = pureRender (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" help = "This is a help" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBe ( 'test_help' ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ? . id ) . toBe ( 'test_help' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( 'input element should not have the prop aria-describedby pointing to the help id when there is a help message and name is not defined' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item help = "This is a help" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBeFalsy ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ? . id ) . toBeFalsy ( ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( 'input element should have the prop aria-describedby concatenated with the form name pointing to the help id when there is a help message' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form name = "form" >
< Form.Item name = "test" help = "This is a help" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBe (
'form_test_help' ,
) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ? . id ) . toBe ( 'form_test_help' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( 'input element should have the prop aria-describedby pointing to the help id when there are errors' , async ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = pureRender (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" rules = { [ { len : 3 } , { type : 'number' } ] } >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'Invalid number' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBe ( 'test_help' ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ? . id ) . toBe ( 'test_help' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( 'input element should have the prop aria-invalid when there are errors' , async ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" rules = { [ { len : 3 } , { type : 'number' } ] } >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'Invalid number' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-invalid' ) ) . toBe ( 'true' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
2022-09-28 12:01:20 +08:00
it ( 'input element should have the prop aria-required when the prop `required` is true' , ( ) = > {
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" required >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-required' ) ) . toBe ( 'true' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
2022-09-28 12:01:20 +08:00
it ( 'input element should have the prop aria-required when there is a rule with required' , ( ) = > {
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-required' ) ) . toBe ( 'true' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( 'input element should have the prop aria-describedby pointing to the extra id when there is a extra message' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" extra = "This is a extra message" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBe ( 'test_extra' ) ;
expect ( container . querySelector ( '.ant-form-item-extra' ) ? . id ) . toBe ( 'test_extra' ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( 'input element should not have the prop aria-describedby pointing to the extra id when there is a extra message and name is not defined' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item extra = "This is a extra message" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBeFalsy ( ) ;
expect ( container . querySelector ( '.ant-form-item-extra' ) ? . id ) . toBeFalsy ( ) ;
2022-08-24 17:07:50 +08:00
} ) ;
it ( ' input element should have the prop aria - describedby pointing to the help and extra id when there is a help and extra message ' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-08-24 17:07:50 +08:00
< Form >
< Form.Item name = "test" help = "This is a help" extra = "This is a extra message" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( 'input' ) ? . getAttribute ( 'aria-describedby' ) ) . toBe (
'test_help test_extra' ,
) ;
2022-08-24 17:07:50 +08:00
} ) ;
2019-07-04 15:00:11 +08:00
describe ( 'scrollToField' , ( ) = > {
2022-09-28 12:01:20 +08:00
const test = ( name : string , genForm : ( ) = > any ) = > {
2019-07-04 15:00:11 +08:00
it ( name , ( ) = > {
2022-09-28 12:01:20 +08:00
let callGetForm : any ;
2019-07-04 15:00:11 +08:00
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > {
2019-07-04 15:00:11 +08:00
const { props , getForm } = genForm ( ) ;
callGetForm = getForm ;
return (
< Form name = "scroll" { ...props } >
< Form.Item name = "test" >
< Input / >
< / Form.Item >
< / Form >
) ;
} ;
2022-09-28 12:01:20 +08:00
render ( < Demo / > ) ;
2019-07-04 15:00:11 +08:00
expect ( scrollIntoView ) . not . toHaveBeenCalled ( ) ;
2020-01-14 22:50:49 +08:00
const form = callGetForm ( ) ;
form . scrollToField ( 'test' , {
block : 'start' ,
} ) ;
const inputNode = document . getElementById ( 'scroll_test' ) ;
expect ( scrollIntoView ) . toHaveBeenCalledWith ( inputNode , {
block : 'start' ,
scrollMode : 'if-needed' ,
} ) ;
2019-07-04 15:00:11 +08:00
} ) ;
2022-09-28 12:01:20 +08:00
} ;
2019-07-04 15:00:11 +08:00
// hooks
test ( 'useForm' , ( ) = > {
const [ form ] = Form . useForm ( ) ;
return {
props : { form } ,
getForm : ( ) = > form ,
} ;
} ) ;
// ref
test ( 'ref' , ( ) = > {
2022-09-28 12:01:20 +08:00
let form : any ;
2019-07-04 15:00:11 +08:00
return {
props : {
2022-09-28 12:01:20 +08:00
ref : ( instance : any ) = > {
2019-07-04 15:00:11 +08:00
form = instance ;
} ,
} ,
getForm : ( ) = > form ,
} ;
} ) ;
} ) ;
2019-11-13 11:48:20 +08:00
2024-04-17 17:56:29 +08:00
describe ( 'scrollToFirstError' , ( ) = > {
it ( 'should work with scrollToFirstError' , async ( ) = > {
const onFinishFailed = jest . fn ( ) ;
2020-02-19 17:38:46 +08:00
2024-04-17 17:56:29 +08:00
const { container } = render (
< Form scrollToFirstError = { { block : 'center' } } onFinishFailed = { onFinishFailed } >
< Form.Item name = "test" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< Form.Item >
< Button htmlType = "submit" > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
2020-02-19 17:38:46 +08:00
2024-04-17 17:56:29 +08:00
expect ( scrollIntoView ) . not . toHaveBeenCalled ( ) ;
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
const inputNode = document . getElementById ( 'test' ) ;
expect ( scrollIntoView ) . toHaveBeenCalledWith ( inputNode , {
block : 'center' ,
scrollMode : 'if-needed' ,
} ) ;
expect ( onFinishFailed ) . toHaveBeenCalled ( ) ;
} ) ;
it ( 'should work with scrollToFirstError with ref' , async ( ) = > {
const ForwardRefInput = React . forwardRef < HTMLInputElement , any > ( ( { id , . . . props } , ref ) = > (
< input { ...props } ref = { ref } / >
) ) ;
const NativeInput = React . forwardRef < any , any > ( ( { id , . . . props } , ref ) = > {
const internalRef = React . useRef < HTMLInputElement > ( null ) ;
React . useImperativeHandle ( ref , ( ) = > ( {
nativeElement : internalRef.current ,
} ) ) ;
return < input { ...props } ref = { internalRef } / > ;
} ) ;
const NormalInput = ( props : any ) = > < input { ...props } / > ;
const { getByRole , getAllByRole } = render (
< Form scrollToFirstError >
< Form.Item name = "foo" rules = { [ { required : true } ] } >
< ForwardRefInput / >
< / Form.Item >
< Form.Item name = "bar" rules = { [ { required : true } ] } >
< NativeInput / >
< / Form.Item >
< Form.Item name = "baz" rules = { [ { required : true } ] } >
< NormalInput / >
< / Form.Item >
< Form.Item >
< Button htmlType = "submit" > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
// click submit to trigger validate
const allInputs = getAllByRole ( 'textbox' ) ;
const button = getByRole ( 'button' ) ;
expect ( allInputs ) . toHaveLength ( 3 ) ;
fireEvent . click ( button ) ;
await waitFakeTimer ( ) ;
expect ( scrollIntoView ) . toHaveBeenNthCalledWith ( 1 , allInputs [ 0 ] , expect . any ( Object ) ) ;
// change the value of the first input
fireEvent . change ( allInputs [ 0 ] , { target : { value : '123' } } ) ;
fireEvent . click ( button ) ;
await waitFakeTimer ( ) ;
expect ( scrollIntoView ) . toHaveBeenNthCalledWith ( 2 , allInputs [ 1 ] , expect . any ( Object ) ) ;
// change the value of the second input
fireEvent . change ( allInputs [ 1 ] , { target : { value : 'abc' } } ) ;
fireEvent . click ( button ) ;
await waitFakeTimer ( ) ;
expect ( scrollIntoView ) . toHaveBeenNthCalledWith ( 3 , allInputs [ 2 ] , expect . any ( Object ) ) ;
expect ( scrollIntoView ) . toHaveBeenCalledTimes ( 3 ) ;
} ) ;
2024-10-15 16:24:27 +08:00
it ( 'should scrollToFirstError work with focus' , async ( ) = > {
const onFinishFailed = jest . fn ( ) ;
const focusSpy = jest . spyOn ( HTMLElement . prototype , 'focus' ) ;
const { container } = render (
< Form scrollToFirstError = { { block : 'center' , focus : true } } onFinishFailed = { onFinishFailed } >
< Form.Item name = "test" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< Form.Item >
< Button htmlType = "submit" > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
expect ( scrollIntoView ) . not . toHaveBeenCalled ( ) ;
expect ( focusSpy ) . not . toHaveBeenCalled ( ) ;
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
const inputNode = document . getElementById ( 'test' ) ;
expect ( focusSpy ) . toHaveBeenCalledWith ( ) ;
expect ( scrollIntoView ) . toHaveBeenCalledWith ( inputNode , {
block : 'center' ,
focus : true ,
scrollMode : 'if-needed' ,
} ) ;
focusSpy . mockRestore ( ) ;
} ) ;
2024-04-17 17:56:29 +08:00
// https://github.com/ant-design/ant-design/issues/28869
it ( 'should work with Upload' , async ( ) = > {
const uploadRef = React . createRef < any > ( ) ;
const { getByRole } = render (
< Form scrollToFirstError >
< Form.Item
name = "demo-form_dragger"
valuePropName = "fileList"
getValueFromEvent = { ( e ) = > ( Array . isArray ( e ) ? e : e?.fileList ) }
rules = { [ { required : true } ] }
>
< Upload name = "files" action = "/upload.do" ref = { uploadRef } / >
< / Form.Item >
< Form.Item >
< Button htmlType = "submit" > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
fireEvent . click ( getByRole ( 'button' ) ) ;
await waitFakeTimer ( ) ;
2022-10-19 16:34:24 +08:00
2024-04-17 17:56:29 +08:00
expect ( scrollIntoView ) . toHaveBeenCalled ( ) ;
expect ( ( scrollIntoView as any ) . mock . calls [ 0 ] [ 0 ] ) . toBe ( uploadRef . current . nativeElement ) ;
2020-12-11 14:35:46 +08:00
} ) ;
2024-05-20 19:41:30 +08:00
// https://github.com/ant-design/ant-design/issues/48981
it ( 'should not throw error when use InputNumber' , async ( ) = > {
const inputNumberRef = React . createRef < any > ( ) ;
const { getByText } = render (
< Form scrollToFirstError >
< Form.Item name = "demo-form_input-number" rules = { [ { required : true } ] } >
< InputNumber ref = { inputNumberRef } / >
< / Form.Item >
< Form.Item >
< Button htmlType = "submit" > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
fireEvent . click ( getByText ( 'Submit' ) ) ;
await waitFakeTimer ( ) ;
expect ( scrollIntoView ) . toHaveBeenCalled ( ) ;
expect ( ( scrollIntoView as any ) . mock . calls [ 0 ] [ 0 ] ) . toBe ( inputNumberRef . current ? . nativeElement ) ;
} ) ;
2020-02-19 17:38:46 +08:00
} ) ;
2019-11-13 11:48:20 +08:00
it ( 'Form.Item should support data-*、aria-* and custom attribute' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2019-11-13 11:48:20 +08:00
< Form >
2022-09-28 12:01:20 +08:00
{ /* @ts-ignore */ }
2019-11-13 11:48:20 +08:00
< Form.Item data - text = "123" aria - hidden = "true" cccc = "bbbb" >
text
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . firstChild ) . toMatchSnapshot ( ) ;
2019-11-13 11:48:20 +08:00
} ) ;
2019-12-11 16:08:59 +08:00
it ( 'warning when use `name` but children is not validate element' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2019-12-11 16:08:59 +08:00
< Form >
< Form.Item name = "warning" > text < / Form.Item >
< / Form > ,
) ;
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.' ,
) ;
} ) ;
2019-12-12 12:05:59 +08:00
2024-04-17 14:38:15 +08:00
it ( 'No warning when use noStyle and children is empty' , ( ) = > {
render (
< Form >
< Form.Item name = "noWarning" noStyle / >
< / Form > ,
) ;
expect ( errorSpy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2022-09-28 12:01:20 +08:00
it ( 'dynamic change required' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2019-12-12 12:05:59 +08:00
< Form >
< Form.Item label = "light" name = "light" valuePropName = "checked" >
< input type = "checkbox" / >
< / Form.Item >
< Form.Item
label = "bamboo"
name = "bamboo"
dependencies = { [ 'light' ] }
rules = { [ ( { getFieldValue } ) = > ( { required : getFieldValue ( 'light' ) } ) ] }
>
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
// should not show alert by default
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toBeFalsy ( ) ;
2019-12-12 12:05:59 +08:00
2022-09-28 12:01:20 +08:00
// click to change the light field value to true
2022-10-19 16:34:24 +08:00
fireEvent . click ( container . querySelector ( 'input' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
// user input something and clear
2022-10-19 16:34:24 +08:00
await changeValue ( 1 , '1' ) ;
await changeValue ( 1 , '' ) ;
2022-09-28 12:01:20 +08:00
// should show alert says that the field is required
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
"'bamboo' is required" ,
2022-09-28 12:01:20 +08:00
) ;
2019-12-12 12:05:59 +08:00
} ) ;
2019-12-27 16:50:44 +08:00
2022-01-10 15:58:53 +08:00
describe ( 'should show related className when customize help' , ( ) = > {
2022-10-19 16:34:24 +08:00
it ( 'normal' , async ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-01-10 15:58:53 +08:00
< Form >
< Form.Item help = "good" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toHaveTextContent ( 'good' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( '.ant-form-item-with-help' ) ) . toBeTruthy ( ) ;
2022-01-10 15:58:53 +08:00
} ) ;
2022-10-19 16:34:24 +08:00
it ( 'empty string' , async ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2022-01-10 15:58:53 +08:00
< Form >
< Form.Item help = "" >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toHaveTextContent ( '' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( '.ant-form-item-with-help' ) ) . toBeTruthy ( ) ;
2022-01-10 15:58:53 +08:00
} ) ;
2019-12-27 16:50:44 +08:00
} ) ;
2019-12-30 12:13:58 +08:00
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.' ,
) ;
} ) ;
2020-01-07 16:20:18 +08:00
// https://github.com/ant-design/ant-design/issues/20706
it ( 'Error change should work' , async ( ) = > {
2022-04-06 11:07:15 +08:00
const { container } = render (
2020-01-07 16:20:18 +08:00
< Form >
< Form.Item
name = "name"
2022-09-28 12:01:20 +08:00
label = "test"
2020-01-07 16:20:18 +08:00
rules = { [
{ required : true } ,
{
validator : ( _ , value ) = > {
if ( value === 'p' ) {
return Promise . reject ( new Error ( 'not a p' ) ) ;
}
return Promise . resolve ( ) ;
} ,
} ,
] }
>
< Input / >
< / Form.Item >
< / Form > ,
) ;
for ( let i = 0 ; i < 3 ; i += 1 ) {
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'bamboo' ) ;
await changeValue ( 0 , '' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( '.ant-form-item-explain' ) ? . textContent ) . toEqual (
2022-04-06 11:07:15 +08:00
"'name' is required" ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'p' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( '.ant-form-item-explain' ) ? . textContent ) . toEqual ( 'not a p' ) ;
2020-01-07 16:20:18 +08:00
}
} ) ;
2020-01-14 14:45:22 +08:00
// https://github.com/ant-design/ant-design/issues/20813
2022-09-28 12:01:20 +08:00
it ( 'should update help directly when provided' , async ( ) = > {
const App : React.FC = ( ) = > {
2020-01-14 14:45:22 +08:00
const [ message , updateMessage ] = React . useState ( '' ) ;
return (
< Form >
< Form.Item label = "hello" help = { message } >
< Input / >
< / Form.Item >
< Button onClick = { ( ) = > updateMessage ( 'bamboo' ) } / >
< / Form >
) ;
2022-09-28 12:01:20 +08:00
} ;
2022-10-19 16:34:24 +08:00
const { container } = render ( < App / > ) ;
2022-09-28 12:01:20 +08:00
// should show initial text
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toHaveTextContent ( '' ) ;
2020-01-14 14:45:22 +08:00
2022-10-19 16:34:24 +08:00
fireEvent . click ( container . querySelector ( 'button' ) ! ) ;
2022-09-28 12:01:20 +08:00
// should show bamboo alert without opacity and hide first alert with opacity: 0
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toHaveTextContent ( 'bamboo' ) ;
2020-01-14 14:45:22 +08:00
} ) ;
2020-01-17 11:50:06 +08:00
it ( 'warning when use `dependencies` but `name` is empty & children is not a render props' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2020-01-17 11:50:06 +08:00
< Form >
< Form.Item dependencies = { [ ] } > text < / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
2022-11-07 17:32:58 +08:00
'Warning: [antd: Form.Item] Must set `name` or use a render function when `dependencies` is set.' ,
2020-01-17 11:50:06 +08:00
) ;
} ) ;
// https://github.com/ant-design/ant-design/issues/20948
it ( 'not repeat render when Form.Item is not a real Field' , async ( ) = > {
2023-06-07 21:59:21 +08:00
const shouldNotRender = jest . fn ( ) ;
2022-10-17 23:14:44 +08:00
const StaticInput : React.FC < React.InputHTMLAttributes < HTMLInputElement > > = ( {
id ,
value = '' ,
} ) = > {
2020-01-17 11:50:06 +08:00
shouldNotRender ( ) ;
2022-10-17 23:14:44 +08:00
return < input id = { id } value = { value } / > ;
2020-01-17 11:50:06 +08:00
} ;
2023-06-07 21:59:21 +08:00
const shouldRender = jest . fn ( ) ;
2022-10-17 23:14:44 +08:00
const DynamicInput : React.FC < React.InputHTMLAttributes < HTMLInputElement > > = ( {
value = '' ,
id ,
} ) = > {
2022-12-26 15:48:50 +08:00
shouldRender ( value ) ;
2022-10-17 23:14:44 +08:00
return < input id = { id } value = { value } / > ;
2020-01-17 11:50:06 +08:00
} ;
2022-12-27 11:49:09 +08:00
const Demo = ( ) = > {
const [ form ] = Form . useForm ( ) ;
2020-01-17 11:50:06 +08:00
2022-12-27 11:49:09 +08:00
return (
< Form form = { form } >
< Form.Item >
< StaticInput / >
< / Form.Item >
< Form.Item name = "light" >
< DynamicInput id = "changed" / >
< / Form.Item >
< Button id = "fill-btn" onClick = { ( ) = > form . setFieldValue ( 'light' , 'bamboo' ) } >
fill
< / Button >
< / Form >
) ;
} ;
const { container } = pureRender ( < Demo / > ) ;
2020-01-17 11:50:06 +08:00
2022-10-17 23:14:44 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector < HTMLInputElement > ( '#changed' ) ! . value ) . toEqual ( '' ) ;
2020-01-17 11:50:06 +08:00
expect ( shouldNotRender ) . toHaveBeenCalledTimes ( 1 ) ;
expect ( shouldRender ) . toHaveBeenCalledTimes ( 1 ) ;
2022-12-27 11:49:09 +08:00
fireEvent . click ( container . querySelector ( '#fill-btn' ) ! ) ;
2022-12-26 15:48:50 +08:00
await waitFakeTimer ( ) ;
2020-01-17 11:50:06 +08:00
expect ( shouldNotRender ) . toHaveBeenCalledTimes ( 1 ) ;
2022-12-26 15:48:50 +08:00
expect ( shouldRender ) . toHaveBeenLastCalledWith ( 'bamboo' ) ;
2020-01-17 11:50:06 +08:00
expect ( shouldRender ) . toHaveBeenCalledTimes ( 2 ) ;
} ) ;
2020-01-20 16:09:59 +08:00
it ( 'empty help should also render' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2020-01-20 16:09:59 +08:00
< Form.Item help = "" >
< input / >
< / Form.Item > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelectorAll ( '.ant-form-item-explain' ) . length ) . toBeTruthy ( ) ;
2020-01-20 16:09:59 +08:00
} ) ;
2020-02-01 20:09:29 +08:00
2020-07-13 12:02:15 +08:00
it ( 'Form.Item with `help` should display error style when validate failed' , async ( ) = > {
2022-04-06 11:07:15 +08:00
const { container } = render (
2020-07-13 12:02:15 +08:00
< Form >
2022-04-06 11:07:15 +08:00
< Form.Item
name = "test"
2022-09-28 12:01:20 +08:00
label = "test"
2022-04-06 11:07:15 +08:00
help = "help"
initialValue = "bamboo"
rules = { [ { required : true , message : 'message' } ] }
>
2020-07-13 12:02:15 +08:00
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '' ) ;
2022-04-06 11:07:15 +08:00
expect ( container . querySelector ( '.ant-form-item' ) ) . toHaveClass ( 'ant-form-item-has-error' ) ;
2022-09-28 12:01:20 +08:00
expect ( container . querySelector ( '.ant-form-item-explain' ) ! . textContent ) . toEqual ( 'help' ) ;
2020-07-13 12:02:15 +08:00
} ) ;
2022-09-28 12:01:20 +08:00
it ( 'clear validation message when' , async ( ) = > {
2022-04-06 11:07:15 +08:00
const { container } = render (
2020-09-18 16:53:18 +08:00
< Form >
2022-09-28 12:01:20 +08:00
< Form.Item name = "test" label = "test" rules = { [ { required : true , message : 'message' } ] } >
2020-09-18 16:53:18 +08:00
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '1' ) ;
2022-04-06 11:07:15 +08:00
expect ( container . querySelectorAll ( '.ant-form-item-explain' ) . length ) . toBeFalsy ( ) ;
2021-06-04 14:44:41 +08:00
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '' ) ;
2022-04-06 11:07:15 +08:00
expect ( container . querySelectorAll ( '.ant-form-item-explain' ) . length ) . toBeTruthy ( ) ;
2021-06-04 14:44:41 +08:00
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '123' ) ;
2022-04-06 11:07:15 +08:00
expect ( container . querySelectorAll ( '.ant-form-item-explain' ) . length ) . toBeFalsy ( ) ;
2020-09-18 16:53:18 +08:00
} ) ;
2020-02-01 20:09:29 +08:00
// https://github.com/ant-design/ant-design/issues/21167
it ( '`require` without `name`' , ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2022-09-28 12:01:20 +08:00
< Form.Item label = "test" name = "test" required >
2020-02-01 20:09:29 +08:00
< input / >
< / Form.Item > ,
) ;
2022-10-19 16:34:24 +08:00
// expect(screen.getByTitle('test')).toHaveClass('ant-form-item-required');
expect ( container . querySelector ( '.ant-form-item-required' ) ) . toBeTruthy ( ) ;
2020-02-01 20:09:29 +08:00
} ) ;
2020-02-02 22:28:05 +08:00
it ( '0 is a validate Field' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
< Form.Item name = { 0 } label = "0" >
2020-02-02 22:28:05 +08:00
< input / >
< / Form.Item > ,
) ;
2022-09-28 12:01:20 +08:00
// if getByLabelText can get element, then it is a validate field with form control and label
expect ( screen . getByLabelText ( '0' ) ) . toBeInTheDocument ( ) ;
2020-02-02 22:28:05 +08:00
} ) ;
2020-02-03 17:54:33 +08:00
it ( '`null` triggers warning and is treated as `undefined`' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
< Form.Item name = { null as unknown as NamePath } label = "test" >
2020-02-03 17:54:33 +08:00
< input / >
< / Form.Item > ,
) ;
2022-09-28 12:01:20 +08:00
// if getByLabelText can get element, then it is a validate field with form control and label
expect ( screen . queryByLabelText ( 'test' ) ) . not . toBeInTheDocument ( ) ;
2020-02-03 17:54:33 +08:00
expect ( errorSpy ) . toHaveBeenCalledWith (
'Warning: [antd: Form.Item] `null` is passed as `name` property' ,
) ;
} ) ;
2020-02-18 15:38:00 +08:00
// https://github.com/ant-design/ant-design/issues/21415
2022-10-19 16:34:24 +08:00
it ( 'should not throw error when Component.props.onChange is null' , async ( ) = > {
2022-09-28 12:01:20 +08:00
const CustomComponent : React.FC = ( ) = > (
< input onChange = { null as unknown as ChangeEventHandler < HTMLInputElement > } / >
) ;
render (
< Form >
< Form.Item name = "custom" >
< CustomComponent / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'aaa' ) ;
2020-02-18 15:38:00 +08:00
} ) ;
2020-03-03 12:10:32 +08:00
2022-09-28 12:01:20 +08:00
it ( 'change `help` should not warning' , async ( ) = > {
const Demo : React.FC = ( ) = > {
const [ error , setError ] = React . useState ( false ) ;
2020-03-03 12:10:32 +08:00
return (
< Form >
< Form.Item
help = { error ? 'This is an error msg' : undefined }
validateStatus = { error ? 'error' : '' }
label = "Username"
name = "username"
>
< input / >
< / Form.Item >
< Form.Item >
< button type = "button" onClick = { ( ) = > setError ( ! error ) } >
Trigger
< / button >
< / Form.Item >
< / Form >
) ;
} ;
2022-10-19 16:34:24 +08:00
const { container } = render ( < Demo / > ) ;
fireEvent . click ( container . querySelector ( 'button' ) ! ) ;
2020-03-03 12:10:32 +08:00
expect ( errorSpy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2020-03-06 12:07:55 +08:00
2020-03-03 21:57:06 +08:00
it ( '`label` support template' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-03-03 21:57:06 +08:00
// eslint-disable-next-line no-template-curly-in-string
< Form validateMessages = { { required : '${label} is good!' } } >
< Form.Item name = "test" label = "Bamboo" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
'Bamboo is good!' ,
) ;
2020-03-03 21:57:06 +08:00
} ) ;
2020-03-06 18:12:39 +08:00
2022-01-13 13:50:18 +08:00
// https://github.com/ant-design/ant-design/issues/33691
it ( 'should keep upper locale in nested ConfigProvider' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2022-01-13 13:50:18 +08:00
< ConfigProvider locale = { zhCN } >
< ConfigProvider >
< Form >
< Form.Item name = "test" label = "Bamboo" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< / Form >
< / ConfigProvider >
2021-12-31 16:56:21 +08:00
< / ConfigProvider > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
'请输入Bamboo' ,
) ;
2021-12-31 16:56:21 +08:00
} ) ;
2021-06-30 11:32:34 +08:00
it ( '`name` support template when label is not provided' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2021-06-30 11:32:34 +08:00
// eslint-disable-next-line no-template-curly-in-string
< Form validateMessages = { { required : '${label} is good!' } } >
< Form.Item name = "Bamboo" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
'Bamboo is good!' ,
) ;
2021-06-30 11:32:34 +08:00
} ) ;
2020-09-10 21:00:21 +08:00
it ( '`messageVariables` support validate' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-09-10 21:00:21 +08:00
// eslint-disable-next-line no-template-curly-in-string
< Form validateMessages = { { required : '${label} is good!' } } >
< Form.Item name = "test" messageVariables = { { label : 'Bamboo' } } rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
'Bamboo is good!' ,
) ;
2020-09-10 21:00:21 +08:00
} ) ;
2020-07-21 22:29:31 +08:00
it ( 'validation message should has alert role' , async ( ) = > {
// https://github.com/ant-design/ant-design/issues/25711
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-07-21 22:29:31 +08:00
< Form validateMessages = { { required : 'name is good!' } } >
< Form.Item name = "test" rules = { [ { required : true } ] } >
< input / >
< / Form.Item >
2022-09-28 12:01:20 +08:00
< Form.Item >
< Button htmlType = "submit" > Submit < / Button >
< / Form.Item >
2020-07-21 22:29:31 +08:00
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . submit ( container . querySelector ( 'form' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ) . toHaveTextContent (
'name is good!' ,
) ;
2020-07-21 22:29:31 +08:00
} ) ;
2022-09-28 12:01:20 +08:00
it ( 'return same form instance' , async ( ) = > {
2020-03-06 12:07:55 +08:00
const instances = new Set ( ) ;
2022-09-28 12:01:20 +08:00
const App : React.FC = ( ) = > {
2020-03-06 12:07:55 +08:00
const [ form ] = Form . useForm ( ) ;
instances . add ( form ) ;
const [ , forceUpdate ] = React . useState ( { } ) ;
return (
< button
type = "button"
onClick = { ( ) = > {
forceUpdate ( { } ) ;
} }
>
Refresh
< / button >
) ;
} ;
2022-10-19 16:34:24 +08:00
const { container } = pureRender ( < App / > ) ;
2022-09-28 12:01:20 +08:00
2020-03-06 12:07:55 +08:00
for ( let i = 0 ; i < 5 ; i += 1 ) {
2022-10-19 16:34:24 +08:00
fireEvent . click ( container . querySelector ( 'button' ) ! ) ;
2024-09-19 03:30:19 +08:00
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
2020-03-06 12:07:55 +08:00
}
2022-09-28 12:01:20 +08:00
expect ( instances . size ) . toBe ( 1 ) ;
2020-03-06 12:07:55 +08:00
} ) ;
2020-03-08 16:28:33 +08:00
2022-09-28 12:01:20 +08:00
it ( 'should avoid re-render' , async ( ) = > {
2020-03-08 16:28:33 +08:00
let renderTimes = 0 ;
2022-09-28 12:01:20 +08:00
const MyInput : React.FC < { value? : string } > = ( { value = '' , . . . props } ) = > {
2020-03-08 16:28:33 +08:00
renderTimes += 1 ;
return < input value = { value } { ...props } / > ;
} ;
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > (
2020-03-08 16:28:33 +08:00
< Form >
2022-09-28 12:01:20 +08:00
< Form.Item name = "username" label = "username" rules = { [ { required : true } ] } >
2020-03-08 16:28:33 +08:00
< MyInput / >
< / Form.Item >
< / Form >
) ;
2022-10-19 16:34:24 +08:00
const { container } = pureRender ( < Demo / > ) ;
2020-03-08 16:28:33 +08:00
renderTimes = 0 ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'a' ) ;
2020-03-08 16:28:33 +08:00
expect ( renderTimes ) . toEqual ( 1 ) ;
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( 'input' ) ) . toHaveValue ( 'a' ) ;
2020-03-08 16:28:33 +08:00
} ) ;
2020-03-24 22:23:40 +08:00
2022-09-28 12:01:20 +08:00
it ( 'should warning with `defaultValue`' , ( ) = > {
render (
2020-03-24 22:23:40 +08:00
< Form >
< Form.Item name = "light" >
< input defaultValue = "should warning" / >
< / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
'Warning: [antd: Form.Item] `defaultValue` will not work on controlled Field. You should use `initialValues` of Form instead.' ,
) ;
} ) ;
2020-04-08 18:47:59 +08:00
2022-09-28 12:01:20 +08:00
it ( 'should remove Field and also reset error' , async ( ) = > {
const Demo : React.FC < { showA? : boolean } > = ( { showA } ) = > (
2022-04-18 21:02:11 +08:00
< Form >
{ showA ? (
< Form.Item name = "a" help = "error" >
< input / >
< / Form.Item >
) : (
< Form.Item name = "b" >
< input / >
< / Form.Item >
) }
< / Form >
) ;
2020-04-08 18:47:59 +08:00
2022-10-19 16:34:24 +08:00
const { container , rerender } = render ( < Demo showA / > ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toBeTruthy ( ) ;
2020-04-08 18:47:59 +08:00
2022-09-28 12:01:20 +08:00
rerender ( < Demo showA = { false } / > ) ;
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toBeFalsy ( ) ;
2020-04-08 18:47:59 +08:00
} ) ;
2020-05-04 12:13:44 +08:00
2020-07-08 23:43:26 +08:00
it ( 'no warning of initialValue & getValueProps & preserve' , ( ) = > {
2022-04-06 11:07:15 +08:00
render (
2020-05-04 12:13:44 +08:00
< Form >
2022-09-28 12:01:20 +08:00
< Form.Item initialValue = "bamboo" getValueProps = { ( ) = > ( { } ) } preserve = { false } >
2020-05-04 12:13:44 +08:00
< Input / >
< / Form.Item >
< / Form > ,
) ;
expect ( errorSpy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2020-06-11 22:25:58 +08:00
2022-09-28 12:01:20 +08:00
it ( 'should customize id when pass with id' , ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-06-11 22:25:58 +08:00
< Form >
< Form.Item name = "light" >
< Input id = "bamboo" / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( 'input' ) ! . id ) . toEqual ( 'bamboo' ) ;
2020-06-11 22:25:58 +08:00
} ) ;
2020-06-14 14:15:17 +08:00
2022-09-28 12:01:20 +08:00
it ( 'should trigger validate when onBlur when pass validateTrigger onBlur' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-06-14 14:15:17 +08:00
< Form validateTrigger = "onBlur" >
2022-09-28 12:01:20 +08:00
< Form.Item name = "light" label = "light" rules = { [ { len : 3 } ] } >
2020-06-14 14:15:17 +08:00
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
// type a invalidate value, not trigger validation
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '7777' ) ;
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toBeFalsy ( ) ;
2022-09-28 12:01:20 +08:00
// tab(onBlur) the input field, trigger and see the alert
2022-10-19 16:34:24 +08:00
fireEvent . blur ( container . querySelector ( 'input' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-explain' ) ) . toBeTruthy ( ) ;
2020-06-14 14:15:17 +08:00
} ) ;
2020-06-19 14:03:16 +08:00
2020-08-05 10:08:57 +08:00
describe ( 'Form item hidden' , ( ) = > {
it ( 'should work' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2020-08-05 10:08:57 +08:00
< Form >
< Form.Item name = "light" hidden >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . firstChild ) . toMatchSnapshot ( ) ;
2020-08-05 10:08:57 +08:00
} ) ;
it ( 'noStyle should not work when hidden' , ( ) = > {
2022-09-28 12:01:20 +08:00
const { container } = render (
2020-08-05 10:08:57 +08:00
< Form >
< Form.Item name = "light" hidden noStyle >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( container . firstChild ) . toMatchSnapshot ( ) ;
2020-08-05 10:08:57 +08:00
} ) ;
2020-06-19 14:03:16 +08:00
} ) ;
2020-08-21 12:58:14 +08:00
2023-12-07 11:22:31 +08:00
describe ( 'legacy hideRequiredMark' , ( ) = > {
it ( 'should work' , ( ) = > {
const { container } = render (
< Form hideRequiredMark role = "form" >
< Form.Item name = "light" label = "light" required >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2020-08-21 12:58:14 +08:00
2023-12-07 11:22:31 +08:00
expect ( container . querySelector ( 'form' ) ! ) . toHaveClass ( 'ant-form-hide-required-mark' ) ;
} ) ;
it ( 'priority should be higher than CP' , ( ) = > {
const { container , rerender } = render (
< ConfigProvider form = { { requiredMark : true } } >
< Form hideRequiredMark role = "form" >
< Form.Item name = "light" label = "light" required >
< Input / >
< / Form.Item >
< / Form >
< / ConfigProvider > ,
) ;
expect ( container . querySelector ( 'form' ) ! ) . toHaveClass ( 'ant-form-hide-required-mark' ) ;
rerender (
< ConfigProvider form = { { requiredMark : undefined } } >
< Form hideRequiredMark role = "form" >
< Form.Item name = "light" label = "light" required >
< Input / >
< / Form.Item >
< / Form >
< / ConfigProvider > ,
) ;
expect ( container . querySelector ( 'form' ) ! ) . toHaveClass ( 'ant-form-hide-required-mark' ) ;
} ) ;
2020-08-21 12:58:14 +08:00
} ) ;
2020-09-17 17:11:45 +08:00
2022-04-29 20:48:10 +08:00
it ( 'form should support disabled' , ( ) = > {
2022-09-28 12:01:20 +08:00
const App : React.FC = ( ) = > (
2022-07-01 21:29:31 +08:00
< Form labelCol = { { span : 4 } } wrapperCol = { { span : 14 } } layout = "horizontal" disabled >
< Form.Item label = "Form disabled" name = "disabled" valuePropName = "checked" >
< Checkbox > disabled < / Checkbox >
< / Form.Item >
< Form.Item label = "Radio" >
< Radio.Group >
2022-09-28 12:01:20 +08:00
< Radio value = "apple" > Apple < / Radio >
< Radio value = "pear" > Pear < / Radio >
2022-07-01 21:29:31 +08:00
< / Radio.Group >
< / Form.Item >
< Form.Item label = "Input" >
< Input / >
< / Form.Item >
< Form.Item label = "Select" >
< Select >
< Select.Option value = "demo" > Demo < / Select.Option >
< / Select >
< / Form.Item >
< Form.Item label = "TreeSelect" >
< TreeSelect
treeData = { [
{
title : 'Light' ,
value : 'light' ,
children : [ { title : 'Bamboo' , value : 'bamboo' } ] ,
} ,
] }
/ >
< / Form.Item >
< Form.Item label = "Cascader" >
< Cascader
options = { [
{
value : 'zhejiang' ,
label : 'Zhejiang' ,
2022-09-28 12:01:20 +08:00
children : [ { value : 'hangzhou' , label : 'Hangzhou' } ] ,
2022-07-01 21:29:31 +08:00
} ,
] }
/ >
< / Form.Item >
< Form.Item label = "DatePicker" >
< DatePicker / >
< / Form.Item >
< Form.Item label = "RangePicker" >
< RangePicker / >
< / Form.Item >
< Form.Item label = "InputNumber" >
< InputNumber / >
< / Form.Item >
< Form.Item label = "TextArea" >
< TextArea rows = { 4 } / >
< / Form.Item >
< Form.Item label = "Switch" valuePropName = "checked" >
< Switch / >
< / Form.Item >
2022-07-18 14:21:52 +08:00
< Form.Item label = "Upload" valuePropName = "fileList" >
< Upload / >
< / Form.Item >
2022-07-01 21:29:31 +08:00
< Form.Item label = "Button" >
< Button > Button < / Button >
< / Form.Item >
2023-06-24 14:20:56 +08:00
< Form.Item label = "Slider" >
< Slider / >
< / Form.Item >
2023-11-21 14:28:50 +08:00
< Form.Item label = "ColorPicker" >
< ColorPicker / >
< / Form.Item >
2022-07-01 21:29:31 +08:00
< / Form >
) ;
2022-09-28 12:01:20 +08:00
const { container } = render ( < App / > ) ;
expect ( container . firstChild ) . toMatchSnapshot ( ) ;
2022-04-29 20:48:10 +08:00
} ) ;
2024-05-31 10:50:47 +08:00
it ( 'form.item should support layout' , ( ) = > {
const App : React.FC = ( ) = > (
< Form labelCol = { { span : 4 } } wrapperCol = { { span : 14 } } layout = "horizontal" >
< Form.Item label = "name" name = "name" >
< Input / >
< / Form.Item >
< Form.Item label = "horizontal" name = "horizontal" layout = "horizontal" >
< Input / >
< / Form.Item >
< Form.Item label = "vertical" name = "vertical" layout = "vertical" >
< Input / >
< / Form.Item >
< / Form >
) ;
const { container } = render ( < App / > ) ;
expect ( container . firstChild ) . toMatchSnapshot ( ) ;
2024-11-08 18:32:20 +08:00
} ) ;
it ( 'form.item should support label = null' , ( ) = > {
// base size
const App : React.FC = ( ) = > (
< Form labelCol = { { span : 4 } } wrapperCol = { { span : 14 } } >
< Form.Item label = "name" name = "name" >
< Input / >
< / Form.Item >
< Form.Item label = { null } >
< Button > Submit < / Button >
< / Form.Item >
< / Form >
) ;
const { container } = render ( < App / > ) ;
const items = container . querySelectorAll ( '.ant-form-item' ) ;
const oneItems = items [ 0 ] . querySelector ( '.ant-row' ) ? . querySelectorAll ( '.ant-col' ) ;
expect ( oneItems ? . [ 0 ] ) . toHaveClass ( 'ant-col-4' ) ;
expect ( oneItems ? . [ 0 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
expect ( oneItems ? . [ 1 ] ) . toHaveClass ( 'ant-col-14' ) ;
expect ( oneItems ? . [ 1 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
const twoItem = items [ 1 ] . querySelector ( '.ant-row' ) ? . querySelector ( '.ant-col' ) ;
expect ( twoItem ) . toHaveClass ( 'ant-col-14 ant-col-offset-4' ) ;
// more size
const list = [ 'xs' , 'sm' , 'md' , 'lg' , 'xl' , 'xxl' ] as const ;
list . forEach ( ( size ) = > {
const { container } = render (
< Form labelCol = { { [ size ] : { span : 4 } } } wrapperCol = { { span : 14 } } >
< Form.Item label = "name" name = "name" >
< Input / >
< / Form.Item >
< Form.Item label = { null } >
< Button > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
const items = container . querySelectorAll ( '.ant-form-item' ) ;
const oneItems = items [ 0 ] . querySelector ( '.ant-row' ) ? . querySelectorAll ( '.ant-col' ) ;
expect ( oneItems ? . [ 0 ] ) . toHaveClass ( ` ant-col- ${ size } -4 ` ) ;
expect ( oneItems ? . [ 0 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
expect ( oneItems ? . [ 1 ] ) . toHaveClass ( 'ant-col-14' ) ;
expect ( oneItems ? . [ 1 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
const twoItem = items [ 1 ] . querySelector ( '.ant-row' ) ? . querySelector ( '.ant-col' ) ;
expect ( twoItem ) . toHaveClass ( ` ant-col-14 ant-col- ${ size } -offset-4 ` ) ;
} ) ;
} ) ;
it ( 'form.item should support label = null and labelCol.span = 24' , ( ) = > {
// base size
const App : React.FC = ( ) = > (
< Form labelCol = { { span : 24 } } wrapperCol = { { span : 24 } } >
< Form.Item label = "name" name = "name" >
< Input / >
< / Form.Item >
< Form.Item label = { null } >
< Button > Submit < / Button >
< / Form.Item >
< / Form >
) ;
const { container } = render ( < App / > ) ;
const items = container . querySelectorAll ( '.ant-form-item' ) ;
const oneItems = items [ 0 ] . querySelector ( '.ant-row' ) ? . querySelectorAll ( '.ant-col' ) ;
expect ( oneItems ? . [ 0 ] ) . toHaveClass ( 'ant-col-24' ) ;
expect ( oneItems ? . [ 0 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
expect ( oneItems ? . [ 1 ] ) . toHaveClass ( 'ant-col-24' ) ;
expect ( oneItems ? . [ 1 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
const twoItem = items [ 1 ] . querySelector ( '.ant-row' ) ? . querySelector ( '.ant-col' ) ;
expect ( twoItem ) . toHaveClass ( 'ant-col-24' ) ;
expect ( twoItem ? . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
// more size
const list = [ 'xs' , 'sm' , 'md' , 'lg' , 'xl' , 'xxl' ] as const ;
list . forEach ( ( size ) = > {
const { container } = render (
< Form labelCol = { { [ size ] : { span : 24 } } } wrapperCol = { { span : 24 } } >
< Form.Item label = "name" name = "name" >
< Input / >
< / Form.Item >
< Form.Item label = { null } >
< Button > Submit < / Button >
< / Form.Item >
< / Form > ,
) ;
const items = container . querySelectorAll ( '.ant-form-item' ) ;
const oneItems = items [ 0 ] . querySelector ( '.ant-row' ) ? . querySelectorAll ( '.ant-col' ) ;
expect ( oneItems ? . [ 0 ] ) . toHaveClass ( ` ant-col- ${ size } -24 ` ) ;
expect ( oneItems ? . [ 0 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
expect ( oneItems ? . [ 1 ] ) . toHaveClass ( 'ant-col-24' ) ;
expect ( oneItems ? . [ 1 ] . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
const twoItem = items [ 1 ] . querySelector ( '.ant-row' ) ? . querySelector ( '.ant-col' ) ;
expect ( twoItem ) . toHaveClass ( ` ant-col-24 ` ) ;
expect ( twoItem ? . className . includes ( 'offset' ) ) . toBeFalsy ( ) ;
} ) ;
2024-05-31 10:50:47 +08:00
} ) ;
2020-11-20 21:43:43 +08:00
it ( '_internalItemRender api test' , ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-11-20 21:43:43 +08:00
< Form >
< Form.Item
name = "light"
2022-09-28 12:01:20 +08:00
// @ts-ignore
2020-11-20 21:43:43 +08:00
_internalItemRender = { {
mark : 'pro_table_render' ,
2022-09-28 12:01:20 +08:00
render : ( _ : any , doms : any ) = > (
< div >
2024-04-21 23:56:41 +08:00
< div className = "bamboo" > warning title < / div >
2020-12-09 17:12:32 +08:00
{ doms . input }
{ doms . errorList }
{ doms . extra }
< / div >
) ,
2020-11-20 21:43:43 +08:00
} }
>
< input defaultValue = "should warning" / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
2024-04-21 23:56:41 +08:00
expect ( container . querySelector ( '.bamboo' ) ! ) . toHaveTextContent ( /warning title/i ) ;
2021-12-12 14:58:49 +08:00
} ) ;
it ( 'Form Item element id will auto add form_item prefix if form name is empty and item name is in the black list' , async ( ) = > {
2023-06-07 21:59:21 +08:00
const mockFn = jest . spyOn ( Util , 'getFieldId' ) ;
2021-12-12 14:58:49 +08:00
const itemName = 'parentNode' ;
2023-08-10 00:43:44 +08:00
// mock getFieldId old logic
// if form name is empty and item name is parentNode
// will get parentNode
2021-12-12 14:58:49 +08:00
mockFn . mockImplementation ( ( ) = > itemName ) ;
const { Option } = Select ;
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > {
2021-12-12 14:58:49 +08:00
const [ open , setOpen ] = useState ( false ) ;
return (
< >
< Form >
2022-09-28 12:01:20 +08:00
< Form.Item name = { itemName } label = { itemName } >
2021-12-12 14:58:49 +08:00
< Select
className = "form_item_parentNode"
defaultValue = "lucy"
open = { open }
style = { { width : 120 } }
>
< Option value = "jack" > Jack < / Option >
< Option value = "lucy" > Lucy < / Option >
< Option value = "Yiminghe" > yiminghe < / Option >
< / Select >
< / Form.Item >
< / Form >
< button
type = "button"
onClick = { ( ) = > {
setOpen ( true ) ;
} }
>
{ open ? 'show' : 'hidden' }
< / button >
< / >
) ;
} ;
2022-10-19 16:34:24 +08:00
const { container , rerender } = render ( < Demo / > ) ;
2021-12-12 14:58:49 +08:00
expect ( mockFn ) . toHaveBeenCalled ( ) ;
2022-09-28 12:01:20 +08:00
expect ( ( Util . getFieldId as ( ) = > string ) ( ) ) . toBe ( itemName ) ;
2021-12-12 14:58:49 +08:00
// make sure input id is parentNode
2023-10-13 20:56:06 +08:00
expect ( screen . getByLabelText ( itemName ) ) . toHaveAccessibleName ( itemName ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
fireEvent . click ( container . querySelector ( 'button' ) ! ) ;
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( 'button' ) ! ) . toHaveTextContent ( 'show' ) ;
2021-12-12 14:58:49 +08:00
mockFn . mockRestore ( ) ;
2022-09-28 12:01:20 +08:00
rerender ( < Demo / > ) ;
expect ( screen . getByLabelText ( itemName ) ) . toBeInTheDocument ( ) ;
2020-11-20 21:43:43 +08:00
} ) ;
2020-09-17 17:11:45 +08:00
describe ( 'tooltip' , ( ) = > {
2022-09-28 12:01:20 +08:00
it ( 'ReactNode' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-09-17 17:11:45 +08:00
< Form >
< Form.Item label = "light" tooltip = { < span > Bamboo < / span > } >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . mouseEnter ( container . querySelector ( '.anticon-question-circle' ) ! ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.ant-tooltip-inner' ) ) . toHaveTextContent ( 'Bamboo' ) ;
2020-09-17 17:11:45 +08:00
} ) ;
2022-09-28 12:01:20 +08:00
it ( 'config tooltip should show when hover on icon' , async ( ) = > {
2022-10-19 16:34:24 +08:00
const { container } = render (
2020-09-17 17:11:45 +08:00
< Form >
< Form.Item label = "light" tooltip = { { title : 'Bamboo' } } >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
fireEvent . mouseEnter ( container . querySelector ( '.anticon-question-circle' ) ! ) ;
2023-11-30 09:53:23 +08:00
fireEvent . click ( container . querySelector ( '.anticon-question-circle' ) ! ) ;
2022-10-19 16:34:24 +08:00
await waitFakeTimer ( ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-tooltip-inner' ) ) . toHaveTextContent ( 'Bamboo' ) ;
2020-09-17 17:11:45 +08:00
} ) ;
} ) ;
2021-09-15 17:13:51 +08:00
it ( 'warningOnly validate' , async ( ) = > {
2022-04-06 11:07:15 +08:00
const { container } = render (
2021-09-15 17:13:51 +08:00
< Form >
< Form.Item >
2022-04-06 11:07:15 +08:00
< Form.Item
name = "test"
2022-09-28 12:01:20 +08:00
label = "test"
2022-04-06 11:07:15 +08:00
initialValue = "bamboo"
rules = { [ { required : true , warningOnly : true } ] }
>
2021-09-15 17:13:51 +08:00
< Input / >
< / Form.Item >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , 'test' ) ;
await changeValue ( 0 , '' ) ;
2022-09-28 12:01:20 +08:00
2022-10-19 16:34:24 +08:00
expect ( container . querySelector ( '.ant-form-item-with-help' ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( '.ant-form-item-has-warning' ) ) . toBeTruthy ( ) ;
2021-09-15 17:13:51 +08:00
} ) ;
it ( 'not warning when remove on validate' , async ( ) = > {
2023-06-07 21:59:21 +08:00
let rejectFn : ( reason? : any ) = > void = jest . fn ( ) ;
2021-09-15 17:13:51 +08:00
2022-10-19 16:34:24 +08:00
const { unmount } = render (
2021-09-15 17:13:51 +08:00
< Form >
< Form.Item >
< Form.Item
noStyle
name = "test"
2022-04-06 11:07:15 +08:00
initialValue = "bamboo"
2021-09-15 17:13:51 +08:00
rules = { [
{
validator : ( ) = >
new Promise ( ( _ , reject ) = > {
rejectFn = reject ;
} ) ,
} ,
] }
>
< Input / >
< / Form.Item >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '' ) ;
2021-09-15 17:13:51 +08:00
2022-04-06 11:07:15 +08:00
unmount ( ) ;
2021-09-15 17:13:51 +08:00
// Delay validate failed
rejectFn ( new Error ( 'delay failed' ) ) ;
expect ( errorSpy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2021-11-11 17:51:33 +08:00
describe ( 'form colon' , ( ) = > {
it ( 'default colon' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2021-11-11 17:51:33 +08:00
< Form >
2022-09-28 12:01:20 +08:00
< Form.Item label = "姓名" name = "姓名" >
2021-11-11 17:51:33 +08:00
< input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( screen . getByText ( '姓名' ) ) . not . toHaveClass ( 'ant-form-item-no-colon' ) ;
2021-11-11 17:51:33 +08:00
} ) ;
it ( 'set Form.Item colon false' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2021-11-11 17:51:33 +08:00
< Form colon >
2022-09-28 12:01:20 +08:00
< Form.Item colon = { false } label = "姓名" name = "姓名" >
2021-11-11 17:51:33 +08:00
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( screen . getByText ( '姓名' ) ) . toHaveClass ( 'ant-form-item-no-colon' ) ;
2021-11-11 17:51:33 +08:00
} ) ;
it ( 'set Form colon false' , ( ) = > {
2022-09-28 12:01:20 +08:00
render (
2021-11-11 17:51:33 +08:00
< Form colon = { false } >
2022-09-28 12:01:20 +08:00
< Form.Item label = "姓名" name = "姓名" >
2021-11-11 17:51:33 +08:00
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-09-28 12:01:20 +08:00
expect ( screen . getByText ( '姓名' ) ) . toHaveClass ( 'ant-form-item-no-colon' ) ;
2021-11-11 17:51:33 +08:00
} ) ;
} ) ;
2022-04-15 15:51:09 +08:00
it ( 'useFormInstance' , ( ) = > {
2024-06-22 21:59:12 +08:00
let formInstance : any ;
let subFormInstance : any ;
2022-04-15 15:51:09 +08:00
const Sub = ( ) = > {
const formSub = Form . useFormInstance ( ) ;
subFormInstance = formSub ;
return null ;
} ;
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > {
2022-04-15 15:51:09 +08:00
const [ form ] = Form . useForm ( ) ;
formInstance = form ;
return (
< Form form = { form } >
< Sub / >
< / Form >
) ;
} ;
render ( < Demo / > ) ;
expect ( subFormInstance ) . toBe ( formInstance ) ;
} ) ;
2022-06-06 23:39:00 +08:00
2023-09-04 10:03:12 +08:00
describe ( 'noStyle with status' , ( ) = > {
it ( 'noStyle should not affect status' , async ( ) = > {
const Demo : React.FC = ( ) = > (
< Form >
{ /* should change status */ }
< Form.Item validateStatus = "error" noStyle >
< Select className = "custom-select" / >
< / Form.Item >
{ /* should follow parent status */ }
2023-09-19 10:27:15 +08:00
< Form.Item validateStatus = "error" hasFeedback >
2023-09-04 10:03:12 +08:00
< Form.Item noStyle >
< Select className = "custom-select-b" / >
< / Form.Item >
< / Form.Item >
{ /* should follow child status */ }
2023-09-19 10:27:15 +08:00
< Form.Item validateStatus = "error" hasFeedback >
< Form.Item noStyle validateStatus = "warning" hasFeedback = { false } >
2023-09-04 10:03:12 +08:00
< Select className = "custom-select-c" / >
< / Form.Item >
< / Form.Item >
{ /* should follow child status */ }
2022-06-06 23:39:00 +08:00
< Form.Item noStyle >
2023-09-04 10:03:12 +08:00
< Form.Item validateStatus = "warning" >
< Select className = "custom-select-d" / >
< / Form.Item >
2022-06-06 23:39:00 +08:00
< / Form.Item >
2023-09-04 10:03:12 +08:00
{ /* should follow child status */ }
< Form.Item validateStatus = "error" >
< Form.Item noStyle validateStatus = "" >
< Select className = "custom-select-e" / >
< / Form.Item >
2022-06-06 23:39:00 +08:00
< / Form.Item >
2023-09-04 10:03:12 +08:00
< / Form >
) ;
const { container } = render ( < Demo / > ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.custom-select' ) ) . toHaveClass ( 'ant-select-status-error' ) ;
expect ( container . querySelector ( '.custom-select' ) ) . not . toHaveClass ( 'ant-select-in-form-item' ) ;
expect ( container . querySelector ( '.custom-select-b' ) ) . toHaveClass ( 'ant-select-status-error' ) ;
expect ( container . querySelector ( '.custom-select-b' ) ) . toHaveClass ( 'ant-select-in-form-item' ) ;
2023-09-19 10:27:15 +08:00
expect (
container
. querySelector ( '.custom-select-b' )
? . querySelector ( '.ant-form-item-feedback-icon-error' ) ,
) . toBeTruthy ( ) ;
2023-09-04 10:03:12 +08:00
expect ( container . querySelector ( '.custom-select-c' ) ) . toHaveClass ( 'ant-select-status-warning' ) ;
expect ( container . querySelector ( '.custom-select-c' ) ) . toHaveClass ( 'ant-select-in-form-item' ) ;
2023-09-19 10:27:15 +08:00
expect (
container
. querySelector ( '.custom-select-c' )
? . querySelector ( '.ant-form-item-feedback-icon-warning' ) ,
) . toBeFalsy ( ) ;
2023-09-04 10:03:12 +08:00
expect ( container . querySelector ( '.custom-select-d' ) ) . toHaveClass ( 'ant-select-status-warning' ) ;
expect ( container . querySelector ( '.custom-select-d' ) ) . toHaveClass ( 'ant-select-in-form-item' ) ;
expect ( container . querySelector ( '.custom-select-e' ) ) . not . toHaveClass (
'ant-select-status-error' ,
) ;
expect ( container . querySelector ( '.custom-select-e' ) ) . toHaveClass ( 'ant-select-in-form-item' ) ;
} ) ;
it ( 'parent pass status' , async ( ) = > {
const { container } = render (
< Form >
< Form.Item label = "name" >
< Form.Item name = "first" noStyle rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< Form.Item name = "last" noStyle >
< Input / >
< / Form.Item >
2022-06-15 00:09:31 +08:00
< / Form.Item >
2023-09-04 10:03:12 +08:00
< / Form > ,
) ;
// Input and set back to empty
await changeValue ( 0 , 'Once' ) ;
await changeValue ( 0 , '' ) ;
expect ( container . querySelector ( '.ant-form-item-explain-error' ) ? . textContent ) . toEqual (
"'first' is required" ,
) ;
expect ( container . querySelectorAll ( 'input' ) [ 0 ] ) . toHaveClass ( 'ant-input-status-error' ) ;
expect ( container . querySelectorAll ( 'input' ) [ 1 ] ) . not . toHaveClass ( 'ant-input-status-error' ) ;
} ) ;
2022-06-06 23:39:00 +08:00
} ) ;
it ( 'should not affect Popup children style' , ( ) = > {
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > (
2022-06-06 23:39:00 +08:00
< Form >
2022-09-28 12:01:20 +08:00
< Form.Item labelCol = { 4 as ColProps } validateStatus = "error" >
2022-10-11 21:33:29 +08:00
< Modal open >
2022-06-06 23:39:00 +08:00
< Select className = "modal-select" / >
< / Modal >
< / Form.Item >
< Form.Item validateStatus = "error" >
2022-10-11 21:33:29 +08:00
< Drawer open >
2022-06-06 23:39:00 +08:00
< Select className = "drawer-select" / >
< / Drawer >
< / Form.Item >
< / Form >
) ;
const { container } = render ( < Demo / > , { container : document.body } ) ;
expect ( container . querySelector ( '.modal-select' ) ? . className ) . not . toContain ( 'in-form-item' ) ;
expect ( container . querySelector ( '.modal-select' ) ? . className ) . not . toContain ( 'status-error' ) ;
expect ( container . querySelector ( '.drawer-select' ) ? . className ) . not . toContain ( 'in-form-item' ) ;
expect ( container . querySelector ( '.drawer-select' ) ? . className ) . not . toContain ( 'status-error' ) ;
} ) ;
2022-07-14 11:01:39 +08:00
2023-03-31 14:27:55 +08:00
it ( 'should be set up correctly marginBottom' , ( ) = > {
render (
< Modal open >
< Form >
2023-04-12 16:15:04 +08:00
< Form.Item help = "This is a help message" >
2023-03-31 14:27:55 +08:00
< Input / >
< / Form.Item >
< / Form >
< / Modal > ,
) ;
expect ( document . querySelector ( '.ant-form-item-margin-offset' ) ) . toBeTruthy ( ) ;
} ) ;
2022-07-14 11:01:39 +08:00
it ( 'Form.Item.useStatus should work' , async ( ) = > {
const {
Item : { useStatus } ,
} = Form ;
2022-09-28 12:01:20 +08:00
const CustomInput : React.FC < { className? : string ; value? : React.ReactNode } > = ( {
className ,
value ,
} ) = > {
2022-07-14 11:01:39 +08:00
const { status } = useStatus ( ) ;
return < div className = { classNames ( className , ` custom-input-status- ${ status } ` ) } > { value } < / div > ;
} ;
2022-09-28 12:01:20 +08:00
const Demo : React.FC = ( ) = > {
2022-07-14 11:01:39 +08:00
const [ form ] = Form . useForm ( ) ;
return (
< Form form = { form } name = "my-form" >
< Form.Item name = "required" rules = { [ { required : true } ] } >
< CustomInput className = "custom-input-required" value = "" / >
< / Form.Item >
< Form.Item name = "warning" validateStatus = "warning" >
< CustomInput className = "custom-input-warning" / >
< / Form.Item >
< Form.Item name = "normal" >
< CustomInput className = "custom-input" / >
< / Form.Item >
< CustomInput className = "custom-input-wrong" / >
< Button onClick = { ( ) = > form . submit ( ) } className = "submit-button" >
Submit
< / Button >
< / Form >
) ;
} ;
const { container } = render ( < Demo / > ) ;
2023-03-09 21:28:54 +08:00
expect ( container . querySelector ( '.custom-input-required' ) ? . className ) . toContain (
2022-07-14 11:01:39 +08:00
'custom-input-status-' ,
) ;
2023-06-07 21:59:21 +08:00
expect ( container . querySelector ( '.custom-input-warning' ) ? . classList ) . toContain (
'custom-input-status-warning' ,
) ;
2023-03-09 21:28:54 +08:00
expect ( container . querySelector ( '.custom-input' ) ? . className ) . toContain ( 'custom-input-status-' ) ;
2023-06-07 21:59:21 +08:00
expect ( container . querySelector ( '.custom-input-wrong' ) ? . classList ) . toContain (
'custom-input-status-undefined' ,
) ;
2022-07-14 11:01:39 +08:00
expect ( errorSpy ) . toHaveBeenCalledWith (
expect . stringContaining ( 'Form.Item.useStatus should be used under Form.Item component.' ) ,
) ;
2022-09-28 12:01:20 +08:00
fireEvent . click ( container . querySelector ( '.submit-button' ) ! ) ;
await waitFakeTimer ( ) ;
2023-06-07 21:59:21 +08:00
expect ( container . querySelector ( '.custom-input-required' ) ? . classList ) . toContain (
'custom-input-status-error' ,
) ;
2022-07-14 11:01:39 +08:00
} ) ;
2022-07-19 16:01:31 +08:00
2023-03-31 11:42:34 +08:00
it ( 'Form.Item.useStatus should supports get error messages and warning messages' , async ( ) = > {
const {
Item : { useStatus } ,
} = Form ;
const ErrorItem : React.FC = ( ) = > {
const { errors } = useStatus ( ) ;
return < div className = "test-error" > { errors [ 0 ] } < / div > ;
} ;
const WarningItem : React.FC = ( ) = > {
const { warnings } = useStatus ( ) ;
return < div className = "test-warning" > { warnings [ 0 ] } < / div > ;
} ;
const Demo : React.FC = ( ) = > {
const [ form ] = Form . useForm ( ) ;
return (
2023-04-12 16:15:04 +08:00
< Form form = { form } name = "test-form" >
2023-03-31 11:42:34 +08:00
< Form.Item name = "error" rules = { [ { required : true , message : 'This is a error message.' } ] } >
< ErrorItem / >
< / Form.Item >
< Form.Item
name = "warning"
rules = { [ { required : true , message : 'This is a warning message.' , warningOnly : true } ] }
>
< WarningItem / >
< / Form.Item >
< Button onClick = { ( ) = > form . submit ( ) } className = "submit-button" >
Submit
< / Button >
< / Form >
) ;
} ;
const { container } = render ( < Demo / > ) ;
fireEvent . click ( container . querySelector ( '.submit-button' ) ! ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelector ( '.test-error' ) ) . toHaveTextContent ( 'This is a error message.' ) ;
expect ( container . querySelector ( '.test-warning' ) ) . toHaveTextContent (
'This is a warning message.' ,
) ;
} ) ;
2022-07-19 16:01:31 +08:00
it ( 'item customize margin' , async ( ) = > {
2023-06-07 21:59:21 +08:00
const computeSpy = jest
2022-09-28 12:01:20 +08:00
. spyOn ( window , 'getComputedStyle' )
2023-07-28 16:17:43 +08:00
. mockImplementation ( ( ) = > ( { marginBottom : 24 } ) as unknown as CSSStyleDeclaration ) ;
2022-07-19 16:01:31 +08:00
const { container } = render (
< Form >
< Form.Item name = "required" initialValue = "bamboo" rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2022-10-19 16:34:24 +08:00
await changeValue ( 0 , '' ) ;
2022-07-19 16:01:31 +08:00
computeSpy . mockRestore ( ) ;
expect ( container . querySelector ( '.ant-form-item-margin-offset' ) ) . toHaveStyle ( {
marginBottom : - 24 ,
} ) ;
} ) ;
2023-03-31 14:27:55 +08:00
2022-09-20 16:48:59 +08:00
it ( 'form child components should be given priority to own disabled props when it in a disabled form' , ( ) = > {
const props = {
name : 'file' ,
2024-04-03 23:37:22 +08:00
action : 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload' ,
2022-09-20 16:48:59 +08:00
headers : {
authorization : 'authorization-text' ,
} ,
capture : true ,
} ;
2022-09-28 15:00:27 +08:00
const renderComps = ( disabled? : boolean ) = > [
2022-09-20 16:48:59 +08:00
< Button key = "Button" disabled = { disabled } type = "primary" htmlType = "submit" >
test
< / Button > ,
< Cascader key = "Cascader" disabled = { disabled } options = { [ ] } / > ,
< Checkbox key = "Checkbox" disabled = { disabled } / > ,
< Checkbox.Group
key = "CheckboxGroup"
disabled = { disabled }
options = { [
{ label : 'male' , value : 0 } ,
{ label : 'female' , value : 1 } ,
] }
/ > ,
2023-11-21 14:28:50 +08:00
< ColorPicker key = "ColorPicker" disabled = { disabled } / > ,
2022-09-20 16:48:59 +08:00
< InputNumber key = "InputNumber" disabled = { disabled } / > ,
< Input key = "Input" disabled = { disabled } / > ,
< Select key = "Select" disabled = { disabled } / > ,
< Switch key = "Switch" disabled = { disabled } / > ,
< TreeSelect key = "TreeSelect" disabled = { disabled } / > ,
< Upload key = "Upload" { ...props } disabled = { disabled } >
< Button disabled = { disabled } > Click to Upload < / Button >
< / Upload > ,
< DatePicker key = "DatePicker" disabled = { disabled } / > ,
< DatePicker.RangePicker key = "DatePicker.RangePicker" disabled = { disabled } / > ,
< DatePicker.MonthPicker key = "DatePicker.MonthPicker" disabled = { disabled } / > ,
< DatePicker.QuarterPicker key = "DatePicker.QuarterPicker" disabled = { disabled } / > ,
< DatePicker.WeekPicker key = "DatePicker.WeekPicker" disabled = { disabled } / > ,
< DatePicker.YearPicker key = "DatePicker.YearPicker" disabled = { disabled } / > ,
< DatePicker.TimePicker key = "DatePicker.TimePicker" disabled = { disabled } / > ,
] ;
2023-07-06 23:54:03 +08:00
const App : React.FC = ( ) = > < Form disabled > { renderComps ( false ) } < / Form > ;
2022-09-20 16:48:59 +08:00
const wrapper = render ( < App / > ) ;
expect ( wrapper . container . querySelectorAll ( '[disabled]' ) . length ) . toBe ( 0 ) ;
const App2 = ( ) = > < Form disabled > { renderComps ( ) } < / Form > ;
const wrapper2 = render ( < App2 / > ) ;
2023-09-21 16:37:54 +08:00
// 时间范围组件中会有两个 input 框, Upload 为叠加
// 因此虽然上述只有 18 个组件,但实际有 20 个 带有 disabled 属性的表单组件
expect ( wrapper2 . container . querySelectorAll ( '[disabled]' ) . length ) . toBe ( 20 ) ;
2022-09-20 16:48:59 +08:00
const App3 = ( ) = > < Form disabled > { renderComps ( true ) } < / Form > ;
const wrapper3 = render ( < App3 / > ) ;
2023-09-21 16:37:54 +08:00
expect ( wrapper3 . container . querySelectorAll ( '[disabled]' ) . length ) . toBe ( 20 ) ;
2022-09-20 16:48:59 +08:00
const App4 = ( ) = > < Form > { renderComps ( true ) } < / Form > ;
const wrapper4 = render ( < App4 / > ) ;
2023-09-21 16:37:54 +08:00
expect ( wrapper4 . container . querySelectorAll ( '[disabled]' ) . length ) . toBe ( 20 ) ;
2022-09-20 16:48:59 +08:00
const App5 = ( ) = > < Form > { renderComps ( ) } < / Form > ;
const wrapper5 = render ( < App5 / > ) ;
expect ( wrapper5 . container . querySelectorAll ( '[disabled]' ) . length ) . toBe ( 0 ) ;
} ) ;
2023-03-09 21:28:54 +08:00
it ( 'success feedback should display when pass hasFeedback prop and current value is valid value' , async ( ) = > {
const App = ( { trigger = false } : { trigger? : boolean } ) = > {
const form = useRef < FormInstance < any > > ( null ) ;
useEffect ( ( ) = > {
2024-02-23 11:01:56 +08:00
if ( ! trigger ) {
return ;
}
2023-03-09 21:28:54 +08:00
form . current ? . validateFields ( ) ;
} , [ trigger ] ) ;
return (
< Form ref = { form } >
< Form.Item
label = "Success"
name = "name1"
hasFeedback
initialValue = "test@qq.com"
rules = { [
{
type : 'email' ,
message : 'Please input your e-mail' ,
} ,
{
required : true ,
message : 'Please input your value' ,
} ,
] }
>
< Input / >
< / Form.Item >
< Form.Item
label = "Success"
name = "name2"
initialValue = "test@qq.com"
rules = { [
{
type : 'email' ,
message : 'Please input your e-mail' ,
} ,
{
required : true ,
message : 'Please input your value' ,
} ,
] }
>
< Input / >
< / Form.Item >
< / Form >
) ;
} ;
const { container , rerender } = render ( < App / > ) ;
expect ( container . querySelectorAll ( '.ant-form-item-has-feedback' ) . length ) . toBe ( 0 ) ;
expect ( container . querySelectorAll ( '.ant-form-item-has-success' ) . length ) . toBe ( 0 ) ;
rerender ( < App trigger / > ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelectorAll ( '.ant-form-item-has-feedback' ) . length ) . toBe ( 1 ) ;
expect ( container . querySelectorAll ( '.ant-form-item-has-success' ) . length ) . toBe ( 1 ) ;
} ) ;
2023-04-02 17:53:29 +08:00
it ( 'feedback should automatically derive the correct state' , async ( ) = > {
const Demo : React.FC = ( ) = > {
const [ form ] = Form . useForm ( ) ;
return (
< Form form = { form } >
< Form.Item name = "success" initialValue = "test" hasFeedback rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< Form.Item
name = "validating"
hasFeedback
rules = { [
{
validator : ( ) = >
new Promise ( ( resolve ) = > {
setTimeout ( ( ) = > resolve ( true ) , 2000 ) ;
} ) ,
} ,
] }
>
< Input / >
< / Form.Item >
< Form.Item name = "warning" hasFeedback rules = { [ { required : true , warningOnly : true } ] } >
< Input / >
< / Form.Item >
< Form.Item name = "error" hasFeedback rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< Button onClick = { ( ) = > form . submit ( ) } className = "submit-button" >
Submit
< / Button >
< / Form >
) ;
} ;
const { container } = render ( < Demo / > ) ;
fireEvent . click ( container . querySelector ( '.submit-button' ) ! ) ;
await waitFakeTimer ( 50 ) ;
expect ( container . querySelector ( '.ant-form-item-has-success' ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( '.ant-form-item-is-validating' ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( '.ant-form-item-has-warning' ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( '.ant-form-item-has-error' ) ) . toBeTruthy ( ) ;
} ) ;
2023-09-04 20:36:45 +08:00
it ( 'custom feedback icons should display when pass hasFeedback prop' , async ( ) = > {
const App = ( { trigger = false } : { trigger? : boolean } ) = > {
const form = useRef < FormInstance < any > > ( null ) ;
useEffect ( ( ) = > {
2024-02-23 11:01:56 +08:00
if ( ! trigger ) {
return ;
}
2023-09-04 20:36:45 +08:00
form . current ? . validateFields ( ) ;
} , [ trigger ] ) ;
return (
< Form
ref = { form }
feedbackIcons = { ( ) = > ( {
error : < AlertFilled id = "custom-error-icon" / > ,
} ) }
>
< Form.Item
label = "Success"
name = "name1"
hasFeedback
rules = { [
{
required : true ,
message : 'Please input your value' ,
} ,
] }
>
< Input / >
< / Form.Item >
< Form.Item
label = "Success"
name = "name1"
hasFeedback = { {
icons : ( ) = > ( {
error : < AlertFilled id = "custom-error-icon2" / > ,
} ) ,
} }
rules = { [
{
required : true ,
message : 'Please input your value 3' ,
} ,
] }
>
< Input / >
< / Form.Item >
< / Form >
) ;
} ;
const { container , rerender } = render ( < App / > ) ;
expect ( container . querySelectorAll ( '.ant-form-item-has-feedback' ) . length ) . toBe ( 0 ) ;
rerender ( < App trigger / > ) ;
await waitFakeTimer ( ) ;
expect ( container . querySelectorAll ( '.ant-form-item-has-feedback' ) . length ) . toBe ( 2 ) ;
expect ( container . querySelectorAll ( '#custom-error-icon, #custom-error-icon2' ) . length ) . toBe ( 2 ) ;
} ) ;
2023-04-04 15:23:43 +08:00
// https://github.com/ant-design/ant-design/issues/41621
it ( 'should not override value when pass `undefined` to require' , async ( ) = > {
// When require is `undefined`, the `isRequire` calculation logic should be preserved
const { container } = render (
< Form >
< Form.Item label = "test" name = "success" required = { undefined } rules = { [ { required : true } ] } >
< Input / >
< / Form.Item >
< / Form > ,
) ;
expect ( container . querySelector ( '.ant-form-item-required' ) ) . toBeTruthy ( ) ;
} ) ;
2023-03-23 16:40:53 +08:00
it ( 'validate status should be change in order' , async ( ) = > {
2023-06-07 21:59:21 +08:00
const onChange = jest . fn ( ) ;
2023-03-23 16:40:53 +08:00
const CustomInput = ( props : any ) = > {
const { status } = Form . Item . useStatus ( ) ;
useEffect ( ( ) = > {
onChange ( status ) ;
} , [ status ] ) ;
return < Input { ...props } / > ;
} ;
2023-07-06 23:54:03 +08:00
const App : React.FC = ( ) = > (
2023-03-23 16:40:53 +08:00
< Form >
< Form.Item >
< Form.Item name = "test" label = "test" rules = { [ { len : 3 , message : 'error.' } ] } >
< CustomInput / >
< / Form.Item >
< / Form.Item >
< / Form >
) ;
render ( < App / > ) ;
await waitFakeTimer ( ) ;
// initial validate
const initTriggerTime = ReactVersion . startsWith ( '18' ) ? 2 : 1 ;
expect ( onChange ) . toHaveBeenCalledTimes ( initTriggerTime ) ;
let idx = 1 ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , '' ) ;
if ( initTriggerTime === 2 ) {
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , '' ) ;
}
// change trigger
await changeValue ( 0 , '1' ) ;
expect ( onChange ) . toHaveBeenCalledTimes ( initTriggerTime + 2 ) ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , 'validating' ) ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , 'error' ) ;
await changeValue ( 0 , '11' ) ;
expect ( onChange ) . toHaveBeenCalledTimes ( initTriggerTime + 4 ) ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , 'validating' ) ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , 'error' ) ;
await changeValue ( 0 , '111' ) ;
expect ( onChange ) . toHaveBeenCalledTimes ( initTriggerTime + 6 ) ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , 'validating' ) ;
expect ( onChange ) . toHaveBeenNthCalledWith ( idx ++ , 'success' ) ;
} ) ;
2023-04-10 12:02:06 +08:00
2023-08-07 15:11:06 +08:00
describe ( 'requiredMark' , ( ) = > {
// https://user-images.githubusercontent.com/32004925/230819163-464fe90d-422d-4a6d-9e35-44a25d4c64f1.png
it ( 'should not render `requiredMark` when Form.Item has no required prop' , ( ) = > {
// Escaping TypeScript error
const genProps = ( value : any ) = > ( { . . . value } ) ;
2023-04-10 12:02:06 +08:00
2023-08-07 15:11:06 +08:00
const { container } = render (
< Form name = "basic" requiredMark = "optional" >
< Form.Item
label = "First Name"
name = "firstName"
required
{ . . . genProps ( { requiredMark : false } ) }
>
< Input / >
< / Form.Item >
< Form.Item
label = "Last Name"
name = "lastName"
required
{ . . . genProps ( { requiredMark : true } ) }
>
< Input / >
< / Form.Item >
< / Form > ,
) ;
expect ( container . querySelectorAll ( '.ant-form-item-required' ) ) . toHaveLength ( 2 ) ;
expect ( container . querySelectorAll ( '.ant-form-item-required-mark-optional' ) ) . toHaveLength ( 2 ) ;
} ) ;
it ( 'customize logic' , ( ) = > {
const { container } = render (
< Form name = "basic" requiredMark = { ( label , info ) = > ` ${ label } : ${ info . required } ` } >
< Form.Item label = "Required" required >
< Input / >
< / Form.Item >
< Form.Item label = "Optional" >
< Input / >
< / Form.Item >
< / Form > ,
) ;
2023-04-10 12:02:06 +08:00
2023-08-07 15:11:06 +08:00
expect ( container . querySelectorAll ( '.ant-form-item-label' ) [ 0 ] . textContent ) . toEqual (
'Required: true' ,
) ;
expect ( container . querySelectorAll ( '.ant-form-item-label' ) [ 1 ] . textContent ) . toEqual (
'Optional: false' ,
) ;
} ) ;
2023-04-10 12:02:06 +08:00
} ) ;
2023-04-12 16:15:04 +08:00
it ( 'children support comment' , ( ) = > {
resetWarned ( ) ;
const { container } = render (
< Form initialValues = { { name : 'bamboo' , age : '14' } } >
< Form.Item name = "name" >
{ /* Comment here */ }
< Input / >
< / Form.Item >
< Form.Item name = "age" > { [ null , < Input key = "input" / > ] } < / Form.Item >
< / Form > ,
) ;
expect ( container . querySelectorAll ( 'input' ) [ 0 ] . value ) . toEqual ( 'bamboo' ) ;
expect ( container . querySelectorAll ( 'input' ) [ 1 ] . value ) . toEqual ( '14' ) ;
expect ( errorSpy ) . not . toHaveBeenCalled ( ) ;
} ) ;
2023-05-09 19:09:21 +08:00
it ( 'duplicated form name' , ( ) = > {
resetWarned ( ) ;
render (
< >
< Form name = "same" / >
< Form name = "same" / >
< / > ,
) ;
expect ( errorSpy ) . toHaveBeenCalledWith (
'Warning: [antd: Form] There exist multiple Form with same `name`.' ,
) ;
} ) ;
2023-08-10 16:48:28 +08:00
// https://github.com/ant-design/ant-design/issues/43044
it ( 'should not pass disabled to modal footer button' , ( ) = > {
render (
// <FormDemo formProps={{ disabled: true }} modalProps={{ open: true }} />,
< Form disabled >
< Form.Item label = "label" >
< Modal open / >
< / Form.Item >
< / Form > ,
) ;
const footerBts = document . querySelectorAll ( '.ant-modal-footer > button' ) ;
expect ( footerBts ) . toBeTruthy ( ) ;
footerBts . forEach ( ( bt ) = > {
expect ( bt ) . not . toHaveAttribute ( 'disabled' ) ;
} ) ;
} ) ;
2023-11-02 17:15:03 +08:00
it ( 'InputNumber with hasFeedback should keep dom stable' , ( ) = > {
const Demo = ( ) = > (
< Form >
< Form.Item
name = "light"
hasFeedback
rules = { [ { required : true , message : 'Please input a entry price' } ] }
>
< InputNumber / >
< / Form.Item >
< / Form >
) ;
const { container } = render ( < Demo / > ) ;
const input = container . querySelector ( 'input' ) ! ;
expect ( container . querySelector ( '.ant-input-number-suffix' ) ) . toBeTruthy ( ) ;
2024-06-10 10:48:21 +08:00
fireEvent . focus ( input ) ;
2023-11-02 17:15:03 +08:00
expect ( container . querySelector ( '.ant-input-number-focused' ) ) . toBeTruthy ( ) ;
fireEvent . change ( input , {
target : { value : '1' } ,
} ) ;
expect ( container . querySelector ( '.ant-input-number-suffix' ) ) . toBeTruthy ( ) ;
expect ( container . querySelector ( '.ant-input-number-focused' ) ) . toBeTruthy ( ) ;
} ) ;
2023-11-09 13:42:25 +08:00
// https://github.com/ant-design/ant-design/issues/20803#issuecomment-601626759
it ( 'without explicitly passing `valuePropName`' , async ( ) = > {
const submit = jest . fn ( ) ;
const Demo = ( ) = > (
< Form
initialValues = { {
foo : true ,
} }
onFinish = { submit }
>
< Form.Item label = "Switch" name = "foo" >
< Switch / >
< / Form.Item >
< button type = "submit" > Submit < / button >
< / Form >
) ;
const { getByRole } = render ( < Demo / > ) ;
await waitFakeTimer ( ) ;
const switchNode = getByRole ( 'switch' ) ;
expect ( switchNode ) . toBeTruthy ( ) ;
expect ( switchNode ) . toBeChecked ( ) ;
fireEvent . click ( switchNode ) ;
expect ( switchNode ) . not . toBeChecked ( ) ;
const submitButton = getByRole ( 'button' ) ;
expect ( submitButton ) . toBeTruthy ( ) ;
fireEvent . click ( submitButton ) ;
await waitFakeTimer ( ) ;
expect ( submit ) . toHaveBeenCalledWith ( {
foo : false ,
} ) ;
} ) ;
2023-12-14 15:55:19 +08:00
it ( 'getValueProps should trigger update' , ( ) = > {
const { container } = render (
< Form >
< Form.Item
name = "remember"
getValueProps = { ( val ) = > ( { checked : val } ) }
getValueFromEvent = { ( e ) = > e . target . checked }
>
< Checkbox / >
< / Form.Item >
< / Form > ,
) ;
expect ( container . querySelector ( 'input' ) ? . checked ) . toBeFalsy ( ) ;
fireEvent . click ( container . querySelector ( 'input' ) ! ) ;
expect ( container . querySelector ( 'input' ) ? . checked ) . toBeTruthy ( ) ;
} ) ;
2019-07-03 20:14:39 +08:00
} ) ;