mirror of
https://github.com/wassname/talk.git
synced 2026-07-31 12:50:48 +08:00
[CORL-845] Account Linking (#2818)
* feat: added new linking backend * feat: added duplicateEmail to hash * fix: stored the duplicate email on the user * feat: initial implmentation of account linking in auth * test: fix unit tests * fix+test: translations and tests added * chore+test: rename view to LINK_ACCOUNT + more tests * feat+test: account linking admin + more tests * feat: Handle incomplete accounts * chore: add some comments * feat: expose duplicateEmail through graphql and impl for stream * feat: admin to use duplicateEmail from graphql * fix: no need to validate password for account linking * fix: dont validate password * fix: no need to render error message when account was incomplete * chore: log to console when encountering incomplete account * chore: adjust comment * chore: simplify + add comments * chore: wording * chore: comments Co-authored-by: Vinh <vinh@vinh.tech> Co-authored-by: Kim Gardner <kgardnr@gmail.com>
This commit is contained in:
co-authored by
Vinh
Kim Gardner
parent
7497e33046
commit
dcb2a10e72
@@ -235,86 +235,90 @@ exports[`renders addEmailAddress view 1`] = `
|
||||
<div
|
||||
className="CompleteAccountBox-main"
|
||||
>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
<div
|
||||
data-testid="addEmailAddress-container"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
For your added security, we require users to add an email address to their accounts.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="email"
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
For your added security, we require users to add an email address to their accounts.
|
||||
</p>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="email"
|
||||
name="email"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="confirmEmail"
|
||||
>
|
||||
Confirm Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="confirmEmail"
|
||||
name="confirmEmail"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Confirm Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="confirmEmail"
|
||||
>
|
||||
Confirm Email Address
|
||||
</label>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="confirmEmail"
|
||||
name="confirmEmail"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Confirm Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Add Email Address
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Add Email Address
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -130,90 +130,92 @@ exports[`renders createPassword view 1`] = `
|
||||
<div
|
||||
className="CompleteAccountBox-main"
|
||||
>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
<div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
To protect against unauthorized changes to your account,
|
||||
we require users to create a password.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-fieldDescription Typography-colorTextSecondary"
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Must be at least 8 characters
|
||||
To protect against unauthorized changes to your account,
|
||||
we require users to create a password.
|
||||
</p>
|
||||
<div
|
||||
className="PasswordField-fullWidth PasswordField-root"
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-fieldDescription Typography-colorTextSecondary"
|
||||
>
|
||||
Must be at least 8 characters
|
||||
</p>
|
||||
<div
|
||||
className="PasswordField-wrapper"
|
||||
className="PasswordField-fullWidth PasswordField-root"
|
||||
>
|
||||
<input
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
|
||||
data-testid="password-field"
|
||||
disabled={false}
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Password"
|
||||
spellCheck={false}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="PasswordField-icon"
|
||||
onClick={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Show password"
|
||||
className="PasswordField-wrapper"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
<input
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
|
||||
data-testid="password-field"
|
||||
disabled={false}
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Password"
|
||||
spellCheck={false}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="PasswordField-icon"
|
||||
onClick={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Show password"
|
||||
>
|
||||
visibility
|
||||
</i>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
visibility
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Create Password
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Create Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -105,65 +105,67 @@ exports[`renders createUsername view 1`] = `
|
||||
<div
|
||||
className="CompleteAccountBox-main"
|
||||
>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
<div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Your username is an identifier that will appear on all of your comments.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="username"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-fieldDescription Typography-colorTextSecondary"
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
You may use “_” and “.” Spaces not permitted.
|
||||
Your username is an identifier that will appear on all of your comments.
|
||||
</p>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="username"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Username"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="username"
|
||||
>
|
||||
Username
|
||||
</label>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-fieldDescription Typography-colorTextSecondary"
|
||||
>
|
||||
You may use “_” and “.” Spaces not permitted.
|
||||
</p>
|
||||
<div
|
||||
className="TextField-root TextField-fullWidth"
|
||||
>
|
||||
<input
|
||||
className="TextField-input TextField-colorRegular"
|
||||
disabled={false}
|
||||
id="username"
|
||||
name="username"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Username"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Create Username
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Create Username
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders link account view 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="completeAccountBox"
|
||||
>
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-justifyCenter"
|
||||
>
|
||||
<div
|
||||
className="CompleteAccountBox-container"
|
||||
>
|
||||
<div
|
||||
className="CompleteAccountBox-header"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading3 Typography-colorTextLight Typography-alignCenter CompleteAccountBox-heading3"
|
||||
>
|
||||
Finish Setting Up Your Account
|
||||
</h1>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextLight Typography-alignCenter"
|
||||
>
|
||||
<span>
|
||||
Link Account
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className="CompleteAccountBox-main"
|
||||
>
|
||||
<div
|
||||
data-testid="linkAccount-container"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-spacing-3"
|
||||
>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
The email
|
||||
<strong>
|
||||
my@email.com
|
||||
</strong>
|
||||
is
|
||||
already associated with an account. If you would like to
|
||||
link these enter your password.
|
||||
</p>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<div
|
||||
className="PasswordField-fullWidth PasswordField-root"
|
||||
>
|
||||
<div
|
||||
className="PasswordField-wrapper"
|
||||
>
|
||||
<input
|
||||
autoCapitalize="off"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
|
||||
disabled={false}
|
||||
id="password"
|
||||
name="password"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
placeholder="Password"
|
||||
spellCheck={false}
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="PasswordField-icon"
|
||||
onClick={[Function]}
|
||||
onKeyUp={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Hide password"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
visibility
|
||||
</i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Link Account
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
className="Box-root Flex-root HorizontalSeparator-root Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<hr
|
||||
className="HorizontalSeparator-hr"
|
||||
/>
|
||||
<div
|
||||
className="HorizontalSeparator-text"
|
||||
>
|
||||
Or
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorRegular Button-variantFilled Button-fullWidth"
|
||||
data-color="regular"
|
||||
data-variant="filled"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Use a different email address
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -11,7 +11,12 @@ import {
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import { emptyModerationQueues, settings, users } from "../fixtures";
|
||||
import {
|
||||
emptyModerationQueues,
|
||||
settings,
|
||||
siteConnection,
|
||||
users,
|
||||
} from "../fixtures";
|
||||
|
||||
const viewer = users.admins[0];
|
||||
|
||||
@@ -20,45 +25,49 @@ async function createTestRenderer(
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "",
|
||||
username: "",
|
||||
profiles: [],
|
||||
}),
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
return act(() => {
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
sites: () => siteConnection,
|
||||
settings: () => settings,
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "",
|
||||
username: "",
|
||||
profiles: [],
|
||||
}),
|
||||
moderationQueues: () => emptyModerationQueues,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("SIGN_IN", "authView");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
context,
|
||||
testRenderer,
|
||||
root: testRenderer.root,
|
||||
};
|
||||
return {
|
||||
context,
|
||||
testRenderer,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
it("renders addEmailAddress view", async () => {
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).queryByText("Add Email Address"));
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).queryByText("Add Email Address")
|
||||
);
|
||||
});
|
||||
|
||||
it("renders createUsername view", async () => {
|
||||
const { root } = await createTestRenderer({
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () =>
|
||||
@@ -70,11 +79,13 @@ it("renders createUsername view", async () => {
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitForElement(() => within(root).queryByText("Create Username"));
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).queryByText("Create Username")
|
||||
);
|
||||
});
|
||||
|
||||
it("renders createPassword view", async () => {
|
||||
const { root } = await createTestRenderer({
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
settings: () => settings,
|
||||
@@ -88,7 +99,9 @@ it("renders createPassword view", async () => {
|
||||
},
|
||||
}),
|
||||
});
|
||||
await waitForElement(() => within(root).queryByText("Create Password"));
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).queryByText("Create Password")
|
||||
);
|
||||
});
|
||||
|
||||
it("do not render createPassword view when local auth is disabled", async () => {
|
||||
@@ -145,3 +158,24 @@ it("complete account", async () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders account linking view", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
resolvers: {
|
||||
Query: {
|
||||
viewer: () =>
|
||||
pureMerge<typeof viewer>(viewer, {
|
||||
email: "",
|
||||
username: "hans",
|
||||
duplicateEmail: "my@email.com",
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
within(testRenderer.root).debug();
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("linkAccount-container")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { ERROR_CODES } from "coral-common/errors";
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
@@ -229,3 +233,46 @@ it("successfully sets email", async () => {
|
||||
expect(toJSON(form)).toMatchSnapshot();
|
||||
expect(resolvers.Mutation!.setEmail!.called).toBe(true);
|
||||
});
|
||||
|
||||
it("switch to link account", async () => {
|
||||
const email = "hans@test.com";
|
||||
const setEmail = sinon.stub().callsFake((_: any, data: any) => {
|
||||
throw new InvalidRequestError({ code: ERROR_CODES.DUPLICATE_EMAIL });
|
||||
});
|
||||
const {
|
||||
testRenderer,
|
||||
form,
|
||||
emailAddressField,
|
||||
confirmEmailAddressField,
|
||||
} = await createTestRenderer({
|
||||
resolvers: {
|
||||
Mutation: {
|
||||
setEmail,
|
||||
},
|
||||
},
|
||||
muteNetworkErrors: true,
|
||||
});
|
||||
const submitButton = form.find(
|
||||
i => i.type === "button" && i.props.type === "submit"
|
||||
);
|
||||
|
||||
act(() => emailAddressField.props.onChange({ target: { value: email } }));
|
||||
act(() =>
|
||||
confirmEmailAddressField.props.onChange({
|
||||
target: { value: email },
|
||||
})
|
||||
);
|
||||
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
expect(emailAddressField.props.disabled).toBe(true);
|
||||
expect(confirmEmailAddressField.props.disabled).toBe(true);
|
||||
expect(submitButton.props.disabled).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("linkAccount-container")
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { pureMerge } from "coral-common/utils";
|
||||
import { GQLResolver } from "coral-framework/schema";
|
||||
import {
|
||||
act,
|
||||
createAccessToken,
|
||||
createResolversStub,
|
||||
CreateTestRendererParams,
|
||||
replaceHistoryLocation,
|
||||
wait,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "coral-framework/testHelpers";
|
||||
|
||||
import create from "../create";
|
||||
import { settings, users } from "../fixtures";
|
||||
|
||||
const viewer = pureMerge<typeof users["admins"][0]>(users.admins[0], {
|
||||
email: "",
|
||||
});
|
||||
|
||||
async function createTestRenderer(
|
||||
params: CreateTestRendererParams<GQLResolver> = {}
|
||||
) {
|
||||
replaceHistoryLocation("http://localhost/admin/login");
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
...params,
|
||||
resolvers: pureMerge(
|
||||
createResolversStub<GQLResolver>({
|
||||
Query: {
|
||||
viewer: () => viewer,
|
||||
settings: () => settings,
|
||||
},
|
||||
}),
|
||||
params.resolvers
|
||||
),
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
localRecord.setValue("LINK_ACCOUNT", "authView");
|
||||
localRecord.setValue("my@email.com", "authDuplicateEmail");
|
||||
if (params.initLocalState) {
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
const container = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("linkAccount-container")
|
||||
);
|
||||
const form = within(container).queryByType("form")!;
|
||||
const passwordField = within(form).getByLabelText("Password");
|
||||
|
||||
return {
|
||||
context,
|
||||
testRenderer,
|
||||
form,
|
||||
container,
|
||||
passwordField,
|
||||
};
|
||||
}
|
||||
|
||||
it("renders link account view", async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
expect(await within(testRenderer.root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("checks for required password", async () => {
|
||||
const { form, container } = await createTestRenderer();
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
within(container).getByText("This field is required", { exact: false });
|
||||
});
|
||||
|
||||
it("performs account linking", async () => {
|
||||
const { form, passwordField, context } = await createTestRenderer();
|
||||
const restMock = sinon.mock(context.rest);
|
||||
restMock
|
||||
.expects("fetch")
|
||||
.withArgs("/auth/link", {
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "my@email.com",
|
||||
password: "testtest",
|
||||
},
|
||||
})
|
||||
.once()
|
||||
.returns({
|
||||
token: createAccessToken(),
|
||||
});
|
||||
|
||||
act(() => {
|
||||
passwordField.props.onChange("testtest");
|
||||
});
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Look for visual cues for form submission progress.
|
||||
expect(passwordField.props.disabled).toBe(true);
|
||||
await act(async () => {
|
||||
await wait(() => expect(passwordField.props.disabled).toBe(false));
|
||||
});
|
||||
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
it("shows server error", async () => {
|
||||
const { form, passwordField, context } = await createTestRenderer();
|
||||
const error = new Error("Server Error");
|
||||
const restMock = sinon.mock(context.rest);
|
||||
restMock
|
||||
.expects("fetch")
|
||||
.withArgs("/auth/link", {
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "my@email.com",
|
||||
password: "testtest",
|
||||
},
|
||||
})
|
||||
.once()
|
||||
.throws(error);
|
||||
|
||||
act(() => {
|
||||
passwordField.props.onChange("testtest");
|
||||
});
|
||||
act(() => {
|
||||
form.props.onSubmit();
|
||||
});
|
||||
|
||||
// Look for visual cues for form submission progress.
|
||||
expect(passwordField.props.disabled).toBe(true);
|
||||
await act(async () => {
|
||||
await wait(() => expect(passwordField.props.disabled).toBe(false));
|
||||
});
|
||||
within(form).getByText("Server Error", { exact: false });
|
||||
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
it("change email view", async () => {
|
||||
const { testRenderer, container } = await createTestRenderer();
|
||||
const button = within(container).getByText("Use a different Email", {
|
||||
exact: false,
|
||||
});
|
||||
act(() => {
|
||||
button.props.onClick();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("addEmailAddress-container")
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user