Skip to content

Commit

Permalink
fix: Registration with password that doesn't meet the requirements (#…
Browse files Browse the repository at this point in the history
…1184)

* fix: Registration with password that doesn't meet the requirements
---------

Co-authored-by: Dima Alipov <[email protected]>
Co-authored-by: Syed Sajjad  Hussain Shah <[email protected]>
  • Loading branch information
3 people authored Mar 15, 2024
1 parent 3635476 commit dc90cf9
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 19 deletions.
13 changes: 7 additions & 6 deletions src/register/RegistrationFields/EmailField/EmailField.jsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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);
Expand All @@ -52,10 +56,7 @@ const EmailField = (props) => {
handleErrorChange('confirm_email', confirmEmailError);
}

dispatch(backupRegistrationFormBegin({
...backedUpFormData,
emailSuggestion: { ...suggestion },
}));
dispatch(setEmailSuggestionInStore(suggestion));
setEmailSuggestion(suggestion);

if (fieldError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ describe('EmailField', () => {
);

const initialState = {
register: {},
register: {
registrationFormData: {
emailSuggestion: {
suggestion: '[email protected]',
type: 'warning',
},
},
},
};

beforeEach(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/register/RegistrationFields/EmailField/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down
2 changes: 1 addition & 1 deletion src/register/RegistrationFields/NameField/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
6 changes: 4 additions & 2 deletions src/register/RegistrationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
backupRegistrationFormBegin,
clearRegistrationBackendError,
registerNewUser,
setEmailSuggestionInStore,
setUserPipelineDataLoaded,
} from './data/actions';
import {
Expand Down Expand Up @@ -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 }));
};

Expand Down Expand Up @@ -225,14 +226,15 @@ const RegistrationPage = (props) => {
}

// Validating form data before submitting
const { isValid, fieldErrors } = isFormValid(
const { isValid, fieldErrors, emailSuggestion } = isFormValid(
payload,
registrationEmbedded ? temporaryErrors : errors,
configurableFormFields,
fieldDescriptions,
formatMessage,
);
setErrors({ ...fieldErrors });
dispatch(setEmailSuggestionInStore(emailSuggestion));

// returning if not valid
if (!isValid) {
Expand Down
48 changes: 48 additions & 0 deletions src/register/RegistrationPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]',
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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]',
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: '[email protected]', 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({
Expand Down
7 changes: 7 additions & 0 deletions src/register/data/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => ({
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions src/register/data/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
23 changes: 23 additions & 0 deletions src/register/data/tests/reducers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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}`,
Expand Down
44 changes: 36 additions & 8 deletions src/register/data/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
});

Expand All @@ -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 };
};

/**
Expand Down

0 comments on commit dc90cf9

Please sign in to comment.