Skip to content

Commit aeec576

Browse files
fix: apply frontend validations (#1041)
Description: Applied frontend validations on all the fields VAN-1614
1 parent 90db7ba commit aeec576

File tree

3 files changed

+182
-11
lines changed

3 files changed

+182
-11
lines changed

src/data/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
3232
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
3333
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
3434
export const LETTER_REGEX = /[a-zA-Z]/;
35+
export const USERNAME_REGEX = /^[a-zA-Z0-9_-]*$/i;
3536
export const NUMBER_REGEX = /\d/;
3637
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
3738

src/register/EmbeddableRegistrationPage.jsx

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import {
2525
FORM_SUBMISSION_ERROR,
2626
} from './data/constants';
2727
import { registrationErrorSelector, validationsSelector } from './data/selectors';
28+
import {
29+
emailRegex, getSuggestionForInvalidEmail, urlRegex, validateCountryField, validateEmailAddress,
30+
} from './data/utils';
2831
import messages from './messages';
2932
import RegistrationFailure from './RegistrationFailure';
3033
import { EmailField, UsernameField } from './registrationFields';
@@ -36,7 +39,7 @@ import {
3639
fieldDescriptionSelector,
3740
} from '../common-components/data/selectors';
3841
import {
39-
DEFAULT_STATE, REDIRECT,
42+
DEFAULT_STATE, LETTER_REGEX, NUMBER_REGEX, REDIRECT, USERNAME_REGEX,
4043
} from '../data/constants';
4144
import {
4245
getAllPossibleQueryParams, setCookie,
@@ -173,16 +176,76 @@ const EmbeddableRegistrationPage = (props) => {
173176
}
174177
}, [registrationResult, host]);
175178

176-
const validateInput = (fieldName, value, payload, shouldValidateFromBackend) => {
179+
const validateInput = (fieldName, value, payload, shouldValidateFromBackend, shouldSetErrors = true) => {
180+
let fieldError = '';
181+
177182
switch (fieldName) {
178-
case 'name':
179-
if (value && !payload.username.trim() && shouldValidateFromBackend) {
180-
validateFromBackend(payload);
183+
case 'name':
184+
if (value && value.match(urlRegex)) {
185+
fieldError = formatMessage(messages['name.validation.message']);
186+
} else if (value && !payload.username.trim() && shouldValidateFromBackend) {
187+
validateFromBackend(payload);
188+
}
189+
break;
190+
case 'email':
191+
if (value.length <= 2) {
192+
fieldError = formatMessage(messages['email.invalid.format.error']);
193+
} else {
194+
const [username, domainName] = value.split('@');
195+
// Check if email address is invalid. If we have a suggestion for invalid email
196+
// provide that along with the error message.
197+
if (!emailRegex.test(value)) {
198+
fieldError = formatMessage(messages['email.invalid.format.error']);
199+
setEmailSuggestion({
200+
suggestion: getSuggestionForInvalidEmail(domainName, username),
201+
type: 'error',
202+
});
203+
} else {
204+
const response = validateEmailAddress(value, username, domainName);
205+
if (response.hasError) {
206+
fieldError = formatMessage(messages['email.invalid.format.error']);
207+
delete response.hasError;
208+
}
209+
setEmailSuggestion({ ...response });
181210
}
182-
break;
183-
default:
184-
break;
211+
}
212+
break;
213+
case 'username':
214+
if (!value.match(USERNAME_REGEX)) {
215+
fieldError = formatMessage(messages['username.format.validation.message']);
216+
}
217+
break;
218+
case 'password':
219+
if (!value || !LETTER_REGEX.test(value) || !NUMBER_REGEX.test(value) || value.length < 8) {
220+
fieldError = formatMessage(messages['password.validation.message']);
221+
}
222+
break;
223+
case 'country':
224+
if (flags.showConfigurableEdxFields || flags.showConfigurableRegistrationFields) {
225+
const {
226+
countryCode, displayValue, error,
227+
} = validateCountryField(value.trim(), countryList, formatMessage(messages['empty.country.field.error']));
228+
fieldError = error;
229+
setConfigurableFormFields(prevState => ({ ...prevState, country: { countryCode, displayValue } }));
230+
}
231+
break;
232+
default:
233+
if (flags.showConfigurableRegistrationFields) {
234+
if (!value && fieldDescriptions[fieldName]?.error_message) {
235+
fieldError = fieldDescriptions[fieldName].error_message;
236+
} else if (fieldName === 'confirm_email' && formFields.email && value !== formFields.email) {
237+
fieldError = formatMessage(messages['email.do.not.match']);
238+
}
239+
}
240+
break;
185241
}
242+
if (shouldSetErrors && fieldError) {
243+
setErrors(prevErrors => ({
244+
...prevErrors,
245+
[fieldName]: fieldError,
246+
}));
247+
}
248+
return fieldError;
186249
};
187250

188251
const isFormValid = (payload) => {
@@ -226,6 +289,10 @@ const EmbeddableRegistrationPage = (props) => {
226289
event.preventDefault();
227290
setErrors(prevErrors => ({ ...prevErrors, [fieldName]: '' }));
228291
switch (fieldName) {
292+
case 'email':
293+
setFormFields(prevState => ({ ...prevState, email: emailSuggestion.suggestion }));
294+
setEmailSuggestion({ suggestion: '', type: '' });
295+
break;
229296
case 'username':
230297
setFormFields(prevState => ({ ...prevState, username: suggestion }));
231298
props.resetUsernameSuggestions();
@@ -267,8 +334,12 @@ const EmbeddableRegistrationPage = (props) => {
267334
value,
268335
{ name: formFields.name, username: formFields.username, form_field_key: name },
269336
!validationApiRateLimited,
337+
false,
270338
);
271339
}
340+
if (name === 'email') {
341+
validateInput(name, value, null, !validationApiRateLimited, false);
342+
}
272343
};
273344

274345
const handleOnFocus = (event) => {
@@ -294,7 +365,6 @@ const EmbeddableRegistrationPage = (props) => {
294365
e.preventDefault();
295366
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
296367
let payload = { ...formFields };
297-
298368
if (!isFormValid(payload)) {
299369
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
300370
return;
@@ -307,12 +377,20 @@ const EmbeddableRegistrationPage = (props) => {
307377
payload[fieldName] = configurableFormFields[fieldName];
308378
}
309379
});
310-
311380
// Don't send the marketing email opt-in value if the flag is turned off
312381
if (!flags.showMarketingEmailOptInCheckbox) {
313382
delete payload.marketingEmailsOptIn;
314383
}
315-
384+
let isValid = true;
385+
Object.entries(payload).forEach(([key, value]) => {
386+
if (validateInput(key, value, payload, false, true) !== '') {
387+
isValid = false;
388+
}
389+
});
390+
if (!isValid) {
391+
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
392+
return;
393+
}
316394
payload = snakeCaseObject(payload);
317395
payload.totalRegistrationTime = totalRegistrationTime;
318396

@@ -350,6 +428,7 @@ const EmbeddableRegistrationPage = (props) => {
350428
handleChange={handleOnChange}
351429
handleBlur={handleOnBlur}
352430
handleFocus={handleOnFocus}
431+
handleSuggestionClick={(e) => handleSuggestionClick(e, 'email')}
353432
handleOnClose={handleEmailSuggestionClosed}
354433
emailSuggestion={emailSuggestion}
355434
errorMessage={errors.email}

src/register/tests/EmbeddableRegistrationPage.test.jsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,97 @@ describe('RegistrationPage', () => {
234234

235235
expect(registrationPage.find('input#username').prop('value')).toEqual('test-user');
236236
});
237+
it('should run username and email frontend validations', () => {
238+
const payload = {
239+
name: 'John Doe',
240+
username: '[email protected]',
241+
242+
password: 'password1',
243+
country: 'Pakistan',
244+
honor_code: true,
245+
totalRegistrationTime: 0,
246+
marketing_emails_opt_in: true,
247+
};
248+
249+
store.dispatch = jest.fn(store.dispatch);
250+
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
251+
populateRequiredFields(registrationPage, payload);
252+
registrationPage.find('input[name="email"]').simulate('focus');
253+
registrationPage.find('input[name="email"]').simulate('blur', { target: { value: '[email protected]', name: 'email' } });
254+
expect(registrationPage.find('.email-suggestion__text').exists()).toBeTruthy();
255+
256+
registrationPage.find('input[name="email"]').simulate('focus');
257+
registrationPage.find('input[name="email"]').simulate('blur', { target: { value: 'asasasasas', name: 'email' } });
258+
259+
registrationPage.find('button.btn-brand').simulate('click');
260+
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeTruthy();
261+
expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeTruthy();
262+
});
263+
it('should run email frontend validations when random string is input', () => {
264+
const payload = {
265+
name: 'John Doe',
266+
username: '[email protected]',
267+
email: 'as',
268+
password: 'password1',
269+
country: 'Pakistan',
270+
honor_code: true,
271+
totalRegistrationTime: 0,
272+
marketing_emails_opt_in: true,
273+
};
274+
275+
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
276+
populateRequiredFields(registrationPage, payload);
277+
278+
registrationPage.find('button.btn-brand').simulate('click');
279+
expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeTruthy();
280+
});
281+
it('should run frontend validations for name field', () => {
282+
const payload = {
283+
name: 'https://localhost.com',
284+
username: '[email protected]',
285+
email: 'as',
286+
password: 'password1',
287+
country: 'Pakistan',
288+
honor_code: true,
289+
totalRegistrationTime: 0,
290+
marketing_emails_opt_in: true,
291+
};
292+
293+
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
294+
populateRequiredFields(registrationPage, payload);
295+
296+
registrationPage.find('button.btn-brand').simulate('click');
297+
expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy();
298+
});
299+
300+
it('should run frontend validations for password field', () => {
301+
const payload = {
302+
name: 'https://localhost.com',
303+
username: '[email protected]',
304+
email: 'as',
305+
password: 'as',
306+
country: 'Pakistan',
307+
honor_code: true,
308+
totalRegistrationTime: 0,
309+
marketing_emails_opt_in: true,
310+
};
311+
312+
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
313+
populateRequiredFields(registrationPage, payload);
314+
315+
registrationPage.find('button.btn-brand').simulate('click');
316+
expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeTruthy();
317+
});
318+
319+
it('should click on email suggestion in case suggestion is avialable', () => {
320+
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
321+
registrationPage.find('input[name="email"]').simulate('focus');
322+
registrationPage.find('input[name="email"]').simulate('blur', { target: { value: '[email protected]', name: 'email' } });
323+
324+
registrationPage.find('a.email-suggestion-alert-warning').simulate('click');
325+
expect(registrationPage.find('input#email').prop('value')).toEqual('[email protected]');
326+
});
327+
237328
it('should remove extra character if username is more than 30 character long', () => {
238329
const registrationPage = mount(reduxWrapper(<IntlEmbedableRegistrationForm {...props} />));
239330
registrationPage.find('input#username').simulate('change', { target: { value: 'why_this_is_not_valid_username_', name: 'username' } });

0 commit comments

Comments
 (0)