How to do Jest testing for form validation errors? - reactjs

I am trying to test a login form validation errors:
<Form className='form' onSubmit={onSubmit} data-testid='form'>
<FormGroup className='form-group'>
<input
type='email'
placeholder='Email Address'
name='email'
value={email}
onChange={onChange}
required
/>
</FormGroup>
<FormGroup className='form-group'>
<input
type='password'
placeholder='Password'
name='password'
value={password}
onChange={onChange}
minLength='6'
/>
</FormGroup>
<input type='submit' value='Login' />
</Form>
but the problem is i am unable to get the error text in the
component = render()
my test is as follows:
it('validate user inputs, and provides error messages', async () => {
const { getByTestId, getByText, getByPlaceholderText } = component
fireEvent.change(screen.queryByPlaceholderText(/Email Address/i), {
target: {value: ""},
});
fireEvent.change(screen.queryByPlaceholderText(/Password/i), {
target: {value: ""},
});
fireEvent.submit(getByTestId("form"));
expect(getByText("Please fill out this field.")).toBeInTheDocument();
})
I am using react testing library with jest

The text displayed on submit is drawn by the browser, not by DOM, so it cannot be inspected with JavaScript.
You may need to add a JavaScript handler for that to sync the actual text to the DOM.
const [errorText, setErrotText] = React.useState("");
const handleInvalid = () => {
setErrorText("Please fill out this field.");
};
const handleValid = () => {
setErrorText("");
};
return (
<input onInvalid={handleInvalid} onValid={handleValid} />
);
Or, you can check the detailed error type by inspecting the ValidityState by accessing element.validity of the input element.
DOM validation is accessible regardless of the testing framework or forms library you use.
For example:
expect(getByPlaceholder("Password").validity.tooShort).toBe(false);

Related

React How do I extract data from a form when triggering onSubmit event?

I am messing around with Riot's API that allows getting information of a player by the player's name. I am trying to get an API key (I only got a 24 hour key) and the target player's name from users' input.
export function PlayerSearch() {
function handlesubmit(e) {
console.log(e.target);
e.preventDefault();
}
return (
<div className='player'>
<div className='inputfield'>
<form onSubmit={handlesubmit} method='GET' autoComplete="off">
<div>
<label htmlFor="key">Your API key:</label>
<input
placeholder='Your API key'
onFocus={(e)=>{e.target.placeholder=''}}
type="text"
id="key"
name="key" />
</div>
<div>
<label htmlFor="name">Player name:</label>
<input
placeholder='Player name'
onFocus={(e)=>{e.target.placeholder=''}}
type="text"
id="name"
name="name" />
</div>
<div>
<input type='submit' />
</div>
</form>
</div>
</div>
);
}
And I got this in the console:
So how exactly do I extract the two inputs from the form?
In addition, is it possible that I can call another component and pass data as props to handle the submitted data so that the code is cleaner?
You should have a state to store your inputs:
const [formData, setFormData] = useState({ key: "", name: "" });
Then you need a function that gets called onChange of your input fields to update your state:
const onChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
};
And you need to pass that function to your input onChange property:
<input
placeholder="Your API key"
onFocus={(e) => {
e.target.placeholder = "";
}}
type="text"
name="key"
value={formData.key}
onChange={onChange}
/>
Then you can access the state in your handleSubmit:
function handlesubmit(e) {
console.log(formData);
e.preventDefault();
}

How can i put value in input?

Why can't I put a value in the input? The problem is: I need to put a 'name' in <Form.Item> to apply the rules. Without that, the rules will not be able to work, but if you remove the name="userName" from <Form.Item the value appears at the input.
How can i solve that?
<Form autoComplete="off" layout="vertical" onFinish={handleFinish}>
<Form.Item name="userName" rules={getTextRule('name')}>
<Input value={fields?.firstName} name="firstName" onChange={(e) => {
handleInputChange(e)
}} />
</Form.Item>
</Form.Item>
simple we can code like
const [text,setText] = useState('')
return(
<input type='text' value={text} onChange={e=>setText(e.target.value)}/>
)
If you use the form you can let Ant Form manage the state by removing the value & the onChange props on the <Input />.
Else if you manage the state by yourself make sure to get the value from the e.target.value.
ex:
const [fields, setFields] = useState({})
const handleOnChange = (e) => {
setFields((state) => {
...state,
[e.target.name]: e.target.value,
})
}
return (
<Input
name="firstName"
value={fields.firstName}
onChange={handleOnChange}
/>
)

React Controlled Input Validation

I noticed a weird behavior where controlled components are not respecting the minLength and allowing a form to submit even though that condition is not met.
Validation works fine for uncontrolled components.
What is the reason for this behavior?
export default function App() {
const [uncontrolledSubmitCount, setUncontrolledSubmitCount] = useState(0);
const [controlledSubmitCount, setControlledSubmitCount] = useState(0);
const [content, setContent] = useState("");
return (
<div className="App">
<h2>Uncontrolled (minLength: 10)</h2>
<form
onSubmit={(e) => {
console.log(e);
e.preventDefault();
setUncontrolledSubmitCount(uncontrolledSubmitCount + 1);
}}
>
<textarea
type="text"
minLength={10}
maxLength={30}
required
/>
<br />
<button type="submit">Submit</button>
<br />
<span>Submit Count: {uncontrolledSubmitCount}</span>
</form>
<h2>Controlled (minLength: 10)</h2>
<form
onSubmit={(e) => {
console.log(e);
e.preventDefault();
setControlledSubmitCount(controlledSubmitCount + 1);
}}
>
<textarea
type="text"
minLength={10}
maxLength={30}
required
value={content}
onChange={(e) => setContent(e.currentTarget.value)}
/>
<br />
<button type="submit">Submit</button>
</form>
<span>Submit Count: {controlledSubmitCount}</span>
</div>
);
}
Here's a reproduction link:
https://codesandbox.io/s/kind-violet-zwcct?file=/src/App.js
This behavior happens only on Chrome as far as I tested it.
Based on React official documentation adding value to textarea it overrides HTML validations and it makes it "controlled" input and it ignores it overall. But only in Chrome.
The documentation also says there is an option to use defaultValue. I tried it at it works.
<textarea
minLength={10}
maxLength={30}
required
defaultValue=""
onChange={(e) => setContent(e.currentTarget.value)}
/>
Working example from your codesanbox
With this solution you will solve Chrome problem but still keep content state with value that you enter inside textarea
NOTE:
If you try to use children value, React will throw an error that it is not supported for controlled textarea

Getting uncontrolled input error even though everything is controlled

I need your help, I'm trying to use a form from react-strap and for some reason I get this error:
Warning: A component is changing a controlled input of type password to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component
this is my code.
everything is controlled but I still get the error, can anyone spot my mistake?
const LoginForm = () => {
const [formData, setFormData] = useState({
email: " ",
password: " "
});
const onChange = e => {
setFormData({
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
console.log("email password are", formData.email, formData.password);
loginUser(formData.email, formData.password);
};
return (
<Form onSubmit={onSubmit}>
<FormGroup>
<h1>Login</h1>
</FormGroup>
<FormGroup>
<Label for="Email">Email</Label>
<Input
type="email"
name="email"
id="Email"
placeholder="example#mail.com"
onChange={onChange}
value={formData.email}
/>
</FormGroup>
<FormGroup>
<Label for="Password">Password</Label>
<Input
type="password"
name="password"
id="Password"
placeholder="password"
onChange={onChange}
value={formData.password}
/>
</FormGroup>
<Button>Submit</Button>
</Form>
);
};
Ok I found the error, I didn't spread the last answer, so by inputting password I overwritten my email, the proper on change should be this:
const onChange = e => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};

How to get an input value using "refs" in react-bootstrap form?

I'm trying to create a form that appears in modal. So when user input a value, that value is stored in local storage. Here's a picture that help's you to understand what I mean:
Here is the code of the form:
function FieldGroup({id, label, help, ...props}) {
return (
<ReactBootstrap.FormGroup controlId={id}>
<ReactBootstrap.ControlLabel>{label}</ReactBootstrap.ControlLabel>
<ReactBootstrap.FormControl {...props} />
{help && <ReactBootstrap.HelpBlock>{help}</ReactBootstrap.HelpBlock>}
</ReactBootstrap.FormGroup>
);
}
const formInstance = (
<form>
<FieldGroup
id="formControlsText"
type="text"
label="Text"
placeholder="Recipe Name"
inputRef={ref => { this.input = ref; }}
/>
<ReactBootstrap.FormGroup controlId="formControlsTextarea">
<ReactBootstrap.ControlLabel>Ingredients</ReactBootstrap.ControlLabel>
<ReactBootstrap.FormControl componentClass="textarea" placeholder="Enter Ingredients" />
</ReactBootstrap.FormGroup>
</form>
);
As I've read in bootstrap React tutorial, I should add
<FormControl inputRef={ref => { this.input = ref; }} /> to the FormControl props. But after adding it I get an error when modal form is invoked:
`
I just made this issue. My code:
<FormControl
componentClass="input"
placeholder="Enter recipe title"
inputRef={(ref) => {this.input = ref}}
defaultValue={title}/>
</FormGroup>
And then you can get the value from <FormControl> in some handler like this:
console.log(this.input.value);
Details can be found in my repo: https://github.com/kerf007/recipebook
I have same problem with you, and this is my solution
const FieldGroup = ({id, label, help, inputRef, ...props}) =>
<FormGroup controlId={id}>
<ControlLabel>{label}</ControlLabel>
<FormControl {...props} inputRef={inputRef}/>
{help && <HelpBlock>{help}</HelpBlock>}
</FormGroup>
and my form
<form>
<FieldGroup
id="bookName"
type="text"
label="Name"
placeholder="Enter name..."
inputRef = {(input) => this.inputName = input }
/>
<FieldGroup
id="bookAuthor"
label="Author"
type="text"
placeholder="author 's name..."
inputRef={(input) => this.inputAuthor = input}
/>
</form>
then you can get book 's name and author 's name value by:
this.inputName.value and this.inputAuthor.value
This issue (or more like a change in the way it works) is related to React-Bootstrap. The way you're doing it won't work anymore.
The <FormControl> component directly renders the or other specified component. If you need to access the value of an uncontrolled <FormControl>, attach a ref to it as you would with an uncontrolled input, then call ReactDOM.findDOMNode(ref) to get the DOM node. You can then interact with that node as you would with any other uncontrolled input.
Here's an example:
var React = require('react');
var ReactDOM = require('react-dom');
var FormControl = require('react-bootstrap').FormControl;
React.createClass({
render: function() {
return (<FormControl ref="formControl" />);
},
getFormControlNode: function() {
// Get the underlying <input> DOM element
return ReactDOM.findDOMNode(this.refs.formControl);
}
});
As soon as you get the DOM element, you will be able to retrieve the value: this.getFormControlNode().value or do anything else that you want.
PS: Here's a related github issue on this topic.
This worked for me, using https://reactjs.org/docs/refs-and-the-dom.html
constructor(props) {
super(props);
this.email = React.createRef();
}
submit() {
var email = this.email.current.value;
console.log(email);
}
render() {
return (
<Form>
<Form.Control type="email" placeholder="Your email" ref={this.email} />
<Button variant="primary" onClick={()=>this.submit()}>Send</Button>
</Form>
);
}
I think what it suggests to use is the ref callback attribute, so just change inputRef to ref.
FYI: https://facebook.github.io/react/docs/refs-and-the-dom.html
Hello this solution worked for me!
<Form
noValidate
validated={validated}
onSubmit={(e) => this.handleSubmit(e)}
style={{ width: '100%' }}
>
<Form.Group controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" inputRef={(ref) => { this.email = ref }} required />
<Form.Text className="text-muted"> Well never share your email with anyone else.
</Form.Text>
</Form.Group>
</Form>
handleSubmit(event) {
console.log(event.target.elements.formBasicPassword.value)
}
I think I found how to get the ref from React-Bootstrap <Form/>.
import React, {createRef} from 'react'
interface definedProps {}
interface definedState {
myRef: Object //I didn't found the more accurate type
}
export default class SomeClass extends React.Component<definedProps,definedState> {
state = {
myRef: React.createRef<Form<any>>() //That's it!
}
const handleClose = () => {
console.log('this.state.myRef: ', this.state.myRef); //Here we can take data from Form
debugger; //todo: remove
}
render() {
return (
<div>
<Form ref={this.state.myRef}> { /*Here we're connecting the form's ref to State*/}
<Form.Group controlId='formName'>
<Form.Control
type='text'
placeholder='Enter Name'
/>
</Form.Group>
...
<Button
variant='primary'
onClick={handleClose}
>
Save Changes
</Button>
</Form>
</div>
)
}
}

Resources