Adds missing backend format validations error on submit.
VAN-283
This commit is contained in:
@@ -3,6 +3,10 @@ import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { Alert } from '@edx/paragon';
|
||||
|
||||
const hasNoErrors = (userErrors) => (
|
||||
userErrors.every((errorList) => (!errorList[0]))
|
||||
);
|
||||
|
||||
const RegistrationFailureMessage = (props) => {
|
||||
const errorMessage = props.errors;
|
||||
const userErrors = [];
|
||||
@@ -24,20 +28,22 @@ const RegistrationFailureMessage = (props) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Alert variant="danger">
|
||||
<Alert.Heading className="text-danger">
|
||||
<FormattedMessage
|
||||
id="logistration.registration.request.failure.header.description.message"
|
||||
defaultMessage="We couldn't create your account."
|
||||
description="error message when registration failure."
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<div>
|
||||
<ul>
|
||||
{userErrors}
|
||||
</ul>
|
||||
</div>
|
||||
</Alert>
|
||||
hasNoErrors(userErrors) ? null : (
|
||||
<Alert variant="danger">
|
||||
<Alert.Heading className="text-danger">
|
||||
<FormattedMessage
|
||||
id="logistration.registration.request.failure.header.description.message"
|
||||
defaultMessage="We couldn't create your account."
|
||||
description="error message when registration failure."
|
||||
/>
|
||||
</Alert.Heading>
|
||||
<div>
|
||||
<ul>
|
||||
{userErrors}
|
||||
</ul>
|
||||
</div>
|
||||
</Alert>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -61,7 +61,15 @@ class RegistrationPage extends React.Component {
|
||||
confirmEmail: '',
|
||||
enableOptionalField: false,
|
||||
validationFieldName: '',
|
||||
emptyFields: {},
|
||||
validationErrorsAlertMessages: {
|
||||
name: [{ user_message: '' }],
|
||||
username: [{ user_message: '' }],
|
||||
email: [{ user_message: '' }],
|
||||
emailFormat: [{ user_message: '' }],
|
||||
password: [{ user_message: '' }],
|
||||
country: [{ user_message: '' }],
|
||||
},
|
||||
currentValidations: null,
|
||||
errors: {
|
||||
email: '',
|
||||
name: '',
|
||||
@@ -78,8 +86,8 @@ class RegistrationPage extends React.Component {
|
||||
countryValid: false,
|
||||
honorCodeValid: true,
|
||||
termsOfServiceValid: false,
|
||||
formValid: false,
|
||||
institutionLogin: false,
|
||||
formValid: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,44 +101,34 @@ class RegistrationPage extends React.Component {
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
if (this.props.validations !== nextProps.validations) {
|
||||
if (nextProps.statusCode !== 403 && this.props.validations !== nextProps.validations) {
|
||||
const { errors } = this.state;
|
||||
const errorMsg = nextProps.validations.validation_decisions[this.state.validationFieldName];
|
||||
errors[this.state.validationFieldName] = errorMsg;
|
||||
const stateValidKey = `${camelCase(this.state.validationFieldName)}Valid`;
|
||||
const stateValidValue = !errorMsg;
|
||||
const currentValidations = nextProps.validations.validation_decisions;
|
||||
|
||||
this.setState(({ [stateValidKey]: stateValidValue }), () => {
|
||||
const {
|
||||
emailValid,
|
||||
nameValid,
|
||||
usernameValid,
|
||||
passwordValid,
|
||||
} = this.state;
|
||||
|
||||
const validityMap = REGISTRATION_VALIDITY_MAP;
|
||||
let extraFieldsValid = true;
|
||||
Object.entries(validityMap).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
const stateValid = `${camelCase(key)}Valid`;
|
||||
extraFieldsValid = extraFieldsValid && this.state[stateValid];
|
||||
}
|
||||
});
|
||||
|
||||
const formValid = emailValid && nameValid && usernameValid && passwordValid && extraFieldsValid;
|
||||
this.setState({
|
||||
errors,
|
||||
formValid,
|
||||
});
|
||||
this.setState({
|
||||
[stateValidKey]: !errorMsg,
|
||||
errors,
|
||||
currentValidations,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.props.thirdPartyAuthContext.pipelineUserDetails !== nextProps.thirdPartyAuthContext.pipelineUserDetails) {
|
||||
this.setState({
|
||||
...nextProps.thirdPartyAuthContext.pipelineUserDetails,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.props.registrationError !== nextProps.registrationError) {
|
||||
this.setState({
|
||||
formValid: false,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -141,19 +139,23 @@ class RegistrationPage extends React.Component {
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
const params = (new URL(document.location)).searchParams;
|
||||
const payload = {
|
||||
name: this.state.name,
|
||||
username: this.state.username,
|
||||
email: this.state.email,
|
||||
password: this.state.password,
|
||||
};
|
||||
const payload = {};
|
||||
const payloadMap = new Map();
|
||||
payloadMap.set('name', this.state.name);
|
||||
payloadMap.set('username', this.state.username);
|
||||
payloadMap.set('email', this.state.email);
|
||||
|
||||
if (!this.props.thirdPartyAuthContext.currentProvider) {
|
||||
payloadMap.set('password', this.state.password);
|
||||
}
|
||||
|
||||
const fieldMap = { ...REGISTRATION_VALIDITY_MAP, ...REGISTRATION_OPTIONAL_MAP };
|
||||
Object.entries(fieldMap).forEach(([key, value]) => {
|
||||
if (value) {
|
||||
payload[key] = this.state[camelCase(key)];
|
||||
payloadMap.set(key, this.state[camelCase(key)]);
|
||||
}
|
||||
});
|
||||
payloadMap.forEach((value, key) => { payload[key] = value; });
|
||||
|
||||
const next = params.get('next');
|
||||
const courseId = params.get('course_id');
|
||||
@@ -163,16 +165,35 @@ class RegistrationPage extends React.Component {
|
||||
if (courseId) {
|
||||
payload.course_id = params.course_id;
|
||||
}
|
||||
if (!this.state.formValid) {
|
||||
|
||||
let finalValidation = this.isFormValid();
|
||||
if (!this.isFormValid()) {
|
||||
// Special case where honor code and tos is a single field, true by default. We don't need
|
||||
// to validate this field
|
||||
Object.entries(payload).filter(([key]) => (key !== 'honor_code' || 'terms_of_service' in REGISTRATION_VALIDITY_MAP))
|
||||
.forEach(([key, value]) => {
|
||||
this.validateInput(key, value);
|
||||
});
|
||||
return;
|
||||
payloadMap.forEach((value, key) => {
|
||||
if (key !== 'honor_code' || 'terms_of_service' in REGISTRATION_VALIDITY_MAP) {
|
||||
finalValidation = this.validateInput(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.props.registerNewUser(payload);
|
||||
if (finalValidation) {
|
||||
this.props.registerNewUser(payload);
|
||||
} else {
|
||||
this.props.fetchRealtimeValidations(payload);
|
||||
}
|
||||
}
|
||||
|
||||
checkNoValidationsErrors(validations) {
|
||||
let keyValidList = null;
|
||||
keyValidList = Object.entries(validations).map(([key]) => {
|
||||
const validation = validations[key][0];
|
||||
return !validation.user_message;
|
||||
});
|
||||
return keyValidList.every((current) => current === true);
|
||||
}
|
||||
|
||||
isFormValid() {
|
||||
return this.state.formValid;
|
||||
}
|
||||
|
||||
handleOnBlur(e) {
|
||||
@@ -206,39 +227,85 @@ class RegistrationPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
validateInput(inputName, value) {
|
||||
handleOnClick(e) {
|
||||
const { errors } = this.state;
|
||||
const { emptyFields } = this.state;
|
||||
let {
|
||||
emailValid,
|
||||
nameValid,
|
||||
usernameValid,
|
||||
passwordValid,
|
||||
countryValid,
|
||||
honorCodeValid,
|
||||
termsOfServiceValid,
|
||||
if (this.state.currentValidations) {
|
||||
const fieldName = e.target.name;
|
||||
errors[fieldName] = this.state.currentValidations[fieldName];
|
||||
const stateValidKey = `${camelCase(fieldName)}Valid`;
|
||||
this.setState(prevState => ({
|
||||
[stateValidKey]: !prevState.currentValidations[fieldName],
|
||||
errors,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
validateInput(inputName, value) {
|
||||
const {
|
||||
errors,
|
||||
validationErrorsAlertMessages,
|
||||
} = this.state;
|
||||
|
||||
let {
|
||||
honorCodeValid,
|
||||
termsOfServiceValid,
|
||||
formValid,
|
||||
} = this.state;
|
||||
|
||||
const validations = this.state.currentValidations;
|
||||
switch (inputName) {
|
||||
case 'email':
|
||||
emailValid = value.length >= 1;
|
||||
emptyFields.email = this.generateUserMessage(emailValid, 'logistration.email.validation.message');
|
||||
if (this.props.statusCode !== 403 && validations && validations.email) {
|
||||
validationErrorsAlertMessages.email = [{ user_message: validations.email }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.email.validation.message');
|
||||
validationErrorsAlertMessages.email = errorEmpty;
|
||||
} else {
|
||||
const errorCharlength = this.generateUserMessage(value.length <= 2, 'logistration.email.ratelimit.less.chars.validation.message');
|
||||
const formatError = this.generateUserMessage(!value.match(/^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i), 'logistration.email.ratelimit.incorrect.format.validation.message');
|
||||
validationErrorsAlertMessages.email = errorCharlength;
|
||||
validationErrorsAlertMessages.emailFormat = formatError;
|
||||
}
|
||||
break;
|
||||
case 'name':
|
||||
nameValid = value.length >= 1;
|
||||
emptyFields.name = this.generateUserMessage(nameValid, 'logistration.fullname.validation.message');
|
||||
if (this.props.statusCode !== 403 && validations && validations.name) {
|
||||
validationErrorsAlertMessages.name = [{ user_message: validations.name }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.fullname.validation.message');
|
||||
validationErrorsAlertMessages.name = errorEmpty;
|
||||
} else {
|
||||
validationErrorsAlertMessages.name = [{ user_message: '' }];
|
||||
}
|
||||
break;
|
||||
case 'username':
|
||||
usernameValid = value.length >= 1;
|
||||
emptyFields.username = this.generateUserMessage(usernameValid, 'logistration.username.validation.message');
|
||||
if (this.props.statusCode !== 403 && validations && validations.username) {
|
||||
validationErrorsAlertMessages.username = [{ user_message: validations.username }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.username.validation.message');
|
||||
validationErrorsAlertMessages.username = errorEmpty;
|
||||
} else {
|
||||
const errorCharLength = this.generateUserMessage(value.length <= 1, 'logistration.username.ratelimit.less.chars.message');
|
||||
validationErrorsAlertMessages.username = errorCharLength;
|
||||
}
|
||||
break;
|
||||
case 'password':
|
||||
passwordValid = value.length >= 1;
|
||||
emptyFields.password = this.generateUserMessage(passwordValid, 'logistration.register.page.password.validation.message');
|
||||
if (this.props.statusCode !== 403 && validations && validations.password) {
|
||||
validationErrorsAlertMessages.password = [{ user_message: validations.password }];
|
||||
} else if (value.length < 1) {
|
||||
const errorEmpty = this.generateUserMessage(value.length < 1, 'logistration.register.page.password.validation.message');
|
||||
validationErrorsAlertMessages.password = errorEmpty;
|
||||
} else {
|
||||
const errorCharLength = this.generateUserMessage(value.length < 8, 'logistration.email.ratelimit.password.validation.message');
|
||||
validationErrorsAlertMessages.password = errorCharLength;
|
||||
}
|
||||
break;
|
||||
case 'country':
|
||||
countryValid = value !== '';
|
||||
emptyFields.country = this.generateUserMessage(countryValid, 'logistration.country.validation.message');
|
||||
if (this.props.statusCode !== 403 && validations && validations.country) {
|
||||
validationErrorsAlertMessages.country = [{ user_message: validations.country }];
|
||||
} else {
|
||||
const emptyError = this.generateUserMessage(value === '', 'logistration.country.validation.message');
|
||||
validationErrorsAlertMessages.country = emptyError;
|
||||
}
|
||||
break;
|
||||
case 'honor_code':
|
||||
honorCodeValid = value !== false;
|
||||
@@ -252,16 +319,14 @@ class RegistrationPage extends React.Component {
|
||||
break;
|
||||
}
|
||||
|
||||
formValid = this.checkNoValidationsErrors(validationErrorsAlertMessages);
|
||||
this.setState({
|
||||
emptyFields,
|
||||
emailValid,
|
||||
nameValid,
|
||||
usernameValid,
|
||||
passwordValid,
|
||||
countryValid,
|
||||
formValid,
|
||||
validationErrorsAlertMessages,
|
||||
honorCodeValid,
|
||||
termsOfServiceValid,
|
||||
});
|
||||
return formValid;
|
||||
}
|
||||
|
||||
addExtraRequiredFields() {
|
||||
@@ -341,6 +406,7 @@ class RegistrationPage extends React.Component {
|
||||
}));
|
||||
props.options = options;
|
||||
props.onBlur = e => this.handleOnBlur(e);
|
||||
props.onClick = e => this.handleOnClick(e);
|
||||
}
|
||||
return (
|
||||
<ValidationFormGroup
|
||||
@@ -409,14 +475,14 @@ class RegistrationPage extends React.Component {
|
||||
return fields;
|
||||
}
|
||||
|
||||
generateUserMessage(isFieldValid, messageID) {
|
||||
return [{ user_message: isFieldValid ? '' : this.intl.formatMessage(messages[messageID]) }];
|
||||
generateUserMessage(isFieldInValid, messageID) {
|
||||
return [{ user_message: isFieldInValid ? this.intl.formatMessage(messages[messageID]) : '' }];
|
||||
}
|
||||
|
||||
renderErrors() {
|
||||
let errorsObject = null;
|
||||
if (Object.keys(this.state.emptyFields).length > 0) {
|
||||
errorsObject = this.state.emptyFields;
|
||||
if (!this.checkNoValidationsErrors(this.state.validationErrorsAlertMessages)) {
|
||||
errorsObject = this.state.validationErrorsAlertMessages;
|
||||
} else if (this.props.registrationError) {
|
||||
errorsObject = this.props.registrationError;
|
||||
} else {
|
||||
@@ -492,6 +558,7 @@ class RegistrationPage extends React.Component {
|
||||
invalid={this.state.errors.name !== ''}
|
||||
invalidMessage={this.state.errors.name}
|
||||
className="mb-0"
|
||||
helpText="This name will be used by any certificates that you earn."
|
||||
>
|
||||
<label htmlFor="name" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.fullname.label'])}
|
||||
@@ -504,6 +571,7 @@ class RegistrationPage extends React.Component {
|
||||
value={this.state.name}
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
onBlur={e => this.handleOnBlur(e)}
|
||||
onClick={e => this.handleOnClick(e)}
|
||||
required
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
@@ -512,6 +580,7 @@ class RegistrationPage extends React.Component {
|
||||
invalid={this.state.errors.username !== ''}
|
||||
invalidMessage={this.state.errors.username}
|
||||
className="mb-0"
|
||||
helpText="The name that will identify you in your courses. It cannot be changed later."
|
||||
>
|
||||
<label htmlFor="username" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.username.label'])}
|
||||
@@ -522,8 +591,10 @@ class RegistrationPage extends React.Component {
|
||||
type="text"
|
||||
placeholder=""
|
||||
value={this.state.username}
|
||||
maxLength="30"
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
onBlur={e => this.handleOnBlur(e)}
|
||||
onClick={e => this.handleOnClick(e)}
|
||||
required
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
@@ -532,6 +603,7 @@ class RegistrationPage extends React.Component {
|
||||
invalid={this.state.errors.email !== ''}
|
||||
invalidMessage={this.state.errors.email}
|
||||
className="mb-0"
|
||||
helpText="This is what you will use to login."
|
||||
>
|
||||
<label htmlFor="email" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.register.page.email.label'])}
|
||||
@@ -544,6 +616,7 @@ class RegistrationPage extends React.Component {
|
||||
value={this.state.email}
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
onBlur={e => this.handleOnBlur(e)}
|
||||
onClick={e => this.handleOnClick(e)}
|
||||
required
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
@@ -553,6 +626,7 @@ class RegistrationPage extends React.Component {
|
||||
invalid={this.state.errors.password !== ''}
|
||||
invalidMessage={this.state.errors.password}
|
||||
className="mb-0"
|
||||
helpText="Your password must contain at least 8 characters, including 1 letter & 1 number."
|
||||
>
|
||||
<label htmlFor="password" className="h6 pt-10">
|
||||
{intl.formatMessage(messages['logistration.password.label'])}
|
||||
@@ -565,6 +639,7 @@ class RegistrationPage extends React.Component {
|
||||
value={this.state.password}
|
||||
onChange={e => this.handleOnChange(e)}
|
||||
onBlur={e => this.handleOnBlur(e)}
|
||||
onClick={e => this.handleOnClick(e)}
|
||||
required
|
||||
/>
|
||||
</ValidationFormGroup>
|
||||
@@ -620,6 +695,7 @@ RegistrationPage.defaultProps = {
|
||||
},
|
||||
formData: null,
|
||||
validations: null,
|
||||
statusCode: null,
|
||||
};
|
||||
|
||||
RegistrationPage.propTypes = {
|
||||
@@ -664,6 +740,7 @@ RegistrationPage.propTypes = {
|
||||
username: PropTypes.string,
|
||||
}),
|
||||
}),
|
||||
statusCode: PropTypes.number,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
@@ -676,6 +753,7 @@ const mapStateToProps = state => {
|
||||
thirdPartyAuthContext,
|
||||
formData: state.logistration.formData,
|
||||
validations: state.logistration.validations,
|
||||
statusCode: state.logistration.statusCode,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@ export const fetchRealtimeValidationsSuccess = (validations) => ({
|
||||
payload: { validations },
|
||||
});
|
||||
|
||||
export const fetchRealtimeValidationsFailure = () => ({
|
||||
export const fetchRealtimeValidationsFailure = (error, statusCode) => ({
|
||||
type: REGISTER_FORM_VALIDATIONS.FAILURE,
|
||||
payload: { error, statusCode },
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ export const defaultState = {
|
||||
registrationResult: {},
|
||||
formData: null,
|
||||
validations: null,
|
||||
statusCode: null,
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
@@ -89,6 +90,8 @@ const reducer = (state = defaultState, action) => {
|
||||
case REGISTER_FORM_VALIDATIONS.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
validations: action.payload.error,
|
||||
statusCode: action.payload.statusCode,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -116,7 +116,10 @@ export function* fetchRealtimeValidations(action) {
|
||||
fieldValidations,
|
||||
));
|
||||
} catch (e) {
|
||||
yield put(fetchRealtimeValidationsFailure());
|
||||
const statusCodes = [403];
|
||||
if (e.response && statusCodes.includes(e.response.status)) {
|
||||
yield put(fetchRealtimeValidationsFailure(e.response.data, e.response.status));
|
||||
}
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +180,16 @@ describe('fetchRealtimeValidations', () => {
|
||||
});
|
||||
|
||||
it('should call service and dispatch error action', async () => {
|
||||
const validationRatelimitResponse = {
|
||||
response: {
|
||||
status: 403,
|
||||
data: {
|
||||
detail: 'You do not have permission to perform this action.',
|
||||
},
|
||||
},
|
||||
};
|
||||
const getFieldsValidations = jest.spyOn(api, 'getFieldsValidations')
|
||||
.mockImplementation(() => Promise.reject(new Error('something went wrong')));
|
||||
.mockImplementation(() => Promise.reject(validationRatelimitResponse));
|
||||
|
||||
const dispatched = [];
|
||||
await runSaga(
|
||||
@@ -194,7 +202,10 @@ describe('fetchRealtimeValidations', () => {
|
||||
expect(loggingService.logError).toHaveBeenCalled();
|
||||
expect(dispatched).toEqual([
|
||||
actions.fetchRealtimeValidationsBegin(),
|
||||
actions.fetchRealtimeValidationsFailure(),
|
||||
actions.fetchRealtimeValidationsFailure(
|
||||
validationRatelimitResponse.response.data,
|
||||
validationRatelimitResponse.response.status,
|
||||
),
|
||||
]);
|
||||
getFieldsValidations.mockClear();
|
||||
});
|
||||
|
||||
@@ -131,6 +131,21 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Please enter your Email.',
|
||||
description: 'Validation message that appears when email address is empty',
|
||||
},
|
||||
'logistration.email.ratelimit.less.chars.validation.message': {
|
||||
id: 'logistration.email.ratelimit.less.chars.validation.message',
|
||||
defaultMessage: 'Email must have 3 characters.',
|
||||
description: 'Validation message that appears when email address is less than 3 characters',
|
||||
},
|
||||
'logistration.email.ratelimit.incorrect.format.validation.message': {
|
||||
id: 'logistration.email.ratelimit.incorrect.format.validation.message',
|
||||
defaultMessage: 'The email address you provided isn\'t formatted correctly.',
|
||||
description: 'Validation message that appears when email address is not formatted correctly with no backend validations available.',
|
||||
},
|
||||
'logistration.email.ratelimit.password.validation.message': {
|
||||
id: 'logistration.email.ratelimit.password.validation.message',
|
||||
defaultMessage: 'Your password must contain at least 8 characters',
|
||||
description: 'Validation message that appears when password is not formatted correctly with no backend validations available.',
|
||||
},
|
||||
'logistration.email.help.message': {
|
||||
id: 'logistration.email.help.message',
|
||||
defaultMessage: 'The email address you used to register with edX.',
|
||||
@@ -176,6 +191,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Please enter your Public Username.',
|
||||
description: 'Validation message that appears when username is invalid',
|
||||
},
|
||||
'logistration.username.ratelimit.less.chars.message': {
|
||||
id: 'logistration.username.ratelimit.less.chars.message',
|
||||
defaultMessage: 'Public Username must have atleast 2 characters.',
|
||||
description: 'Validation message that appears when username is less than 2 characters and with no backend validations available.',
|
||||
},
|
||||
'logistration.country.validation.message': {
|
||||
id: 'logistration.country.validation.message',
|
||||
defaultMessage: 'Select your country or region of residence.',
|
||||
|
||||
@@ -223,6 +223,58 @@ describe('./RegistrationPage.js', () => {
|
||||
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations(formPayload));
|
||||
});
|
||||
|
||||
it('tests shouldComponentUpdate change validations and formValid state', () => {
|
||||
const nextProps = {
|
||||
validations: {
|
||||
validation_decisions: {
|
||||
username: 'Username must be between 2 and 30 characters long.',
|
||||
},
|
||||
},
|
||||
thirdPartyAuthContext: {
|
||||
pipelineUserDetails: {
|
||||
name: 'test',
|
||||
email: 'test@example.com',
|
||||
username: 'test-username',
|
||||
},
|
||||
},
|
||||
registrationError: {
|
||||
username: [{ username: 'Username must be between 2 and 30 characters long.' }],
|
||||
},
|
||||
};
|
||||
|
||||
const root = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
|
||||
const shouldUpdate = root.find('RegistrationPage').instance().shouldComponentUpdate(nextProps);
|
||||
expect(root.find('RegistrationPage').state('currentValidations')).not.toEqual(null);
|
||||
expect(root.find('RegistrationPage').state('formValid')).not.toEqual(true);
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
it('tests onClick should change errors state via realtime validation', () => {
|
||||
const nextProps = {
|
||||
validations: {
|
||||
validation_decisions: {
|
||||
username: 'Username must be between 2 and 30 characters long.',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const errors = {
|
||||
email: '',
|
||||
name: '',
|
||||
username: 'Username must be between 2 and 30 characters long.',
|
||||
password: '',
|
||||
country: '',
|
||||
honorCode: '',
|
||||
termsOfService: '',
|
||||
};
|
||||
|
||||
const root = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
|
||||
root.find('RegistrationPage').instance().shouldComponentUpdate(nextProps);
|
||||
root.find('input#username').simulate('click', { target: { value: '', name: 'username' } });
|
||||
expect(root.find('RegistrationPage').state('errors')).toEqual(errors);
|
||||
});
|
||||
|
||||
it('should not dispatch registerNewUser on Submit', () => {
|
||||
const formPayload = {
|
||||
email: '',
|
||||
|
||||
@@ -44,17 +44,24 @@ exports[`./RegistrationPage.js should display no password field when current pro
|
||||
Full Name (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="name-help-text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="name-help-text"
|
||||
>
|
||||
This name will be used by any certificates that you earn.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -66,17 +73,25 @@ exports[`./RegistrationPage.js should display no password field when current pro
|
||||
Public Username (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="username-help-text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="username-help-text"
|
||||
>
|
||||
The name that will identify you in your courses. It cannot be changed later.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -88,17 +103,24 @@ exports[`./RegistrationPage.js should display no password field when current pro
|
||||
Email (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="email-help-text"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder="username@domain.com"
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="email-help-text"
|
||||
>
|
||||
This is what you will use to login.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group custom-control mb-0"
|
||||
@@ -248,17 +270,24 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
|
||||
Full Name (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="name-help-text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="name-help-text"
|
||||
>
|
||||
This name will be used by any certificates that you earn.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -270,17 +299,25 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
|
||||
Public Username (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="username-help-text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="username-help-text"
|
||||
>
|
||||
The name that will identify you in your courses. It cannot be changed later.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -292,17 +329,24 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
|
||||
Email (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="email-help-text"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder="username@domain.com"
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="email-help-text"
|
||||
>
|
||||
This is what you will use to login.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -314,17 +358,24 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
|
||||
Password (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="password-help-text"
|
||||
className="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="password-help-text"
|
||||
>
|
||||
Your password must contain at least 8 characters, including 1 letter & 1 number.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group custom-control mb-0"
|
||||
@@ -434,17 +485,24 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
|
||||
Full Name (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="name-help-text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="name-help-text"
|
||||
>
|
||||
This name will be used by any certificates that you earn.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -456,17 +514,25 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
|
||||
Public Username (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="username-help-text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="username-help-text"
|
||||
>
|
||||
The name that will identify you in your courses. It cannot be changed later.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -478,17 +544,24 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
|
||||
Email (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="email-help-text"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder="username@domain.com"
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="email-help-text"
|
||||
>
|
||||
This is what you will use to login.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -500,17 +573,24 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
|
||||
Password (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="password-help-text"
|
||||
className="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="password-help-text"
|
||||
>
|
||||
Your password must contain at least 8 characters, including 1 letter & 1 number.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group custom-control mb-0"
|
||||
@@ -620,17 +700,24 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
|
||||
Full Name (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="name-help-text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="name-help-text"
|
||||
>
|
||||
This name will be used by any certificates that you earn.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -642,17 +729,25 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
|
||||
Public Username (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="username-help-text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="username-help-text"
|
||||
>
|
||||
The name that will identify you in your courses. It cannot be changed later.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -664,17 +759,24 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
|
||||
Email (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="email-help-text"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder="username@domain.com"
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="email-help-text"
|
||||
>
|
||||
This is what you will use to login.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -686,17 +788,24 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
|
||||
Password (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="password-help-text"
|
||||
className="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="password-help-text"
|
||||
>
|
||||
Your password must contain at least 8 characters, including 1 letter & 1 number.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group custom-control mb-0"
|
||||
@@ -841,17 +950,24 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
|
||||
Full Name (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="name-help-text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="name-help-text"
|
||||
>
|
||||
This name will be used by any certificates that you earn.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -863,17 +979,25 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
|
||||
Public Username (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="username-help-text"
|
||||
className="form-control"
|
||||
id="username"
|
||||
maxLength="30"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="username-help-text"
|
||||
>
|
||||
The name that will identify you in your courses. It cannot be changed later.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -885,17 +1009,24 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
|
||||
Email (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="email-help-text"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder="username@domain.com"
|
||||
required={true}
|
||||
type="email"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="email-help-text"
|
||||
>
|
||||
This is what you will use to login.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group mb-0"
|
||||
@@ -907,17 +1038,24 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
|
||||
Password (required)
|
||||
</label>
|
||||
<input
|
||||
aria-describedby=""
|
||||
aria-describedby="password-help-text"
|
||||
className="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onClick={[Function]}
|
||||
placeholder=""
|
||||
required={true}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<small
|
||||
className="form-text text-muted"
|
||||
id="password-help-text"
|
||||
>
|
||||
Your password must contain at least 8 characters, including 1 letter & 1 number.
|
||||
</small>
|
||||
</div>
|
||||
<div
|
||||
className="form-group custom-control mb-0"
|
||||
|
||||
Reference in New Issue
Block a user