fix: Registration with password that doesn't meet the requirements (#1184)
* fix: Registration with password that doesn't meet the requirements --------- Co-authored-by: Dima Alipov <dimaalipov@MacBook-Pro-Dima.local> Co-authored-by: Syed Sajjad Hussain Shah <ssajjad@2u.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
@@ -9,9 +9,9 @@ import PropTypes from 'prop-types';
|
||||
import validateEmail from './validator';
|
||||
import { FormGroup } from '../../../common-components';
|
||||
import {
|
||||
backupRegistrationFormBegin,
|
||||
clearRegistrationBackendError,
|
||||
fetchRealtimeValidations,
|
||||
setEmailSuggestionInStore,
|
||||
} from '../../data/actions';
|
||||
import messages from '../../messages';
|
||||
|
||||
@@ -44,6 +44,10 @@ const EmailField = (props) => {
|
||||
|
||||
const [emailSuggestion, setEmailSuggestion] = useState({ ...backedUpFormData?.emailSuggestion });
|
||||
|
||||
useEffect(() => {
|
||||
setEmailSuggestion(backedUpFormData.emailSuggestion);
|
||||
}, [backedUpFormData.emailSuggestion]);
|
||||
|
||||
const handleOnBlur = (e) => {
|
||||
const { value } = e.target;
|
||||
const { fieldError, confirmEmailError, suggestion } = validateEmail(value, confirmEmailValue, formatMessage);
|
||||
@@ -52,10 +56,7 @@ const EmailField = (props) => {
|
||||
handleErrorChange('confirm_email', confirmEmailError);
|
||||
}
|
||||
|
||||
dispatch(backupRegistrationFormBegin({
|
||||
...backedUpFormData,
|
||||
emailSuggestion: { ...suggestion },
|
||||
}));
|
||||
dispatch(setEmailSuggestionInStore(suggestion));
|
||||
setEmailSuggestion(suggestion);
|
||||
|
||||
if (fieldError) {
|
||||
|
||||
@@ -46,7 +46,14 @@ describe('EmailField', () => {
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
register: {},
|
||||
register: {
|
||||
registrationFormData: {
|
||||
emailSuggestion: {
|
||||
suggestion: 'example@gmail.com',
|
||||
type: 'warning',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -91,7 +91,7 @@ export const validateEmailAddress = (value, username, domainName) => {
|
||||
const validateEmail = (value, confirmEmailValue, formatMessage) => {
|
||||
let fieldError = '';
|
||||
let confirmEmailError = '';
|
||||
let emailSuggestion = {};
|
||||
let emailSuggestion = { suggestion: '', type: '' };
|
||||
|
||||
if (!value) {
|
||||
fieldError = formatMessage(messages['empty.email.field.error']);
|
||||
|
||||
@@ -10,7 +10,7 @@ export const HTML_REGEX = /<|>/u;
|
||||
export const INVALID_NAME_REGEX = /https?:\/\/(?:[-\w.]|(?:%[\da-fA-F]{2}))*/g;
|
||||
|
||||
const validateName = (value, formatMessage) => {
|
||||
let fieldError;
|
||||
let fieldError = '';
|
||||
if (!value.trim()) {
|
||||
fieldError = formatMessage(messages['empty.name.field.error']);
|
||||
} else if (URL_REGEX.test(value) || HTML_REGEX.test(value) || INVALID_NAME_REGEX.test(value)) {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
backupRegistrationFormBegin,
|
||||
clearRegistrationBackendError,
|
||||
registerNewUser,
|
||||
setEmailSuggestionInStore,
|
||||
setUserPipelineDataLoaded,
|
||||
} from './data/actions';
|
||||
import {
|
||||
@@ -190,8 +191,8 @@ const RegistrationPage = (props) => {
|
||||
const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
|
||||
if (registrationError[name]) {
|
||||
dispatch(clearRegistrationBackendError(name));
|
||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||
}
|
||||
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
||||
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
||||
};
|
||||
|
||||
@@ -225,7 +226,7 @@ const RegistrationPage = (props) => {
|
||||
}
|
||||
|
||||
// Validating form data before submitting
|
||||
const { isValid, fieldErrors } = isFormValid(
|
||||
const { isValid, fieldErrors, emailSuggestion } = isFormValid(
|
||||
payload,
|
||||
registrationEmbedded ? temporaryErrors : errors,
|
||||
configurableFormFields,
|
||||
@@ -233,6 +234,7 @@ const RegistrationPage = (props) => {
|
||||
formatMessage,
|
||||
);
|
||||
setErrors({ ...fieldErrors });
|
||||
dispatch(setEmailSuggestionInStore(emailSuggestion));
|
||||
|
||||
// returning if not valid
|
||||
if (!isValid) {
|
||||
|
||||
@@ -221,6 +221,54 @@ describe('RegistrationPage', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ ...formPayload, country: 'PK' }));
|
||||
});
|
||||
|
||||
it('should display an error when form is submitted with an invalid email', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
const emailError = "We couldn't create your account.Please check your responses and try again.";
|
||||
|
||||
const formPayload = {
|
||||
name: 'Petro',
|
||||
username: 'petro_qa',
|
||||
email: 'petro @example.com',
|
||||
password: 'password1',
|
||||
country: 'Ukraine',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(emailError);
|
||||
});
|
||||
|
||||
it('should display an error when form is submitted with an invalid username', () => {
|
||||
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
|
||||
const usernameError = "We couldn't create your account.Please check your responses and try again.";
|
||||
|
||||
const formPayload = {
|
||||
name: 'Petro',
|
||||
username: 'petro qa',
|
||||
email: 'petro@example.com',
|
||||
password: 'password1',
|
||||
country: 'Ukraine',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store.dispatch = jest.fn(store.dispatch);
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(usernameError);
|
||||
});
|
||||
|
||||
it('should submit form with marketing email opt in value', () => {
|
||||
mergeConfig({
|
||||
MARKETING_EMAILS_OPT_IN: 'true',
|
||||
|
||||
@@ -348,6 +348,49 @@ describe('ConfigurableRegistrationForm', () => {
|
||||
expect(confirmEmailErrorElement.textContent).toEqual('The email addresses do not match.');
|
||||
});
|
||||
|
||||
it('should show error if email and confirm email fields do not match on submit click', () => {
|
||||
const formPayload = {
|
||||
name: 'Petro',
|
||||
username: 'petro_qa',
|
||||
email: 'petro@example.com',
|
||||
password: 'password1',
|
||||
country: 'Ukraine',
|
||||
honor_code: true,
|
||||
totalRegistrationTime: 0,
|
||||
};
|
||||
|
||||
store = mockStore({
|
||||
...initialState,
|
||||
commonComponents: {
|
||||
...initialState.commonComponents,
|
||||
fieldDescriptions: {
|
||||
confirm_email: {
|
||||
name: 'confirm_email', type: 'text', label: 'Confirm Email',
|
||||
},
|
||||
country: { name: 'country' },
|
||||
},
|
||||
},
|
||||
});
|
||||
const { getByLabelText, container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
|
||||
|
||||
populateRequiredFields(getByLabelText, formPayload, true);
|
||||
fireEvent.change(
|
||||
getByLabelText('Confirm Email'),
|
||||
{ target: { value: 'test2@gmail.com', name: 'confirm_email' } },
|
||||
);
|
||||
|
||||
const button = container.querySelector('button.btn-brand');
|
||||
fireEvent.click(button);
|
||||
|
||||
const confirmEmailErrorElement = container.querySelector('div#confirm_email-error');
|
||||
expect(confirmEmailErrorElement.textContent).toEqual('The email addresses do not match.');
|
||||
|
||||
const validationErrors = container.querySelector('#validation-errors');
|
||||
expect(validationErrors.textContent).toContain(
|
||||
"We couldn't create your account.Please check your responses and try again.",
|
||||
);
|
||||
});
|
||||
|
||||
it('should run validations for configurable focused field on form submission', () => {
|
||||
const professionError = 'Enter your profession';
|
||||
store = mockStore({
|
||||
|
||||
@@ -7,6 +7,7 @@ export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_
|
||||
export const REGISTRATION_CLEAR_BACKEND_ERROR = 'REGISTRATION_CLEAR_BACKEND_ERROR';
|
||||
export const REGISTER_SET_COUNTRY_CODE = 'REGISTER_SET_COUNTRY_CODE';
|
||||
export const REGISTER_SET_USER_PIPELINE_DATA_LOADED = 'REGISTER_SET_USER_PIPELINE_DATA_LOADED';
|
||||
export const REGISTER_SET_EMAIL_SUGGESTIONS = 'REGISTER_SET_EMAIL_SUGGESTIONS';
|
||||
|
||||
// Backup registration form
|
||||
export const backupRegistrationForm = () => ({
|
||||
@@ -37,6 +38,12 @@ export const fetchRealtimeValidationsFailure = () => ({
|
||||
type: REGISTER_FORM_VALIDATIONS.FAILURE,
|
||||
});
|
||||
|
||||
// Set email field frontend validations
|
||||
export const setEmailSuggestionInStore = (emailSuggestion) => ({
|
||||
type: REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
payload: { emailSuggestion },
|
||||
});
|
||||
|
||||
// Register
|
||||
export const registerNewUser = registrationInfo => ({
|
||||
type: REGISTER_NEW_USER.BASE,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
REGISTER_FORM_VALIDATIONS,
|
||||
REGISTER_NEW_USER,
|
||||
REGISTER_SET_COUNTRY_CODE,
|
||||
REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||
REGISTRATION_CLEAR_BACKEND_ERROR,
|
||||
} from './actions';
|
||||
@@ -120,6 +121,14 @@ const reducer = (state = defaultState, action = {}) => {
|
||||
userPipelineDataLoaded: value,
|
||||
};
|
||||
}
|
||||
case REGISTER_SET_EMAIL_SUGGESTIONS:
|
||||
return {
|
||||
...state,
|
||||
registrationFormData: {
|
||||
...state.registrationFormData,
|
||||
emailSuggestion: action.payload.emailSuggestion,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
REGISTER_FORM_VALIDATIONS,
|
||||
REGISTER_NEW_USER,
|
||||
REGISTER_SET_COUNTRY_CODE,
|
||||
REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
|
||||
REGISTRATION_CLEAR_BACKEND_ERROR,
|
||||
} from '../actions';
|
||||
@@ -65,6 +66,28 @@ describe('Registration Reducer Tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should set email suggestions', () => {
|
||||
const emailSuggestion = {
|
||||
type: 'test type',
|
||||
suggestion: 'test suggestion',
|
||||
};
|
||||
const action = {
|
||||
type: REGISTER_SET_EMAIL_SUGGESTIONS,
|
||||
payload: { emailSuggestion },
|
||||
};
|
||||
|
||||
expect(reducer(defaultState, action)).toEqual(
|
||||
{
|
||||
...defaultState,
|
||||
registrationFormData: {
|
||||
...defaultState.registrationFormData,
|
||||
emailSuggestion: {
|
||||
type: 'test type', suggestion: 'test suggestion',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should set redirect url dashboard on registration success action', () => {
|
||||
const payload = {
|
||||
redirectUrl: `${getConfig().BASE_URL}${DEFAULT_REDIRECT_URL}`,
|
||||
|
||||
@@ -2,6 +2,9 @@ import { snakeCaseObject } from '@edx/frontend-platform';
|
||||
|
||||
import { LETTER_REGEX, NUMBER_REGEX } from '../../data/constants';
|
||||
import messages from '../messages';
|
||||
import validateEmail from '../RegistrationFields/EmailField/validator';
|
||||
import validateName from '../RegistrationFields/NameField/validator';
|
||||
import validateUsername from '../RegistrationFields/UsernameField/validator';
|
||||
|
||||
/**
|
||||
* It validates the password field value
|
||||
@@ -35,12 +38,39 @@ export const isFormValid = (
|
||||
) => {
|
||||
const fieldErrors = { ...errors };
|
||||
let isValid = true;
|
||||
let emailSuggestion = { suggestion: '', type: '' };
|
||||
|
||||
Object.keys(payload).forEach(key => {
|
||||
if (!payload[key]) {
|
||||
fieldErrors[key] = formatMessage(messages[`empty.${key}.field.error`]);
|
||||
switch (key) {
|
||||
case 'name':
|
||||
fieldErrors.name = validateName(payload.name, formatMessage);
|
||||
if (fieldErrors.name) { isValid = false; }
|
||||
break;
|
||||
case 'email': {
|
||||
const {
|
||||
fieldError, confirmEmailError, suggestion,
|
||||
} = validateEmail(payload.email, configurableFormFields?.confirm_email, formatMessage);
|
||||
if (fieldError) {
|
||||
fieldErrors.email = fieldError;
|
||||
isValid = false;
|
||||
}
|
||||
if (confirmEmailError) {
|
||||
fieldErrors.confirm_email = confirmEmailError;
|
||||
isValid = false;
|
||||
}
|
||||
emailSuggestion = suggestion;
|
||||
break;
|
||||
}
|
||||
if (fieldErrors[key]) {
|
||||
isValid = false;
|
||||
case 'username':
|
||||
fieldErrors.username = validateUsername(payload.username, formatMessage);
|
||||
if (fieldErrors.username) { isValid = false; }
|
||||
break;
|
||||
case 'password':
|
||||
fieldErrors.password = validatePasswordField(payload.password, formatMessage);
|
||||
if (fieldErrors.password) { isValid = false; }
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,12 +89,10 @@ export const isFormValid = (
|
||||
} else if (!configurableFormFields[key]) {
|
||||
fieldErrors[key] = fieldDescriptions[key].error_message;
|
||||
}
|
||||
if (fieldErrors[key]) {
|
||||
isValid = false;
|
||||
}
|
||||
if (fieldErrors[key]) { isValid = false; }
|
||||
});
|
||||
|
||||
return { isValid, fieldErrors };
|
||||
return { isValid, fieldErrors, emailSuggestion };
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user