Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
scratch-www
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
xpstem
scratch-www
Commits
e0658585
Unverified
Commit
e0658585
authored
Oct 21, 2019
by
Benjamin Wheeler
Committed by
GitHub
Oct 21, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3462 from benjiwheeler/join-flow-error-state-limit-one-retry
Join flow error state limit one retry
parents
0fb41cdd
6d3a379d
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
141 additions
and
23 deletions
+141
-23
src/components/join-flow/email-step.jsx
src/components/join-flow/email-step.jsx
+4
-2
src/components/join-flow/join-flow.jsx
src/components/join-flow/join-flow.jsx
+30
-4
src/components/join-flow/registration-error-step.jsx
src/components/join-flow/registration-error-step.jsx
+7
-3
src/l10n.json
src/l10n.json
+2
-1
test/unit/components/join-flow.test.jsx
test/unit/components/join-flow.test.jsx
+69
-0
test/unit/components/registration-error-step.test.jsx
test/unit/components/registration-error-step.test.jsx
+29
-13
No files found.
src/components/join-flow/email-step.jsx
View file @
e0658585
...
...
@@ -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
;
...
...
src/components/join-flow/join-flow.jsx
View file @
e0658585
...
...
@@ -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 */
on
TryAgain=
{
()
=>
this
.
handleSubmitRegistration
(
this
.
state
.
formData
)
}
on
Submit=
{
this
.
handleErrorNext
}
/* eslint-enable react/jsx-no-bind */
/>
)
:
(
...
...
src/components/join-flow/registration-error-step.jsx
View file @
e0658585
...
...
@@ -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
.
on
TryAgain
();
this
.
props
.
on
Submit
();
}
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
,
on
TryAgain
:
PropTypes
.
func
on
Submit
:
PropTypes
.
func
};
const
IntlRegistrationErrorStep
=
injectIntl
(
RegistrationErrorStep
);
...
...
src/l10n.json
View file @
e0658585
...
...
@@ -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
P
roject"
,
"registration.makeProject"
:
"Make a
p
roject"
,
"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"
,
...
...
test/unit/components/join-flow.test.jsx
View file @
e0658585
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
);
});
});
test/unit/components/registration-error-step.test.jsx
View file @
e0658585
...
...
@@ -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
(
on
TryAgain
).
toHaveBeenCalled
();
expect
(
on
Submit
).
toHaveBeenCalled
();
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment