Merge pull request #3367 from picklesrus/captcha-error-handling

Handle errors of captcha loading by setting error state on JoinFlow.
This commit is contained in:
picklesrus 2019-09-23 23:57:13 -04:00 committed by GitHub
commit 5f3d6506c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 3 deletions

View file

@ -56,7 +56,11 @@ class EmailStep extends React.Component {
this.emailInput = emailInputRef; this.emailInput = emailInputRef;
} }
onCaptchaError () { onCaptchaError () {
// TODO send user to error step once we have one. this.props.onRegistrationError(
this.props.intl.formatMessage({
id: 'registation.troubleReload'
})
);
} }
onCaptchaLoad () { onCaptchaLoad () {
this.setState({captchaIsLoading: false}); this.setState({captchaIsLoading: false});
@ -64,9 +68,9 @@ class EmailStep extends React.Component {
if (!this.grecaptcha) { if (!this.grecaptcha) {
// According to the reCaptcha documentation, this callback shouldn't get // According to the reCaptcha documentation, this callback shouldn't get
// called unless window.grecaptcha exists. This is just here to be extra defensive. // called unless window.grecaptcha exists. This is just here to be extra defensive.
// TODO: Put up the error screen when we have one. this.onCaptchaError();
return;
} }
// TODO: Add in error callback for render once we have an error screen.
this.widgetId = this.grecaptcha.render(this.captchaRef, this.widgetId = this.grecaptcha.render(this.captchaRef,
{ {
callback: this.captchaSolved, callback: this.captchaSolved,
@ -208,6 +212,7 @@ class EmailStep extends React.Component {
EmailStep.propTypes = { EmailStep.propTypes = {
intl: intlShape, intl: intlShape,
onNextStep: PropTypes.func, onNextStep: PropTypes.func,
onRegistrationError: PropTypes.func,
waiting: PropTypes.bool waiting: PropTypes.bool
}; };

View file

@ -23,6 +23,7 @@ class JoinFlow extends React.Component {
super(props); super(props);
bindAll(this, [ bindAll(this, [
'handleAdvanceStep', 'handleAdvanceStep',
'handleRegistrationError',
'handlePrepareToRegister', 'handlePrepareToRegister',
'handleRegistrationResponse', 'handleRegistrationResponse',
'handleSubmitRegistration' 'handleSubmitRegistration'
@ -34,6 +35,14 @@ class JoinFlow extends React.Component {
waiting: false waiting: false
}; };
} }
handleRegistrationError (message) {
if (!message) {
message = this.props.intl.formatMessage({
id: 'registration.generalError'
});
}
this.setState({registrationError: message});
}
handlePrepareToRegister (newFormData) { handlePrepareToRegister (newFormData) {
newFormData = newFormData || {}; newFormData = newFormData || {};
const newState = { const newState = {
@ -143,6 +152,7 @@ class JoinFlow extends React.Component {
<EmailStep <EmailStep
waiting={this.state.waiting} waiting={this.state.waiting}
onNextStep={this.handlePrepareToRegister} onNextStep={this.handlePrepareToRegister}
onRegistrationError={this.handleRegistrationError}
/> />
<WelcomeStep <WelcomeStep
email={this.state.formData.email} email={this.state.formData.email}

View file

@ -187,6 +187,7 @@
"registration.selectCountry": "select country", "registration.selectCountry": "select country",
"registration.studentPersonalStepDescription": "This information will not appear on the Scratch website.", "registration.studentPersonalStepDescription": "This information will not appear on the Scratch website.",
"registration.showPassword": "Show password", "registration.showPassword": "Show password",
"registration.troubleReload": "Scratch is having trouble finishing registration. Try reloading the page or try again in another browser.",
"registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to one day.", "registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to one day.",
"registration.usernameStepDescriptionNonEducator": "Create projects, share ideas, make friends. Its free!", "registration.usernameStepDescriptionNonEducator": "Create projects, share ideas, make friends. Its free!",
"registration.usernameStepRealName": "Please do not use any portion of your real name in your username.", "registration.usernameStepRealName": "Please do not use any portion of your real name in your username.",

View file

@ -50,6 +50,7 @@ describe('EmailStep test', () => {
expect(emailInputWrapper.props().onSetRef).toEqual(formikWrapper.instance().handleSetEmailRef); expect(emailInputWrapper.props().onSetRef).toEqual(formikWrapper.instance().handleSetEmailRef);
expect(emailInputWrapper.props().validate).toEqual(formikWrapper.instance().validateEmail); expect(emailInputWrapper.props().validate).toEqual(formikWrapper.instance().validateEmail);
}); });
test('props sent to FormikCheckbox for subscribe', () => { test('props sent to FormikCheckbox for subscribe', () => {
const wrapper = shallowWithIntl(<EmailStep />); const wrapper = shallowWithIntl(<EmailStep />);
// Dive to get past the intl wrapper // Dive to get past the intl wrapper
@ -63,6 +64,7 @@ describe('EmailStep test', () => {
expect(checkboxWrapper.first().props().label).toEqual('registration.receiveEmails'); expect(checkboxWrapper.first().props().label).toEqual('registration.receiveEmails');
expect(checkboxWrapper.first().props().name).toEqual('subscribe'); expect(checkboxWrapper.first().props().name).toEqual('subscribe');
}); });
test('handleValidSubmit passes formData to next step', () => { test('handleValidSubmit passes formData to next step', () => {
const formikBag = { const formikBag = {
setSubmitting: jest.fn() setSubmitting: jest.fn()
@ -82,6 +84,7 @@ describe('EmailStep test', () => {
expect(formikBag.setSubmitting).toHaveBeenCalledWith(false); expect(formikBag.setSubmitting).toHaveBeenCalledWith(false);
expect(global.grecaptcha.execute).toHaveBeenCalled(); expect(global.grecaptcha.execute).toHaveBeenCalled();
}); });
test('captchaSolved sets token and goes to next step', () => { test('captchaSolved sets token and goes to next step', () => {
const props = { const props = {
onNextStep: jest.fn() onNextStep: jest.fn()
@ -116,6 +119,38 @@ describe('EmailStep test', () => {
})); }));
expect(formikBag.setSubmitting).toHaveBeenCalledWith(true); expect(formikBag.setSubmitting).toHaveBeenCalledWith(true);
}); });
test('onCaptchaError calls error function with correct message', () => {
const props = {
onRegistrationError: jest.fn()
};
const wrapper = shallowWithIntl(
<EmailStep
{...props}
/>);
const formikWrapper = wrapper.dive();
formikWrapper.instance().onCaptchaError();
expect(props.onRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
});
test('Captcha load error calls error function', () => {
const props = {
onRegistrationError: jest.fn()
};
// Set this to null to force an error.
global.grecaptcha = null;
const wrapper = shallowWithIntl(
<EmailStep
{...props}
/>);
const formikWrapper = wrapper.dive();
formikWrapper.instance().onCaptchaLoad();
expect(props.onRegistrationError).toHaveBeenCalledWith('registation.troubleReload');
});
test('validateEmail test email empty', () => { test('validateEmail test email empty', () => {
const wrapper = shallowWithIntl( const wrapper = shallowWithIntl(
<EmailStep />); <EmailStep />);
@ -123,6 +158,7 @@ describe('EmailStep test', () => {
const val = formikWrapper.instance().validateEmail(''); const val = formikWrapper.instance().validateEmail('');
expect(val).toBe('general.required'); expect(val).toBe('general.required');
}); });
test('validateEmail test email null', () => { test('validateEmail test email null', () => {
const wrapper = shallowWithIntl( const wrapper = shallowWithIntl(
<EmailStep />); <EmailStep />);
@ -130,6 +166,7 @@ describe('EmailStep test', () => {
const val = formikWrapper.instance().validateEmail(null); const val = formikWrapper.instance().validateEmail(null);
expect(val).toBe('general.required'); expect(val).toBe('general.required');
}); });
test('validateEmail test email undefined', () => { test('validateEmail test email undefined', () => {
const wrapper = shallowWithIntl( const wrapper = shallowWithIntl(
<EmailStep />); <EmailStep />);

View file

@ -126,6 +126,19 @@ describe('JoinFlow', () => {
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled(); expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError (400)'); expect(joinFlowInstance.state.registrationError).toBe('registration.generalError (400)');
}); });
test('handleRegistrationError with no message ', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
joinFlowInstance.setState({});
joinFlowInstance.handleRegistrationError();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError');
});
test('handleRegistrationError with message ', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
joinFlowInstance.setState({});
joinFlowInstance.handleRegistrationError('my message');
expect(joinFlowInstance.state.registrationError).toBe('my message');
});
test('handleAdvanceStep', () => { test('handleAdvanceStep', () => {
const joinFlowInstance = getJoinFlowWrapper().instance(); const joinFlowInstance = getJoinFlowWrapper().instance();