mock function error in react-testing-library - reactjs

Hii Everyone I am new to react testing ,I am trying to do some example for practise , I am getting a Error,Need your help , this is my App Component
const[firstName,setFirstName]=useState("")
const[lastName,setLastName]=useState("")
const [data,setData] = useState({})
const handleFirstName = (e) =>{
setFirstName(e.target.value)
}
const handleLastName = (e) =>{
setLastName(e.target.value)
}
const handleSubmit = (e) =>{
e.preventDefault();
setData({firstName,lastName})
console.log(firstName,lastName)
}
return (
<div className="App">
<form onSubmit={handleSubmit} data-testid="form" >
<div>
<label>FirstName
<input type="text" name="firstName" id="firstName" value={firstName} onChange={handleFirstName}/>
</label>
</div>
{firstName && firstName.length > 10 && <p data-testid="error-msg" >FirstName is not valid</p>}
<div>
<label>lastName
<input type="text" name="lastName" id="lastName" value={lastName} onChange={handleLastName}/>
</label>
</div>
<button type="submit" name="submit" disabled={firstName === ""} >submit</button>
</form>
</div>
);
}
this is my testing logic
const mockFunction = jest.fn();
const {getByText}=render(<App onSubmit={mockFunction}/>)
const firstNameLabel = screen.getByRole("textbox",{'name':'FirstName'})
fireEvent.change(firstNameLabel,{"target":{'value':"dhffssß"}})
const lastNameLabel = screen.getByRole("textbox",{"name":"lastName"})
fireEvent.change(lastNameLabel,{"target":{'value':"dhfffsß"}})
const btn = screen.getByRole('button',{'name':'submit'})
fireEvent.click(btn)
expect(mockFunction).toHaveBeenCalledTimes(1)
})
I am testing simple form but getting this error
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0

Try to call onSubmit function which you pass in props in your handleSubmit handler. In this case you should be able to track if the onSubmit callback was called in your tests.
const App = ({ onSubmit }) => {
const[firstName,setFirstName]=useState("")
const[lastName,setLastName]=useState("")
const [data,setData] = useState({})
const handleFirstName = (e) =>{
setFirstName(e.target.value)
}
const handleLastName = (e) =>{
setLastName(e.target.value)
}
const handleSubmit = (e) =>{
e.preventDefault();
setData({firstName,lastName});
console.log(firstName,lastName);
onSubmit();
}
return (
<div className="App">
<form onSubmit={handleSubmit} data-testid="form" >
<div>
<label>FirstName
<input type="text" name="firstName" id="firstName" value={firstName} onChange={handleFirstName}/>
</label>
</div>
{firstName && firstName.length > 10 && <p data-testid="error-msg" >FirstName is not valid</p>}
<div>
<label>lastName
<input type="text" name="lastName" id="lastName" value={lastName} onChange={handleLastName}/>
</label>
</div>
<button type="submit" name="submit" disabled={firstName === ""} >submit</button>
</form>
</div>
);
}
}

You may need to wait until the submit button is enabled before trying to interact with it, after you changed the firstName input, and wait for the callback call (not sure the second wait is usefull) :
import { render, waitFor } from '#testing-library/react';
const mockFunction = jest.fn();
const {getByText}=render(<App onSubmit={mockFunction}/>)
const firstNameLabel = screen.getByRole("textbox",{'name':'FirstName'})
fireEvent.change(firstNameLabel,{"target":{'value':"dhffssß"}})
const lastNameLabel = screen.getByRole("textbox",{"name":"lastName"})
fireEvent.change(lastNameLabel,{"target":{'value':"dhfffsß"}})
// wait until button is enabled
await waitFor(() => expect(getByText('submit').toBeEnabled());
fireEvent.click(getByText('submit'));
await waitFor(() => expect(mockFunction).toHaveBeenCalled();

Related

Fields tied to the state doesn't clear on successful form submission

I have a contact form, when the form submission is successful it should clear the form field name, email and message which is tied up to the state. The form submission is successful but form fields name, email and message doesn't clear.
For test purpose what I'm doing is passing default values to the state but this values doesn't get filled in form during initial load.
export default function Contact() {
const [name, setName] = useState('John Doe')
const [email, setEmail] = useState('me#example.com')
const [message, setMessage] = useState('Hello, this is test message.')
const onSubmit = (e) => {
e.preventDefault()
const form = {"name": name, "email": email, "message": message}
return fetch('/api/contact', {
method: 'POST',
body: JSON.stringify(form),
headers: {'Content-Type': 'application/json'}
}).then(response => {
if(response.status === 200){
setName('')
setEmail('')
setMessage('')
} else {
}
}).catch(err => err)
}
return (
<section>
<form method='post'>
<div>
<label>Name</label>
<input type="text" name="name" onChange={(e) => {setName(e.target.value)}}/>
</div>
<div>
<label>Email Address</label>
<input type="email" name="email" onChange={(e) => {setEmail(e.target.value)}}/>
</div>
<div>
<label>Message</label>
<textarea minLength={5} maxLength={2000} rows="6" name="message" onChange={(e) => {setMessage(e.target.value)}}></textarea>
</div>
<div>
<button type='submit' onClick={(e) => {onSubmit(e)}}>Send Message</button>
</div>
</form>
</section>
);
}
As User456 said, you need to add value={variable} for every <input />.
Example:
import { useState } from "react";
export default function Contact() {
const [name, setName] = useState("John Doe");
const [email, setEmail] = useState("me#example.com");
const [message, setMessage] = useState("Hello, this is test message.");
const onSubmit = (e: any) => {
e.preventDefault();
setName("");
setEmail("");
setMessage("");
};
return (
<section>
<form method="post">
<div>
<label>Name</label>
<input
type="text"
name="name"
value={name}
onChange={e => {
setName(e.target.value);
}}
/>
</div>
<div>
<label>Email Address</label>
<input
type="email"
name="email"
value={email}
onChange={e => {
setEmail(e.target.value);
}}
/>
</div>
<div>
<label>Message</label>
<textarea
minLength={5}
maxLength={2000}
name="message"
value={message}
onChange={e => {
setMessage(e.target.value);
}}></textarea>
</div>
<div>
<button
type="submit"
onClick={e => {
onSubmit(e);
}}>
Send Message
</button>
</div>
</form>
</section>
);
}

Need little help on React hooks

I try to make a todo list with React and Redux Toolkit. In handleSubmit function to add item on list, my setText somehow not set the value to empty string.
Here are my full code on Stackblitz: https://stackblitz.com/edit/react-ts-bqmjz1?file=components%2FTodoForm.tsx
const TodoForm = (): JSX.Element => {
//Input value
const [text, setText] = useState('');
const dispatch = useDispatch();
//setText not working
const handleSubmit = (e: any) => {
e.preventDefault();
if (text.trim().length === 0) {
return;
}
dispatch(addItem({ title: text }));
setText('');
};
return (
<form className="container-fluid" onSubmit={handleSubmit}>
<div className="input-group">
<input
className="form-control"
placeholder="Enter the value..."
onChange={(e: { target: HTMLInputElement }) =>
setText(e.target.value)
}
/>
{/* Submit button */}
<button type="submit" className="btn btn-primary">
Add
</button>
</div>
</form>
);
};
You're very close! You just missed the value prop on the input:
value={text}
const TodoForm = (): JSX.Element => {
//Input value
const [text, setText] = useState('');
const dispatch = useDispatch();
//setText not working
const handleSubmit = (e: any) => {
e.preventDefault();
if (text.trim().length === 0) {
return;
}
dispatch(addItem({ title: text }));
setText('');
};
return (
<form className="container-fluid" onSubmit={handleSubmit}>
<div className="input-group">
<input
className="form-control"
placeholder="Enter the value..."
value={text}
onChange={(e: { target: HTMLInputElement }) =>
setText(e.target.value)
}
/>
{/* Submit button */}
<button type="submit" className="btn btn-primary">
Add
</button>
</div>
</form>
);
};
If I understood well the input does not become empty when you click on the add button. To do that you just have to add value={text} in your input
<input
className="form-control"
placeholder="Enter the value..."
value={text}
onChange={(e: { target: HTMLInputElement }) =>
setText(e.target.value)
}
/>

How to add email validation to below code?

The below code is using cards to show account creation, deposit and other module.
I need to refractor the code to add email, name, and password field validation, but I am not able to do it.
context.js
const Route = ReactRouterDOM.Route;
const Link = ReactRouterDOM.Link;
const HashRouter = ReactRouterDOM.HashRouter;
const UserContext = React.createContext(null);
function Card(props){
function classes(){
const bg = props.bgcolor ? ' bg-' + props.bgcolor : ' ';
const txt = props.txtcolor ? ' text-' + props.txtcolor: ' text-white';
return 'card mb-3 ' + bg + txt;
}
return (
<div className={classes()} style={{maxWidth: "18rem"}}>
<div className="card-header">{props.header}</div>
<div className="card-body">
{props.title && (<h5 className="card-title">{props.title}</h5>)}
{props.text && (<p className="card-text">{props.text}</p>)}
{props.body}
{props.status && (<div id='createStatus'>{props.status}</div>)}
</div>
</div>
);
}
function CardForm(props) {
const ctx = React.useContext(UserContext);
return (
<>
<div style={{maxWidth: "18rem"}}>
<div className="name-field" style={{display: props.showName}}>
Name<br/>
<input type="input"
id="txtName"
className="form-control"
placeholder="Enter name"
onChange={e => ctx.name=e.currentTarget.value} /><br/>
</div>
<div className="email-field" style={{display: props.showEmail}}>
Email address<br/>
<input type="input"
id="txtEmail"
className="form-control"
placeholder="Enter email"
onChange={e => ctx.email=e.currentTarget.value}/><br/>
</div>
<div className="password-field" style={{display: props.showPassword}}>
Password<br/>
<input type="password"
id="txtPassword"
className="form-control"
placeholder="Enter password"
onChange={e => ctx.password=e.currentTarget.value}/><br/>
</div>
<div className="amount-field" style={{display: props.showAmount}}>
Amount<br/>
<input type="number"
className="form-control"
placeholder="Enter amount"
onChange={e => ctx.balance=e.currentTarget.value}/><br/>
</div>
</div>
</>
)
}
creatAccount.js
function CreateAccount(props){
const [show, setShow] = React.useState(true);
const [status, setStatus] = React.useState('');
const ctx = React.useContext(UserContext);
function addUser() {
ctx.balance = '0';
fetch(`/account/find/${ctx.email}`)
.then(response => response.json())
.then(data => {
console.log(data);
if (data.length===0) ctx.user = true;
})
.then(() => {
if (ctx.user===true) {
const url = `/account/create/${ctx.name}/${ctx.email}/${ctx.password}/${ctx.balance}`;
(async () => {
var res = await fetch(url);
var data = await res.json();
console.log(data);
})();
ctx.user='';
setShow(false);
} else {
ctx.user='';
setStatus('User already exists with that email');
setTimeout(() => setStatus(''),3000);
}})
}
return (
<Card
bgcolor="primary"
header="Create Account"
text=""
status={status}
body={
<>
{show ?
<>
<CardForm setShow={setShow} showAmount="none"/>
{<button type="submit" className="btn btn-light" onClick={addUser}>Create Account</button>}
</>
:
<Success setShow={setShow}/>}
</>
}
/>
);
}
function Success(props) {
return (
<>
<h5>Success!</h5><br/>
<button type="submit"
className="btn btn-light"
onClick={() => props.setShow(true)}>Add another account</button>
</>
)
}
I have tried multiple solutions from online content, but they do not solve the problem.
Lets simplify this
Using NPM package email-validator
Install via NPM
npm install email-validator
import * as EmailValidator from 'email-validator';
Bind your local state to your input field e.g
const [email, setEmail] = useState("");
const [valid, setIsValid] = useState(false);
<input
type="text"
name="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
Create a function to check for email validation like below
const validateEmail = (email) => {
setIsValid(EmailValidator.validate(email));
};
Now there comes two cases. One is to show the validation on change the input field or check on submitting the whole form. To give the validation message while inputting email, you have to use the useEffect hook just like below
useEffect(() => {
if (email) {
validateEmail(email);
}
}, [email, validateEmail]);
You can use the valid state for validation message while user is entering the email something like below
<span>{valid ? 'Email format valid' : 'Invalid email format'} <span>
Second Case
just call the function we created above for email validation and check the valid state.

the state inside hooks are not updated for first time on form submit in react

I was trying to implement contactUS form in react using hooks.Contact us form is placed inside hooks.When I first submit the form the state in hooks are not updated ,when I click 2nd time states are set .and I am returning state to class component there api call are made.
//contactushook.js
import React, { useState } from 'react';
const ContactUshook = ({ parentCallBack }) => {
const [data, setData] = useState([]);
const handleSubmit = (event) => {
event.preventDefault();
setData({ name: document.getElementById('name').value, email: document.getElementById('email').value, message: document.getElementById('message').value });
console.log(data);
parentCallBack(data);
}
return <React.Fragment>
<div className="form-holder">
<form onSubmit={handleSubmit}>
<div>
<input id="name" type="text" placeholder="enter the name"></input>
</div>
<div>
<input id="email" type="email" placeholder="enter the email"></input>
</div>
<div>
<textarea id="message" placeholder="Type message here"></textarea>
</div>
<button type="submit" >Submit</button>
</form>
</div>
</React.Fragment >
}
export default ContactUshook;
//contactus.js
import React, { Component } from 'react';
import ContactUshook from './hooks/contactushook';
import '../contactUs/contactus.css';
class ContactComponent extends Component {
onSubmit = (data) => {
console.log('in onsubmit');
console.log(data);
}
render() {
return (
<div>
<h4>hook</h4>
<ContactUshook parentCallBack={this.onSubmit}></ContactUshook>
</div>
);
}
}
export default ContactComponent;
Stop using document queries and start using state instead!
Your ContactUshook component should look like this:
const ContactUshook = ({ parentCallBack }) => {
const [data, setData] = useState({ name: '', email: '', message: '' });
const handleSubmit = () => {
event.preventDefault();
parentCallBack(data);
}
const handleChange = (event, field) => {
const newData = { ...data };
newData[field] = event.target.value;
setData(newData);
}
return (
<div className="form-holder">
<form onSubmit={handleSubmit}>
<div>
<input
id="name"
type="text"
value={data.name}
placeholder="enter the name"
onChange={(e) => handleChange(e,'name')} />
</div>
<div>
<input
id="email"
type="email"
value={data.email}
placeholder="enter the email"
onChange={(e) => handleChange(e,'email')} />
</div>
<div>
<textarea
id="message"
value={data.message}
placeholder="Type message here"
onChange={(e) => handleChange(e,'message')} />
</div>
<button type="submit" >Submit</button>
</form>
</div>
);
}

Template doesn't react to the change of variable React

I have a component in which I want to display another component after form submit.
When I'm running function on form submit I'm changing submitted value to true and if I do console.log(submitted) it changes to true but in the template it is still false so Alert component doesn't show up.
I'm trying to learn hooks and maybe the problem with how I'm using them?
My component looks like this
export const SignupForm = () => {
let name:any = handleUserInput('');
let email:any = handleUserInput('');
let password:any = handleUserInput('');
let submitted:any = false;
function registerUser(event: React.FormEvent<HTMLFormElement>): void {
event.preventDefault();
submitted = true;
const registerInfo = Object.assign({}, {
email: email.value,
password: password.value,
name: name.value
});
axios.post('/register', registerInfo)
.then(response => {
errors = response.data.errors
})
.catch(error => console.log(error))
}
function handleUserInput(initialValue: string): object {
const [value, setValue] = useState(initialValue);
function handleChange(event: Event): void {
let element = event.target as HTMLInputElement;
setValue(element.value);
}
return {
value,
onChange: handleChange
}
}
return (
<div>
<div dangerouslySetInnerHTML={{__html: submitted}}></div>
{submitted ? <Alert /> : ''}
<div className="form-holder">
<form action="POST" className="form" onSubmit={(e) => registerUser(e)}>
<label htmlFor="Email">Email</label>
<input type="text" id="email" className="form__input" {...email} required />
<label htmlFor="password">Password</label>
<input type="password" id="password" className="form__input" {...password} required />
<label htmlFor="name">Name</label>
<input type="text" id="name" className="form__input" {...name} required />
<button type="submit" className="form__button">Signup</button>
</form>
</div>
</div>
);
}
submitted local variable is assigned asynchronously. This won't result in component update.
It should be:
...
const [submitted, setSubmitted] = useState(false);
function registerUser(event: React.FormEvent<HTMLFormElement>): void {
event.preventDefault();
setSubmitted(true);
...

Resources