VAN-299/fix_error_message_preference (#125)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
@@ -26,7 +26,15 @@ const ResetPasswordPage = (props) => {
|
||||
const [passwordValid, setPasswordValidValue] = useState(true);
|
||||
const [passwordMatch, setPasswordMatchValue] = useState(true);
|
||||
const [validationMessage, setvalidationMessage] = useState('');
|
||||
const [emptyFieldError, setEmptyFieldError] = useState('');
|
||||
const [bannerErrorMessage, setbannerErrorMessage] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (props.errors) {
|
||||
setbannerErrorMessage(props.errors);
|
||||
setvalidationMessage(props.errors);
|
||||
setPasswordValidValue(false);
|
||||
}
|
||||
}, [props.status === 'failure']);
|
||||
|
||||
const validatePasswordFromBackend = async (newPassword) => {
|
||||
let errorMessage;
|
||||
@@ -42,7 +50,13 @@ const ResetPasswordPage = (props) => {
|
||||
const handleNewPasswordChange = (e) => {
|
||||
const newPassword = e.target.value;
|
||||
setNewPasswordValue(newPassword);
|
||||
validatePasswordFromBackend(newPassword);
|
||||
|
||||
if (newPassword === '') {
|
||||
setPasswordValidValue(false);
|
||||
setvalidationMessage(intl.formatMessage(messages['reset.password.empty.new.password.field.error']));
|
||||
} else {
|
||||
validatePasswordFromBackend(newPassword);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirmPasswordChange = (e) => {
|
||||
@@ -53,23 +67,24 @@ const ResetPasswordPage = (props) => {
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
|
||||
window.scrollTo({ left: 0, top: 0, behavior: 'smooth' });
|
||||
if (newPasswordInput === '') {
|
||||
setEmptyFieldError(intl.formatMessage(messages['reset.password.empty.new.password.field.error']));
|
||||
setPasswordValidValue(false);
|
||||
setvalidationMessage(intl.formatMessage(messages['reset.password.empty.new.password.field.error']));
|
||||
setbannerErrorMessage(intl.formatMessage(messages['reset.password.empty.new.password.field.error']));
|
||||
return;
|
||||
}
|
||||
if (newPasswordInput !== confirmPasswordInput) {
|
||||
setPasswordMatchValue(false);
|
||||
return;
|
||||
}
|
||||
if (passwordValid && passwordMatch) {
|
||||
const formPayload = {
|
||||
new_password1: newPasswordInput,
|
||||
new_password2: confirmPasswordInput,
|
||||
};
|
||||
props.resetPassword(formPayload, props.token, params);
|
||||
}
|
||||
|
||||
const formPayload = {
|
||||
new_password1: newPasswordInput,
|
||||
new_password2: confirmPasswordInput,
|
||||
};
|
||||
props.resetPassword(formPayload, props.token, params);
|
||||
};
|
||||
|
||||
if (props.token_status === 'pending') {
|
||||
@@ -87,12 +102,12 @@ const ResetPasswordPage = (props) => {
|
||||
<>
|
||||
<div id="main" className="d-flex justify-content-center m-4">
|
||||
<div className="d-flex flex-column mw-500">
|
||||
{(emptyFieldError || props.status) && (
|
||||
{bannerErrorMessage ? (
|
||||
<Alert id="validation-errors" variant="danger">
|
||||
<Alert.Heading>{intl.formatMessage(messages['forgot.password.empty.new.password.error.heading'])}</Alert.Heading>
|
||||
<ul><li>{emptyFieldError || props.errors}</li></ul>
|
||||
<ul><li>{bannerErrorMessage}</li></ul>
|
||||
</Alert>
|
||||
)}
|
||||
) : null}
|
||||
<Form>
|
||||
<h3 className="mt-3">
|
||||
{intl.formatMessage(messages['reset.password.page.heading'])}
|
||||
@@ -102,8 +117,8 @@ const ResetPasswordPage = (props) => {
|
||||
</p>
|
||||
<ValidationFormGroup
|
||||
for="reset-password-input"
|
||||
invalid={!passwordValid || emptyFieldError !== ''}
|
||||
invalidMessage={validationMessage || emptyFieldError}
|
||||
invalid={!passwordValid}
|
||||
invalidMessage={validationMessage}
|
||||
className="w-100"
|
||||
>
|
||||
<Form.Label htmlFor="reset-password-input" className="h6 mr-1">
|
||||
@@ -114,7 +129,6 @@ const ResetPasswordPage = (props) => {
|
||||
id="reset-password-input"
|
||||
type="password"
|
||||
placeholder=""
|
||||
onChange={() => setEmptyFieldError('')}
|
||||
onBlur={e => handleNewPasswordChange(e)}
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
|
||||
@@ -21,6 +21,9 @@ describe('ResetPasswordPage', () => {
|
||||
let props = {};
|
||||
let store = {};
|
||||
|
||||
const emptyFieldError = 'Please enter your New Password.';
|
||||
const validationMessage = 'This password is too short. It must contain at least 8 characters. This password must contain at least 1 number.';
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<Provider store={store}>{children}</Provider>
|
||||
@@ -114,25 +117,7 @@ describe('ResetPasswordPage', () => {
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should match unsuccessful reset message section snapshot', () => {
|
||||
props = {
|
||||
...props,
|
||||
token_status: 'valid',
|
||||
status: 'failure',
|
||||
errors: 'Password reset was unsuccessful.',
|
||||
};
|
||||
const tree = renderer.create(reduxWrapper(<IntlResetPasswordPage {...props} />))
|
||||
.toJSON();
|
||||
expect(tree).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should display invalid password message', async () => {
|
||||
const validationMessage = 'This password is too short. It must contain at least 8 characters. This password must contain at least 1 number.';
|
||||
const data = {
|
||||
validation_decisions: {
|
||||
password: validationMessage,
|
||||
},
|
||||
};
|
||||
props = {
|
||||
...props,
|
||||
token_status: 'valid',
|
||||
@@ -140,19 +125,47 @@ describe('ResetPasswordPage', () => {
|
||||
|
||||
auth.getHttpClient = jest.fn(() => ({
|
||||
post: async () => ({
|
||||
data,
|
||||
data: {
|
||||
validation_decisions: {
|
||||
password: validationMessage,
|
||||
},
|
||||
},
|
||||
catch: () => {},
|
||||
}),
|
||||
}));
|
||||
|
||||
const resetPasswordPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
|
||||
|
||||
// Focus out of empty field
|
||||
await act(async () => {
|
||||
await resetPasswordPage.find('input#reset-password-input').simulate('blur');
|
||||
});
|
||||
resetPasswordPage.update();
|
||||
expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError);
|
||||
|
||||
// Enter non-compliant password
|
||||
await act(async () => {
|
||||
await resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: 'invalid' } });
|
||||
});
|
||||
expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(validationMessage);
|
||||
});
|
||||
|
||||
it('should display error message on empty form submission', () => {
|
||||
const bannerMessage = 'We couldn\'t reset your password.'.concat(emptyFieldError);
|
||||
props = {
|
||||
...props,
|
||||
token_status: 'valid',
|
||||
token: 'token',
|
||||
};
|
||||
|
||||
const resetPasswordPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
|
||||
resetPasswordPage.find('button.btn-primary').simulate('click');
|
||||
|
||||
resetPasswordPage.update();
|
||||
expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError);
|
||||
expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage);
|
||||
});
|
||||
|
||||
it('with valid inputs resetPassword action is dispatch', async () => {
|
||||
const newPassword = 'test-password1';
|
||||
props = {
|
||||
@@ -204,20 +217,24 @@ describe('ResetPasswordPage', () => {
|
||||
resetPasswordPage.unmount();
|
||||
});
|
||||
|
||||
it('should display empty new password field error', () => {
|
||||
const validationMessage = 'Please enter your New Password.';
|
||||
it('should not update the banner message on focus out', async () => {
|
||||
const bannerMessage = 'We couldn\'t reset your password.'.concat(validationMessage);
|
||||
props = {
|
||||
...props,
|
||||
token_status: 'valid',
|
||||
token: 'token',
|
||||
errors: validationMessage,
|
||||
};
|
||||
|
||||
const resetPasswordPage = mount(reduxWrapper(<IntlResetPasswordPage {...props} />));
|
||||
resetPasswordPage.find('button.btn-primary').simulate('click');
|
||||
expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage);
|
||||
|
||||
resetPasswordPage.update();
|
||||
expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(validationMessage);
|
||||
expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual('We couldn\'t reset your password.'.concat(validationMessage));
|
||||
await act(async () => {
|
||||
await resetPasswordPage.find('input#reset-password-input').simulate('blur', { target: { value: '' } });
|
||||
});
|
||||
// On blur event, the banner message remains same
|
||||
expect(resetPasswordPage.find('#reset-password-input-invalid-feedback').text()).toEqual(emptyFieldError);
|
||||
expect(resetPasswordPage.find('#validation-errors').first().text()).toEqual(bannerMessage);
|
||||
});
|
||||
|
||||
it('check cookie rendered', () => {
|
||||
|
||||
@@ -47,20 +47,6 @@ exports[`ResetPasswordPage should match pending reset message section snapshot 1
|
||||
<div
|
||||
className="d-flex flex-column mw-500"
|
||||
>
|
||||
<div
|
||||
className="fade alert alert-danger show"
|
||||
id="validation-errors"
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
className="alert-heading h4"
|
||||
>
|
||||
We couldn't reset your password.
|
||||
</div>
|
||||
<ul>
|
||||
<li />
|
||||
</ul>
|
||||
</div>
|
||||
<form
|
||||
className=""
|
||||
>
|
||||
@@ -89,7 +75,6 @@ exports[`ResetPasswordPage should match pending reset message section snapshot 1
|
||||
id="reset-password-input"
|
||||
name="new-password1"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
type="password"
|
||||
/>
|
||||
@@ -196,7 +181,6 @@ exports[`ResetPasswordPage should match reset password default section snapshot
|
||||
id="reset-password-input"
|
||||
name="new-password1"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
type="password"
|
||||
/>
|
||||
@@ -284,108 +268,6 @@ exports[`ResetPasswordPage should match successful reset message section snapsho
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ResetPasswordPage should match unsuccessful reset message section snapshot 1`] = `
|
||||
<div
|
||||
className="d-flex justify-content-center m-4"
|
||||
id="main"
|
||||
>
|
||||
<div
|
||||
className="d-flex flex-column mw-500"
|
||||
>
|
||||
<div
|
||||
className="fade alert alert-danger show"
|
||||
id="validation-errors"
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
className="alert-heading h4"
|
||||
>
|
||||
We couldn't reset your password.
|
||||
</div>
|
||||
<ul>
|
||||
<li>
|
||||
Password reset was unsuccessful.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<form
|
||||
className=""
|
||||
>
|
||||
<h3
|
||||
className="mt-3"
|
||||
>
|
||||
Reset your password
|
||||
</h3>
|
||||
<p
|
||||
className="mb-4"
|
||||
>
|
||||
Enter and confirm your new password.
|
||||
</p>
|
||||
<div
|
||||
className="form-group w-100"
|
||||
>
|
||||
<label
|
||||
className="h6 mr-1 form-label"
|
||||
htmlFor="reset-password-input"
|
||||
>
|
||||
New Password
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
className="form-control"
|
||||
id="reset-password-input"
|
||||
name="new-password1"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
type="password"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="form-group w-100"
|
||||
>
|
||||
<label
|
||||
className="h6 mr-1 form-label"
|
||||
htmlFor="confirm-password-input"
|
||||
>
|
||||
Confirm Password
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
className="form-control"
|
||||
id="confirm-password-input"
|
||||
name="new-password2"
|
||||
onChange={[Function]}
|
||||
placeholder=""
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<strong
|
||||
className="invalid-feedback"
|
||||
id="confirm-password-input-invalid-feedback"
|
||||
>
|
||||
Passwords do not match.
|
||||
</strong>
|
||||
</div>
|
||||
<button
|
||||
aria-disabled={false}
|
||||
aria-live="assertive"
|
||||
className="pgn__stateful-btn pgn__stateful-btn-state-failure btn-primary btn btn-primary"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
className="d-flex align-items-center justify-content-center"
|
||||
>
|
||||
Reset my password
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ResetPasswordPage show spinner component during token validation 1`] = `
|
||||
<div
|
||||
className="container position-absolute h-90"
|
||||
|
||||
Reference in New Issue
Block a user