Full-page stories
This commit is contained in:
@@ -7,7 +7,9 @@
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
stories: ["../{src,stories}/**/*.stories.@(js|jsx|ts|tsx)"],
|
||||
|
||||
staticDirs: ["../stories/static"],
|
||||
|
||||
addons: [
|
||||
// Automatic docs pages
|
||||
@@ -31,6 +33,9 @@ const config: StorybookConfig = {
|
||||
// Theme switch toolbar
|
||||
"@storybook/addon-toolbars",
|
||||
|
||||
// Interactions
|
||||
"@storybook/addon-interactions",
|
||||
|
||||
// i18next integration
|
||||
"storybook-react-i18next",
|
||||
],
|
||||
@@ -48,6 +53,10 @@ const config: StorybookConfig = {
|
||||
docs: {
|
||||
autodocs: true,
|
||||
},
|
||||
|
||||
env: {
|
||||
STORYBOOK: "true",
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -11,14 +11,25 @@ import type {
|
||||
Preview,
|
||||
} from "@storybook/react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { initialize, mswLoader } from "msw-storybook-addon";
|
||||
import { useLayoutEffect } from "react";
|
||||
|
||||
import "../src/shared.css";
|
||||
import i18n from "../src/i18n";
|
||||
import i18n, { setupI18n } from "../src/i18n";
|
||||
|
||||
import { DummyRouter } from "../src/test-utils/router";
|
||||
import { handlers } from "../tests/mocks/handlers";
|
||||
import localazyMetadata from "./locales";
|
||||
|
||||
initialize(
|
||||
{
|
||||
onUnhandledRequest: "bypass",
|
||||
},
|
||||
handlers,
|
||||
);
|
||||
|
||||
setupI18n();
|
||||
|
||||
export const parameters: Parameters = {
|
||||
controls: {
|
||||
matchers: {
|
||||
@@ -104,13 +115,14 @@ const locales = Object.fromEntries(
|
||||
);
|
||||
|
||||
const preview: Preview = {
|
||||
globals: {
|
||||
initialGlobals: {
|
||||
locale: localazyMetadata.baseLocale,
|
||||
locales,
|
||||
},
|
||||
parameters: {
|
||||
i18n,
|
||||
},
|
||||
loaders: [mswLoader],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
1767
frontend/package-lock.json
generated
1767
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -53,9 +53,11 @@
|
||||
"@graphql-codegen/client-preset": "^4.5.0",
|
||||
"@graphql-codegen/typescript-msw": "^3.0.0",
|
||||
"@graphql-codegen/urql-introspection": "^3.0.0",
|
||||
"@storybook/addon-essentials": "^8.4.3",
|
||||
"@storybook/react": "^8.4.3",
|
||||
"@storybook/react-vite": "^8.4.3",
|
||||
"@storybook/addon-essentials": "^8.4.4",
|
||||
"@storybook/addon-interactions": "^8.4.4",
|
||||
"@storybook/react": "^8.4.4",
|
||||
"@storybook/react-vite": "^8.4.4",
|
||||
"@storybook/test": "^8.4.4",
|
||||
"@tanstack/react-query-devtools": "^5.59.20",
|
||||
"@tanstack/router-devtools": "^1.81.5",
|
||||
"@tanstack/router-vite-plugin": "^1.79.0",
|
||||
@@ -74,11 +76,12 @@
|
||||
"happy-dom": "^15.11.4",
|
||||
"i18next-parser": "^9.0.2",
|
||||
"msw": "^2.6.4",
|
||||
"msw-storybook-addon": "^2.0.4",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-nesting": "^13.0.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"storybook": "^8.3.5",
|
||||
"storybook": "^8.4.4",
|
||||
"storybook-react-i18next": "^3.1.7",
|
||||
"tailwindcss": "^3.4.14",
|
||||
"typescript": "5.6.3",
|
||||
@@ -87,5 +90,8 @@
|
||||
"vite-plugin-graphql-codegen": "^3.3.8",
|
||||
"vite-plugin-manifest-sri": "^0.2.0",
|
||||
"vitest": "^2.1.2"
|
||||
},
|
||||
"msw": {
|
||||
"workerDirectory": ["stories/static"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@ import { type FragmentType, graphql, useFragment } from "../../gql";
|
||||
|
||||
import styles from "./AccountManagementPasswordPreview.module.css";
|
||||
|
||||
const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment PasswordChange_siteConfig on SiteConfig {
|
||||
id
|
||||
passwordChangeAllowed
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -19,7 +19,7 @@ import styles from "./UserEmail.module.css";
|
||||
// This component shows a single user email address, with controls to verify it,
|
||||
// resend the verification email, remove it, and set it as the primary email address.
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmail_email on UserEmail {
|
||||
id
|
||||
email
|
||||
@@ -27,9 +27,8 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmail_siteConfig on SiteConfig {
|
||||
id
|
||||
emailChangeAllowed
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -49,7 +49,7 @@ const QUERY = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmailList_user on User {
|
||||
id
|
||||
primaryEmail {
|
||||
@@ -58,9 +58,8 @@ const FRAGMENT = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
|
||||
export const CONFIG_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment UserEmailList_siteConfig on SiteConfig {
|
||||
id
|
||||
...UserEmail_siteConfig
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as types from './graphql';
|
||||
* Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size
|
||||
*/
|
||||
const documents = {
|
||||
"\n fragment PasswordChange_siteConfig on SiteConfig {\n id\n passwordChangeAllowed\n }\n": types.PasswordChange_SiteConfigFragmentDoc,
|
||||
"\n fragment PasswordChange_siteConfig on SiteConfig {\n passwordChangeAllowed\n }\n": types.PasswordChange_SiteConfigFragmentDoc,
|
||||
"\n fragment BrowserSession_session on BrowserSession {\n id\n createdAt\n finishedAt\n userAgent {\n raw\n name\n os\n model\n deviceType\n }\n lastActiveIp\n lastActiveAt\n lastAuthentication {\n id\n createdAt\n }\n }\n": types.BrowserSession_SessionFragmentDoc,
|
||||
"\n mutation EndBrowserSession($id: ID!) {\n endBrowserSession(input: { browserSessionId: $id }) {\n status\n browserSession {\n id\n ...BrowserSession_session\n }\n }\n }\n": types.EndBrowserSessionDocument,
|
||||
"\n fragment OAuth2Client_detail on Oauth2Client {\n id\n clientId\n clientName\n clientUri\n logoUri\n tosUri\n policyUri\n redirectUris\n }\n": types.OAuth2Client_DetailFragmentDoc,
|
||||
@@ -31,7 +31,7 @@ const documents = {
|
||||
"\n fragment OAuth2Session_detail on Oauth2Session {\n id\n scope\n createdAt\n finishedAt\n lastActiveIp\n lastActiveAt\n client {\n id\n clientId\n clientName\n clientUri\n logoUri\n }\n }\n": types.OAuth2Session_DetailFragmentDoc,
|
||||
"\n fragment UnverifiedEmailAlert_user on User {\n unverifiedEmails: emails(first: 0, state: PENDING) {\n totalCount\n }\n }\n": types.UnverifiedEmailAlert_UserFragmentDoc,
|
||||
"\n fragment UserEmail_email on UserEmail {\n id\n email\n confirmedAt\n }\n": types.UserEmail_EmailFragmentDoc,
|
||||
"\n fragment UserEmail_siteConfig on SiteConfig {\n id\n emailChangeAllowed\n }\n": types.UserEmail_SiteConfigFragmentDoc,
|
||||
"\n fragment UserEmail_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n": types.UserEmail_SiteConfigFragmentDoc,
|
||||
"\n mutation RemoveEmail($id: ID!) {\n removeEmail(input: { userEmailId: $id }) {\n status\n\n user {\n id\n }\n }\n }\n": types.RemoveEmailDocument,
|
||||
"\n mutation SetPrimaryEmail($id: ID!) {\n setPrimaryEmail(input: { userEmailId: $id }) {\n status\n user {\n id\n primaryEmail {\n id\n }\n }\n }\n }\n": types.SetPrimaryEmailDocument,
|
||||
"\n fragment UserGreeting_user on User {\n id\n matrix {\n mxid\n displayName\n }\n }\n": types.UserGreeting_UserFragmentDoc,
|
||||
@@ -40,7 +40,7 @@ const documents = {
|
||||
"\n mutation AddEmail($userId: ID!, $email: String!) {\n addEmail(input: { userId: $userId, email: $email }) {\n status\n violations\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.AddEmailDocument,
|
||||
"\n query UserEmailList(\n $userId: ID!\n $first: Int\n $after: String\n $last: Int\n $before: String\n ) {\n user(id: $userId) {\n id\n\n emails(first: $first, after: $after, last: $last, before: $before) {\n edges {\n cursor\n node {\n id\n ...UserEmail_email\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n": types.UserEmailListDocument,
|
||||
"\n fragment UserEmailList_user on User {\n id\n primaryEmail {\n id\n }\n }\n": types.UserEmailList_UserFragmentDoc,
|
||||
"\n fragment UserEmailList_siteConfig on SiteConfig {\n id\n ...UserEmail_siteConfig\n }\n": types.UserEmailList_SiteConfigFragmentDoc,
|
||||
"\n fragment UserEmailList_siteConfig on SiteConfig {\n ...UserEmail_siteConfig\n }\n": types.UserEmailList_SiteConfigFragmentDoc,
|
||||
"\n fragment BrowserSessionsOverview_user on User {\n id\n\n browserSessions(first: 0, state: ACTIVE) {\n totalCount\n }\n }\n": types.BrowserSessionsOverview_UserFragmentDoc,
|
||||
"\n fragment UserEmail_verifyEmail on UserEmail {\n id\n email\n }\n": types.UserEmail_VerifyEmailFragmentDoc,
|
||||
"\n mutation DoVerifyEmail($id: ID!, $code: String!) {\n verifyEmail(input: { userEmailId: $id, code: $code }) {\n status\n\n user {\n id\n primaryEmail {\n id\n }\n }\n\n email {\n id\n ...UserEmail_email\n }\n }\n }\n": types.DoVerifyEmailDocument,
|
||||
@@ -65,7 +65,7 @@ const documents = {
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment PasswordChange_siteConfig on SiteConfig {\n id\n passwordChangeAllowed\n }\n"): typeof import('./graphql').PasswordChange_SiteConfigFragmentDoc;
|
||||
export function graphql(source: "\n fragment PasswordChange_siteConfig on SiteConfig {\n passwordChangeAllowed\n }\n"): typeof import('./graphql').PasswordChange_SiteConfigFragmentDoc;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -129,7 +129,7 @@ export function graphql(source: "\n fragment UserEmail_email on UserEmail {\n
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment UserEmail_siteConfig on SiteConfig {\n id\n emailChangeAllowed\n }\n"): typeof import('./graphql').UserEmail_SiteConfigFragmentDoc;
|
||||
export function graphql(source: "\n fragment UserEmail_siteConfig on SiteConfig {\n emailChangeAllowed\n }\n"): typeof import('./graphql').UserEmail_SiteConfigFragmentDoc;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
@@ -165,7 +165,7 @@ export function graphql(source: "\n fragment UserEmailList_user on User {\n
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment UserEmailList_siteConfig on SiteConfig {\n id\n ...UserEmail_siteConfig\n }\n"): typeof import('./graphql').UserEmailList_SiteConfigFragmentDoc;
|
||||
export function graphql(source: "\n fragment UserEmailList_siteConfig on SiteConfig {\n ...UserEmail_siteConfig\n }\n"): typeof import('./graphql').UserEmailList_SiteConfigFragmentDoc;
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -1417,7 +1417,7 @@ export type Viewer = Anonymous | User;
|
||||
/** Represents the current viewer's session */
|
||||
export type ViewerSession = Anonymous | BrowserSession | Oauth2Session;
|
||||
|
||||
export type PasswordChange_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, passwordChangeAllowed: boolean } & { ' $fragmentName'?: 'PasswordChange_SiteConfigFragment' };
|
||||
export type PasswordChange_SiteConfigFragment = { __typename?: 'SiteConfig', passwordChangeAllowed: boolean } & { ' $fragmentName'?: 'PasswordChange_SiteConfigFragment' };
|
||||
|
||||
export type BrowserSession_SessionFragment = { __typename?: 'BrowserSession', id: string, createdAt: string, finishedAt?: string | null, lastActiveIp?: string | null, lastActiveAt?: string | null, userAgent?: { __typename?: 'UserAgent', raw: string, name?: string | null, os?: string | null, model?: string | null, deviceType: DeviceType } | null, lastAuthentication?: { __typename?: 'Authentication', id: string, createdAt: string } | null } & { ' $fragmentName'?: 'BrowserSession_SessionFragment' };
|
||||
|
||||
@@ -1473,7 +1473,7 @@ export type UnverifiedEmailAlert_UserFragment = { __typename?: 'User', unverifie
|
||||
|
||||
export type UserEmail_EmailFragment = { __typename?: 'UserEmail', id: string, email: string, confirmedAt?: string | null } & { ' $fragmentName'?: 'UserEmail_EmailFragment' };
|
||||
|
||||
export type UserEmail_SiteConfigFragment = { __typename?: 'SiteConfig', id: string, emailChangeAllowed: boolean } & { ' $fragmentName'?: 'UserEmail_SiteConfigFragment' };
|
||||
export type UserEmail_SiteConfigFragment = { __typename?: 'SiteConfig', emailChangeAllowed: boolean } & { ' $fragmentName'?: 'UserEmail_SiteConfigFragment' };
|
||||
|
||||
export type RemoveEmailMutationVariables = Exact<{
|
||||
id: Scalars['ID']['input'];
|
||||
@@ -1529,7 +1529,7 @@ export type UserEmailListQuery = { __typename?: 'Query', user?: { __typename?: '
|
||||
export type UserEmailList_UserFragment = { __typename?: 'User', id: string, primaryEmail?: { __typename?: 'UserEmail', id: string } | null } & { ' $fragmentName'?: 'UserEmailList_UserFragment' };
|
||||
|
||||
export type UserEmailList_SiteConfigFragment = (
|
||||
{ __typename?: 'SiteConfig', id: string }
|
||||
{ __typename?: 'SiteConfig' }
|
||||
& { ' $fragmentRefs'?: { 'UserEmail_SiteConfigFragment': UserEmail_SiteConfigFragment } }
|
||||
) & { ' $fragmentName'?: 'UserEmailList_SiteConfigFragment' };
|
||||
|
||||
@@ -1727,7 +1727,6 @@ export class TypedDocumentString<TResult, TVariables>
|
||||
}
|
||||
export const PasswordChange_SiteConfigFragmentDoc = new TypedDocumentString(`
|
||||
fragment PasswordChange_siteConfig on SiteConfig {
|
||||
id
|
||||
passwordChangeAllowed
|
||||
}
|
||||
`, {"fragmentName":"PasswordChange_siteConfig"}) as unknown as TypedDocumentString<PasswordChange_SiteConfigFragment, unknown>;
|
||||
@@ -1916,17 +1915,14 @@ export const UserEmailList_UserFragmentDoc = new TypedDocumentString(`
|
||||
`, {"fragmentName":"UserEmailList_user"}) as unknown as TypedDocumentString<UserEmailList_UserFragment, unknown>;
|
||||
export const UserEmail_SiteConfigFragmentDoc = new TypedDocumentString(`
|
||||
fragment UserEmail_siteConfig on SiteConfig {
|
||||
id
|
||||
emailChangeAllowed
|
||||
}
|
||||
`, {"fragmentName":"UserEmail_siteConfig"}) as unknown as TypedDocumentString<UserEmail_SiteConfigFragment, unknown>;
|
||||
export const UserEmailList_SiteConfigFragmentDoc = new TypedDocumentString(`
|
||||
fragment UserEmailList_siteConfig on SiteConfig {
|
||||
id
|
||||
...UserEmail_siteConfig
|
||||
}
|
||||
fragment UserEmail_siteConfig on SiteConfig {
|
||||
id
|
||||
emailChangeAllowed
|
||||
}`, {"fragmentName":"UserEmailList_siteConfig"}) as unknown as TypedDocumentString<UserEmailList_SiteConfigFragment, unknown>;
|
||||
export const BrowserSessionsOverview_UserFragmentDoc = new TypedDocumentString(`
|
||||
@@ -2141,7 +2137,6 @@ export const UserProfileDocument = new TypedDocumentString(`
|
||||
}
|
||||
}
|
||||
fragment PasswordChange_siteConfig on SiteConfig {
|
||||
id
|
||||
passwordChangeAllowed
|
||||
}
|
||||
fragment UserEmail_email on UserEmail {
|
||||
@@ -2150,7 +2145,6 @@ fragment UserEmail_email on UserEmail {
|
||||
confirmedAt
|
||||
}
|
||||
fragment UserEmail_siteConfig on SiteConfig {
|
||||
id
|
||||
emailChangeAllowed
|
||||
}
|
||||
fragment UserEmailList_user on User {
|
||||
@@ -2160,7 +2154,6 @@ fragment UserEmailList_user on User {
|
||||
}
|
||||
}
|
||||
fragment UserEmailList_siteConfig on SiteConfig {
|
||||
id
|
||||
...UserEmail_siteConfig
|
||||
}`) as unknown as TypedDocumentString<UserProfileQuery, UserProfileQueryVariables>;
|
||||
export const SessionDetailDocument = new TypedDocumentString(`
|
||||
|
||||
@@ -31,7 +31,9 @@ export const Route = createRootRouteWithContext<{
|
||||
<ScrollRestoration />
|
||||
<Outlet />
|
||||
|
||||
{import.meta.env.DEV && !import.meta.env.TEST && (
|
||||
{import.meta.env.DEV &&
|
||||
!import.meta.env.TEST &&
|
||||
!import.meta.env.STORYBOOK && (
|
||||
<>
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
<ReactQueryDevtools buttonPosition="top-right" />
|
||||
|
||||
27
frontend/stories/routes/app.tsx
Normal file
27
frontend/stories/routes/app.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { QueryClientProvider } from "@tanstack/react-query";
|
||||
import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import i18n from "i18next";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { queryClient } from "../../src/graphql";
|
||||
import { router } from "../../src/router";
|
||||
|
||||
export const App: React.FC<{ route: string }> = ({ route }) => {
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [route],
|
||||
});
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<RouterProvider
|
||||
router={router}
|
||||
history={history}
|
||||
context={{ queryClient }}
|
||||
/>
|
||||
</I18nextProvider>
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
36
frontend/stories/routes/index.stories.tsx
Normal file
36
frontend/stories/routes/index.stories.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { expect, userEvent, waitFor, within } from "@storybook/test";
|
||||
import i18n from "i18next";
|
||||
import { App } from "./app";
|
||||
|
||||
const meta = {
|
||||
title: "Pages/Index",
|
||||
render: () => <App route="/" />,
|
||||
tags: ["!autodocs"],
|
||||
} satisfies Meta;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj;
|
||||
|
||||
export const Index: Story = {};
|
||||
|
||||
export const EditProfile: Story = {
|
||||
play: async ({ canvasElement, globals }) => {
|
||||
const t = i18n.getFixedT(globals.locale);
|
||||
await i18n.loadLanguages(globals.locale);
|
||||
const page = within(document.body);
|
||||
const canvas = within(canvasElement);
|
||||
const button = await waitFor(() =>
|
||||
canvas.getByRole("button", { name: t("action.edit") }),
|
||||
);
|
||||
await userEvent.click(button);
|
||||
|
||||
const dialog = page.getByRole("dialog");
|
||||
expect(dialog).toHaveTextContent(t("frontend.account.edit_profile.title"));
|
||||
},
|
||||
};
|
||||
295
frontend/stories/static/mockServiceWorker.js
Normal file
295
frontend/stories/static/mockServiceWorker.js
Normal file
@@ -0,0 +1,295 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
|
||||
/**
|
||||
* Mock Service Worker.
|
||||
* @see https://github.com/mswjs/msw
|
||||
* - Please do NOT modify this file.
|
||||
* - Please do NOT serve this file on production.
|
||||
*/
|
||||
|
||||
const PACKAGE_VERSION = "2.6.4";
|
||||
const INTEGRITY_CHECKSUM = "ca7800994cc8bfb5eb961e037c877074";
|
||||
const IS_MOCKED_RESPONSE = Symbol("isMockedResponse");
|
||||
const activeClientIds = new Set();
|
||||
|
||||
self.addEventListener("install", () => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener("message", async (event) => {
|
||||
const clientId = event.source.id;
|
||||
|
||||
if (!clientId || !self.clients) {
|
||||
return;
|
||||
}
|
||||
|
||||
const client = await self.clients.get(clientId);
|
||||
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: "window",
|
||||
});
|
||||
|
||||
switch (event.data) {
|
||||
case "KEEPALIVE_REQUEST": {
|
||||
sendToClient(client, {
|
||||
type: "KEEPALIVE_RESPONSE",
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "INTEGRITY_CHECK_REQUEST": {
|
||||
sendToClient(client, {
|
||||
type: "INTEGRITY_CHECK_RESPONSE",
|
||||
payload: {
|
||||
packageVersion: PACKAGE_VERSION,
|
||||
checksum: INTEGRITY_CHECKSUM,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "MOCK_ACTIVATE": {
|
||||
activeClientIds.add(clientId);
|
||||
|
||||
sendToClient(client, {
|
||||
type: "MOCKING_ENABLED",
|
||||
payload: {
|
||||
client: {
|
||||
id: client.id,
|
||||
frameType: client.frameType,
|
||||
},
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case "MOCK_DEACTIVATE": {
|
||||
activeClientIds.delete(clientId);
|
||||
break;
|
||||
}
|
||||
|
||||
case "CLIENT_CLOSED": {
|
||||
activeClientIds.delete(clientId);
|
||||
|
||||
const remainingClients = allClients.filter((client) => {
|
||||
return client.id !== clientId;
|
||||
});
|
||||
|
||||
// Unregister itself when there are no more clients
|
||||
if (remainingClients.length === 0) {
|
||||
self.registration.unregister();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const { request } = event;
|
||||
|
||||
// Bypass navigation requests.
|
||||
if (request.mode === "navigate") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Opening the DevTools triggers the "only-if-cached" request
|
||||
// that cannot be handled by the worker. Bypass such requests.
|
||||
if (request.cache === "only-if-cached" && request.mode !== "same-origin") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Bypass all requests when there are no active clients.
|
||||
// Prevents the self-unregistered worked from handling requests
|
||||
// after it's been deleted (still remains active until the next reload).
|
||||
if (activeClientIds.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate unique request ID.
|
||||
const requestId = crypto.randomUUID();
|
||||
event.respondWith(handleRequest(event, requestId));
|
||||
});
|
||||
|
||||
async function handleRequest(event, requestId) {
|
||||
const client = await resolveMainClient(event);
|
||||
const response = await getResponse(event, client, requestId);
|
||||
|
||||
// Send back the response clone for the "response:*" life-cycle events.
|
||||
// Ensure MSW is active and ready to handle the message, otherwise
|
||||
// this message will pend indefinitely.
|
||||
if (client && activeClientIds.has(client.id)) {
|
||||
(async () => {
|
||||
const responseClone = response.clone();
|
||||
|
||||
sendToClient(
|
||||
client,
|
||||
{
|
||||
type: "RESPONSE",
|
||||
payload: {
|
||||
requestId,
|
||||
isMockedResponse: IS_MOCKED_RESPONSE in response,
|
||||
type: responseClone.type,
|
||||
status: responseClone.status,
|
||||
statusText: responseClone.statusText,
|
||||
body: responseClone.body,
|
||||
headers: Object.fromEntries(responseClone.headers.entries()),
|
||||
},
|
||||
},
|
||||
[responseClone.body],
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Resolve the main client for the given event.
|
||||
// Client that issues a request doesn't necessarily equal the client
|
||||
// that registered the worker. It's with the latter the worker should
|
||||
// communicate with during the response resolving phase.
|
||||
async function resolveMainClient(event) {
|
||||
const client = await self.clients.get(event.clientId);
|
||||
|
||||
if (activeClientIds.has(event.clientId)) {
|
||||
return client;
|
||||
}
|
||||
|
||||
if (client?.frameType === "top-level") {
|
||||
return client;
|
||||
}
|
||||
|
||||
const allClients = await self.clients.matchAll({
|
||||
type: "window",
|
||||
});
|
||||
|
||||
return allClients
|
||||
.filter((client) => {
|
||||
// Get only those clients that are currently visible.
|
||||
return client.visibilityState === "visible";
|
||||
})
|
||||
.find((client) => {
|
||||
// Find the client ID that's recorded in the
|
||||
// set of clients that have registered the worker.
|
||||
return activeClientIds.has(client.id);
|
||||
});
|
||||
}
|
||||
|
||||
async function getResponse(event, client, requestId) {
|
||||
const { request } = event;
|
||||
|
||||
// Clone the request because it might've been already used
|
||||
// (i.e. its body has been read and sent to the client).
|
||||
const requestClone = request.clone();
|
||||
|
||||
function passthrough() {
|
||||
// Cast the request headers to a new Headers instance
|
||||
// so the headers can be manipulated with.
|
||||
const headers = new Headers(requestClone.headers);
|
||||
|
||||
// Remove the "accept" header value that marked this request as passthrough.
|
||||
// This prevents request alteration and also keeps it compliant with the
|
||||
// user-defined CORS policies.
|
||||
headers.delete("accept", "msw/passthrough");
|
||||
|
||||
return fetch(requestClone, { headers });
|
||||
}
|
||||
|
||||
// Bypass mocking when the client is not active.
|
||||
if (!client) {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Bypass initial page load requests (i.e. static assets).
|
||||
// The absence of the immediate/parent client in the map of the active clients
|
||||
// means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
|
||||
// and is not ready to handle requests.
|
||||
if (!activeClientIds.has(client.id)) {
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
// Notify the client that a request has been intercepted.
|
||||
const requestBuffer = await request.arrayBuffer();
|
||||
const clientMessage = await sendToClient(
|
||||
client,
|
||||
{
|
||||
type: "REQUEST",
|
||||
payload: {
|
||||
id: requestId,
|
||||
url: request.url,
|
||||
mode: request.mode,
|
||||
method: request.method,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
cache: request.cache,
|
||||
credentials: request.credentials,
|
||||
destination: request.destination,
|
||||
integrity: request.integrity,
|
||||
redirect: request.redirect,
|
||||
referrer: request.referrer,
|
||||
referrerPolicy: request.referrerPolicy,
|
||||
body: requestBuffer,
|
||||
keepalive: request.keepalive,
|
||||
},
|
||||
},
|
||||
[requestBuffer],
|
||||
);
|
||||
|
||||
switch (clientMessage.type) {
|
||||
case "MOCK_RESPONSE": {
|
||||
return respondWithMock(clientMessage.data);
|
||||
}
|
||||
|
||||
case "PASSTHROUGH": {
|
||||
return passthrough();
|
||||
}
|
||||
}
|
||||
|
||||
return passthrough();
|
||||
}
|
||||
|
||||
function sendToClient(client, message, transferrables = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
|
||||
channel.port1.onmessage = (event) => {
|
||||
if (event.data?.error) {
|
||||
return reject(event.data.error);
|
||||
}
|
||||
|
||||
resolve(event.data);
|
||||
};
|
||||
|
||||
client.postMessage(
|
||||
message,
|
||||
[channel.port2].concat(transferrables.filter(Boolean)),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async function respondWithMock(response) {
|
||||
// Setting response status code to 0 is a no-op.
|
||||
// However, when responding with a "Response.error()", the produced Response
|
||||
// instance will have status code set to 0. Since it's not possible to create
|
||||
// a Response instance with status code 0, handle that use-case separately.
|
||||
if (response.status === 0) {
|
||||
return Response.error();
|
||||
}
|
||||
|
||||
const mockedResponse = new Response(response.body, response);
|
||||
|
||||
Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
|
||||
value: true,
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
return mockedResponse;
|
||||
}
|
||||
153
frontend/tests/mocks/handlers.ts
Normal file
153
frontend/tests/mocks/handlers.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { HttpResponse } from "msw";
|
||||
import { CONFIG_FRAGMENT as PASSWORD_CHANGE_CONFIG_FRAGMENT } from "../../src/components/AccountManagementPasswordPreview/AccountManagementPasswordPreview";
|
||||
import { FRAGMENT as FOOTER_FRAGMENT } from "../../src/components/Footer/Footer";
|
||||
import { UNVERIFIED_EMAILS_FRAGMENT } from "../../src/components/UnverifiedEmailAlert/UnverifiedEmailAlert";
|
||||
import {
|
||||
CONFIG_FRAGMENT as USER_EMAIL_CONFIG_FRAGMENT,
|
||||
FRAGMENT as USER_EMAIL_FRAGMENT,
|
||||
} from "../../src/components/UserEmail/UserEmail";
|
||||
import {
|
||||
CONFIG_FRAGMENT as USER_GREETING_CONFIG_FRAGMENT,
|
||||
FRAGMENT as USER_GREETING_FRAGMENT,
|
||||
} from "../../src/components/UserGreeting/UserGreeting";
|
||||
import { CONFIG_FRAGMENT as USER_EMAIL_LIST_CONFIG_FRAGMENT } from "../../src/components/UserProfile/UserEmailList";
|
||||
import { makeFragmentData } from "../../src/gql";
|
||||
import {
|
||||
mockCurrentUserGreetingQuery,
|
||||
mockFooterQuery,
|
||||
mockUserEmailListQuery,
|
||||
mockUserProfileQuery,
|
||||
} from "../../src/gql/graphql";
|
||||
|
||||
export const handlers = [
|
||||
mockFooterQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
siteConfig: {
|
||||
id: "siteConfig",
|
||||
|
||||
...makeFragmentData(
|
||||
{
|
||||
id: "siteConfig",
|
||||
policyUri: "https://matrix.org/policy",
|
||||
tosUri: "https://matrix.org/tos",
|
||||
imprint:
|
||||
"All Rights Reserved. The Super Chat name, logo and device are registered trade marks of BigCorp Ltd.",
|
||||
},
|
||||
FOOTER_FRAGMENT,
|
||||
),
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
mockCurrentUserGreetingQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
viewerSession: {
|
||||
__typename: "BrowserSession",
|
||||
|
||||
id: "session-id",
|
||||
user: Object.assign(
|
||||
makeFragmentData(
|
||||
{
|
||||
id: "user-id",
|
||||
matrix: {
|
||||
mxid: "@alice:example.com",
|
||||
displayName: "Alice",
|
||||
},
|
||||
},
|
||||
USER_GREETING_FRAGMENT,
|
||||
),
|
||||
|
||||
makeFragmentData(
|
||||
{
|
||||
unverifiedEmails: {
|
||||
totalCount: 0,
|
||||
},
|
||||
},
|
||||
UNVERIFIED_EMAILS_FRAGMENT,
|
||||
),
|
||||
),
|
||||
},
|
||||
|
||||
siteConfig: makeFragmentData(
|
||||
{
|
||||
displayNameChangeAllowed: true,
|
||||
},
|
||||
USER_GREETING_CONFIG_FRAGMENT,
|
||||
),
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
mockUserProfileQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
viewer: {
|
||||
__typename: "User",
|
||||
id: "user-id",
|
||||
primaryEmail: {
|
||||
id: "primary-email-id",
|
||||
...makeFragmentData(
|
||||
{
|
||||
id: "primary-email-id",
|
||||
email: "alice@example.com",
|
||||
confirmedAt: new Date().toISOString(),
|
||||
},
|
||||
USER_EMAIL_FRAGMENT,
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
siteConfig: Object.assign(
|
||||
{
|
||||
emailChangeAllowed: true,
|
||||
passwordLoginEnabled: true,
|
||||
},
|
||||
makeFragmentData(
|
||||
{
|
||||
emailChangeAllowed: true,
|
||||
},
|
||||
USER_EMAIL_CONFIG_FRAGMENT,
|
||||
),
|
||||
makeFragmentData(
|
||||
makeFragmentData(
|
||||
{
|
||||
emailChangeAllowed: true,
|
||||
},
|
||||
USER_EMAIL_CONFIG_FRAGMENT,
|
||||
),
|
||||
USER_EMAIL_LIST_CONFIG_FRAGMENT,
|
||||
),
|
||||
makeFragmentData(
|
||||
{
|
||||
passwordChangeAllowed: true,
|
||||
},
|
||||
PASSWORD_CHANGE_CONFIG_FRAGMENT,
|
||||
),
|
||||
),
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
mockUserEmailListQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
user: {
|
||||
id: "user-id",
|
||||
emails: {
|
||||
edges: [],
|
||||
totalCount: 0,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
];
|
||||
@@ -2,47 +2,47 @@
|
||||
|
||||
exports[`Account home page > display name edit box > displays an error if the display name is invalid 1`] = `
|
||||
<div
|
||||
aria-describedby="radix-:r45:"
|
||||
aria-labelledby="radix-:r44:"
|
||||
aria-describedby="radix-:r41:"
|
||||
aria-labelledby="radix-:r40:"
|
||||
class="_body_9cf7b0"
|
||||
data-state="open"
|
||||
id="radix-:r43:"
|
||||
id="radix-:r3v:"
|
||||
role="dialog"
|
||||
style="pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h2
|
||||
class="_title_9cf7b0"
|
||||
id="radix-:r44:"
|
||||
id="radix-:r40:"
|
||||
>
|
||||
Edit profile
|
||||
</h2>
|
||||
<span
|
||||
aria-label="@user:example.com"
|
||||
aria-label="@alice:example.com"
|
||||
class="_avatar_mcap2_17 self-center _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-color="6"
|
||||
data-type="round"
|
||||
role="img"
|
||||
style="--cpd-avatar-size: 88px;"
|
||||
>
|
||||
U
|
||||
A
|
||||
</span>
|
||||
bound HTMLFormElement {
|
||||
"0": <input
|
||||
aria-describedby="radix-:r4r:"
|
||||
aria-describedby="radix-:r4m:"
|
||||
aria-invalid="true"
|
||||
autocomplete="name"
|
||||
class="_control_9gon8_18 _control_1qov4_22"
|
||||
data-invalid="true"
|
||||
id="radix-:r4k:"
|
||||
id="radix-:r4f:"
|
||||
name="displayname"
|
||||
title=""
|
||||
type="text"
|
||||
value="User"
|
||||
value="Alice"
|
||||
/>,
|
||||
"1": <button
|
||||
aria-controls=":r4l:"
|
||||
aria-labelledby=":r4m:"
|
||||
aria-controls=":r4g:"
|
||||
aria-labelledby=":r4h:"
|
||||
class="_action_1qov4_33"
|
||||
type="button"
|
||||
>
|
||||
@@ -61,11 +61,11 @@ exports[`Account home page > display name edit box > displays an error if the di
|
||||
</button>,
|
||||
"2": <input
|
||||
class="_control_9gon8_18"
|
||||
id="radix-:r4s:"
|
||||
id="radix-:r4n:"
|
||||
name="mxid"
|
||||
readonly=""
|
||||
title=""
|
||||
value="@user:example.com"
|
||||
value="@alice:example.com"
|
||||
/>,
|
||||
"3": <button
|
||||
aria-disabled="false"
|
||||
@@ -90,7 +90,7 @@ exports[`Account home page > display name edit box > displays an error if the di
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby=":r4t:"
|
||||
aria-labelledby=":r4o:"
|
||||
class="_close_9cf7b0"
|
||||
type="button"
|
||||
>
|
||||
@@ -111,45 +111,45 @@ exports[`Account home page > display name edit box > displays an error if the di
|
||||
|
||||
exports[`Account home page > display name edit box > lets edit the display name 1`] = `
|
||||
<div
|
||||
aria-describedby="radix-:rp:"
|
||||
aria-labelledby="radix-:ro:"
|
||||
aria-describedby="radix-:ro:"
|
||||
aria-labelledby="radix-:rn:"
|
||||
class="_body_9cf7b0"
|
||||
data-state="open"
|
||||
id="radix-:rn:"
|
||||
id="radix-:rm:"
|
||||
role="dialog"
|
||||
style="pointer-events: auto;"
|
||||
tabindex="-1"
|
||||
>
|
||||
<h2
|
||||
class="_title_9cf7b0"
|
||||
id="radix-:ro:"
|
||||
id="radix-:rn:"
|
||||
>
|
||||
Edit profile
|
||||
</h2>
|
||||
<span
|
||||
aria-label="@user:example.com"
|
||||
aria-label="@alice:example.com"
|
||||
class="_avatar_mcap2_17 self-center _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-color="6"
|
||||
data-type="round"
|
||||
role="img"
|
||||
style="--cpd-avatar-size: 88px;"
|
||||
>
|
||||
U
|
||||
A
|
||||
</span>
|
||||
bound HTMLFormElement {
|
||||
"0": <input
|
||||
aria-describedby="radix-:r1f:"
|
||||
aria-describedby="radix-:r1d:"
|
||||
autocomplete="name"
|
||||
class="_control_9gon8_18 _control_1qov4_22"
|
||||
id="radix-:r18:"
|
||||
id="radix-:r16:"
|
||||
name="displayname"
|
||||
title=""
|
||||
type="text"
|
||||
value="User"
|
||||
value="Alice"
|
||||
/>,
|
||||
"1": <button
|
||||
aria-controls=":r19:"
|
||||
aria-labelledby=":r1a:"
|
||||
aria-controls=":r17:"
|
||||
aria-labelledby=":r18:"
|
||||
class="_action_1qov4_33"
|
||||
type="button"
|
||||
>
|
||||
@@ -168,11 +168,11 @@ exports[`Account home page > display name edit box > lets edit the display name
|
||||
</button>,
|
||||
"2": <input
|
||||
class="_control_9gon8_18"
|
||||
id="radix-:r1g:"
|
||||
id="radix-:r1e:"
|
||||
name="mxid"
|
||||
readonly=""
|
||||
title=""
|
||||
value="@user:example.com"
|
||||
value="@alice:example.com"
|
||||
/>,
|
||||
"3": <button
|
||||
aria-disabled="false"
|
||||
@@ -197,7 +197,7 @@ exports[`Account home page > display name edit box > lets edit the display name
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
aria-labelledby=":r1h:"
|
||||
aria-labelledby=":r1f:"
|
||||
class="_close_9cf7b0"
|
||||
type="button"
|
||||
>
|
||||
@@ -263,14 +263,14 @@ exports[`Account home page > renders the page 1`] = `
|
||||
class="_user_66f22a"
|
||||
>
|
||||
<span
|
||||
aria-label="@user:example.com"
|
||||
aria-label="@alice:example.com"
|
||||
class="_avatar_mcap2_17 _avatar_66f22a _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-color="6"
|
||||
data-type="round"
|
||||
role="img"
|
||||
style="--cpd-avatar-size: var(--cpd-space-14x);"
|
||||
>
|
||||
U
|
||||
A
|
||||
</span>
|
||||
<div
|
||||
class="_meta_66f22a"
|
||||
@@ -278,12 +278,12 @@ exports[`Account home page > renders the page 1`] = `
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-lg-semibold_yh5dq_83"
|
||||
>
|
||||
User
|
||||
Alice
|
||||
</p>
|
||||
<p
|
||||
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59 _mxid_66f22a"
|
||||
>
|
||||
@user:example.com
|
||||
@alice:example.com
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -358,14 +358,14 @@ exports[`Account home page > renders the page 1`] = `
|
||||
>
|
||||
bound HTMLFormElement {
|
||||
"0": <input
|
||||
aria-describedby="radix-:rc: radix-:rd:"
|
||||
aria-describedby="radix-:rc:"
|
||||
class="_control_9gon8_18 _userEmailField_e2a518"
|
||||
id="radix-:rb:"
|
||||
name="email"
|
||||
readonly=""
|
||||
title=""
|
||||
type="email"
|
||||
value=""
|
||||
value="alice@example.com"
|
||||
/>,
|
||||
}
|
||||
<div
|
||||
@@ -391,9 +391,9 @@ exports[`Account home page > renders the page 1`] = `
|
||||
</div>
|
||||
bound HTMLFormElement {
|
||||
"0": <input
|
||||
aria-describedby="radix-:rf:"
|
||||
aria-describedby="radix-:re:"
|
||||
class="_control_9gon8_18"
|
||||
id="radix-:re:"
|
||||
id="radix-:rd:"
|
||||
name="input"
|
||||
required=""
|
||||
title=""
|
||||
@@ -409,9 +409,9 @@ exports[`Account home page > renders the page 1`] = `
|
||||
/>
|
||||
bound HTMLFormElement {
|
||||
"0": <input
|
||||
aria-describedby="radix-:rh:"
|
||||
aria-describedby="radix-:rg:"
|
||||
class="_control_9gon8_18"
|
||||
id="radix-:rg:"
|
||||
id="radix-:rf:"
|
||||
name="password_preview"
|
||||
readonly=""
|
||||
title=""
|
||||
@@ -429,7 +429,7 @@ exports[`Account home page > renders the page 1`] = `
|
||||
data-state="closed"
|
||||
>
|
||||
<button
|
||||
aria-controls="radix-:ri:"
|
||||
aria-controls="radix-:rh:"
|
||||
aria-expanded="false"
|
||||
class="_trigger_f1daaa"
|
||||
data-state="closed"
|
||||
@@ -461,7 +461,7 @@ exports[`Account home page > renders the page 1`] = `
|
||||
class="_content_f1daaa"
|
||||
data-state="closed"
|
||||
hidden=""
|
||||
id="radix-:ri:"
|
||||
id="radix-:rh:"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,105 +9,11 @@ import { act, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { HttpResponse } from "msw";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { UNVERIFIED_EMAILS_FRAGMENT } from "../../../src/components/UnverifiedEmailAlert/UnverifiedEmailAlert";
|
||||
import {
|
||||
CONFIG_FRAGMENT as USER_GREETING_CONFIG_FRAGMENT,
|
||||
FRAGMENT as USER_GREETING_FRAGMENT,
|
||||
} from "../../../src/components/UserGreeting/UserGreeting";
|
||||
import { makeFragmentData } from "../../../src/gql";
|
||||
import {
|
||||
mockCurrentUserGreetingQuery,
|
||||
mockSetDisplayNameMutation,
|
||||
mockUserEmailListQuery,
|
||||
mockUserProfileQuery,
|
||||
} from "../../../src/gql/graphql";
|
||||
import { mockSetDisplayNameMutation } from "../../../src/gql/graphql";
|
||||
import { renderPage, server } from "../render";
|
||||
|
||||
const handlers = [
|
||||
mockCurrentUserGreetingQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
viewerSession: {
|
||||
__typename: "BrowserSession",
|
||||
|
||||
id: "session-id",
|
||||
user: Object.assign(
|
||||
makeFragmentData(
|
||||
{
|
||||
id: "user-id",
|
||||
matrix: {
|
||||
mxid: "@user:example.com",
|
||||
displayName: "User",
|
||||
},
|
||||
},
|
||||
USER_GREETING_FRAGMENT,
|
||||
),
|
||||
|
||||
makeFragmentData(
|
||||
{
|
||||
unverifiedEmails: {
|
||||
totalCount: 0,
|
||||
},
|
||||
},
|
||||
UNVERIFIED_EMAILS_FRAGMENT,
|
||||
),
|
||||
),
|
||||
},
|
||||
|
||||
siteConfig: makeFragmentData(
|
||||
{
|
||||
displayNameChangeAllowed: true,
|
||||
},
|
||||
USER_GREETING_CONFIG_FRAGMENT,
|
||||
),
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
mockUserProfileQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
viewer: {
|
||||
__typename: "User",
|
||||
id: "user-id",
|
||||
primaryEmail: {
|
||||
id: "primary-email-id",
|
||||
},
|
||||
},
|
||||
|
||||
siteConfig: {
|
||||
emailChangeAllowed: true,
|
||||
passwordLoginEnabled: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
|
||||
mockUserEmailListQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
user: {
|
||||
id: "user-id",
|
||||
emails: {
|
||||
edges: [],
|
||||
totalCount: 0,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
describe("Account home page", () => {
|
||||
it("renders the page", async () => {
|
||||
server.use(...handlers);
|
||||
|
||||
const { asFragment } = await renderPage("/");
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
@@ -137,7 +43,6 @@ describe("Account home page", () => {
|
||||
},
|
||||
});
|
||||
}),
|
||||
...handlers,
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
@@ -164,8 +69,6 @@ describe("Account home page", () => {
|
||||
});
|
||||
|
||||
it("closes with escape", async () => {
|
||||
server.use(...handlers);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await renderPage("/");
|
||||
|
||||
@@ -181,8 +84,6 @@ describe("Account home page", () => {
|
||||
});
|
||||
|
||||
it("closes with cancel", async () => {
|
||||
server.use(...handlers);
|
||||
|
||||
const user = userEvent.setup();
|
||||
await renderPage("/");
|
||||
|
||||
@@ -210,7 +111,6 @@ describe("Account home page", () => {
|
||||
},
|
||||
}),
|
||||
),
|
||||
...handlers,
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
@@ -8,38 +8,12 @@ import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";
|
||||
import { type RenderResult, render } from "@testing-library/react";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import i18n from "i18next";
|
||||
import { HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { I18nextProvider } from "react-i18next";
|
||||
import { afterAll, afterEach, beforeAll } from "vitest";
|
||||
import { FRAGMENT } from "../../src/components/Footer/Footer";
|
||||
import { makeFragmentData } from "../../src/gql";
|
||||
import { mockFooterQuery } from "../../src/gql/graphql";
|
||||
import { queryClient } from "../../src/graphql";
|
||||
import { router } from "../../src/router";
|
||||
|
||||
const handlers = [
|
||||
mockFooterQuery(() =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
siteConfig: {
|
||||
id: "siteConfig",
|
||||
|
||||
...makeFragmentData(
|
||||
{
|
||||
id: "siteConfig",
|
||||
policyUri: "https://matrix.org/policy",
|
||||
tosUri: "https://matrix.org/tos",
|
||||
imprint:
|
||||
"All Rights Reserved. The Super Chat name, logo and device are registered trade marks of BigCorp Ltd.",
|
||||
},
|
||||
FRAGMENT,
|
||||
),
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
];
|
||||
import { handlers } from "../mocks/handlers";
|
||||
|
||||
export const server = setupServer(...handlers);
|
||||
|
||||
@@ -54,9 +28,11 @@ afterEach(() => server.resetHandlers());
|
||||
|
||||
async function renderPage(route: string): Promise<RenderResult> {
|
||||
await router.load();
|
||||
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [route],
|
||||
});
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "tests", "locales", ".storybook/preview.tsx"],
|
||||
"include": ["src", "tests", "stories", "locales", ".storybook/preview.tsx"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
|
||||
Reference in New Issue
Block a user