Jest/Enzyme | Test a mask/unmask Password function - javascript

I have a form, in my react component, that has 2 fields that call a function, that on click show button masks and unmasks the specific fields. Basically, I need some help on how to test the function itself.
The Function:
togglePasswordMask = e => {
const { type } = this.state;
e.preventDefault();
this.setState(prevState => ({
passwordIsMasked: !prevState.passwordIsMasked,
type: type === 'password' ? 'input' : 'password'
}));
};
I call that function, in my render method like this:
<div className="input-group mb-3">
<Field
type={type}
className={classNames('form-control', {
'is-invalid': errors.password && touched.password
})}
placeholder="Password (Required)"
name="password"
/>
<div className="input-group-append">
<span className="input-group-text">
<div
className={type === 'password' ?
'fa fa-eye fa-lg' : 'fa fa-eye-slash fa-lg'}
onClick={this.togglePasswordMask}
/>
</span>
</div>
</div>
It also has an INITIAL_STATE:
state = {
type: 'password',
groups: []
};
Can you help me, write the test cases for this, using Jest and Enzyme. I tried the following, but they don't seem to work:
describe('UserCreateForm TogglePassword', () => {
it('Should unmask password and confirmPassword on click', () => {
const maskElement = wrapper.find('.fa fa-eye fa-lg');
const maskFn = maskElement.simulate('click');
expect(maskFn().state()).toEqual('input');
});
});
I get this error: TypeError: Cannot read property 'preventDefault' of undefined.
I iterated a little bit after I found another answer, and my test now looks something like this:
it('Should unmask password and confirmPassword on click', () => {
console.log(wrapper.debug());
const maskElement = wrapper.find('.fa-eye');
const maskFn = maskElement.simulate('click', {
preventDefault: () => {}
});
expect(maskFn().state()).toEqual('input');
});
And now, I get another error: maskFn, is not a function.

your immediate problem is because maskElement.simulate returns Self according to the Enzyme docs, and that's an object not a function. Get rid of maskFn completely, call maskElement.simulate and ignore its return value, and just run your expect against maskElement.state().
(Also, instead of testing against your component's internal state - which some feel to be an anti-pattern because you're testing component implementation and not component behavior - consider expecting the component to render a <Field type="password" /> vs a <Field type="text" />)

Related

How to DRY up VueJS + Vuelidate server side validation error code?

I'm working on a VueJS project (using the Quasar framework). When building a form component, for example to edit user settings, I have frontend validation, to check required fields etc. using vuelidate, and I want to also show backend validation errors. Currently my setup (for a sample form) is as follows:
script:
export default {
name: 'UserEditForm',
mixins: [formServerValidation],
data: function () {
return {
form: {
...
name: this.$store.state.currentUser.attributes.name,
currentPassword: 'winnerwinner',
serverErrors: {
// Todo: Autogenerate all server errors
}
}
}
},
methods: {
onSubmit () {
const name = this.form.name
const currentPassword = this.form.currentPassword
this.clearServerErrors()
this.$store.dispatch('updateUser', { name, currentPassword, }).then((result) => {
console.log("Server update success")
}).catch(err => {
console.log("Server update error")
const mappedErrors = this.mapServerErrors(err.response.data.errors)
merge(this.form.serverErrors, mappedErrors)
this.$refs.form.validate()
})
},
serverError: function (fieldName) {
return (value, vm) => {
return !(
Object.prototype.hasOwnProperty.call(vm, 'serverErrors') &&
Object.prototype.hasOwnProperty.call(vm.serverErrors, fieldName)
)
}
}
},
validation: {
form: {
...
name: {
required,
serverError: this.serverError('name')
},
currentPassword: {
required,
serverError: this.serverError('currentPassword')
},
...
}
}
}
template:
<template>
<q-form #submit="onSubmit" ref="form">
...
<q-input
field-name="name"
type="text"
v-model="form.name"
label="Name"
lazy-rules
#input="clearServerError('name', $event)"
:rules="[
val => $v.form.name.required || 'Enter name',
val => $v.form.name.serverError || form.serverErrors.name,
]" />
<q-input
field-name="currentPassword"
type="password"
v-model="form.currentPassword"
label="Current password*"
lazy-rules
#input="clearServerError('currentPassword', $event)"
:rules="[
val => $v.form.currentPassword.required || 'Confirm password',
val => $v.form.currentPassword.serverError || form.serverErrors.currentPassword
]"
/>
...
<div>
<q-btn label="Save" type="submit" color="primary" />
</div>
</q-form>
</template>
This all works perfectly fine, however it seems to be very WET (not DRY). I have to define the following things for each field manually:
serverError: this.serverError('name') in validation
A rule val => $v.form.name.serverError || form.serverErrors.name in the template
#input="clearServerError('name', $event)" in the template
But I know I want to do this for every input in my form component, so it feels very repetitive to do this manually for every input component. Is there a correct "Vue-ish" way to DRY this up?
One thing I tried is to find all input fields by traversing all descendants of my form component using $children. Then:
I tried to tackle 1., by defining these server errors dynamically by looping over all input components.
2. is harder to tackle since you can not directly update the property :rules since Vue will override it on a new render of the parent component. (This also throws a warning, saying you should not directly update properties, but use v-model etc.)
I tried to tackle 3. by defining input event listeners dynamically from my Form component using the $on method on the actual input components.
The DRY-up for 1 and 3 seem to work, but is it the best way of doing it? And what is a correct approach of drying up 2?

Why onSubmit is not working with React ES6 syntax?

I am a newbie in React world. Actually, I come across a situation. When I use modern syntax, I am not getting things done. But, with bind.this method everything is working smoothly. Below is my code. Can you please find out mistakes. It giver error like "cant find state of undefined". Thank you.
import React, { Component } from 'react';
class Login extends Component {
state = {
email: '',
password: '',
}
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
signin(e) {
e.preventDefault();
const { email, password } = this.state;
if (email === 'xyz#gmail.com' && password === '123456') {
console.log('logged in')
}
}
render() {
return(
<div className='login'>
<div className='login-div'>
<form
onSubmit={this.signin}>
<fieldset>
<h2 className='heading'>Sign into your account</h2>
<label htmlFor="email">
<input
type="email"
name="email"
placeholder="email"
value={this.state.email}
onChange={this.handleChange}
/>
</label>
<label htmlFor="password">
<input
type="password"
name="password"
placeholder="password"
value={this.state.password}
onChange={this.handleChange}
/>
</label>
<button type="submit">Sign In!</button>
</fieldset>
</form>
</div>
</div>
)
}
}
export default Login;
can you change your function to this
signin = (e) => {
e.preventDefault();
const { email, password } = this.state;
if (email === 'xyz#gmail.com' && password === '123456') {
console.log('logged in')
}
}
Just to explain what's going on :
It's because inside signin function, this refers to the context of the execution (the event handler) but not to your React component.
You can specify which this the signin function will be bound to using bind method.
Add this line at the begining of the class :
this.signin = this.signin.bind(this);
Note : You can avoid binding all your functions by writing them using arrow function syntax.

Why do my error messages not update when a field is filled out? (React)

This seems like a simple thing to do but after much fiddling, I can't figure out what's wrong. I'm a noob to React so forgive me.
I have a form for logging in. Like most login forms, it's asking for a username and password. Then it has a button to submit. My understanding is that a component will re-render if the state is changed. I have onchange events on each input field that updates the state. So if the field is empty, I press the button to submit, I would expect that the error will show. If I fill in a field, I would expect the error message to go away because the state changed. Am I misunderstanding?
Here is my event handler:
handleLogin(event) {
event.preventDefault();
if (this.state.username == '') {
this.setState({usernameError: "Need to enter a username"})
return;
}
if (this.state.password == '') {
this.setState({passwordError: "Need to enter a password"})
return;
}
}
And the form:
render() {
return(
<form className="login-form">
<h1 className="login-form__header"><FontAwesomeIcon icon="key" className="registration-form__icon"/><i className="fal fa-route-highway"></i>Log Into Your Account</h1>
<input type="text" name="username" placeholder="Username" className="login-form__input" onChange={(event,newValue) => this.setState({username:newValue})}/>
{this.state.usernameError &&
<p class="login-form__error"><FontAwesomeIcon icon="times-circle"/> {this.state.usernameError}</p>
}
<input type="password" name="password" placeholder="Password" className="login-form__input" onChange={(event,newValue) => this.setState({password:newValue})}/>
{this.state.passwordError &&
<p class="login-form__error"><FontAwesomeIcon icon="times-circle"/> {this.state.passwordError}</p>
}
<button className="login-form__button" onClick={this.handleLogin}>Log Into Your Account</button>
</form>
);
}
Right, but you never configured any logic to clear the errors if the field is not empty. Currently, there isnt any logic set-up to turn usernameError and passwordError back to an empty-string or null value.
You might be under the impression that the state is cleared when you re-render but that is not the case. The state-object prior to the re-render still persists, only changing the key-value pair(s) you last updated within this.setState().
Try this:
handleLogin(event) {
event.preventDefault();
const { username, password } = this.state
this.setState({
...this.state,
usernameError: username.length > 0 ? "" : "Need to enter a username",
passwordError: password.length > 0 ? "" : "Need to enter a password"
})
}
Here's a working sandbox with a sligtly modified version of your code. (I removed the FontAwesomeIcons). https://codesandbox.io/s/cool-meninsky-y9r4y
First of all, onChange={(event,newValue) => this.setState({username:newValue})} this is not a good approach, as it will create a new function on every render. So I would suggest to create a dedicated function like -
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
// Make sure the name is as per with your html username password and update the state accordingly
this.setState({
[name]: value
});
}
Do remember to reset the usernameError and passwordError properties of your state on each related onChange event. Otherwise the error message will persist on HTML.
Hi you can try the below code
function validate(email, username, password) {
// true means invalid, so our conditions got reversed
return {
username: username.length === 0, //true if username is empty
password: password.length === 0, //true if password is empty
};
}
class LoginForm extends React.Component {
constructor() {
super();
this.state = {
username: '',
password: '',
touched: {
username: false,
password: false,
},
};
}
handleUsernameChange = (evt) => {
this.setState({ username: evt.target.value });
}
handlePasswordChange = (evt) => {
this.setState({ password: evt.target.value });
}
handleBlur = (field) => (evt) => {
this.setState({
touched: { ...this.state.touched, [field]: true },
});
}
handleSubmit = (evt) => {
if (!this.canBeSubmitted()) {
evt.preventDefault();
return;
}
const { username, password } = this.state;
alert(`Logined: ${email} password: ${password}`);
}
canBeSubmitted() {
const errors = validate(this.state.username, this.state.password);
const isDisabled = Object.keys(errors).some(x => errors[x]);
return !isDisabled;
}
render() {
const errors = validate(this.state.username, this.state.password);
const isDisabled = Object.keys(errors).some(x => errors[x]);
const shouldMarkError = (field) => {
const hasError = errors[field];
const shouldShow = this.state.touched[field];
return hasError ? shouldShow : false;
};
return (
<form className="login-form" onSubmit={this.handleSubmit}>
<h1 className="login-form__header">
<FontAwesomeIcon icon="key" className="registration-form__icon"/>
<i className="fal fa-route-highway"></i>Log Into Your Account
</h1>
<input
className={shouldMarkError('username') ? "error" : ""}
type="text" name="username" placeholder="Username"
value={this.state.username}
onBlur={this.handleBlur('username')
onChange={this.handleUsernameChange} />
<p className={shouldMarkError('username') ? "error" : "hidden"}>
<FontAwesomeIcon icon="times-circle"/> Need to enter a username
</p>
<input type="password" name="password" placeholder="Password"
className={shouldMarkError('password') ? "error" : ""}
value={this.state.password}
onChange={this.handlePasswordChange}
onBlur={this.handleBlur('password')} />
<p className={shouldMarkError('password') ? "error" : "hidden"}>
<FontAwesomeIcon icon="times-circle"/> Need to enter a password
</p>
<button disabled={isDisabled} className="login-form__button"
onClick={this.handleLogin}>Log Into Your Account</button>
</form>
)
}
}
Hope the code could helpful for you.
You can also refer the Demo Validation for Signup
As mentioned by #Satyaki onChange={(event,newValue) => this.setState({username:newValue})} is not a good option you should define separate method for this, alternatively you can change your code to as follows:
onChange={(event,newValue) => this.setState({username:newValue})}, () => {})
This will make sure to complete the lifecycle of setState.

Validate form when setState doesn't run immediately

I've creating form in React and I have a problem with validation.
When we click (example and demo below) Submit Form without filling the input fields, we should get an error. This works fine. However, at the same time we also get the success message, which means this condition (!this.state.firstNameError && !this.state.lastNameError) returns true, but it shouldn't. It works fine only on second click.
This happens because setState is not updating immediately I guess.
I'm looking for a good solution to this issue.
I can do validate and submit in one method, and set success to true on callback after error's setState. But I don't want to hold so much code in one method (my really form is much bigger). What would be a better solution?
A simple code example:
class App extends Component {
state = {
firstName: "",
firstNameError: false,
lastName: "",
lastNameError: false,
success: false
};
inputHandler = e => this.setState({ [e.target.name]: e.target.value });
validateForm = () => {
this.setState({
firstNameError: false,
lastNameError: false,
success: false
});
if (!this.state.firstName) this.setState({ firstNameError: true });
if (!this.state.lastName) this.setState({ lastNameError: true });
};
formSubmit = e => {
e.preventDefault();
this.validateForm();
if (!this.state.firstNameError && !this.state.lastNameError) {
this.setState({ success: true });
}
};
render() {
const { firstNameError, lastNameError, success } = this.state;
return (
<div className="container">
<form onSubmit={this.formSubmit}>
<div className="row">
<label>First Name: </label>
<input onChange={this.inputHandler} name="firstName" type="text" />
</div>
{firstNameError && <div className="error">Enter First Name</div>}
<div className="row">
<label>Last Name: </label>
<input onChange={this.inputHandler} name="lastName" type="text" />
</div>
{lastNameError && <div className="error">Enter Last Name</div>}
<div className="row">
<button>Submit Form</button>
</div>
{success && <div className="success">Success</div>}
</form>
</div>
);
}
}
Demo: https://codesandbox.io/s/30p90v6kp
In validateForm, figure out the valid state of each field, then use that to determine whether the form as a whole is valid or not and only call setState once.
validateForm = () => {
const firstNameError = !this.state.firstName;
const lastNameError = !this.state.lastName;
this.setState({
firstNameError,
lastNameError,
success: !lastNameError && !firstNameError,
});
};
formSubmit = e => {
e.preventDefault();
this.validateForm();
};
Note - depending on how complex this is and how your form is actually submitted, you may decide whether or not this needs to be two different functions or not. If you're waiting for this.state.success to be true to submit your form, use the callback of setState to do so.
this.setState(validatedState, newState => if (this.newState.success) submit() );
You can display success msg by verifying first name and last name values also is like if(this.state.firstName !== "" && this.state.lastName !== "") {
this.setState({ success: true });
}it may help you. I provided the link below please find here https://codesandbox.io/s/q3ykw7wl3j

Uncaught TypeError: Cannot read property 'state' of undefined reactjs

I have pasted my whole there is very minor problem in this code but I'm not able to resolve the problem. Please review this code.
It's showing following error while consoling:
Uncaught TypeError: Cannot read property 'state' of undefined reactjs
Code:
export class Login extends Component {
constructor(props) {
super(props);
this.handleChangeforSignUp = this.handleChangeforSignUp.bind(this);
this.createNewUserWithEmailPassword = this.createNewUserWithEmailPassword.bind(this);
this.state = {
sign_up_details: {
email: "",
password: "",
confirmpassword: "",
notification: ""
}
};
}
createNewUserWithEmailPassword(event){
event.preventDefault();
if((this.state.sign_up_details.password !== "") && (this.state.sign_up_details.confirmpassword !== "")){
if(this.state.sign_up_details.password === this.state.sign_up_details.confirmpassword){
let updateNotification = Object.assign({}, this.state.sign_up_details, {notification: "Password matched"});
this.setState({
sign_up_details: updateNotification
});
app.auth().createUserWithEmailAndPassword(this.state.sign_up_details.email, this.state.sign_up_details.password)
.catch(function(error) {
// Handle Errors here.
let errorCode = error.code;
console.log(errorCode);
let errorMessage = error.message;
if (errorCode == 'auth/weak-password') {
let notify = Object.assign({}, this.state.sign_up_details, {notification: errorMessage});
this.setState({
sign_up_details: notify
});
} else {
// alert(errorMessage);
let notify = Object.assign({}, this.state.sign_up_details, {notification: errorMessage});
this.setState({
sign_up_details: notify
},
() => {
console.log(this.state.sign_up_details);
} );
}
console.log(error);
}).then(
function(onResolve, onReject){
console.log(onResolve);
console.log(onReject);
}
);
}else{
let notify = Object.assign({}, this.state.sign_up_details, {notification: "Password not matched"});
this.setState({
sign_up_details: notify
});
}
}
}
handleChangeforSignUp(event){
// console.log(event);
const target = event.target;
let emailInput = "";
let passwordInput = "";
let confirmpasswordInput = "";
if(target.type === 'email'){
emailInput = target.value;
let updateEmail = Object.assign({}, this.state.sign_up_details, {email: emailInput});
this.setState({
sign_up_details: updateEmail
});
}
if(target.type === 'password' && target.name === 'password'){
passwordInput = target.value;
let updatePassword = Object.assign({}, this.state.sign_up_details, {password: passwordInput});
this.setState({
sign_up_details: updatePassword
});
}
if(target.type === 'password' && target.name === 'confirmpassword'){
confirmpasswordInput = target.value;
let updateConfirmpassword = Object.assign({}, this.state.sign_up_details, {confirmpassword: confirmpasswordInput});
this.setState({
sign_up_details: updateConfirmpassword
});
}
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
// const { from } = this.props.history.location || { from: { pathname: '/' } }
if (this.state.redirect === true) {
return <Redirect to={from} />
}
return (
<div id="register" class="container tab-pane fade animated fadeIn">
<form onSubmit={this.createNewUserWithEmailPassword} className="formfield" ref={(form) => { this.signupForm = form }} method="post">
<h1 className="text-center login-heading pt-2 pb-4"><i className="fa fa-pencil fa-lg mx-2" ></i> Register</h1>
{/* <input value={this.state.sign_up_details.name} onChange={this.handleChangeforSignUp} className="form-control input mb-2" name="name" type="text" placeholder="John Doe" /> */}
<input value={this.state.sign_up_details.email} onChange={this.handleChangeforSignUp} className="form-control input mb-2" name="email" type="email" placeholder="john.doe#jd.com" />
<input value={this.state.sign_up_details.password} onChange={this.handleChangeforSignUp} className="form-control input mb-2" name="password" type="password" placeholder="*******" />
<input value={this.state.sign_up_details.confirmpassword} onChange={this.handleChangeforSignUp} className="form-control input mb-2" name="confirmpassword" type="password" placeholder="*******" />
<p className="text_ter text_pink">{this.state.sign_up_details.notification !== "" ? this.state.sign_up_details.notification : ""}</p>
<input type="submit" className="btn btn-primary btn-sm btn-block p-2 mt-4 mb-2" value="Log In"></input>
</form>
</div>
)
}
}
ReactDOM.render(<Login/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div id="app"><div>
enter image description here
You seem to be using react-router (which gives you the Redirect component for example) but it's never imported or included anywhere?
So this error is the equivalent to you having written undefined.state
Take a look at your event handlers such as handleChangeForSignup() which you are using bad practice in your naming, its not completely camel case, you have handleChangeforSignup().
Specifically relating to this error though, look at createNewUserWithEmailPassword().
Did you try testing it out first by doing:
createNewUserWithEmailPassword(event) {
event.preventDefault();
console.log(this.state.sign_up_details);
}
Do you see another problem here? Again, naming convention. You need this event handler to do one thing at a time. You see how the console log I added would not work? It would only check for email, but if you would have done this first, you know take it one step at a time and test your code along the way, you probably would have gotten this error earlier.
JavaScript thinks that inside your createNewUserWithEmailPassword() is not equal to this, but instead this is equal to undefined.
You need to review what the this keyword is used for inside of a class and how the value of this is determined inside a function.
Your Login instance should have a few different properties such as state, render() and createNewUserWithEmailPassword().
You should be able to reference the keyword this which is a reference back to the class itself.
So you are saying give me the reference to the instance of Login that I am writing code inside of. That should give you access to state, render() and createNewUserWithEmailPassword().
I would clean up the code a bit like so:
class Login extends React.Component {
state = {
sign_up_details: {
email: "",
password: "",
confirmpassword: "",
notification: ""
}
};
}
unless you are doing import React, { Component } from 'react'; but you did not include that in your post so I did React.Component.
So why is this is going wrong inside of createNewUserWithEmailPassword()?
To understand that, we need a good idea of how the value of this is determined inside a function.
class Car {
setDriveSound(){
this.sound = sound;
}
drive() {
return this.sound;
}
}
This is just a dummy class that allows us to set a value and later return that value.
class Car {
setDriveSound(sound){
this.sound = sound;
}
drive() {
return this.sound;
}
}
const car = new Car();
car.setDriveSound('vroom');
car.drive();
This will output vroom.
So here is an important rule to understand. Whenever we want to figure out what the value of this is going to be inside of a method on a class, don't look at the method itself, look at where you called the method.
In my example above, we look at car.drive();.
We find the function name, in this case drive(), we look to the dot to the left of the function, and look at the variable that is referenced inside.
So this from return this.sound; inside the drive() method is going to be equal to the car variable inside the Car class.
So, don't look at the function, look at where the function is called. What is to the left of dot when the function gets called.
So when drive() is called on car, this becomes equal to the instance of the car.
I will create an object called truck and rip off the drive() function from the instance of car and assign it to the truck object:
const truck = {
sound: "honk_honk",
driveMyTruck: car.drive
}
truck.driveMyTruck();
This will return honk_honk because truck is equal to this inside the drive() function.
The latter is the equivalent to doing:
drive() {
return truck.sound;
}
Lets apply the above so what is going on in your case.
Now if I try this:
const drive = car.drive;
drive();
Now I would get Cannot read property 'sound' of undefined.
So when the callback is passed down the form element at some point in time we are going to call createNewUserWithEmailPassword() and when it gets invoked, there is no Login createNewUserWithEmailPassword.
This example here:
class Car {
setDriveSound(sound){
this.sound = sound;
}
drive() {
return this.sound;
}
}
const car = new Car();
car.setDriveSound('vroom');
const drive = car.drive;
drive();
is exactly what you did, or what you have going on.
This may still be confusing, it's not an easy concept to understand, I get this type of error message very frequently as well.
So how would you apply what I just explained?
There are several ways to solve your problem.
Now, you added a constructor(props) method with super(props) because it extends React.Component and you did this.createNewUserWithEmailPassword = this.createNewUserWithEmailPassword.bind(this);
This should have produced a new version of the function and it should have been fixed with the correct value of this, but it didn't solve it.
So try this:
class Login extends React.Component {
state = {
sign_up_details: {
email: "",
password: "",
confirmpassword: "",
notification: ""
}
};
createNewUserWithEmailPassword = (event) => {
event.preventDefault();
console.log(this.state.sign_up_details);
}
}

Categories

Resources