mirror of
https://github.com/element-hq/matrix-authentication-service.git
synced 2026-10-23 21:46:14 +00:00
Rename 'Emails' route to 'Profile' (#1567)
* rename /account/emails route to profile * remove support for /emails * bad unit test for Layout * update snapshots, fix layout test * fix snapshots from old version of compound * better layout test * coverage?
This commit is contained in:
1 parent
70e6489f17
commit
c7311eea79
15 files changed
+382
-23
No files matched your search
@@ -0,0 +1,78 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
|
||||
import { segmentsToRoute } from "./Router";
|
||||
|
||||
describe("Router", () => {
|
||||
describe("segmentsToRoute", () => {
|
||||
it("returns home for route with no segments", () => {
|
||||
const segments: string[] = [];
|
||||
expect(segmentsToRoute(segments)).toEqual({ type: "home" });
|
||||
});
|
||||
|
||||
it("returns home for route with and empty string segment", () => {
|
||||
const segments: string[] = [""];
|
||||
expect(segmentsToRoute(segments)).toEqual({ type: "home" });
|
||||
});
|
||||
|
||||
it("returns profile for route with profile", () => {
|
||||
const segments: string[] = ["profile"];
|
||||
expect(segmentsToRoute(segments)).toEqual({ type: "profile" });
|
||||
});
|
||||
|
||||
it("returns browser session list for sessions", () => {
|
||||
const segments: string[] = ["sessions"];
|
||||
expect(segmentsToRoute(segments)).toEqual({
|
||||
type: "browser-session-list",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns compat session list for compat-sessions", () => {
|
||||
const segments: string[] = ["compat-sessions"];
|
||||
expect(segmentsToRoute(segments)).toEqual({
|
||||
type: "compat-session-list",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns oauth session list for oauth2-sessions", () => {
|
||||
const segments: string[] = ["oauth2-sessions"];
|
||||
expect(segmentsToRoute(segments)).toEqual({
|
||||
type: "oauth2-session-list",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns client detail route correctly", () => {
|
||||
const segments: string[] = ["clients", "client-id"];
|
||||
expect(segmentsToRoute(segments)).toEqual({
|
||||
type: "client",
|
||||
id: "client-id",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns session detail route correctly", () => {
|
||||
const segments: string[] = ["sessions", "session-id"];
|
||||
expect(segmentsToRoute(segments)).toEqual({
|
||||
type: "session",
|
||||
id: "session-id",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns unknown for other segments", () => {
|
||||
const segments: string[] = ["just", "testing"];
|
||||
expect(segmentsToRoute(segments)).toEqual({ type: "unknown", segments });
|
||||
});
|
||||
});
|
||||
});
|
||||
+10
-10
@@ -25,7 +25,7 @@ type Location = {
|
||||
};
|
||||
|
||||
type HomeRoute = { type: "home" };
|
||||
type EmailListRoute = { type: "email-list" };
|
||||
type ProfileRoute = { type: "profile" };
|
||||
type OAuth2ClientRoute = { type: "client"; id: string };
|
||||
type OAuth2SessionList = { type: "oauth2-session-list" };
|
||||
type BrowserSessionRoute = { type: "session"; id: string };
|
||||
@@ -36,7 +36,7 @@ type UnknownRoute = { type: "unknown"; segments: string[] };
|
||||
|
||||
export type Route =
|
||||
| HomeRoute
|
||||
| EmailListRoute
|
||||
| ProfileRoute
|
||||
| OAuth2ClientRoute
|
||||
| OAuth2SessionList
|
||||
| BrowserSessionRoute
|
||||
@@ -49,8 +49,8 @@ const routeToSegments = (route: Route): string[] => {
|
||||
switch (route.type) {
|
||||
case "home":
|
||||
return [];
|
||||
case "email-list":
|
||||
return ["emails"];
|
||||
case "profile":
|
||||
return ["profile"];
|
||||
case "verify-email":
|
||||
return ["emails", route.id, "verify"];
|
||||
case "client":
|
||||
@@ -90,7 +90,7 @@ const segmentMatches = (
|
||||
return true;
|
||||
};
|
||||
|
||||
const segmentsToRoute = (segments: string[]): Route => {
|
||||
export const segmentsToRoute = (segments: string[]): Route => {
|
||||
const matches = (...pattern: PatternItem[]): boolean =>
|
||||
segmentMatches(segments, ...pattern);
|
||||
|
||||
@@ -99,8 +99,8 @@ const segmentsToRoute = (segments: string[]): Route => {
|
||||
return { type: "home" };
|
||||
}
|
||||
|
||||
if (matches("emails")) {
|
||||
return { type: "email-list" };
|
||||
if (matches("profile")) {
|
||||
return { type: "profile" };
|
||||
}
|
||||
|
||||
if (matches("sessions")) {
|
||||
@@ -169,7 +169,7 @@ export const routeAtom = atom(
|
||||
);
|
||||
|
||||
const Home = lazy(() => import("./pages/Home"));
|
||||
const EmailList = lazy(() => import("./pages/EmailList"));
|
||||
const Profile = lazy(() => import("./pages/Profile"));
|
||||
const OAuth2Client = lazy(() => import("./pages/OAuth2Client"));
|
||||
const BrowserSession = lazy(() => import("./pages/BrowserSession"));
|
||||
const BrowserSessionList = lazy(() => import("./pages/BrowserSessionList"));
|
||||
@@ -183,8 +183,8 @@ const InnerRouter: React.FC = () => {
|
||||
switch (route.type) {
|
||||
case "home":
|
||||
return <Home />;
|
||||
case "email-list":
|
||||
return <EmailList />;
|
||||
case "profile":
|
||||
return <Profile />;
|
||||
case "oauth2-session-list":
|
||||
return <OAuth2SessionList />;
|
||||
case "browser-session-list":
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Router > routes 1`] = `"Loading..."`;
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { render } from "@testing-library/react";
|
||||
import { Provider } from "jotai";
|
||||
import { useHydrateAtoms } from "jotai/utils";
|
||||
import { Suspense } from "react";
|
||||
import { describe, expect, it, vi, afterAll, beforeEach } from "vitest";
|
||||
|
||||
import { appConfigAtom, locationAtom } from "../Router";
|
||||
import { currentUserIdAtom, GqlResult } from "../atoms";
|
||||
|
||||
import Layout from "./Layout";
|
||||
|
||||
beforeEach(async () => {
|
||||
// For some reason, the locationAtom gets updated with `about:black` on render,
|
||||
// so we need to set a "real" location and wait for the next tick
|
||||
window.location.assign("https://example.com/");
|
||||
// Wait the next tick for the location to update
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
});
|
||||
|
||||
const HydrateLocation: React.FC<React.PropsWithChildren<{ path: string }>> = ({
|
||||
children,
|
||||
path,
|
||||
}) => {
|
||||
useHydrateAtoms([
|
||||
[appConfigAtom, { root: "/" }],
|
||||
[locationAtom, { pathname: path }],
|
||||
]);
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
const WithLocation: React.FC<React.PropsWithChildren<{ path: string }>> = ({
|
||||
children,
|
||||
path,
|
||||
}) => {
|
||||
return (
|
||||
<Provider>
|
||||
<Suspense>
|
||||
<HydrateLocation path={path}>{children}</HydrateLocation>
|
||||
</Suspense>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
describe("<Layout />", () => {
|
||||
beforeEach(() => {
|
||||
vi.spyOn(currentUserIdAtom, "read").mockResolvedValue(
|
||||
"abc123" as unknown as GqlResult<string | null>,
|
||||
);
|
||||
});
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
it("renders app navigation correctly", async () => {
|
||||
const component = render(
|
||||
<WithLocation path="/account">
|
||||
<Layout />
|
||||
</WithLocation>,
|
||||
);
|
||||
|
||||
expect(await component.findByText("Profile")).toMatchSnapshot();
|
||||
expect(await component.findByText("Home")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -43,7 +43,7 @@ const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
|
||||
<NavBar>
|
||||
<NavItem route={{ type: "home" }}>Home</NavItem>
|
||||
<NavItem route={{ type: "email-list" }}>Emails</NavItem>
|
||||
<NavItem route={{ type: "profile" }}>Profile</NavItem>
|
||||
</NavBar>
|
||||
|
||||
<main>{children}</main>
|
||||
|
||||
@@ -30,7 +30,7 @@ const meta = {
|
||||
<WithHomePage>
|
||||
<NavBar {...props}>
|
||||
<NavItem route={{ type: "home" }}>Home</NavItem>
|
||||
<NavItem route={{ type: "email-list" }}>Emails</NavItem>
|
||||
<NavItem route={{ type: "profile" }}>Emails</NavItem>
|
||||
<ExternalLink href="https://example.com">External</ExternalLink>
|
||||
</NavBar>
|
||||
</WithHomePage>
|
||||
|
||||
@@ -53,7 +53,7 @@ export const Active: Story = {
|
||||
|
||||
export const Inactive: Story = {
|
||||
args: {
|
||||
route: { type: "email-list" },
|
||||
children: "Emails",
|
||||
route: { type: "profile" },
|
||||
children: "Profile",
|
||||
},
|
||||
};
|
||||
@@ -77,7 +77,7 @@ describe("NavItem", () => {
|
||||
it("renders a different route", () => {
|
||||
const component = create(
|
||||
<WithLocation path="/">
|
||||
<NavItem route={{ type: "email-list" }}>Emails</NavItem>
|
||||
<NavItem route={{ type: "profile" }}>Emails</NavItem>
|
||||
</WithLocation>,
|
||||
);
|
||||
const tree = component.toJSON();
|
||||
|
||||
@@ -36,7 +36,7 @@ exports[`NavItem > renders a different route 1`] = `
|
||||
>
|
||||
<a
|
||||
className="_navItem_8603fc"
|
||||
href="/emails"
|
||||
href="/profile"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Emails
|
||||
|
||||
@@ -65,7 +65,7 @@ const UserHome: React.FC<{
|
||||
{data.unverifiedEmails.totalCount > 0 && !dismiss && (
|
||||
<Alert type="critical" title="Unverified email" onClose={doDismiss}>
|
||||
You have {data.unverifiedEmails.totalCount} unverified email
|
||||
address(es). <Link route={{ type: "email-list" }}>Check</Link>
|
||||
address(es). <Link route={{ type: "profile" }}>Check</Link>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -78,7 +78,7 @@ const UserHome: React.FC<{
|
||||
{data.confirmedEmails.totalCount > 1 && (
|
||||
<Body>
|
||||
{data.confirmedEmails.totalCount - 1} additional emails.{" "}
|
||||
<Link route={{ type: "email-list" }}>View all</Link>
|
||||
<Link route={{ type: "profile" }}>View all</Link>
|
||||
</Body>
|
||||
)}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ exports[`UserHome > render a <UserHome /> with additional emails 1`] = `
|
||||
additional emails.
|
||||
|
||||
<a
|
||||
href="/emails"
|
||||
href="/profile"
|
||||
onClick={[Function]}
|
||||
>
|
||||
View all
|
||||
@@ -128,7 +128,7 @@ exports[`UserHome > render a <UserHome /> with an unverified email 1`] = `
|
||||
1
|
||||
unverified email address(es).
|
||||
<a
|
||||
href="/emails"
|
||||
href="/profile"
|
||||
onClick={[Function]}
|
||||
>
|
||||
Check
|
||||
|
||||
@@ -127,7 +127,7 @@ const VerifyEmail: React.FC<{
|
||||
e.currentTarget?.reset();
|
||||
|
||||
if (result.data?.verifyEmail.status === "VERIFIED") {
|
||||
setRoute({ type: "email-list" });
|
||||
setRoute({ type: "profile" });
|
||||
} else {
|
||||
fieldRef.current?.focus();
|
||||
fieldRef.current?.select();
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<VerifyEmail /> > renders an active session 1`] = `
|
||||
[
|
||||
<header
|
||||
className="_header_07ded5"
|
||||
>
|
||||
<svg
|
||||
className="_icon_07ded5"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.878.878 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<h1
|
||||
className="_title_07ded5"
|
||||
>
|
||||
Verify your email
|
||||
</h1>
|
||||
<p
|
||||
className="_tagline_07ded5"
|
||||
>
|
||||
Enter the 6-digit code sent to
|
||||
|
||||
<span
|
||||
className="_email_07ded5"
|
||||
>
|
||||
ernie@sesame.st
|
||||
</span>
|
||||
</p>
|
||||
</header>,
|
||||
<form
|
||||
className="_root_xzzw3_23 _form_07ded5"
|
||||
onInvalid={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="_field_xzzw3_32"
|
||||
>
|
||||
<label
|
||||
className="_label_xzzw3_41"
|
||||
htmlFor="radix-:r0:"
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
6-digit code
|
||||
</label>
|
||||
<input
|
||||
className="_control_xzzw3_55"
|
||||
id="radix-:r0:"
|
||||
inputMode="numeric"
|
||||
name="code"
|
||||
onChange={[Function]}
|
||||
onInvalid={[Function]}
|
||||
placeholder="xxxxxx"
|
||||
title=""
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="_button_lls7s_17 _submitButton_07ded5 _submitButton_07ded5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
type="submit"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
<button
|
||||
className="_button_lls7s_17"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
Resend email
|
||||
</button>
|
||||
</form>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`<VerifyEmail /> > renders verify screen for email 1`] = `
|
||||
[
|
||||
<header
|
||||
className="_header_07ded5"
|
||||
>
|
||||
<svg
|
||||
className="_icon_07ded5"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.55 17.575c-.133 0-.258-.02-.375-.063a.878.878 0 0 1-.325-.212L4.55 13c-.183-.183-.27-.42-.263-.713.009-.291.105-.529.288-.712a.948.948 0 0 1 .7-.275.95.95 0 0 1 .7.275L9.55 15.15l8.475-8.475c.183-.183.42-.275.713-.275.291 0 .529.092.712.275.183.183.275.42.275.713 0 .291-.092.529-.275.712l-9.2 9.2c-.1.1-.208.17-.325.212a1.106 1.106 0 0 1-.375.063Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<h1
|
||||
className="_title_07ded5"
|
||||
>
|
||||
Verify your email
|
||||
</h1>
|
||||
<p
|
||||
className="_tagline_07ded5"
|
||||
>
|
||||
Enter the 6-digit code sent to
|
||||
|
||||
<span
|
||||
className="_email_07ded5"
|
||||
>
|
||||
ernie@sesame.st
|
||||
</span>
|
||||
</p>
|
||||
</header>,
|
||||
<form
|
||||
className="_root_xzzw3_23 _form_07ded5"
|
||||
onInvalid={[Function]}
|
||||
onReset={[Function]}
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="_field_xzzw3_32"
|
||||
>
|
||||
<label
|
||||
className="_label_xzzw3_41"
|
||||
htmlFor="radix-:r0:"
|
||||
onMouseDown={[Function]}
|
||||
>
|
||||
6-digit code
|
||||
</label>
|
||||
<input
|
||||
className="_control_xzzw3_55"
|
||||
id="radix-:r0:"
|
||||
inputMode="numeric"
|
||||
name="code"
|
||||
onChange={[Function]}
|
||||
onInvalid={[Function]}
|
||||
placeholder="xxxxxx"
|
||||
title=""
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="_button_lls7s_17 _submitButton_07ded5 _submitButton_07ded5"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
type="submit"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
<button
|
||||
className="_button_lls7s_17"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
Resend email
|
||||
</button>
|
||||
</form>,
|
||||
]
|
||||
`;
|
||||
@@ -0,0 +1,20 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<Layout /> > renders app navigation correctly 1`] = `
|
||||
<a
|
||||
class="_navItem_8603fc"
|
||||
href="/profile"
|
||||
>
|
||||
Profile
|
||||
</a>
|
||||
`;
|
||||
|
||||
exports[`<Layout /> > renders app navigation correctly 2`] = `
|
||||
<a
|
||||
aria-current="page"
|
||||
class="_navItem_8603fc"
|
||||
href="/"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
`;
|
||||
@@ -20,7 +20,7 @@ import NotLoggedIn from "../components/NotLoggedIn";
|
||||
import UserEmailList from "../components/UserEmailList";
|
||||
import { isErr, unwrapErr, unwrapOk } from "../result";
|
||||
|
||||
const EmailList: React.FC = () => {
|
||||
const Profile: React.FC = () => {
|
||||
const result = useAtomValue(currentUserIdAtom);
|
||||
if (isErr(result)) return <GraphQLError error={unwrapErr(result)} />;
|
||||
|
||||
@@ -30,4 +30,4 @@ const EmailList: React.FC = () => {
|
||||
return <UserEmailList userId={userId} />;
|
||||
};
|
||||
|
||||
export default EmailList;
|
||||
export default Profile;
|
||||
Reference in new issue
Block a user