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
  • Loading branch information
Dima Alipov authored and Dima Alipov committed Mar 13, 2024
1 parent abea379 commit 988fead
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 22 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: 'example@gmail.com',
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 @@ -4,7 +4,7 @@ export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{
export const urlRegex = new RegExp(INVALID_NAME_REGEX);

const validateName = (value, formatMessage) => {
let fieldError;
let fieldError = '';
if (!value.trim()) {
fieldError = formatMessage(messages['empty.name.field.error']);
} else if (value && value.match(urlRegex)) {
Expand Down
6 changes: 4 additions & 2 deletions src/register/RegistrationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
backupRegistrationFormBegin,
clearRegistrationBackendError,
registerNewUser,
setEmailSuggestionInStore,
setUserPipelineDataLoaded,
} from './data/actions';
import {
Expand Down Expand Up @@ -185,8 +186,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 @@ -220,14 +221,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
47 changes: 47 additions & 0 deletions src/register/RegistrationPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,53 @@ 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 = 'Enter a valid email address';

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 registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(registrationPage, formPayload, true);
registrationPage.find('button.btn-brand').simulate('click');
expect(
registrationPage.find('div[feedback-for="email"]').text(),
).toEqual(emailError);
});

it('should display an error when form is submitted with an invalid username', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const usernameError = 'Usernames can only contain letters (A-Z, a-z), numerals (0-9), '
+ 'underscores (_), and hyphens (-). Usernames cannot contain spaces';

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 registrationPage = mount(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
populateRequiredFields(registrationPage, formPayload, true);
registrationPage.find('button.btn-brand').simulate('click');
expect(
registrationPage.find('div[feedback-for="username"]').text(),
).toEqual(usernameError);
});

it('should submit form with marketing email opt in value', () => {
mergeConfig({
MARKETING_EMAILS_OPT_IN: 'true',
Expand Down
61 changes: 61 additions & 0 deletions src/register/components/ConfigurableRegistrationForm.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import configureStore from 'redux-mock-store';

import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
import { FIELDS } from '../data/constants';
import RegistrationPage from '../RegistrationPage';

jest.mock('@edx/frontend-platform/analytics', () => ({
sendPageEvent: jest.fn(),
Expand All @@ -22,7 +23,20 @@ jest.mock('@edx/frontend-platform/i18n', () => ({
}));

const IntlConfigurableRegistrationForm = injectIntl(ConfigurableRegistrationForm);
const IntlRegistrationPage = injectIntl(RegistrationPage);
const mockStore = configureStore();
const populateRequiredFields = (registrationPage, payload, isThirdPartyAuth = false) => {
registrationPage.find('input#name').simulate('change', { target: { value: payload.name, name: 'name' } });
registrationPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } });
registrationPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } });

registrationPage.find('input[name="country"]').simulate('change', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: payload.country, name: 'country' } });

if (!isThirdPartyAuth) {
registrationPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } });
}
};

jest.mock('react-router-dom', () => {
const mockNavigation = jest.fn();
Expand Down Expand Up @@ -182,5 +196,52 @@ describe('ConfigurableRegistrationForm', () => {
[FIELDS.TERMS_OF_SERVICE]: true,
});
});

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 registrationPage = mount(routerWrapper(reduxWrapper(
<IntlRegistrationPage {...props} />,
)));

populateRequiredFields(registrationPage, formPayload, true);
registrationPage.find('input#confirm_email').simulate(
'change', { target: { value: 'test2@gmail.com', name: 'confirm_email' } },
);

const button = registrationPage.find('button.btn-brand');
button.simulate('click');

registrationPage.update();

const confirmEmailErrorElement = registrationPage.find('div#confirm_email-error');
expect(confirmEmailErrorElement.text()).toEqual('The email addresses do not match.');

const validationErrors = registrationPage.find('#validation-errors');
const firstValidationErrorText = validationErrors.first().text();
expect(firstValidationErrorText).toContain(
"We couldn't create your account.Please check your responses and try again.",
);
});
});
});
7 changes: 7 additions & 0 deletions src/register/data/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_N
export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_SUGGESTIONS';
export const REGISTRATION_CLEAR_BACKEND_ERROR = 'REGISTRATION_CLEAR_BACKEND_ERROR';
export const REGISTER_SET_COUNTRY_CODE = 'REGISTER_SET_COUNTRY_CODE';
export const REGISTER_SET_EMAIL_SUGGESTIONS = 'REGISTER_SET_EMAIL_SUGGESTIONS';
export const REGISTER_SET_USER_PIPELINE_DATA_LOADED = 'REGISTER_SET_USER_PIPELINE_DATA_LOADED';

// Backup registration form
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
13 changes: 12 additions & 1 deletion src/register/data/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import {
REGISTER_CLEAR_USERNAME_SUGGESTIONS,
REGISTER_FORM_VALIDATIONS,
REGISTER_NEW_USER,
REGISTER_SET_COUNTRY_CODE, REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTER_SET_COUNTRY_CODE,
REGISTER_SET_EMAIL_SUGGESTIONS,
REGISTER_SET_USER_PIPELINE_DATA_LOADED,
REGISTRATION_CLEAR_BACKEND_ERROR,
} from './actions';
import {
Expand Down Expand Up @@ -119,6 +121,15 @@ 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
24 changes: 24 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 @@ -64,6 +65,29 @@ 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

0 comments on commit 988fead

Please sign in to comment.