[next] Admin Authentication (#2155)

* feat: Support sign in via E-Mail, and social logins + permission check

* feat: no permission info

* test: unit test

* test: fix remaining tests

* test: add integration tests

* feat: add admin account completion

* test: auth completion feature tests

* fix: translation

* fix: comment marker design

* fix: linting issues

* chore: address pr review comments

* chore: add comment
This commit is contained in:
Kiwi
2019-02-04 21:26:41 +00:00
committed by Wyatt Johnson
parent 939152ee81
commit 7e8ef2189d
143 changed files with 5991 additions and 2048 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,595 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`accepts valid email 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
`;
exports[`accepts valid email confirmation 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={true}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={true}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="hans@test.com"
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
`;
exports[`checks for invalid email 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="invalid-email"
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
Please enter a valid email address.
</span>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="invalid-confirmation-email"
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
Emails do not match. Try again.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
`;
exports[`renders addEmailAddress view 1`] = `
<div>
<div
data-testid="completeAccountBox"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="CompleteAccountBox-container"
>
<div
className="CompleteAccountBox-header"
>
<h1
className="Typography-root Typography-heading3 Typography-colorTextLight Typography-alignCenter CompleteAccountBox-heading3"
>
Finish Setting Up Your Account
</h1>
<h1
className="Typography-root Typography-heading1 Typography-colorTextLight Typography-alignCenter"
>
<span>
Add Email Address
</span>
</h1>
</div>
<div
className="CompleteAccountBox-main"
>
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
`;
exports[`shows error when submitting empty form 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
`;
exports[`shows server error 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="CallOut-root CallOut-colorError CallOut-fullWidth"
>
server error
GraphQL request (4:3)
3: ) {
4: setEmail(input: $input) {
^
5: user {
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value="hans@test.com"
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
`;
exports[`successfully sets email 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
For your added security, we require users to add an email address to their accounts.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="confirmEmail"
>
Confirm Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="confirmEmail"
name="confirmEmail"
onChange={[Function]}
placeholder="Confirm Email Address"
type="text"
value=""
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Add Email Address
</button>
</div>
</form>
`;
@@ -0,0 +1,458 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`checks for invalid password 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
To protect against unauthorized changes to your account,
we require users to create a password.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Must be at least 8 characters
</p>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorError PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value="x"
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
Password must contain at least 8 characters.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Password
</button>
</div>
</form>
`;
exports[`renders createPassword view 1`] = `
<div>
<div
data-testid="completeAccountBox"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="CompleteAccountBox-container"
>
<div
className="CompleteAccountBox-header"
>
<h1
className="Typography-root Typography-heading3 Typography-colorTextLight Typography-alignCenter CompleteAccountBox-heading3"
>
Finish Setting Up Your Account
</h1>
<h1
className="Typography-root Typography-heading1 Typography-colorTextLight Typography-alignCenter"
>
<span>
Create Password
</span>
</h1>
</div>
<div
className="CompleteAccountBox-main"
>
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
To protect against unauthorized changes to your account,
we require users to create a password.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Must be at least 8 characters
</p>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Password
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
`;
exports[`shows error when submitting empty form 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
To protect against unauthorized changes to your account,
we require users to create a password.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Must be at least 8 characters
</p>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorError PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Password
</button>
</div>
</form>
`;
exports[`shows server error 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
To protect against unauthorized changes to your account,
we require users to create a password.
</p>
<div
className="CallOut-root CallOut-colorError CallOut-fullWidth"
>
server error
GraphQL request (4:3)
3: ) {
4: setPassword(input: $input) {
^
5: user {
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Must be at least 8 characters
</p>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value="secretpassword"
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Password
</button>
</div>
</form>
`;
exports[`successfully sets password 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
To protect against unauthorized changes to your account,
we require users to create a password.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
Must be at least 8 characters
</p>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Password
</button>
</div>
</form>
`;
@@ -0,0 +1,397 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`accepts valid username 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Your username is a unique identifier that will appear on all of your comments.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="username"
>
Username
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={true}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="hans"
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
disabled={true}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Username
</button>
</div>
</form>
`;
exports[`checks for invalid username 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Your username is a unique identifier that will appear on all of your comments.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="username"
>
Username
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="x"
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
Username must contain at least 3 characters.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Username
</button>
</div>
</form>
`;
exports[`renders createUsername view 1`] = `
<div>
<div
data-testid="completeAccountBox"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="CompleteAccountBox-container"
>
<div
className="CompleteAccountBox-header"
>
<h1
className="Typography-root Typography-heading3 Typography-colorTextLight Typography-alignCenter CompleteAccountBox-heading3"
>
Finish Setting Up Your Account
</h1>
<h1
className="Typography-root Typography-heading1 Typography-colorTextLight Typography-alignCenter"
>
<span>
Create Username
</span>
</h1>
</div>
<div
className="CompleteAccountBox-main"
>
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Your username is a unique identifier that will appear on all of your comments.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="username"
>
Username
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Username
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
`;
exports[`shows error when submitting empty form 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Your username is a unique identifier that will appear on all of your comments.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="username"
>
Username
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Username
</button>
</div>
</form>
`;
exports[`shows server error 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Your username is a unique identifier that will appear on all of your comments.
</p>
<div
className="CallOut-root CallOut-colorError CallOut-fullWidth"
>
server error
GraphQL request (4:3)
3: ) {
4: setUsername(input: $input) {
^
5: user {
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="username"
>
Username
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value="hans"
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Username
</button>
</div>
</form>
`;
exports[`successfully sets username 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
>
Your username is a unique identifier that will appear on all of your comments.
</p>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="username"
>
Username
</label>
<p
className="Typography-root Typography-detail Typography-colorTextSecondary"
>
You may use “_” and “.” Spaces not permitted.
</p>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="username"
name="username"
onChange={[Function]}
placeholder="Username"
type="text"
value=""
/>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
Create Username
</button>
</div>
</form>
`;
@@ -0,0 +1,134 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`show restricted screen for commenters 1`] = `
<div
data-testid="authBox"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="HorizontalGutter-root AuthBox-container HorizontalGutter-double"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="AuthBox-brandIcon"
>
<svg
className="BrandIcon-base BrandIcon-lg"
data-name="Layer 1"
id="Layer_1"
viewBox="0 0 541.77 557.72"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<clipPath
id="clip-path"
>
<rect
fill="none"
height="570"
width="554"
/>
</clipPath>
<clipPath
id="clip-path-2"
transform="translate(-8.12 -8.14)"
>
<path
clipPath="url(#clip-path)"
clipRule="evenodd"
d="M61.63,350.45c-.67,10.22,1.34,21.13,6,32.21a95.36,95.36,0,0,0,27.22,36.41c26,21.16,52.14,17.65,77.43,14.24,21.22-2.85,43.17-5.8,69,5.43,33.54,14.57,59.71,45.39,66.65,78.46a99.16,99.16,0,0,1,.11,38.66H219.65a16.89,16.89,0,0,0-.71-2.44c-16.54-44.09-38.46-69.52-67.09-77.78-9.37-2.66-18-3-25.68-3.27-9.91-.37-17.75-.66-26-5.6s-14.93-13.28-19.59-24.42a17,17,0,0,0-22.12-9.07,16.69,16.69,0,0,0-9.17,21.88c7.45,17.8,18.6,31.31,33.25,40.21,15.85,9.48,30.54,10,42.35,10.48,6.55.24,12.2.45,17.55,2,15.64,4.51,29.43,20.65,41.07,48H63.07a45,45,0,0,1-44.95-45V322.78a144.51,144.51,0,0,0,43.51,27.67Zm13-31.55a109.22,109.22,0,0,1-56.48-52.22v-66a88.41,88.41,0,0,0,35.59,10.37c18.52,1.13,34.77-4.64,47-16.71,11.63-11.51,14.6-24.24,18.05-39,2.88-12.32,6.46-27.66,16.66-48.7,10.44-21.72,17.13-30,24.69-30.51,11-.79,29.3,13.46,32.09,34.75,2,14.93-3.58,25.14-10.07,37C175,161,166,177.28,172.4,198.15c6.81,22.16,25.44,31.45,40.4,38.91,15.61,7.78,25.42,13.21,30.37,26.2,4.26,11.27,4.87,27.9-.71,33.32-3.74,3.66-16.43.64-29.86-2.55-23-5.47-54.5-12.95-91.49-1.57-9.77,3-32.07,9.72-46.51,26.44ZM357.73,18.14c-15,17.37-28.76,40.23-34.85,69-4.25,19.33-13.09,59.57,13.51,88.26A71.68,71.68,0,0,0,388.11,198a61.12,61.12,0,0,0,22.65-4.23c31.63-12.61,46.84-43.4,49.25-56.36,3.24-17.51,5-35.76,6.33-51.32a11.43,11.43,0,0,1,5.38-8.61l.5-.31a12.23,12.23,0,0,1,13.33,1.63c19.82,16.56,36.73,31.82,51.69,46.63a17.07,17.07,0,0,0,2.64,2.14v93.21a16.92,16.92,0,0,0-3.87,4.69c-9.23,16.43-23.71,36.42-40.11,38.31-6.55.7-10.3-1.49-20.21-7.9S452.37,240.8,431,236c-23.2-5.22-38.56-2.06-50.9.48-10.61,2.18-17.62,3.62-28.9,0-27.43-9-42.31-36.22-51.21-52.47l-.24-.45c-19.81-36.54-18.23-71.85-16.84-103l0-.58a287.16,287.16,0,0,1,9.81-61.77Zm49.14,0h88.06a45,45,0,0,1,44.95,45V80.79c-10.08-9.22-21-18.69-32.79-28.58a45.72,45.72,0,0,0-50.93-5.38,19.1,19.1,0,0,0-2.55,1.52,46.1,46.1,0,0,0-21,34.46,1.49,1.49,0,0,0,0,.21c-1.24,14.7-2.91,31.88-5.86,47.9-.88,3.66-9.12,23.11-28.33,30.76-12.81,5.1-29.11-1.09-37.41-10-14.22-15.33-8.26-42.48-5-57.19C363.43,59.29,386.3,35,404.17,20.82a18,18,0,0,0,2.7-2.68Zm-149.42,0a319.88,319.88,0,0,0-8.67,60.21l0,.62c-1.43,32-3.38,75.91,21,120.82l.28.52c9.81,17.93,30.25,55.25,70.55,68.52,19.95,6.48,34,3.59,46.44,1,11-2.26,20.48-4.21,36.49-.61,15.43,3.48,24.69,9.46,33.63,15.25C467,290.87,478,298,493.8,298a54,54,0,0,0,5.86-.32c14.5-1.67,27.83-8.41,40.22-20.42v45.53a179.81,179.81,0,0,1-47.68.48c-17-2.18-28.9-6.21-40.39-10.1-16.13-5.46-31.37-10.63-54.46-8.79-12.34.86-49.5,3.52-68.67,32.46-21.27,32-4.79,71.47-1.27,79.05,0,.06,0,.11.07.17,11.15,23.37,28,33.16,44.25,42.63,9.57,5.57,19.46,11.33,29.45,20.4C421,497.23,435.05,523,443,555.86H342.61a132,132,0,0,0-1.29-45.63c-9.16-43.62-43.1-84-86.46-102.82-34.51-15-63.68-11.11-87.12-8-24.18,3.25-37.44,4.42-51.35-6.91-15-12.14-24-32.83-19.67-45.1,4.74-13.34,25.44-19.62,34.35-22.32,28.19-8.67,52.33-2.93,73.64,2.14,21.83,5.18,44.4,10.55,61.57-6.21,17.7-17.22,17-48.29,8.81-69.92-10-26.15-30.53-36.41-47.06-44.65-13.09-6.53-20.6-10.6-23-18.37-2-6.43.36-11.62,7-23.75,7.41-13.48,17.55-31.93,14-58-4.77-36.43-36.65-66.5-68.28-64.3-30.19,2.1-44.53,32-53.08,49.73-11.83,24.42-16.07,42.54-19.16,55.78-2.9,12.39-4.37,18.06-8.79,22.43-5.21,5.13-12.26,7.47-21,6.93-13-.8-27.57-8-37.68-18.45V63.15a45,45,0,0,1,45-45Zm282.43,449v43.76a45,45,0,0,1-44.95,45H477.46a16.09,16.09,0,0,0-.36-2.56c-4.89-22.41-12.24-42.37-22-59.73a120.79,120.79,0,0,1,39-21,119.51,119.51,0,0,1,45.77-5.45Zm0-34.82a152.6,152.6,0,0,0-56,7.12,154.85,154.85,0,0,0-48.66,25.94,153.26,153.26,0,0,0-11.29-11.5c-12.68-11.52-24.67-18.5-35.24-24.65-14.45-8.41-24-14-30.62-27.78-.87-1.9-12.66-28.42-1.23-45.62,10-15.1,33.94-16.77,42.95-17.4l.17,0c16.24-1.3,26.66,2.23,41.07,7.11,12.45,4.22,26.57,9,46.95,11.61a212.66,212.66,0,0,0,51.92.08v75.11Z"
fill="none"
/>
</clipPath>
</defs>
<title>
logo mark2018
</title>
<g
clipPath="url(#clip-path-2)"
>
<rect
fill="#f7705f"
height="557.72"
width="541.77"
/>
</g>
</svg>
</div>
</div>
<div>
<h1
className="Typography-root Typography-heading3 Typography-colorTextPrimary Typography-alignCenter"
>
<span>
Currently signed in to
</span>
</h1>
<h1
className="Typography-root Typography-heading1 Typography-colorTextPrimary Typography-alignCenter BrandName-root BrandName-lg"
>
Talk
</h1>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-double"
>
<div>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<span
aria-hidden="true"
className="Icon-root Restricted-lockIcon Icon-lg"
>
lock
</span>
</div>
<h1
className="Typography-root Typography-heading3 Typography-colorTextPrimary Typography-alignCenter Restricted-noPermission"
>
You do not have permission to access this page.
</h1>
</div>
<div>
<div
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Typography-alignCenter"
>
You are signed in as:
<h1
className="Typography-root Typography-heading1 Typography-colorTextPrimary Typography-alignCenter"
>
Markus
</h1>
</div>
</div>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<button
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled"
onBlur={[Function]}
onClick={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="button"
>
Sign in with a different account
</button>
</div>
<p
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Typography-alignCenter Restricted-contactAdmin"
>
If you think this is an error, please contact your administrator for assistance.
</p>
</div>
</div>
</div>
</div>
`;
@@ -0,0 +1,735 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`accepts correct password 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value="testtest"
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
<span
aria-hidden="true"
className="Icon-root ButtonIcon-root Icon-md"
>
email
</span>
<span>
Sign in with Email
</span>
</button>
</div>
</form>
`;
exports[`accepts valid email 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorError PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
<span
aria-hidden="true"
className="Icon-root ButtonIcon-root Icon-md"
>
email
</span>
<span>
Sign in with Email
</span>
</button>
</div>
</form>
`;
exports[`checks for invalid email 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="invalid-email"
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
Please enter a valid email address.
</span>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorError PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
<span
aria-hidden="true"
className="Icon-root ButtonIcon-root Icon-md"
>
email
</span>
<span>
Sign in with Email
</span>
</button>
</div>
</form>
`;
exports[`renders sign in form 1`] = `
<div>
<div
data-testid="authBox"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="HorizontalGutter-root AuthBox-container HorizontalGutter-double"
>
<div
className="Flex-root Flex-flex Flex-justifyCenter"
>
<div
className="AuthBox-brandIcon"
>
<svg
className="BrandIcon-base BrandIcon-lg"
data-name="Layer 1"
id="Layer_1"
viewBox="0 0 541.77 557.72"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<clipPath
id="clip-path"
>
<rect
fill="none"
height="570"
width="554"
/>
</clipPath>
<clipPath
id="clip-path-2"
transform="translate(-8.12 -8.14)"
>
<path
clipPath="url(#clip-path)"
clipRule="evenodd"
d="M61.63,350.45c-.67,10.22,1.34,21.13,6,32.21a95.36,95.36,0,0,0,27.22,36.41c26,21.16,52.14,17.65,77.43,14.24,21.22-2.85,43.17-5.8,69,5.43,33.54,14.57,59.71,45.39,66.65,78.46a99.16,99.16,0,0,1,.11,38.66H219.65a16.89,16.89,0,0,0-.71-2.44c-16.54-44.09-38.46-69.52-67.09-77.78-9.37-2.66-18-3-25.68-3.27-9.91-.37-17.75-.66-26-5.6s-14.93-13.28-19.59-24.42a17,17,0,0,0-22.12-9.07,16.69,16.69,0,0,0-9.17,21.88c7.45,17.8,18.6,31.31,33.25,40.21,15.85,9.48,30.54,10,42.35,10.48,6.55.24,12.2.45,17.55,2,15.64,4.51,29.43,20.65,41.07,48H63.07a45,45,0,0,1-44.95-45V322.78a144.51,144.51,0,0,0,43.51,27.67Zm13-31.55a109.22,109.22,0,0,1-56.48-52.22v-66a88.41,88.41,0,0,0,35.59,10.37c18.52,1.13,34.77-4.64,47-16.71,11.63-11.51,14.6-24.24,18.05-39,2.88-12.32,6.46-27.66,16.66-48.7,10.44-21.72,17.13-30,24.69-30.51,11-.79,29.3,13.46,32.09,34.75,2,14.93-3.58,25.14-10.07,37C175,161,166,177.28,172.4,198.15c6.81,22.16,25.44,31.45,40.4,38.91,15.61,7.78,25.42,13.21,30.37,26.2,4.26,11.27,4.87,27.9-.71,33.32-3.74,3.66-16.43.64-29.86-2.55-23-5.47-54.5-12.95-91.49-1.57-9.77,3-32.07,9.72-46.51,26.44ZM357.73,18.14c-15,17.37-28.76,40.23-34.85,69-4.25,19.33-13.09,59.57,13.51,88.26A71.68,71.68,0,0,0,388.11,198a61.12,61.12,0,0,0,22.65-4.23c31.63-12.61,46.84-43.4,49.25-56.36,3.24-17.51,5-35.76,6.33-51.32a11.43,11.43,0,0,1,5.38-8.61l.5-.31a12.23,12.23,0,0,1,13.33,1.63c19.82,16.56,36.73,31.82,51.69,46.63a17.07,17.07,0,0,0,2.64,2.14v93.21a16.92,16.92,0,0,0-3.87,4.69c-9.23,16.43-23.71,36.42-40.11,38.31-6.55.7-10.3-1.49-20.21-7.9S452.37,240.8,431,236c-23.2-5.22-38.56-2.06-50.9.48-10.61,2.18-17.62,3.62-28.9,0-27.43-9-42.31-36.22-51.21-52.47l-.24-.45c-19.81-36.54-18.23-71.85-16.84-103l0-.58a287.16,287.16,0,0,1,9.81-61.77Zm49.14,0h88.06a45,45,0,0,1,44.95,45V80.79c-10.08-9.22-21-18.69-32.79-28.58a45.72,45.72,0,0,0-50.93-5.38,19.1,19.1,0,0,0-2.55,1.52,46.1,46.1,0,0,0-21,34.46,1.49,1.49,0,0,0,0,.21c-1.24,14.7-2.91,31.88-5.86,47.9-.88,3.66-9.12,23.11-28.33,30.76-12.81,5.1-29.11-1.09-37.41-10-14.22-15.33-8.26-42.48-5-57.19C363.43,59.29,386.3,35,404.17,20.82a18,18,0,0,0,2.7-2.68Zm-149.42,0a319.88,319.88,0,0,0-8.67,60.21l0,.62c-1.43,32-3.38,75.91,21,120.82l.28.52c9.81,17.93,30.25,55.25,70.55,68.52,19.95,6.48,34,3.59,46.44,1,11-2.26,20.48-4.21,36.49-.61,15.43,3.48,24.69,9.46,33.63,15.25C467,290.87,478,298,493.8,298a54,54,0,0,0,5.86-.32c14.5-1.67,27.83-8.41,40.22-20.42v45.53a179.81,179.81,0,0,1-47.68.48c-17-2.18-28.9-6.21-40.39-10.1-16.13-5.46-31.37-10.63-54.46-8.79-12.34.86-49.5,3.52-68.67,32.46-21.27,32-4.79,71.47-1.27,79.05,0,.06,0,.11.07.17,11.15,23.37,28,33.16,44.25,42.63,9.57,5.57,19.46,11.33,29.45,20.4C421,497.23,435.05,523,443,555.86H342.61a132,132,0,0,0-1.29-45.63c-9.16-43.62-43.1-84-86.46-102.82-34.51-15-63.68-11.11-87.12-8-24.18,3.25-37.44,4.42-51.35-6.91-15-12.14-24-32.83-19.67-45.1,4.74-13.34,25.44-19.62,34.35-22.32,28.19-8.67,52.33-2.93,73.64,2.14,21.83,5.18,44.4,10.55,61.57-6.21,17.7-17.22,17-48.29,8.81-69.92-10-26.15-30.53-36.41-47.06-44.65-13.09-6.53-20.6-10.6-23-18.37-2-6.43.36-11.62,7-23.75,7.41-13.48,17.55-31.93,14-58-4.77-36.43-36.65-66.5-68.28-64.3-30.19,2.1-44.53,32-53.08,49.73-11.83,24.42-16.07,42.54-19.16,55.78-2.9,12.39-4.37,18.06-8.79,22.43-5.21,5.13-12.26,7.47-21,6.93-13-.8-27.57-8-37.68-18.45V63.15a45,45,0,0,1,45-45Zm282.43,449v43.76a45,45,0,0,1-44.95,45H477.46a16.09,16.09,0,0,0-.36-2.56c-4.89-22.41-12.24-42.37-22-59.73a120.79,120.79,0,0,1,39-21,119.51,119.51,0,0,1,45.77-5.45Zm0-34.82a152.6,152.6,0,0,0-56,7.12,154.85,154.85,0,0,0-48.66,25.94,153.26,153.26,0,0,0-11.29-11.5c-12.68-11.52-24.67-18.5-35.24-24.65-14.45-8.41-24-14-30.62-27.78-.87-1.9-12.66-28.42-1.23-45.62,10-15.1,33.94-16.77,42.95-17.4l.17,0c16.24-1.3,26.66,2.23,41.07,7.11,12.45,4.22,26.57,9,46.95,11.61a212.66,212.66,0,0,0,51.92.08v75.11Z"
fill="none"
/>
</clipPath>
</defs>
<title>
logo mark2018
</title>
<g
clipPath="url(#clip-path-2)"
>
<rect
fill="#f7705f"
height="557.72"
width="541.77"
/>
</g>
</svg>
</div>
</div>
<div>
<h1
className="Typography-root Typography-heading3 Typography-colorTextPrimary Typography-alignCenter"
>
<span>
Sign in to
</span>
</h1>
<h1
className="Typography-root Typography-heading1 Typography-colorTextPrimary Typography-alignCenter BrandName-root BrandName-lg"
>
Talk
</h1>
</div>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
>
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
<span
aria-hidden="true"
className="Icon-root ButtonIcon-root Icon-md"
>
email
</span>
<span>
Sign in with Email
</span>
</button>
</div>
</form>
<div
className="HorizontalGutter-root HorizontalGutter-full"
/>
</div>
</div>
</div>
</div>
</div>
`;
exports[`shows error when submitting empty form 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorError TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value=""
/>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorError PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value=""
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
<div
className="Message-root Message-colorError Message-fullWidth"
>
<span
aria-hidden="true"
className="Icon-root MessageIcon-root Icon-sm"
>
warning
</span>
<span>
This field is required.
</span>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
<span
aria-hidden="true"
className="Icon-root ButtonIcon-root Icon-md"
>
email
</span>
<span>
Sign in with Email
</span>
</button>
</div>
</form>
`;
exports[`shows server error 1`] = `
<form
autoComplete="off"
onSubmit={[Function]}
>
<div
className="HorizontalGutter-root HorizontalGutter-full"
>
<div
className="CallOut-root CallOut-colorError CallOut-fullWidth"
>
Server Error
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="email"
>
Email Address
</label>
<input
className="TextField-root TextField-colorRegular TextField-fullWidth"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder="Email Address"
type="text"
value="hans@test.com"
/>
</div>
<div
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
>
<label
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
htmlFor="password"
>
Password
</label>
<div
className="PasswordField-fullWidth PasswordField-root"
>
<div
className="PasswordField-wrapper"
>
<input
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
disabled={false}
id="password"
name="password"
onChange={[Function]}
placeholder="Password"
type="password"
value="testtest"
/>
<div
className="PasswordField-icon"
onClick={[Function]}
role="button"
tabIndex={0}
title="Show password"
>
<span
aria-hidden="true"
className="Icon-root Icon-sm"
>
visibility
</span>
</div>
</div>
</div>
</div>
<button
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
disabled={false}
onBlur={[Function]}
onFocus={[Function]}
onMouseOut={[Function]}
onMouseOver={[Function]}
onTouchEnd={[Function]}
type="submit"
>
<span
aria-hidden="true"
className="Icon-root ButtonIcon-root Icon-md"
>
email
</span>
<span>
Sign in with Email
</span>
</button>
</div>
</form>
`;
exports[`submits form successfully 1`] = `"http://localhost/admin/login#accessToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzMWIyNjU5MS00ZTlhLTQzODgtYTdmZi1lMWJkYzVkOTdjY2UifQ=="`;
@@ -0,0 +1,127 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import {
createAuthToken,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { emptyModerationQueues, settings, users } from "../fixtures";
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
moderationQueues: sinon.stub().returns(emptyModerationQueues),
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
me: sinon
.stub()
.returns(
merge(
{ ...users[0], email: "", username: "", profiles: [] },
get(customResolver, "Query.me")
)
),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
localRecord.setValue("SIGN_IN", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
},
});
return {
context,
testRenderer,
root: testRenderer.root,
};
}
it("renders addEmailAddress view", async () => {
const { root } = await createTestRenderer();
await waitForElement(() => within(root).queryByText("Add Email Address"));
});
it("renders createUsername view", async () => {
const { root } = await createTestRenderer({
Query: {
me: {
email: "hans@test.com",
},
},
});
await waitForElement(() => within(root).queryByText("Create Username"));
});
it("renders createPassword view", async () => {
const { root } = await createTestRenderer({
Query: {
me: {
email: "hans@test.com",
username: "hans",
},
},
});
await waitForElement(() => within(root).queryByText("Create Password"));
});
it("do not render createPassword view when local auth is disabled", async () => {
await createTestRenderer({
Query: {
me: {
email: "hans@test.com",
username: "hans",
},
settings: {
auth: {
integrations: {
local: {
enabled: false,
},
},
},
},
},
});
await wait(() =>
expect(window.location.toString()).toBe(
"http://localhost/admin/moderate/reported"
)
);
});
it("complete account", async () => {
await createTestRenderer({
Query: {
me: {
email: "hans@test.com",
username: "hans",
profiles: [{ __typename: "LocalProfile" }],
},
},
});
await wait(() =>
expect(window.location.toString()).toBe(
"http://localhost/admin/moderate/reported"
)
);
});
@@ -0,0 +1,187 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import {
createAuthToken,
replaceHistoryLocation,
toJSON,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
me: sinon.stub().returns({ ...users[0], email: "" }),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
localRecord.setValue("ADD_EMAIL_ADDRESS", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
},
});
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("completeAccountBox")
);
const form = within(container).getByType("form");
const emailAddressField = within(form).getByLabelText("Email Address");
const confirmEmailAddressField = within(form).getByLabelText(
"Confirm Email Address"
);
return {
context,
testRenderer,
form,
root: testRenderer.root,
emailAddressField,
confirmEmailAddressField,
container,
};
}
it("renders addEmailAddress view", async () => {
const { root } = await createTestRenderer();
expect(toJSON(root)).toMatchSnapshot();
});
it("shows error when submitting empty form", async () => {
const { form } = await createTestRenderer();
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("checks for invalid email", async () => {
const {
form,
emailAddressField,
confirmEmailAddressField,
} = await createTestRenderer();
emailAddressField.props.onChange({ target: { value: "invalid-email" } });
confirmEmailAddressField.props.onChange({
target: { value: "invalid-confirmation-email" },
});
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("accepts valid email", async () => {
const { form, emailAddressField } = await createTestRenderer();
emailAddressField.props.onChange({ target: { value: "hans@test.com" } });
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("accepts valid email confirmation", async () => {
const {
form,
emailAddressField,
confirmEmailAddressField,
} = await createTestRenderer();
emailAddressField.props.onChange({ target: { value: "hans@test.com" } });
confirmEmailAddressField.props.onChange({
target: { value: "hans@test.com" },
});
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("shows server error", async () => {
const email = "hans@test.com";
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
throw new Error("server error");
});
const {
form,
emailAddressField,
confirmEmailAddressField,
} = await createTestRenderer(
{
Mutation: {
setEmail,
},
},
{ muteNetworkErrors: true }
);
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
emailAddressField.props.onChange({ target: { value: email } });
confirmEmailAddressField.props.onChange({
target: { value: email },
});
form.props.onSubmit();
expect(emailAddressField.props.disabled).toBe(true);
expect(confirmEmailAddressField.props.disabled).toBe(true);
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
});
it("successfully sets email", async () => {
const email = "hans@test.com";
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
expect(data.input).toEqual({
email,
clientMutationId: data.input.clientMutationId,
});
return {
user: {
id: "me",
email,
},
clientMutationId: data.input.clientMutationId,
};
});
const {
form,
emailAddressField,
confirmEmailAddressField,
} = await createTestRenderer({
Mutation: {
setEmail,
},
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
emailAddressField.props.onChange({ target: { value: email } });
confirmEmailAddressField.props.onChange({
target: { value: email },
});
form.props.onSubmit();
expect(emailAddressField.props.disabled).toBe(true);
expect(confirmEmailAddressField.props.disabled).toBe(true);
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
expect(setEmail.called).toBe(true);
});
@@ -0,0 +1,139 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import {
createAuthToken,
replaceHistoryLocation,
toJSON,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings, users } from "../fixtures";
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
me: sinon.stub().returns({ ...users[0], profiles: [] }),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
localRecord.setValue("CREATE_PASSWORD", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
},
});
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("completeAccountBox")
);
const form = within(container).getByType("form");
const passwordField = within(form).getByLabelText("Password");
return {
context,
testRenderer,
form,
root: testRenderer.root,
passwordField,
container,
};
}
it("renders createPassword view", async () => {
const { root } = await createTestRenderer();
expect(toJSON(root)).toMatchSnapshot();
});
it("shows error when submitting empty form", async () => {
const { form } = await createTestRenderer();
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("checks for invalid password", async () => {
const { form, passwordField } = await createTestRenderer();
passwordField.props.onChange({ target: { value: "x" } });
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("shows server error", async () => {
const password = "secretpassword";
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
throw new Error("server error");
});
const { form, passwordField } = await createTestRenderer(
{
Mutation: {
setPassword,
},
},
{ muteNetworkErrors: true }
);
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
passwordField.props.onChange({ target: { value: password } });
form.props.onSubmit();
expect(passwordField.props.disabled).toBe(true);
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
});
it("successfully sets password", async () => {
const password = "secretpassword";
const setPassword = sinon.stub().callsFake((_: any, data: any) => {
expect(data.input).toEqual({
password,
clientMutationId: data.input.clientMutationId,
});
return {
user: {
id: "me",
profiles: [],
},
clientMutationId: data.input.clientMutationId,
};
});
const { form, passwordField } = await createTestRenderer({
Mutation: {
setPassword,
},
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
passwordField.props.onChange({ target: { value: password } });
form.props.onSubmit();
expect(passwordField.props.disabled).toBe(true);
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
expect(setPassword.called).toBe(true);
});
@@ -0,0 +1,145 @@
import { get, merge } from "lodash";
import sinon from "sinon";
import {
createAuthToken,
replaceHistoryLocation,
toJSON,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings } from "../fixtures";
async function createTestRenderer(
customResolver: any = {},
options: { muteNetworkErrors?: boolean; logNetwork?: boolean } = {}
) {
replaceHistoryLocation("http://localhost/admin/login");
const resolvers = {
...customResolver,
Query: {
...customResolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(customResolver, "Query.settings"))),
},
};
const { testRenderer, context } = create({
// Set this to true, to see graphql responses.
logNetwork: options.logNetwork,
muteNetworkErrors: options.muteNetworkErrors,
resolvers,
initLocalState: localRecord => {
localRecord.setValue("CREATE_USERNAME", "authView");
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
},
});
const container = await waitForElement(() =>
within(testRenderer.root).getByTestID("completeAccountBox")
);
const form = within(container).getByType("form");
const usernameField = within(form).getByLabelText("Username");
return {
context,
testRenderer,
form,
root: testRenderer.root,
usernameField,
container,
};
}
it("renders createUsername view", async () => {
const { root } = await createTestRenderer();
expect(toJSON(root)).toMatchSnapshot();
});
it("shows error when submitting empty form", async () => {
const { form } = await createTestRenderer();
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("checks for invalid username", async () => {
const { form, usernameField } = await createTestRenderer();
usernameField.props.onChange({ target: { value: "x" } });
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("accepts valid username", async () => {
const { form, usernameField } = await createTestRenderer();
usernameField.props.onChange({ target: { value: "hans" } });
form.props.onSubmit();
expect(toJSON(form)).toMatchSnapshot();
});
it("shows server error", async () => {
const username = "hans";
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
throw new Error("server error");
});
const { form, usernameField } = await createTestRenderer(
{
Mutation: {
setUsername,
},
},
{ muteNetworkErrors: true }
);
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
usernameField.props.onChange({ target: { value: username } });
form.props.onSubmit();
expect(usernameField.props.disabled).toBe(true);
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
});
it("successfully sets username", async () => {
const username = "hans";
const setUsername = sinon.stub().callsFake((_: any, data: any) => {
expect(data.input).toEqual({
username,
clientMutationId: data.input.clientMutationId,
});
return {
user: {
id: "me",
username,
},
clientMutationId: data.input.clientMutationId,
};
});
const { form, usernameField } = await createTestRenderer({
Mutation: {
setUsername,
},
});
const submitButton = form.find(
i => i.type === "button" && i.props.type === "submit"
);
usernameField.props.onChange({ target: { value: username } });
form.props.onSubmit();
expect(usernameField.props.disabled).toBe(true);
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(toJSON(form)).toMatchSnapshot();
expect(setUsername.called).toBe(true);
});
@@ -1,19 +1,24 @@
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { replaceHistoryLocation } from "talk-framework/testHelpers";
import {
createAuthToken,
replaceHistoryLocation,
wait,
} from "talk-framework/testHelpers";
import create from "./create";
import create from "../create";
import {
emptyModerationQueues,
emptyRejectedComments,
settings,
} from "./fixtures";
users,
} from "../fixtures";
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
me: sinon.stub().returns(users[0]),
},
};
@@ -25,11 +30,13 @@ it("redirect when already logged in", async () => {
logNetwork: false,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
},
});
await timeout();
expect(window.location.toString()).toBe(
"http://localhost/admin/moderate/reported"
await wait(() =>
expect(window.location.toString()).toBe(
"http://localhost/admin/moderate/reported"
)
);
});
@@ -41,9 +48,11 @@ it("redirect to redirectPath when already logged in", async () => {
logNetwork: false,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
localRecord.setValue("/admin/community", "redirectPath");
},
});
await timeout();
expect(window.location.toString()).toBe("http://localhost/admin/community");
await wait(() =>
expect(window.location.toString()).toBe("http://localhost/admin/community")
);
});
@@ -1,17 +1,16 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { replaceHistoryLocation } from "talk-framework/testHelpers";
import { replaceHistoryLocation, wait } from "talk-framework/testHelpers";
import create from "./create";
import create from "../create";
import {
emptyModerationQueues,
emptyRejectedComments,
settings,
} from "./fixtures";
} from "../fixtures";
function createTestRenderer(): {
testRenderer: ReactTestRenderer;
@@ -31,6 +30,7 @@ function createTestRenderer(): {
logNetwork: false,
initLocalState: localRecord => {
localRecord.setValue(false, "loggedIn");
localRecord.setValue("SIGN_IN", "authView");
},
});
return { testRenderer, context };
@@ -38,12 +38,13 @@ function createTestRenderer(): {
it("redirect when not logged in", async () => {
const { context } = createTestRenderer();
await timeout();
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.redirectPath
).toBe("/admin/moderate/reported");
expect(window.location.toString()).toBe("http://localhost/admin/login");
await wait(() => {
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.redirectPath
).toBe("/admin/moderate/reported");
expect(window.location.toString()).toBe("http://localhost/admin/login");
});
});
@@ -0,0 +1,107 @@
import { ReactTestRenderer } from "react-test-renderer";
import sinon from "sinon";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import {
createAuthToken,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import {
emptyModerationQueues,
emptyRejectedComments,
settings,
users,
} from "../fixtures";
function createTestRenderer(
userDiff: any = {}
): {
testRenderer: ReactTestRenderer;
context: TalkContext;
} {
replaceHistoryLocation("http://localhost/admin/moderate/reported");
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
me: sinon.stub().returns({ ...users[0], ...userDiff }),
},
};
const { testRenderer, context } = create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
localRecord.setValue(true, "loggedIn");
localRecord.setValue(createAuthToken(), "authToken");
localRecord.setValue("SIGN_IN", "authView");
},
});
return { testRenderer, context };
}
it("show restricted screen for commenters", async () => {
const { testRenderer } = createTestRenderer({ role: "COMMENTER" });
const authBox = await waitForElement(() =>
within(testRenderer.root).getByTestID("authBox")
);
expect(within(authBox).toJSON()).toMatchSnapshot();
});
it("show restricted screen when email is not set", async () => {
const { testRenderer } = createTestRenderer({ email: "" });
await waitForElement(() => within(testRenderer.root).getByTestID("authBox"));
});
it("show restricted screen when username is not set", async () => {
const { testRenderer } = createTestRenderer({ username: "" });
await waitForElement(() => within(testRenderer.root).getByTestID("authBox"));
});
it("show restricted screen local was not set (password)", async () => {
const { testRenderer } = createTestRenderer({ profiles: [] });
await waitForElement(() => within(testRenderer.root).getByTestID("authBox"));
});
it("sign out when clicking on sign in as", async () => {
const { context, testRenderer } = createTestRenderer({ role: "COMMENTER" });
const authBox = await waitForElement(() =>
within(testRenderer.root).getByTestID("authBox")
);
const restMock = sinon.mock(context.rest);
restMock
.expects("fetch")
.withArgs("/tenant/auth", {
method: "DELETE",
})
.once()
.returns({});
within(authBox)
.getByText("Sign in with a different account")
.props.onClick();
await wait(() => {
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.redirectPath
).toBe("/admin/moderate/reported");
});
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.loggedIn
).toBeFalsy();
});
@@ -1,21 +1,20 @@
import { ReactTestInstance, ReactTestRenderer } from "react-test-renderer";
import { ReactTestInstance } from "react-test-renderer";
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { TalkContext } from "talk-framework/lib/bootstrap";
import { LOCAL_ID } from "talk-framework/lib/relay";
import {
createAuthToken,
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "./create";
import create from "../create";
import {
emptyModerationQueues,
emptyRejectedComments,
settings,
} from "./fixtures";
} from "../fixtures";
const resolvers = {
Query: {
@@ -29,65 +28,71 @@ const inputPredicate = (name: string) => (n: ReactTestInstance) => {
return n.props.name === name && n.props.onChange;
};
let context: TalkContext;
let testRenderer: ReactTestRenderer;
let form: ReactTestInstance;
beforeEach(async () => {
async function createTestRenderer() {
// deliberately setting to a different route,
// it should be smart enough to reroute to /admin/login.
replaceHistoryLocation("http://localhost/admin/moderate");
({ testRenderer, context } = create({
const { testRenderer, context } = create({
resolvers,
// Set this to true, to see graphql responses.
logNetwork: false,
initLocalState: localRecord => {
localRecord.setValue(false, "loggedIn");
localRecord.setValue("SIGN_IN", "authView");
},
}));
await timeout();
form = testRenderer.root.findByType("form");
});
});
const form = await waitForElement(() =>
within(testRenderer.root).getByType("form")
);
return { testRenderer, form, context };
}
it("renders sign in form", async () => {
const { testRenderer } = await createTestRenderer();
expect(testRenderer.toJSON()).toMatchSnapshot();
});
it("shows error when submitting empty form", async () => {
const { form } = await createTestRenderer();
form.props.onSubmit();
expect(testRenderer.toJSON()).toMatchSnapshot();
expect(within(form).toJSON()).toMatchSnapshot();
});
it("checks for invalid email", async () => {
form
.find(inputPredicate("email"))
const { form } = await createTestRenderer();
within(form)
.getByLabelText("Email Address")
.props.onChange({ target: { value: "invalid-email" } });
form.props.onSubmit();
expect(testRenderer.toJSON()).toMatchSnapshot();
expect(within(form).toJSON()).toMatchSnapshot();
});
it("accepts valid email", async () => {
form
.find(inputPredicate("email"))
const { form } = await createTestRenderer();
within(form)
.getByLabelText("Email Address")
.props.onChange({ target: { value: "hans@test.com" } });
form.props.onSubmit();
expect(testRenderer.toJSON()).toMatchSnapshot();
expect(within(form).toJSON()).toMatchSnapshot();
});
it("accepts correct password", async () => {
form
.find(inputPredicate("password"))
const { form } = await createTestRenderer();
within(form)
.getByLabelText("Password")
.props.onChange({ target: { value: "testtest" } });
form.props.onSubmit();
expect(testRenderer.toJSON()).toMatchSnapshot();
expect(within(form).toJSON()).toMatchSnapshot();
});
it("shows server error", async () => {
form
.find(inputPredicate("email"))
const { form, context } = await createTestRenderer();
within(form)
.getByLabelText("Email Address")
.props.onChange({ target: { value: "hans@test.com" } });
form
.find(inputPredicate("password"))
within(form)
.getByLabelText("Password")
.props.onChange({ target: { value: "testtest" } });
const error = new Error("Server Error");
@@ -105,13 +110,17 @@ it("shows server error", async () => {
.throws(error);
form.props.onSubmit();
expect(testRenderer.toJSON()).toMatchSnapshot();
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
const submitButton = within(form).getByText("Sign in with Email", {
selector: "button",
});
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(within(form).toJSON()).toMatchSnapshot();
restMock.verify();
});
it("submits form successfully", async () => {
const { form, context } = await createTestRenderer();
form
.find(inputPredicate("email"))
.props.onChange({ target: { value: "hans@test.com" } });
@@ -137,17 +146,12 @@ it("submits form successfully", async () => {
const historyMock = sinon.mock(window.history);
form.props.onSubmit();
expect(testRenderer.toJSON()).toMatchSnapshot();
await timeout();
expect(testRenderer.toJSON()).toMatchSnapshot();
const submitButton = within(form).getByText("Sign in with Email", {
selector: "button",
});
expect(submitButton.props.disabled).toBe(true);
await wait(() => expect(submitButton.props.disabled).toBe(false));
expect(location.toString()).toMatchSnapshot();
restMock.verify();
await timeout();
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.loggedIn
).toBeTruthy();
await timeout();
historyMock.verify();
});
@@ -1,21 +1,27 @@
import sinon from "sinon";
import { timeout } from "talk-common/utils";
import { LOCAL_ID } from "talk-framework/lib/relay";
import { replaceHistoryLocation } from "talk-framework/testHelpers";
import {
replaceHistoryLocation,
wait,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "./create";
import create from "../create";
import {
emptyModerationQueues,
emptyRejectedComments,
settings,
} from "./fixtures";
users,
} from "../fixtures";
const resolvers = {
Query: {
settings: sinon.stub().returns(settings),
moderationQueues: sinon.stub().returns(emptyModerationQueues),
comments: sinon.stub().returns(emptyRejectedComments),
me: sinon.stub().returns(users[0]),
},
};
@@ -40,17 +46,17 @@ it("logs out", async () => {
.once()
.returns({});
await timeout();
testRenderer.root
.find(i => i.props.id === "navigation-signOutButton" && i.props.onClick)
.props.onClick();
const signOutButton = await waitForElement(() =>
within(testRenderer.root).getByText("Sign Out")
);
signOutButton.props.onClick();
await timeout();
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.loggedIn
).toBeFalsy();
await wait(() => {
expect(
context.relayEnvironment
.getStore()
.getSource()
.get(LOCAL_ID)!.loggedIn
).toBeFalsy();
});
});
@@ -1703,6 +1703,7 @@ exports[`renders configure auth 1`] = `
>
<div
className="HorizontalGutter-root HorizontalGutter-double"
data-testid="configure-authContainer"
>
<div
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
@@ -7,10 +7,12 @@ import {
inputPredicate,
limitSnapshotTo,
replaceHistoryLocation,
waitForElement,
within,
} from "talk-framework/testHelpers";
import create from "../create";
import { settings } from "../fixtures";
import { settingsWithEmptyAuth, users } from "../fixtures";
beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/configure/auth");
@@ -23,7 +25,10 @@ const createTestRenderer = async (resolver: any = {}) => {
...resolver.Query,
settings: sinon
.stub()
.returns(merge({}, settings, get(resolver, "Query.settings"))),
.returns(
merge({}, settingsWithEmptyAuth, get(resolver, "Query.settings"))
),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({
@@ -34,24 +39,27 @@ const createTestRenderer = async (resolver: any = {}) => {
localRecord.setValue(true, "loggedIn");
},
});
await timeout();
return testRenderer;
const configureContainer = await waitForElement(() =>
within(testRenderer.root).getByTestID("configure-container")
);
const authContainer = await waitForElement(() =>
within(configureContainer).getByTestID("configure-authContainer")
);
return { testRenderer, configureContainer, authContainer };
};
it("renders configure auth", async () => {
const testRenderer = await createTestRenderer();
expect(
limitSnapshotTo("configure-container", testRenderer.toJSON())
).toMatchSnapshot();
const { configureContainer } = await createTestRenderer();
expect(within(configureContainer).toJSON()).toMatchSnapshot();
});
it("regenerate sso key", async () => {
const testRenderer = await createTestRenderer({
const { testRenderer } = await createTestRenderer({
Mutation: {
regenerateSSOKey: createSinonStub(s =>
s.callsFake((_: any, data: any) => {
return {
settings: merge({}, settings, {
settings: merge({}, settingsWithEmptyAuth, {
auth: {
integrations: {
sso: {
@@ -83,7 +91,7 @@ it("regenerate sso key", async () => {
});
it("prevents admin lock out", async () => {
const testRenderer = await createTestRenderer();
const { testRenderer } = await createTestRenderer();
// Let's disable local auth.
testRenderer.root
@@ -99,8 +107,8 @@ it("prevents admin lock out", async () => {
});
it("prevents stream lock out", async () => {
let settingsRecord = cloneDeep(settings);
const testRenderer = await createTestRenderer({
let settingsRecord = cloneDeep(settingsWithEmptyAuth);
const { testRenderer } = await createTestRenderer({
Mutation: {
updateSettings: createSinonStub(s =>
s.callsFake((_: any, data: any) => {
@@ -159,8 +167,8 @@ it("prevents stream lock out", async () => {
});
it("change settings", async () => {
let settingsRecord = cloneDeep(settings);
const testRenderer = await createTestRenderer({
let settingsRecord = cloneDeep(settingsWithEmptyAuth);
const { testRenderer } = await createTestRenderer({
Query: {
discoverOIDCConfiguration: createSinonStub(s =>
s.callsFake((_: any, data: any) => {
+3 -2
View File
@@ -1,6 +1,5 @@
import { EventEmitter2 } from "eventemitter2";
import { IResolvers } from "graphql-tools";
import { noop } from "lodash";
import React from "react";
import TestRenderer from "react-test-renderer";
import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime";
@@ -19,6 +18,7 @@ import createNodeMock from "./createNodeMock";
interface CreateParams {
logNetwork?: boolean;
resolvers?: IResolvers<any, any>;
muteNetworkErrors?: boolean;
initLocalState?: (
local: RecordProxy,
source: RecordSourceProxy,
@@ -31,6 +31,7 @@ export default function create(params: CreateParams) {
// Set this to true, to see graphql responses.
logNetwork: params.logNetwork,
resolvers: params.resolvers,
muteNetworkErrors: params.muteNetworkErrors,
initLocalState: (localRecord, source, env) => {
if (params.initLocalState) {
params.initLocalState(localRecord, source, env);
@@ -48,7 +49,7 @@ export default function create(params: CreateParams) {
browserInfo: { ios: false },
uuidGenerator: createUUIDGenerator(),
eventEmitter: new EventEmitter2({ wildcard: true, maxListeners: 20 }),
clearSession: noop,
clearSession: () => Promise.resolve(),
};
const testRenderer = TestRenderer.create(
@@ -5,6 +5,7 @@ import { createRelayEnvironment } from "talk-framework/testHelpers";
interface CreateEnvironmentParams {
logNetwork?: boolean;
resolvers?: IResolvers<any, any>;
muteNetworkErrors?: boolean;
initLocalState?: (
local: RecordProxy,
source: RecordSourceProxy,
@@ -16,6 +17,7 @@ export default function createEnvironment(params: CreateEnvironmentParams) {
return createRelayEnvironment({
network: {
logNetwork: params.logNetwork,
muteNetworkErrors: params.muteNetworkErrors,
resolvers: params.resolvers || {},
projectName: "tenant",
},
@@ -11,7 +11,7 @@ import {
} from "talk-framework/testHelpers";
import create from "../create";
import { moderationActions, settings } from "../fixtures";
import { moderationActions, settings, users } from "../fixtures";
beforeEach(async () => {
replaceHistoryLocation("http://localhost/admin/configure/auth");
@@ -62,7 +62,7 @@ const createTestRenderer = async (resolver: any = {}) => {
Query: {
...resolver.Query,
me: sinon.stub().returns({
id: "me",
...users[0],
commentModerationActionHistory,
}),
settings: sinon
+78
View File
@@ -39,6 +39,7 @@ export const settings = {
clientID: "",
clientSecret: "",
callbackURL: "http://localhost/google/callback",
redirectURL: "http://localhost/google",
},
facebook: {
enabled: false,
@@ -50,6 +51,7 @@ export const settings = {
clientID: "",
clientSecret: "",
callbackURL: "http://localhost/facebook/callback",
redirectURL: "http://localhost/facebook",
},
oidc: {
enabled: false,
@@ -58,7 +60,74 @@ export const settings = {
admin: true,
stream: true,
},
name: "OIDC",
callbackURL: "http://localhost/oidc/callback",
redirectURL: "http://localhost/oidc",
},
},
},
};
export const settingsWithEmptyAuth = {
...settings,
id: "settings",
auth: {
displayName: {
enabled: false,
},
integrations: {
local: {
enabled: true,
allowRegistration: true,
targetFilter: {
admin: true,
stream: true,
},
},
sso: {
enabled: false,
allowRegistration: true,
targetFilter: {
admin: true,
stream: true,
},
key: null,
keyGeneratedAt: null,
},
google: {
enabled: false,
allowRegistration: true,
targetFilter: {
admin: true,
stream: true,
},
clientID: "",
clientSecret: "",
callbackURL: "http://localhost/google/callback",
redirectURL: "http://localhost/google",
},
facebook: {
enabled: false,
allowRegistration: true,
targetFilter: {
admin: true,
stream: true,
},
clientID: "",
clientSecret: "",
callbackURL: "http://localhost/facebook/callback",
redirectURL: "http://localhost/facebook",
},
oidc: {
enabled: false,
allowRegistration: false,
targetFilter: {
admin: true,
stream: true,
},
name: "",
callbackURL: "http://localhost/oidc/callback",
redirectURL: "http://localhost/oidc",
},
},
},
@@ -151,14 +220,23 @@ export const users = [
{
id: "user-0",
username: "Markus",
email: "markus@test.com",
role: "ADMIN",
profiles: [{ __typename: "LocalProfile" }],
},
{
id: "user-1",
username: "Lukas",
email: "lukas@test.com",
role: "MODERATOR",
profiles: [{ __typename: "LocalProfile" }],
},
{
id: "user-2",
username: "Isabelle",
email: "isabelle@test.com",
role: "COMMENTER",
profiles: [{ __typename: "LocalProfile" }],
},
];
@@ -17,6 +17,7 @@ import {
rejectedComments,
reportedComments,
settings,
users,
} from "../fixtures";
beforeEach(async () => {
@@ -43,6 +44,7 @@ const createTestRenderer = async (resolver: any = {}) => {
comments:
get(resolver, "Query.comments") ||
sinon.stub().returns(emptyRejectedComments),
me: sinon.stub().returns(users[0]),
},
};
const { testRenderer } = create({