Writing jest tests for antd FormItem - reactjs

I am trying to write jest tests for my component which uses antd that uses Form. Pasting a snippet of the code.
Component.tsx:
render() {
const { Option } = Select;
return (
<div>
<Form
ref={this.formRef}
{...formItemLayout}
onFinish={(values) => {this.props.onSubmit(values)}
>
<Form.Item
name="foo"
label="Foo"
hasFeedback
rules={[
{ required: true, message: 'Foo is required' },
() => ({
validator(rule, value) {
if(!isUniqueFoo(value)) {
return Promise.reject(new Error('wrong foo'));
}
if (value.length > 10) {
return Promise.reject(new Error('too many characters'));
}
return Promise.reject(new Error('something went wrong')));
}
})
]}
>
<Input type="text" />
</Form.Item>
<Form.Item
name="bar"
label="Bar"
hasFeedback
rules={[
{ required: true, message: 'bar is required' },
() => ({
validator(rule, value) {
if (isValidInput(value)) {
return Promise.resolve();
}
return Promise.reject(new Error('wrong value')));
}
})
]}
>
<Input />
</Form.Item>
<Form.Item
name="baz"
label="Baz"
>
<Select
showSearch
className="select-dropdown"
optionFilterProp="children"
onChange={this.onChange}
>
{data.map((d: Data) => (
<Option key={d.id} value={d.id}>{d.name}</Option>
))
}
</Select>
</Form.Item>
</>
)}
</Form>
</div>
}
Trying to write Jest tests for this above component.
This is a test that works:
it('test', () => {
const onSubmit = jest.fn();
const wrapper = shallow(<Component {...defaultProps} onSubmit={onSubmit} />);
wrapper.find('ForwardRef(InternalForm)').props().onFinish(values);
expect(onSubmit).toHaveBeenCalledWith({
...defaultProps.baz,
// Changed items.
foo: 'test',
bar: 'other test'
});
});
While the above test works, I would like to test other things like validations. None of these code snippets work. I am trying to test whether the field 'Foo' is entered and the length of text is < 10 chars, and is validated, etc.
console.log("test1", wrapper.find('ForwardRef(InternalForm)').shallow().find('Input'));
console.log("test2", wrapper.find('ForwardRef(InternalForm)').find('Input'));
console.log("test3", wrapper.find('Input'));
Another thing that irks me is having to use wrapper.find('ForwardRef(InternalForm)') instead of wrapper.find('Form')
Any thoughts?

Related

how to do validation for a antd Text (typography) component?

It seems that we couldn't add validation for antd editable <Text> (typography).
The component doesn't accept value props like <Input> component. How do we do validation in that case?
Code:
const [title, setTitle] = useState("Battle");
<>
<Form id="myForm" name="basic" onFinish={onSubmit}>
<Form.Item
name="title"
rules={[
{
required: true,
message: "Please input your title!"
}
]}
>
<Text editable>{title}</Text>
</Form.Item>
</Form>
<Button form="myForm" key="formSubmit" htmlType="submit" type="primary">
Create
</Button>
</>
Above code validation works but if the data is still there, it show validation error.
Codesandbox link : https://codesandbox.io/s/basic-antd-4-16-9-forked-o8nxdl?file=/index.js
Here is a code that does some simple validations
function reducer (action , state) {
switch (action.type){
case 'title':
return {...state , data : { ...state.data , title : action.payload }
, errors : { ...state.errors
, title : title.length > 3 null : '😈'} } // title length must be > 3
}
}
const [state , dispatch] = useReducer (reducer , {data : {} , errors : {})
console.log (state)
return ( <> <Form.Item
name="title"
rules={[
{
required: true,
message: "Please input your title!"
}
]}
onChange = {(e) => dispatch ({type : 'title' , payload : e.target.value})
>
<Text editable>{title}</Text>
</Form.Item> </>)
Before submitting make sure all errors must be null
you can handle it using the onChange like this working demo is here
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Button, Input, Typography } from "antd";
import SecForm from "./SecForm";
const { Text } = Typography;
const App = () => {
const [title, setTitle] = useState('Battle');
const data = ["1", "2", "3"];
const onHandleChange = (event) => {
setTitle(event);
};
const onSubmit = (e) => {
e.preventDefault();
console.log("Submitted");
};
return (
<>
<Form id="myForm" name="basic" onFinish={onSubmit}>
<Form.Item
label="name"
name="name"
rules={[
{
required: true,
message: "Please input your name!"
}
]}
>
<Input placeholder="Name" />
</Form.Item>
<Form.Item
label="rank"
name="rank"
rules={[
{
required: true,
message: "Please input your rank!"
}
]}
>
<Input placeholder="Rank" />
</Form.Item>
<Form.Item
name="title"
valuePropName={title}
rules={[
{
required: title?.length < 1,
message: "Please input your title!"
}
]}
>
<Text editable={{ onChange: onHandleChange }}>{title}</Text>
</Form.Item>
{data.map((d, index) => (
<SecForm index={index} />
))}
</Form>
<Button form="myForm" key="formSubmit" htmlType="submit" type="primary">
Create
</Button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));

How to Trim White Spaces from input field in ant-design form?

I have a form with ant design. I want to add a rule for each input field that the user can't enter spaces to fill the input. (spaces forbidden)
I try this method { transform: (value) => value.trim() }but it doesn't work.
I appreciate your help.
<>
<Form.Item
label={t("forms.inputs.Name.label")}
rules={[
{
required: true,
message: t("forms.inputs.Name.rules.required"),
},
{
min: 3,
message: t("forms.inputs.Name.rules.minLength"),
},
]}>
<Input />
</Form.Item>
<Form.Item
label={t("forms.inputs.job.label")}
rules={[
{
required: true,
message: t("forms.inputs.job.rules.required"),
},
]}>
<Input />
</Form.Item>
<Form.Item
label={t("forms.inputs.Company.label")}
rules={[
{
required: true,
message: t("forms.inputs.Company.rules.required"),
},
]}>
<Input placeholder={t("forms.inputs.currentCompany.placeholder")} />
</Form.Item>
</>
Just write a custom validation rule:
<Form.Item
label="Username"
name="username"
rules={[
{
required: true,
message: "Required"
},
{
validator: (_, value) =>
!value.includes(" ")
? Promise.resolve()
: Promise.reject(new Error("No spaces allowed"))
}
]}
>
<Input />
</Form.Item>
For email validation, you can use the following regex pattern:
<Form.Item
label="Email"
name="email"
rules={[
{
required: true,
message: "Required"
},
{
pattern: /([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\"([]!#-[^-~ \t]|(\\[\t -~]))+\")#([-!#-'*+/-9=?A-Z^-~]+(\.[-!#-'*+/-9=?A-Z^-~]+)*|\[[\t -Z^-~]*])/,
message: "Invalid email"
}
]}
normalize={(value, prevVal, prevVals) => value.trim()}
>
<Input />
</Form.Item>
DEMO
Instead of trimming onChange, do that in an onBlur callback:
formatInput = (event) => { const attribute = event.target.getAttribute('name') this.setState({ [attribute]: event.target.value.trim() }) }
or when you click enter
onKeyPress={(e) => {if (e.key === "Enter") {setValue(e.target.value.trim())} }}
you can do like this instead of using trim()
import React, { useState } from 'react';
import 'antd/dist/antd.css';
import { Form, Input } from 'antd';
const App = () => {
const [inputValue, setValue] = useState({input1: '', input2: '', input3: ''});
const onFinish = (values) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
};
const handleOnChange = (e) => {
let {value} = e.target;
let data = {...inputValue};
if (value.length && value[0] === ' ') {
data[e.target.name] = '';
setValue(data);
return;
}
data[e.target.name] = value;
setValue(data);
}
return (
<Form
name="basic"
labelCol={{
span: 8,
}}
wrapperCol={{
span: 16,
}}
initialValues={{
remember: true,
}}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<>
<Form.Item
label={t("forms.inputs.Name.label")}
rules={[
{
required: true,
message: t("forms.inputs.Name.rules.required"),
},
{
min: 3,
message: t("forms.inputs.Name.rules.minLength"),
},
]}>
<Input name="input1" value={inputValue.input1} onChange={handleOnChange} />
</Form.Item>
<Form.Item
label={t("forms.inputs.job.label")}
rules={[
{
required: true,
message: t("forms.inputs.job.rules.required"),
},
]}>
<Input name="input2" value={inputValue.input2} onChange={handleOnChange} />
</Form.Item>
<Form.Item
label={t("forms.inputs.Company.label")}
rules={[
{
required: true,
message: t("forms.inputs.Company.rules.required"),
},
]}>
<Input name="input3" value={inputValue.input3} onChange={handleOnChange} placeholder={t("forms.inputs.currentCompany.placeholder")} />
</Form.Item>
</>
</Form>
);
};
export default App;

React - Close Modal on Form Submit

New to React. I am trying to find out how I can close a Modal after the Form Submit event has been triggered.
export default class UserAdmin extends Component {
constructor(props) {
super(props);
this.state = {
show_user_modal : false
}
}
// Handle User Modal
handleUserModalOpen = () => {
this.setState({ show_user_modal: true});
}
handleUserModalClose = () => {
this.setState({ show_user_modal: false});
}
render() {
const { show_user_modal } = this.state;
return (
<Content>
<div className="site-layout-background">
<div className="contentBody">
<Button type="primary"onClick={this.handleUserModalOpen}>
Add User
</Button>
{show_user_modal && <AddUserModal handleClose={this.handleUserModalClose}/>}
</div>
</div>
</Content>
)
}
}
This works perfectly to open and close the modal, and the submit is working perfectly inside the addUserModal, however I am unsure how I should close the modal after this has been completed. I have tried to setState() from the parent to the child but it doesn't want to even then show the modal. Any help appreciated!
**Adding addUserModal function:
function AddUserModal({handleClose}){
const [addUserForm] = Form.useForm();
/** POST User */
const postUser = (values) => {
axios.post('http://localhost:5000/portal/add-user', values)
.then(res => {
if (res.status === 200) {
console.log(res.data);
}
})
};
return(
<Modal title="Add User" okText="Confirm" visible={true} onCancel={handleClose}
onOk={() => {
addUserForm
.validateFields()
.then((values) => {
postUser(values);
addUserForm.resetFields();
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form
form={addUserForm}
name="addUserForm"
labelCol={{span: 5,}}
wrapperCol={{span: 16,}}
initialValues={{remember: false,}}
>
<Form.Item label="Username" name="username"
rules={[
{
required: true,
message: 'Please input a username!',
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Email" name="email"
rules={[
{
required: true,
message: 'Please input an email address',
},
]}
><Input />
</Form.Item>
<Form.Item label="Password" name="password"
rules={[
{
required: true,
message: 'Please input a password',
},
]}
><Input.Password />
</Form.Item>
</Form>
</Modal>
);
}
export default AddUserModal;
Your modal has visible property always set to true. Pass show_user_modal variable to the child and use it in the modal visiblestate. The {show_user_modal && <AddUserModal... is unnecessary
handleClose is calling handleUserModalClose. I think the issue is something else. So you can try calling handleClose in .then of your API call and pass the visible prop as well
export default class UserAdmin extends Component {
constructor(props) {
super(props);
this.state = {
show_user_modal : false
}
}
// Handle User Modal
handleUserModalOpen = () => {
this.setState({ show_user_modal: true});
}
handleUserModalClose = () => {
this.setState({ show_user_modal: false});
}
render() {
const { show_user_modal } = this.state;
return (
<Content>
<div className="site-layout-background">
<div className="contentBody">
<Button type="primary"onClick={this.handleUserModalOpen}>
Add User
</Button>
{show_user_modal && <AddUserModal visible={show_user_modal} handleClose={this.handleUserModalClose}/>}
</div>
</div>
</Content>
)
}
}
and use it in AddUserModal
function AddUserModal({visible, handleClose}){
const [addUserForm] = Form.useForm();
/** POST User */
const postUser = (values) => {
axios.post('http://localhost:5000/portal/add-user', values)
.then(res => {
if (res.status === 200) {
console.log(res.data);
handleClose();
}
})
};
return(
<Modal title="Add User" okText="Confirm" visible={visible} onCancel={handleClose}
onOk={() => {
addUserForm
.validateFields()
.then((values) => {
postUser(values);
addUserForm.resetFields();
})
.catch((info) => {
console.log('Validate Failed:', info);
});
}}
>
<Form
form={addUserForm}
name="addUserForm"
labelCol={{span: 5,}}
wrapperCol={{span: 16,}}
initialValues={{remember: false,}}
>
<Form.Item label="Username" name="username"
rules={[
{
required: true,
message: 'Please input a username!',
},
]}
>
<Input />
</Form.Item>
<Form.Item label="Email" name="email"
rules={[
{
required: true,
message: 'Please input an email address',
},
]}
><Input />
</Form.Item>
<Form.Item label="Password" name="password"
rules={[
{
required: true,
message: 'Please input a password',
},
]}
><Input.Password />
</Form.Item>
</Form>
</Modal>
);
}
export default AddUserModal;

Antd form doesn't identify input values

I have created my react form with antd. I have added antd validation for the form. But my form doesn't know whether I have filled the form or not. Whenever I filled the form and submitted it, it doesn't call onFinish method. Instead it fails and calls onFinishFailed method and gives me validation error messages.
I have created it in correct way according to my knowledge. But there is something missing I think. Here's my code.
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const history = useHistory();
const [form] = Form.useForm();
const layout = {
labelCol: { span: 4 },
wrapperCol: { span: 8 },
};
const onChangeName = (e) => {
setName(e.target.value);
console.log(name);
}
const onAddCategory = (values) => {
let req = {
"name": values.name,
"description": values.description
}
postCategory(req).then((response) => {
if (response.status === 201) {
message.success('Category created successfully');
history.push('/categorylist');
}
}).catch((error) => {
console.log(error);
message.error('Oops, error occured while adding category. Please try again');
});
}
const onFinishFailed = (errorInfo) => {
console.log('Failed:', errorInfo);
console.log('State:', name, description);
};
return (
<React.Fragment>
<Form
form={form}
name="control-hooks"
onFinish={onAddCategory}
onFinishFailed={onFinishFailed}
{...layout}
size="large"
>
<Form.Item
name="name"
rules={[
{
required: true,
message: 'You can’t keep this as empty'
}, {
max: 100,
message: 'The category name is too lengthy.',
}
]}
>
<label>Category name</label>
<Input
placeholder="Category name"
className="form-control"
value={name}
onChange={onChangeName}
/>
</Form.Item>
<Form.Item
name="description"
rules={[
{
required: true,
message: 'You can’t keep this as empty'
}, {
max: 250,
message: 'The description is too lengthy',
}
]}
>
<label>Description</label>
<Input.TextArea
placeholder="Description"
className="form-control"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</Form.Item>
<Form.Item shouldUpdate={true}>
<Button
type="primary"
htmlType="submit"
className="btn btn-primary"
>
Add category
</Button>
</Form.Item>
</Form>
</React.Fragment>
)
In this form I have managed state using hooks. In onFinishFailed method I have logged my input values with state and they have values. But form doesn't identify it.
How do I resolve this. Please help.
I found the issue. Here I had added label inside form item. It was the reason for the unexpected behavior. Once I took the label outside the form item problem was solved.
<label>Category name</label>
<Form.Item
name="name"
rules={[
{
required: true,
message: 'You can’t keep this as empty'
}, {
max: 100,
message: 'The category name is too lengthy.',
}
]}
>
<Input
placeholder="Category name"
className="form-control"
value={name}
onChange={onChangeName}
/>
</Form.Item>

How to get the value of Upload from form.item in Antd?

Even though the file is uploaded antd Form.item rules throws error. Which mean it's not getting the value from Upload to the form.item how should i solve this error. Note im using antd latest version 4.x in which getFieldDecorator has been deprecated. And i also want to upload file manually.
import React, { useState, useEffect } from 'react';
import { Form, Input, Button, Alert, Upload } from 'antd';
import { UserOutlined, LockOutlined, UploadOutlined } from '#ant-design/icons';
import styles from './test1.module.css';
const Test1 = () => {
const [form] = Form.useForm();
const [state, setState] = useState({
fileList: [
{
thumbUrl:
'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
},
],
});
const handleChange = (info) => {
console.log(info);
let fileList = [...info.fileList];
// 1. Limit the number of uploaded files
// Only to show two recent uploaded files, and old ones will be replaced by the new
fileList = fileList.slice(-1);
setState({ fileList: fileList });
};
const onFinish = (values) => {
console.log(form.getFieldsValue());
};
return (
<div className={styles.login_page}>
<Form
name='normal_login'
className={styles.form}
form={form}
onFinish={onFinish}
>
<Form.Item
name='email'
rules={[
{
required: true,
message: 'Please input your Email!',
},
]}
>
<Input
prefix={<UserOutlined className='site-form-item-icon' />}
placeholder='Email'
/>
</Form.Item>
<Form.Item
name='password'
rules={[
{
required: true,
message: 'Please input your Password!',
},
]}
>
<Input
prefix={<LockOutlined className='site-form-item-icon' />}
type='password'
placeholder='Password'
/>
</Form.Item>
<Form.Item
name='image'
rules={[
{
required: true,
message: 'input Image',
},
]}
>
<Upload
beforeUpload={(file) => {
// console.log(file);
return false;
}}
onChange={handleChange}
multiple={false}
listType='picture'
defaultFileList={state.fileList}
>
<Button icon={<UploadOutlined />}>Click to Upload</Button>
</Upload>
</Form.Item>
<Form.Item>
<Button type='primary' htmlType='submit'>
Log in
</Button>
</Form.Item>
</Form>
</div>
);
};
export default Test1;
Is there any way i can bind the onChange event listener of Upload with form.Item
Use prop getValueFromEvent in Form.Item and pass the function returning a file object.
const getFile = (e) => {
console.log('Upload event:', e);
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
};
and Form.Item for image will be like this
<Form.Item name='image' getValueFromEvent={getFile}>
<Upload >
....
</Upload>
</Form.Item>
and you can get all form values in onFinish
const onFinish = (values) => {
console.log(values)
}
for further refer this
You can use maxCount prop of Upload component. It will limit the upload file to specified value.
reference: Upload Component

Resources