Skip to content

Commit

Permalink
fix: apply frontend validations (#1041)
Browse files Browse the repository at this point in the history
Description:
Applied frontend validations on all the fields
VAN-1614
  • Loading branch information
ahtesham-quraish authored Aug 24, 2023
1 parent 90db7ba commit aeec576
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/data/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
export const LETTER_REGEX = /[a-zA-Z]/;
export const USERNAME_REGEX = /^[a-zA-Z0-9_-]*$/i;
export const NUMBER_REGEX = /\d/;
export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi; // eslint-disable-line no-useless-escape

Expand Down
101 changes: 90 additions & 11 deletions src/register/EmbeddableRegistrationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import {
FORM_SUBMISSION_ERROR,
} from './data/constants';
import { registrationErrorSelector, validationsSelector } from './data/selectors';
import {
emailRegex, getSuggestionForInvalidEmail, urlRegex, validateCountryField, validateEmailAddress,
} from './data/utils';
import messages from './messages';
import RegistrationFailure from './RegistrationFailure';
import { EmailField, UsernameField } from './registrationFields';
Expand All @@ -36,7 +39,7 @@ import {
fieldDescriptionSelector,
} from '../common-components/data/selectors';
import {
DEFAULT_STATE, REDIRECT,
DEFAULT_STATE, LETTER_REGEX, NUMBER_REGEX, REDIRECT, USERNAME_REGEX,
} from '../data/constants';
import {
getAllPossibleQueryParams, setCookie,
Expand Down Expand Up @@ -173,16 +176,76 @@ const EmbeddableRegistrationPage = (props) => {
}
}, [registrationResult, host]);

const validateInput = (fieldName, value, payload, shouldValidateFromBackend) => {
const validateInput = (fieldName, value, payload, shouldValidateFromBackend, shouldSetErrors = true) => {
let fieldError = '';

switch (fieldName) {
case 'name':
if (value && !payload.username.trim() && shouldValidateFromBackend) {
validateFromBackend(payload);
case 'name':
if (value && value.match(urlRegex)) {
fieldError = formatMessage(messages['name.validation.message']);
} else if (value && !payload.username.trim() && shouldValidateFromBackend) {
validateFromBackend(payload);
}
break;
case 'email':
if (value.length <= 2) {
fieldError = formatMessage(messages['email.invalid.format.error']);
} else {
const [username, domainName] = value.split('@');
// Check if email address is invalid. If we have a suggestion for invalid email
// provide that along with the error message.
if (!emailRegex.test(value)) {
fieldError = formatMessage(messages['email.invalid.format.error']);
setEmailSuggestion({
suggestion: getSuggestionForInvalidEmail(domainName, username),
type: 'error',
});
} else {
const response = validateEmailAddress(value, username, domainName);
if (response.hasError) {
fieldError = formatMessage(messages['email.invalid.format.error']);
delete response.hasError;
}
setEmailSuggestion({ ...response });
}
break;
default:
break;
}
break;
case 'username':
if (!value.match(USERNAME_REGEX)) {
fieldError = formatMessage(messages['username.format.validation.message']);
}
break;
case 'password':
if (!value || !LETTER_REGEX.test(value) || !NUMBER_REGEX.test(value) || value.length < 8) {
fieldError = formatMessage(messages['password.validation.message']);
}
break;
case 'country':
if (flags.showConfigurableEdxFields || flags.showConfigurableRegistrationFields) {
const {
countryCode, displayValue, error,
} = validateCountryField(value.trim(), countryList, formatMessage(messages['empty.country.field.error']));
fieldError = error;
setConfigurableFormFields(prevState => ({ ...prevState, country: { countryCode, displayValue } }));
}
break;
default:
if (flags.showConfigurableRegistrationFields) {
if (!value && fieldDescriptions[fieldName]?.error_message) {
fieldError = fieldDescriptions[fieldName].error_message;
} else if (fieldName === 'confirm_email' && formFields.email && value !== formFields.email) {
fieldError = formatMessage(messages['email.do.not.match']);
}
}
break;
}
if (shouldSetErrors && fieldError) {
setErrors(prevErrors => ({
...prevErrors,
[fieldName]: fieldError,
}));
}
return fieldError;
};

const isFormValid = (payload) => {
Expand Down Expand Up @@ -226,6 +289,10 @@ const EmbeddableRegistrationPage = (props) => {
event.preventDefault();
setErrors(prevErrors => ({ ...prevErrors, [fieldName]: '' }));
switch (fieldName) {
case 'email':
setFormFields(prevState => ({ ...prevState, email: emailSuggestion.suggestion }));
setEmailSuggestion({ suggestion: '', type: '' });
break;
case 'username':
setFormFields(prevState => ({ ...prevState, username: suggestion }));
props.resetUsernameSuggestions();
Expand Down Expand Up @@ -267,8 +334,12 @@ const EmbeddableRegistrationPage = (props) => {
value,
{ name: formFields.name, username: formFields.username, form_field_key: name },
!validationApiRateLimited,
false,
);
}
if (name === 'email') {
validateInput(name, value, null, !validationApiRateLimited, false);
}
};

const handleOnFocus = (event) => {
Expand All @@ -294,7 +365,6 @@ const EmbeddableRegistrationPage = (props) => {
e.preventDefault();
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
let payload = { ...formFields };

if (!isFormValid(payload)) {
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
return;
Expand All @@ -307,12 +377,20 @@ const EmbeddableRegistrationPage = (props) => {
payload[fieldName] = configurableFormFields[fieldName];
}
});

// Don't send the marketing email opt-in value if the flag is turned off
if (!flags.showMarketingEmailOptInCheckbox) {
delete payload.marketingEmailsOptIn;
}

let isValid = true;
Object.entries(payload).forEach(([key, value]) => {
if (validateInput(key, value, payload, false, true) !== '') {
isValid = false;
}
});
if (!isValid) {
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
return;
}
payload = snakeCaseObject(payload);
payload.totalRegistrationTime = totalRegistrationTime;

Expand Down Expand Up @@ -350,6 +428,7 @@ const EmbeddableRegistrationPage = (props) => {
handleChange={handleOnChange}
handleBlur={handleOnBlur}
handleFocus={handleOnFocus}
handleSuggestionClick={(e) => handleSuggestionClick(e, 'email')}
handleOnClose={handleEmailSuggestionClosed}
emailSuggestion={emailSuggestion}
errorMessage={errors.email}
Expand Down
91 changes: 91 additions & 0 deletions src/register/tests/EmbeddableRegistrationPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,97 @@ describe('RegistrationPage', () => {

expect(registrationPage.find('input#username').prop('value')).toEqual('test-user');
});
it('should run username and email frontend validations', () => {
const payload = {
name: 'John Doe',
username: '[email protected]',
email: '[email protected]',
password: 'password1',
country: 'Pakistan',
honor_code: true,
totalRegistrationTime: 0,
marketing_emails_opt_in: true,
};

store.dispatch = jest.fn(store.dispatch);
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
populateRequiredFields(registrationPage, payload);
registrationPage.find('input[name="email"]').simulate('focus');
registrationPage.find('input[name="email"]').simulate('blur', { target: { value: '[email protected]', name: 'email' } });
expect(registrationPage.find('.email-suggestion__text').exists()).toBeTruthy();

registrationPage.find('input[name="email"]').simulate('focus');
registrationPage.find('input[name="email"]').simulate('blur', { target: { value: 'asasasasas', name: 'email' } });

registrationPage.find('button.btn-brand').simulate('click');
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeTruthy();
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeTruthy();
});
it('should run email frontend validations when random string is input', () => {
const payload = {
name: 'John Doe',
username: '[email protected]',
email: 'as',
password: 'password1',
country: 'Pakistan',
honor_code: true,
totalRegistrationTime: 0,
marketing_emails_opt_in: true,
};

const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
populateRequiredFields(registrationPage, payload);

registrationPage.find('button.btn-brand').simulate('click');
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeTruthy();
});
it('should run frontend validations for name field', () => {
const payload = {
name: 'https://localhost.com',
username: '[email protected]',
email: 'as',
password: 'password1',
country: 'Pakistan',
honor_code: true,
totalRegistrationTime: 0,
marketing_emails_opt_in: true,
};

const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
populateRequiredFields(registrationPage, payload);

registrationPage.find('button.btn-brand').simulate('click');
expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy();
});

it('should run frontend validations for password field', () => {
const payload = {
name: 'https://localhost.com',
username: '[email protected]',
email: 'as',
password: 'as',
country: 'Pakistan',
honor_code: true,
totalRegistrationTime: 0,
marketing_emails_opt_in: true,
};

const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
populateRequiredFields(registrationPage, payload);

registrationPage.find('button.btn-brand').simulate('click');
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeTruthy();
});

it('should click on email suggestion in case suggestion is avialable', () => {
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
registrationPage.find('input[name="email"]').simulate('focus');
registrationPage.find('input[name="email"]').simulate('blur', { target: { value: '[email protected]', name: 'email' } });

registrationPage.find('a.email-suggestion-alert-warning').simulate('click');
expect(registrationPage.find('input#email').prop('value')).toEqual('[email protected]');
});

it('should remove extra character if username is more than 30 character long', () => {
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
registrationPage.find('input#username').simulate('change', { target: { value: 'why_this_is_not_valid_username_', name: 'username' } });
Expand Down

0 comments on commit aeec576

Please sign in to comment.