diff --git a/src/register/RegistrationFields/EmailField/EmailField.jsx b/src/register/RegistrationFields/EmailField/EmailField.jsx
index 819414b58a..6521f6d727 100644
--- a/src/register/RegistrationFields/EmailField/EmailField.jsx
+++ b/src/register/RegistrationFields/EmailField/EmailField.jsx
@@ -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) {
diff --git a/src/register/RegistrationFields/EmailField/EmailField.test.jsx b/src/register/RegistrationFields/EmailField/EmailField.test.jsx
index 43825ca9d2..52d96ed9cd 100644
--- a/src/register/RegistrationFields/EmailField/EmailField.test.jsx
+++ b/src/register/RegistrationFields/EmailField/EmailField.test.jsx
@@ -46,7 +46,14 @@ describe('EmailField', () => {
);
const initialState = {
- register: {},
+ register: {
+ registrationFormData: {
+ emailSuggestion: {
+ suggestion: 'example@gmail.com',
+ type: 'warning',
+ },
+ },
+ },
};
beforeEach(() => {
diff --git a/src/register/RegistrationFields/EmailField/validator.js b/src/register/RegistrationFields/EmailField/validator.js
index c59b9e67a1..44fd2fa1c8 100644
--- a/src/register/RegistrationFields/EmailField/validator.js
+++ b/src/register/RegistrationFields/EmailField/validator.js
@@ -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']);
diff --git a/src/register/RegistrationFields/NameField/validator.js b/src/register/RegistrationFields/NameField/validator.js
index e62c227d08..aefaedfb3f 100644
--- a/src/register/RegistrationFields/NameField/validator.js
+++ b/src/register/RegistrationFields/NameField/validator.js
@@ -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)) {
diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx
index f56106ff93..3a9426c2e5 100644
--- a/src/register/RegistrationPage.jsx
+++ b/src/register/RegistrationPage.jsx
@@ -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) {
diff --git a/src/register/RegistrationPage.test.jsx b/src/register/RegistrationPage.test.jsx
index ba0ce4d4e6..2634713f63 100644
--- a/src/register/RegistrationPage.test.jsx
+++ b/src/register/RegistrationPage.test.jsx
@@ -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()));
+ 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()));
+ 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',
diff --git a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx
index 88d3e39f97..537d11eb7e 100644
--- a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx
+++ b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx
@@ -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()));
+
+ 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({
diff --git a/src/register/data/actions.js b/src/register/data/actions.js
index d1316ed783..9fa5aed500 100644
--- a/src/register/data/actions.js
+++ b/src/register/data/actions.js
@@ -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,
diff --git a/src/register/data/reducers.js b/src/register/data/reducers.js
index 0c9701b219..70c3a994d0 100644
--- a/src/register/data/reducers.js
+++ b/src/register/data/reducers.js
@@ -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,
diff --git a/src/register/data/tests/reducers.test.js b/src/register/data/tests/reducers.test.js
index 07badb9cda..3e2270ab93 100644
--- a/src/register/data/tests/reducers.test.js
+++ b/src/register/data/tests/reducers.test.js
@@ -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}`,
diff --git a/src/register/data/utils.js b/src/register/data/utils.js
index e1d0db7f75..b044f93dce 100644
--- a/src/register/data/utils.js
+++ b/src/register/data/utils.js
@@ -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 };
};
/**