mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Merge pull request #3462 from benjiwheeler/join-flow-error-state-limit-one-retry
Join flow error state limit one retry
This commit is contained in:
commit
e0658585cc
6 changed files with 141 additions and 23 deletions
|
@ -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",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue