Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split fullname #1159

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/register/RegistrationFields/NameField/NameField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,23 @@ const NameField = (props) => {
const {
handleErrorChange,
shouldFetchUsernameSuggestions,
name,
fullName,
} = props;

const handleOnBlur = (e) => {
const { value } = e.target;
const fieldError = validateName(value, formatMessage);
const fieldError = validateName(value, name, formatMessage);
if (fieldError) {
handleErrorChange('name', fieldError);
handleErrorChange(name, fieldError);
} else if (shouldFetchUsernameSuggestions && !validationApiRateLimited) {
dispatch(fetchRealtimeValidations({ name: value }));
dispatch(fetchRealtimeValidations({ name: fullName.trim() }));
}
};

const handleOnFocus = () => {
handleErrorChange('name', '');
dispatch(clearRegistrationBackendError('name'));
handleErrorChange(name, '');
dispatch(clearRegistrationBackendError(name));
};

return (
Expand All @@ -56,6 +58,7 @@ const NameField = (props) => {
NameField.defaultProps = {
errorMessage: '',
shouldFetchUsernameSuggestions: false,
fullName: '',
};

NameField.propTypes = {
Expand All @@ -64,6 +67,8 @@ NameField.propTypes = {
value: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired,
handleErrorChange: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
fullName: PropTypes.string,
};

export default NameField;
78 changes: 55 additions & 23 deletions src/register/RegistrationFields/NameField/NameField.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('NameField', () => {
beforeEach(() => {
store = mockStore(initialState);
props = {
name: 'name',
name: '',
value: '',
errorMessage: '',
handleChange: jest.fn(),
Expand All @@ -66,43 +66,44 @@ describe('NameField', () => {
});

describe('Test Name Field', () => {
const fieldValidation = { name: 'Enter your full name' };

it('should run name field validation when onBlur is fired', () => {
it('should run first name field validation when onBlur is fired', () => {
props.name = 'firstName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

const nameInput = container.querySelector('input#name');
fireEvent.blur(nameInput, { target: { value: '', name: 'name' } });
const firstNameInput = container.querySelector('input#firstName');
fireEvent.blur(firstNameInput, { target: { value: '', name: 'firstName' } });

expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'name',
fieldValidation.name,
'firstName',
'Enter your first name',
);
});

it('should update errors for frontend validations', () => {
it('should update first name field error for frontend validations', () => {
props.name = 'firstName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

const nameInput = container.querySelector('input#name');
fireEvent.blur(nameInput, { target: { value: 'https://invalid-name.com', name: 'name' } });
const firstNameInput = container.querySelector('input#firstName');
fireEvent.blur(firstNameInput, { target: { value: 'https://invalid-name.com', name: 'firstName' } });

expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'name',
'Enter a valid name',
'firstName',
'Enter a valid first name',
);
});

it('should clear error on focus', () => {
it('should clear first name error on focus', () => {
props.name = 'firstName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

const nameInput = container.querySelector('input#name');
fireEvent.focus(nameInput, { target: { value: '', name: 'name' } });
const firstNameInput = container.querySelector('input#firstName');
fireEvent.focus(firstNameInput, { target: { value: '', name: 'firstName' } });

expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'name',
'firstName',
'',
);
});
Expand All @@ -112,14 +113,16 @@ describe('NameField', () => {
props = {
...props,
shouldFetchUsernameSuggestions: true,
fullName: 'test test',
};
props.name = 'lastName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

const nameInput = container.querySelector('input#name');
const lastNameInput = container.querySelector('input#lastName');
// Enter a valid name so that frontend validations are passed
fireEvent.blur(nameInput, { target: { value: 'test', name: 'name' } });
fireEvent.blur(lastNameInput, { target: { value: 'test', name: 'lastName' } });

expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ name: 'test' }));
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ name: props.fullName }));
});

it('should clear the registration validation error on focus on field', () => {
Expand All @@ -134,14 +137,43 @@ describe('NameField', () => {
},
});

props.name = 'lastName';
store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

const nameInput = container.querySelector('input#name');
const lastNameInput = container.querySelector('input#lastName');

fireEvent.focus(lastNameInput, { target: { value: 'test', name: 'lastName' } });

fireEvent.focus(nameInput, { target: { value: 'test', name: 'name' } });
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('lastName'));
});

it('should run last name field validation when onBlur is fired', () => {
props.name = 'lastName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('name'));
const lastNameInput = container.querySelector('input#lastName');
fireEvent.blur(lastNameInput, { target: { value: '', name: 'lastName' } });

expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'lastName',
'Enter your last name',
);
});

it('should update last name field error for frontend validation', () => {
props.name = 'lastName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));

const lastNameInput = container.querySelector('input#lastName');
fireEvent.blur(lastNameInput, { target: { value: 'https://invalid-name.com', name: 'lastName' } });

expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'lastName',
'Enter a valid last name',
);
});
});
});
10 changes: 7 additions & 3 deletions src/register/RegistrationFields/NameField/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ export const HTML_REGEX = /<|>/u;
// regex from backend
export const INVALID_NAME_REGEX = /https?:\/\/(?:[-\w.]|(?:%[\da-fA-F]{2}))*/g;

const validateName = (value, formatMessage) => {
const validateName = (value, fieldName, formatMessage) => {
let fieldError;
if (!value.trim()) {
fieldError = formatMessage(messages['empty.name.field.error']);
fieldError = fieldName === 'lastName'
? formatMessage(messages['empty.lastName.field.error'])
: formatMessage(messages['empty.firstName.field.error']);
} else if (URL_REGEX.test(value) || HTML_REGEX.test(value) || INVALID_NAME_REGEX.test(value)) {
fieldError = formatMessage(messages['name.validation.message']);
fieldError = fieldName === 'lastName'
? formatMessage(messages['lastName.validation.message'])
: formatMessage(messages['firstName.validation.message']);
}
return fieldError;
};
Expand Down
24 changes: 17 additions & 7 deletions src/register/RegistrationPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ const RegistrationPage = (props) => {
setErrorCode(prevState => ({ type: TPA_AUTHENTICATION_FAILURE, count: prevState.count + 1 }));
}
if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0) {
const { name = '', username = '', email = '' } = pipelineUserDetails;
const {
firstName = '', lastName = '', username = '', email = '',
} = pipelineUserDetails;
setFormFields(prevState => ({
...prevState, name, username, email,
...prevState, firstName, lastName, username, email,
}));
dispatch(setUserPipelineDataLoaded(true));
}
Expand Down Expand Up @@ -310,14 +312,22 @@ const RegistrationPage = (props) => {
/>
<Form id="registration-form" name="registration-form">
<NameField
name="name"
value={formFields.name}
name="firstName"
value={formFields.firstName}
handleChange={handleOnChange}
handleErrorChange={handleErrorChange}
errorMessage={errors.firstName}
floatingLabel={formatMessage(messages['registration.firstName.label'])}
/>
<NameField
name="lastName"
value={formFields.lastName}
shouldFetchUsernameSuggestions={!formFields.username.trim()}
fullName={`${formFields.firstName} ${formFields.lastName}`}
handleChange={handleOnChange}
handleErrorChange={handleErrorChange}
errorMessage={errors.name}
helpText={[formatMessage(messages['help.text.name'])]}
floatingLabel={formatMessage(messages['registration.fullname.label'])}
errorMessage={errors.lastName}
floatingLabel={formatMessage(messages['registration.lastName.label'])}
/>
<EmailField
name="email"
Expand Down
40 changes: 26 additions & 14 deletions src/register/RegistrationPage.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ describe('RegistrationPage', () => {
marketingEmailsOptIn: true,
},
formFields: {
name: '', email: '', username: '', password: '',
firstName: '', lastName: '', email: '', username: '', password: '',
},
emailSuggestion: {
suggestion: '', type: '',
},
errors: {
name: '', email: '', username: '', password: '',
firstName: '', lastName: '', email: '', username: '', password: '',
},
};

Expand Down Expand Up @@ -134,7 +134,8 @@ describe('RegistrationPage', () => {
});

const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => {
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } });
fireEvent.change(getByLabelText('First name'), { target: { value: payload.first_name, name: 'firstName' } });
fireEvent.change(getByLabelText('Last name'), { target: { value: payload.last_name, name: 'lastName' } });
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });

Expand All @@ -152,7 +153,8 @@ describe('RegistrationPage', () => {
});

const emptyFieldValidation = {
name: 'Enter your full name',
firstName: 'Enter your first name',
lastName: 'Enter your last name',
username: 'Username must be between 2 and 30 characters',
email: 'Enter your email',
password: 'Password criteria has not been met',
Expand All @@ -169,7 +171,8 @@ describe('RegistrationPage', () => {
window.location = { href: getConfig().BASE_URL, search: '?next=/course/demo-course-url' };

const payload = {
name: 'John Doe',
first_name: 'John',
last_name: 'Doe',
username: 'john_doe',
email: '[email protected]',
password: 'password1',
Expand All @@ -192,7 +195,8 @@ describe('RegistrationPage', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);

const formPayload = {
name: 'John Doe',
first_name: 'John',
last_name: 'Doe',
username: 'john_doe',
email: '[email protected]',
country: 'Pakistan',
Expand Down Expand Up @@ -228,7 +232,8 @@ describe('RegistrationPage', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0);

const payload = {
name: 'John Doe',
first_name: 'John',
last_name: 'Doe',
username: 'john_doe',
email: '[email protected]',
password: 'password1',
Expand Down Expand Up @@ -579,7 +584,8 @@ describe('RegistrationPage', () => {
registrationFormData: {
...registrationFormData,
formFields: {
name: 'John Doe',
firstName: 'John',
lastName: 'Doe',
username: 'john_doe',
email: '[email protected]',
password: 'password1',
Expand All @@ -593,13 +599,15 @@ describe('RegistrationPage', () => {

const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));

const fullNameInput = container.querySelector('input#name');
const firstNameInput = container.querySelector('input#firstName');
const lastNameInput = container.querySelector('input#lastName');
const usernameInput = container.querySelector('input#username');
const emailInput = container.querySelector('input#email');
const passwordInput = container.querySelector('input#password');
const emailSuggestion = container.querySelector('.email-suggestion-alert-warning');

expect(fullNameInput.value).toEqual('John Doe');
expect(firstNameInput.value).toEqual('John');
expect(lastNameInput.value).toEqual('Doe');
expect(usernameInput.value).toEqual('john_doe');
expect(emailInput.value).toEqual('[email protected]');
expect(passwordInput.value).toEqual('password1');
Expand Down Expand Up @@ -720,7 +728,8 @@ describe('RegistrationPage', () => {
thirdPartyAuthContext: {
...initialState.commonComponents.thirdPartyAuthContext,
pipelineUserDetails: {
name: 'John Doe',
firstName: 'John',
lastName: 'Doe',
username: 'john_doe',
email: '[email protected]',
},
Expand Down Expand Up @@ -751,7 +760,8 @@ describe('RegistrationPage', () => {
registrationFormData: {
...registrationFormData,
formFields: {
name: 'John Doe',
firstName: 'John',
lastName: 'Doe',
username: 'john_doe',
email: '[email protected]',
},
Expand All @@ -771,7 +781,8 @@ describe('RegistrationPage', () => {
...initialState.commonComponents.thirdPartyAuthContext,
currentProvider: 'Apple',
pipelineUserDetails: {
name: 'John Doe',
firstName: 'John',
lastName: 'Doe',
username: 'john_doe',
email: '[email protected]',
},
Expand All @@ -783,7 +794,8 @@ describe('RegistrationPage', () => {

render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({
name: 'John Doe',
first_name: 'John',
last_name: 'Doe',
username: 'john_doe',
email: '[email protected]',
country: 'PK',
Expand Down
Loading
Loading