Unverified Commit e0658585 authored by Benjamin Wheeler's avatar Benjamin Wheeler Committed by GitHub

Merge pull request #3462 from benjiwheeler/join-flow-error-state-limit-one-retry

Join flow error state limit one retry
parents 0fb41cdd 6d3a379d
......@@ -39,8 +39,10 @@ class EmailStep extends React.Component {
// automatically start with focus on username field
if (this.emailInput) this.emailInput.focus();
// If grecaptcha doesn't exist on window, we havent loaded the captcha js yet. Load it.
if (!window.grecaptcha) {
if (window.grecaptcha) {
this.onCaptchaLoad();
} else {
// If grecaptcha doesn't exist on window, we havent loaded the captcha js yet. Load it.
// ReCaptcha calls a callback when the grecatpcha object is usable. That callback
// needs to be global so set it on the window.
window.grecaptchaOnLoad = this.onCaptchaLoad;
......
......@@ -23,17 +23,26 @@ class JoinFlow extends React.Component {
super(props);
bindAll(this, [
'handleAdvanceStep',
'handleErrorNext',
'handleRegistrationError',
'handlePrepareToRegister',
'handleRegistrationResponse',
'handleSubmitRegistration'
]);
this.state = {
this.initialState = {
numAttempts: 0,
formData: {},
registrationError: null,
step: 0,
waiting: false
};
// it's ok to set state by reference, because state is treated as immutable,
// so any changes to its fields will result in a new state which does not
// reference its past fields
this.state = this.initialState;
}
canTryAgain () {
return (this.state.numAttempts <= 1);
}
handleRegistrationError (message) {
if (!message) {
......@@ -64,7 +73,11 @@ class JoinFlow extends React.Component {
// "success": false
// }
// ]
this.setState({waiting: false}, () => {
// username: 'username exists'
this.setState({
numAttempts: this.state.numAttempts + 1,
waiting: false
}, () => {
let errStr = '';
if (!err && res.statusCode === 200) {
if (body && body[0]) {
......@@ -100,7 +113,9 @@ class JoinFlow extends React.Component {
});
}
handleSubmitRegistration (formData) {
this.setState({waiting: true}, () => {
this.setState({
waiting: true
}, () => {
api({
host: '',
uri: '/accounts/register_new_user/',
......@@ -133,14 +148,25 @@ class JoinFlow extends React.Component {
step: this.state.step + 1
});
}
handleErrorNext () {
if (this.canTryAgain()) {
this.handleSubmitRegistration(this.state.formData);
} else {
this.resetState();
}
}
resetState () {
this.setState(this.initialState);
}
render () {
return (
<React.Fragment>
{this.state.registrationError ? (
<RegistrationErrorStep
canTryAgain={this.canTryAgain()}
errorMsg={this.state.registrationError}
/* eslint-disable react/jsx-no-bind */
onTryAgain={() => this.handleSubmitRegistration(this.state.formData)}
onSubmit={this.handleErrorNext}
/* eslint-enable react/jsx-no-bind */
/>
) : (
......
......@@ -19,14 +19,17 @@ class RegistrationErrorStep extends React.Component {
// But here, we're not really submitting, so we need to prevent
// the form from navigating away from the current page.
e.preventDefault();
this.props.onTryAgain();
this.props.onSubmit();
}
render () {
return (
<JoinFlowStep
description={this.props.errorMsg}
innerClassName="join-flow-registration-error"
nextButton={this.props.intl.formatMessage({id: 'general.tryAgain'})}
nextButton={this.props.canTryAgain ?
this.props.intl.formatMessage({id: 'general.tryAgain'}) :
this.props.intl.formatMessage({id: 'general.startOver'})
}
title={this.props.intl.formatMessage({id: 'registration.generalError'})}
onSubmit={this.handleSubmit}
/>
......@@ -35,9 +38,10 @@ class RegistrationErrorStep extends React.Component {
}
RegistrationErrorStep.propTypes = {
canTryAgain: PropTypes.bool,
errorMsg: PropTypes.string,
intl: intlShape,
onTryAgain: PropTypes.func
onSubmit: PropTypes.func
};
const IntlRegistrationErrorStep = injectIntl(RegistrationErrorStep);
......
......@@ -82,6 +82,7 @@
"general.search": "Search",
"general.searchEmpty": "Nothing found",
"general.signIn": "Sign in",
"general.startOver": "Start over",
"general.statistics": "Statistics",
"general.studios": "Studios",
"general.support": "Support",
......@@ -182,7 +183,7 @@
"registration.invitedBy": "invited by",
"registration.lastStepTitle": "Thank you for requesting a Scratch Teacher Account",
"registration.lastStepDescription": "We are currently processing your application. ",
"registration.makeProject": "Make a Project",
"registration.makeProject": "Make a project",
"registration.mustBeNewStudent": "You must be a new student to complete your registration",
"registration.nameStepTooltip": "This information is used for verification and to aggregate usage statistics.",
"registration.newPassword": "New Password",
......
import React from 'react';
const {shallowWithIntl} = require('../../helpers/intl-helpers.jsx');
const defaults = require('lodash.defaultsdeep');
import configureStore from 'redux-mock-store';
import JoinFlow from '../../../src/components/join-flow/join-flow';
import Progression from '../../../src/components/progression/progression.jsx';
......@@ -126,6 +127,7 @@ describe('JoinFlow', () => {
expect(joinFlowInstance.props.refreshSession).not.toHaveBeenCalled();
expect(joinFlowInstance.state.registrationError).toBe('registration.generalError (400)');
});
test('handleRegistrationError with no message ', () => {
const joinFlowInstance = getJoinFlowWrapper().instance();
joinFlowInstance.setState({});
......@@ -175,4 +177,71 @@ describe('JoinFlow', () => {
expect(registrationErrorWrapper).toHaveLength(0);
expect(progressionWrapper).toHaveLength(1);
});
test('when numAttempts is 0, RegistrationErrorStep receives canTryAgain prop with value true', () => {
const joinFlowWrapper = getJoinFlowWrapper();
joinFlowWrapper.instance().setState({
numAttempts: 0,
registrationError: 'halp there is a errors!!'
});
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(true);
});
test('when numAttempts is 1, RegistrationErrorStep receives canTryAgain prop with value true', () => {
const joinFlowWrapper = getJoinFlowWrapper();
joinFlowWrapper.instance().setState({
numAttempts: 1,
registrationError: 'halp there is a errors!!'
});
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(true);
});
test('when numAttempts is 2, RegistrationErrorStep receives canTryAgain prop with value false', () => {
const joinFlowWrapper = getJoinFlowWrapper();
joinFlowWrapper.instance().setState({
numAttempts: 2,
registrationError: 'halp there is a errors!!'
});
const registrationErrorWrapper = joinFlowWrapper.find(RegistrationErrorStep);
expect(registrationErrorWrapper.first().props().canTryAgain).toEqual(false);
});
test('resetState resets entire state, does not leave any state keys out', () => {
const joinFlowWrapper = getJoinFlowWrapper();
const joinFlowInstance = joinFlowWrapper.instance();
Object.keys(joinFlowInstance.state).forEach(key => {
joinFlowInstance.setState({[key]: 'Different than the initial value'});
});
joinFlowInstance.resetState();
Object.keys(joinFlowInstance.state).forEach(key => {
expect(joinFlowInstance.state[key]).not.toEqual('Different than the initial value');
});
});
test('resetState makes each state field match initial state', () => {
const joinFlowWrapper = getJoinFlowWrapper();
const joinFlowInstance = joinFlowWrapper.instance();
const stateSnapshot = {};
Object.keys(joinFlowInstance.state).forEach(key => {
stateSnapshot[key] = joinFlowInstance.state[key];
});
joinFlowInstance.resetState();
Object.keys(joinFlowInstance.state).forEach(key => {
expect(stateSnapshot[key]).toEqual(joinFlowInstance.state[key]);
});
});
test('calling resetState results in state.formData which is not same reference as before', () => {
const joinFlowWrapper = getJoinFlowWrapper();
const joinFlowInstance = joinFlowWrapper.instance();
joinFlowInstance.setState({
formData: defaults({}, {username: 'abcdef'})
});
const formDataReference = joinFlowInstance.state.formData;
joinFlowInstance.resetState();
expect(formDataReference).not.toBe(joinFlowInstance.state.formData);
expect(formDataReference).not.toEqual(joinFlowInstance.state.formData);
});
});
......@@ -4,30 +4,46 @@ import JoinFlowStep from '../../../src/components/join-flow/join-flow-step';
import RegistrationErrorStep from '../../../src/components/join-flow/registration-error-step';
describe('RegistrationErrorStep', () => {
const onTryAgain = jest.fn();
let wrapper;
const onSubmit = jest.fn();
beforeEach(() => {
wrapper = shallowWithIntl(
const getRegistrationErrorStepWrapper = props => {
const wrapper = shallowWithIntl(
<RegistrationErrorStep
errorMsg={'error message'}
onTryAgain={onTryAgain}
onSubmit={onSubmit}
{...props}
/>
);
});
return wrapper
.dive(); // unwrap injectIntl()
};
test('shows JoinFlowStep with props', () => {
// Dive to get past the anonymous component.
const joinFlowStepWrapper = wrapper.dive().find(JoinFlowStep);
test('when canTryAgain is true, show tryAgain message', () => {
const props = {canTryAgain: true};
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
expect(joinFlowStepWrapper).toHaveLength(1);
expect(joinFlowStepWrapper.props().description).toBe('error message');
expect(joinFlowStepWrapper.props().nextButton).toBe('general.tryAgain');
});
test('when submitted, onTryAgain is called', () => {
// Dive to get past the anonymous component.
const joinFlowStepWrapper = wrapper.dive().find(JoinFlowStep);
test('when canTryAgain is false, show startOver message', () => {
const props = {canTryAgain: false};
const joinFlowStepWrapper = getRegistrationErrorStepWrapper(props).find(JoinFlowStep);
expect(joinFlowStepWrapper).toHaveLength(1);
expect(joinFlowStepWrapper.props().description).toBe('error message');
expect(joinFlowStepWrapper.props().nextButton).toBe('general.startOver');
});
test('when canTryAgain is missing, show startOver message', () => {
const joinFlowStepWrapper = getRegistrationErrorStepWrapper().find(JoinFlowStep);
expect(joinFlowStepWrapper).toHaveLength(1);
expect(joinFlowStepWrapper.props().description).toBe('error message');
expect(joinFlowStepWrapper.props().nextButton).toBe('general.startOver');
});
test('when submitted, onSubmit is called', () => {
const joinFlowStepWrapper = getRegistrationErrorStepWrapper().find(JoinFlowStep);
joinFlowStepWrapper.props().onSubmit(new Event('event')); // eslint-disable-line no-undef
expect(onTryAgain).toHaveBeenCalled();
expect(onSubmit).toHaveBeenCalled();
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment