From ad4a6d1885eb437557d4ac6212a1dbf81e18fe26 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 8 Oct 2024 16:46:55 +0100 Subject: [PATCH 1/6] Refactor CreateCrossSigningDialog --- src/CreateCrossSigning.ts | 98 ++++++++ src/components/structures/auth/E2eSetup.tsx | 2 +- .../security/CreateCrossSigningDialog.tsx | 215 +++++------------- 3 files changed, 159 insertions(+), 156 deletions(-) create mode 100644 src/CreateCrossSigning.ts diff --git a/src/CreateCrossSigning.ts b/src/CreateCrossSigning.ts new file mode 100644 index 0000000000..db85aec02a --- /dev/null +++ b/src/CreateCrossSigning.ts @@ -0,0 +1,98 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2018, 2019 New Vector Ltd + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import { logger } from "matrix-js-sdk/src/logger"; +import { AuthDict, CrossSigningKeys, MatrixClient, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix"; + +import { SSOAuthEntry } from "./components/views/auth/InteractiveAuthEntryComponents"; +import Modal from "./Modal"; +import { _t } from "./languageHandler"; +import InteractiveAuthDialog from "./components/views/dialogs/InteractiveAuthDialog"; + +async function canUploadKeysWithPasswordOnly(cli: MatrixClient): Promise { + try { + await cli.uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys); + // We should never get here: the server should always require + // UI auth to upload device signing keys. If we do, we upload + // no keys which would be a no-op. + logger.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!"); + return false; + } catch (error) { + if (!(error instanceof MatrixError) || !error.data || !error.data.flows) { + logger.log("uploadDeviceSigningKeys advertised no flows!"); + return false; + } + const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => { + return f.stages.length === 1 && f.stages[0] === "m.login.password"; + }); + return canUploadKeysWithPasswordOnly; + } +} + +export async function createCrossSigning( + cli: MatrixClient, + isTokenLogin: boolean, + accountPassword?: string, +): Promise { + const cryptoApi = cli.getCrypto(); + if (!cryptoApi) { + throw new Error("No crypto API found!"); + } + + const doBootstrapUIAuth = async ( + makeRequest: (authData: AuthDict) => Promise>, + ): Promise => { + if (accountPassword && (await canUploadKeysWithPasswordOnly(cli))) { + await makeRequest({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: cli.getUserId(), + }, + password: accountPassword, + }); + } else if (isTokenLogin) { + // We are hoping the grace period is active + await makeRequest({}); + } else { + const dialogAesthetics = { + [SSOAuthEntry.PHASE_PREAUTH]: { + title: _t("auth|uia|sso_title"), + body: _t("auth|uia|sso_preauth_body"), + continueText: _t("auth|sso"), + continueKind: "primary", + }, + [SSOAuthEntry.PHASE_POSTAUTH]: { + title: _t("encryption|confirm_encryption_setup_title"), + body: _t("encryption|confirm_encryption_setup_body"), + continueText: _t("action|confirm"), + continueKind: "primary", + }, + }; + + const { finished } = Modal.createDialog(InteractiveAuthDialog, { + title: _t("encryption|bootstrap_title"), + matrixClient: cli, + makeRequest, + aestheticsForStagePhases: { + [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, + [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, + }, + }); + const [confirmed] = await finished; + if (!confirmed) { + throw new Error("Cross-signing key upload auth canceled"); + } + } + }; + + await cryptoApi.bootstrapCrossSigning({ + authUploadDeviceSigningKeys: doBootstrapUIAuth, + }); +} diff --git a/src/components/structures/auth/E2eSetup.tsx b/src/components/structures/auth/E2eSetup.tsx index 9e103f2ac5..22e24824e1 100644 --- a/src/components/structures/auth/E2eSetup.tsx +++ b/src/components/structures/auth/E2eSetup.tsx @@ -15,7 +15,7 @@ import CreateCrossSigningDialog from "../../views/dialogs/security/CreateCrossSi interface IProps { onFinished: () => void; accountPassword?: string; - tokenLogin?: boolean; + tokenLogin: boolean; } export default class E2eSetup extends React.Component { diff --git a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx index bc5bc6b21e..0f322633d3 100644 --- a/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx +++ b/src/components/views/dialogs/security/CreateCrossSigningDialog.tsx @@ -7,189 +7,94 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only Please see LICENSE files in the repository root for full details. */ -import React from "react"; -import { CrossSigningKeys, AuthDict, MatrixError, UIAFlow, UIAResponse } from "matrix-js-sdk/src/matrix"; +import React, { useCallback, useEffect, useState } from "react"; import { logger } from "matrix-js-sdk/src/logger"; -import { MatrixClientPeg } from "../../../../MatrixClientPeg"; import { _t } from "../../../../languageHandler"; -import Modal from "../../../../Modal"; -import { SSOAuthEntry } from "../../auth/InteractiveAuthEntryComponents"; import DialogButtons from "../../elements/DialogButtons"; import BaseDialog from "../BaseDialog"; import Spinner from "../../elements/Spinner"; -import InteractiveAuthDialog from "../InteractiveAuthDialog"; +import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { createCrossSigning } from "../../../../CreateCrossSigning"; -interface IProps { +interface Props { accountPassword?: string; - tokenLogin?: boolean; + tokenLogin: boolean; onFinished: (success?: boolean) => void; } -interface IState { - error: boolean; - canUploadKeysWithPasswordOnly: boolean | null; - accountPassword: string; -} - /* * Walks the user through the process of creating a cross-signing keys. In most * cases, only a spinner is shown, but for more complex auth like SSO, the user * may need to complete some steps to proceed. */ -export default class CreateCrossSigningDialog extends React.PureComponent { - public constructor(props: IProps) { - super(props); - - this.state = { - error: false, - // Does the server offer a UI auth flow with just m.login.password - // for /keys/device_signing/upload? - // If we have an account password in memory, let's simplify and - // assume it means password auth is also supported for device - // signing key upload as well. This avoids hitting the server to - // test auth flows, which may be slow under high load. - canUploadKeysWithPasswordOnly: props.accountPassword ? true : null, - accountPassword: props.accountPassword || "", - }; - - if (!this.state.accountPassword) { - this.queryKeyUploadAuth(); - } - } - - public componentDidMount(): void { - this.bootstrapCrossSigning(); - } - - private async queryKeyUploadAuth(): Promise { - try { - await MatrixClientPeg.safeGet().uploadDeviceSigningKeys(undefined, {} as CrossSigningKeys); - // We should never get here: the server should always require - // UI auth to upload device signing keys. If we do, we upload - // no keys which would be a no-op. - logger.log("uploadDeviceSigningKeys unexpectedly succeeded without UI auth!"); - } catch (error) { - if (!(error instanceof MatrixError) || !error.data || !error.data.flows) { - logger.log("uploadDeviceSigningKeys advertised no flows!"); - return; - } - const canUploadKeysWithPasswordOnly = error.data.flows.some((f: UIAFlow) => { - return f.stages.length === 1 && f.stages[0] === "m.login.password"; - }); - this.setState({ - canUploadKeysWithPasswordOnly, - }); - } - } +const CreateCrossSigningDialog: React.FC = ({ accountPassword, tokenLogin, onFinished }) => { + const [error, setError] = useState(false); - private doBootstrapUIAuth = async ( - makeRequest: (authData: AuthDict) => Promise>, - ): Promise => { - if (this.state.canUploadKeysWithPasswordOnly && this.state.accountPassword) { - await makeRequest({ - type: "m.login.password", - identifier: { - type: "m.id.user", - user: MatrixClientPeg.safeGet().getUserId(), - }, - password: this.state.accountPassword, - }); - } else if (this.props.tokenLogin) { - // We are hoping the grace period is active - await makeRequest({}); - } else { - const dialogAesthetics = { - [SSOAuthEntry.PHASE_PREAUTH]: { - title: _t("auth|uia|sso_title"), - body: _t("auth|uia|sso_preauth_body"), - continueText: _t("auth|sso"), - continueKind: "primary", - }, - [SSOAuthEntry.PHASE_POSTAUTH]: { - title: _t("encryption|confirm_encryption_setup_title"), - body: _t("encryption|confirm_encryption_setup_body"), - continueText: _t("action|confirm"), - continueKind: "primary", - }, - }; + const cli = useMatrixClientContext(); - const { finished } = Modal.createDialog(InteractiveAuthDialog, { - title: _t("encryption|bootstrap_title"), - matrixClient: MatrixClientPeg.safeGet(), - makeRequest, - aestheticsForStagePhases: { - [SSOAuthEntry.LOGIN_TYPE]: dialogAesthetics, - [SSOAuthEntry.UNSTABLE_LOGIN_TYPE]: dialogAesthetics, - }, - }); - const [confirmed] = await finished; - if (!confirmed) { - throw new Error("Cross-signing key upload auth canceled"); - } - } - }; + const bootstrapCrossSigning = useCallback(async () => { + const cryptoApi = cli.getCrypto(); + if (!cryptoApi) return; - private bootstrapCrossSigning = async (): Promise => { - this.setState({ - error: false, - }); + setError(false); try { - const cli = MatrixClientPeg.safeGet(); - await cli.bootstrapCrossSigning({ - authUploadDeviceSigningKeys: this.doBootstrapUIAuth, - }); - this.props.onFinished(true); + await createCrossSigning(cli, tokenLogin, accountPassword); + onFinished(true); } catch (e) { - if (this.props.tokenLogin) { + if (tokenLogin) { // ignore any failures, we are relying on grace period here - this.props.onFinished(false); + onFinished(false); return; } - this.setState({ error: true }); + setError(true); logger.error("Error bootstrapping cross-signing", e); } - }; - - private onCancel = (): void => { - this.props.onFinished(false); - }; - - public render(): React.ReactNode { - let content; - if (this.state.error) { - content = ( -
-

{_t("encryption|unable_to_setup_keys_error")}

-
- -
+ }, [cli, tokenLogin, accountPassword, onFinished]); + + const onCancel = useCallback(() => { + onFinished(false); + }, [onFinished]); + + useEffect(() => { + bootstrapCrossSigning(); + }, [bootstrapCrossSigning]); + + let content; + if (error) { + content = ( +
+

{_t("encryption|unable_to_setup_keys_error")}

+
+
- ); - } else { - content = ( -
- -
- ); - } - - return ( - -
{content}
-
+
+ ); + } else { + content = ( +
+ +
); } -} + + return ( + +
{content}
+
+ ); +}; + +export default CreateCrossSigningDialog; From 8d547afff4d8f043e577fa574fb40beb4831049c Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 8 Oct 2024 16:57:18 +0100 Subject: [PATCH 2/6] Pass in matrixclient Because we don't have the context wrapper for that screen (it probably ought to but that can wait for a refactor of the matrixchat screens). --- src/components/structures/MatrixChat.tsx | 1 + src/components/structures/auth/E2eSetup.tsx | 3 +++ .../dialogs/security/CreateCrossSigningDialog.tsx | 13 ++++++------- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 7689017856..27b14444c0 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -2084,6 +2084,7 @@ export default class MatrixChat extends React.PureComponent { } else if (this.state.view === Views.E2E_SETUP) { view = ( void; accountPassword?: string; tokenLogin: boolean; @@ -24,6 +26,7 @@ export default class E2eSetup extends React.Component { void; @@ -28,19 +29,17 @@ interface Props { * cases, only a spinner is shown, but for more complex auth like SSO, the user * may need to complete some steps to proceed. */ -const CreateCrossSigningDialog: React.FC = ({ accountPassword, tokenLogin, onFinished }) => { +const CreateCrossSigningDialog: React.FC = ({ matrixClient, accountPassword, tokenLogin, onFinished }) => { const [error, setError] = useState(false); - const cli = useMatrixClientContext(); - const bootstrapCrossSigning = useCallback(async () => { - const cryptoApi = cli.getCrypto(); + const cryptoApi = matrixClient.getCrypto(); if (!cryptoApi) return; setError(false); try { - await createCrossSigning(cli, tokenLogin, accountPassword); + await createCrossSigning(matrixClient, tokenLogin, accountPassword); onFinished(true); } catch (e) { if (tokenLogin) { @@ -52,7 +51,7 @@ const CreateCrossSigningDialog: React.FC = ({ accountPassword, tokenLogin setError(true); logger.error("Error bootstrapping cross-signing", e); } - }, [cli, tokenLogin, accountPassword, onFinished]); + }, [matrixClient, tokenLogin, accountPassword, onFinished]); const onCancel = useCallback(() => { onFinished(false); From 8b455e037b485cae39a29638df77867a2fd01357 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 10 Oct 2024 11:32:07 +0100 Subject: [PATCH 3/6] Add bootstrapCrossSigning mock to the crypto mock and remove other crypto mock that's now unnecessary --- test/components/structures/MatrixChat-test.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/test/components/structures/MatrixChat-test.tsx b/test/components/structures/MatrixChat-test.tsx index 8876fdc802..7e10260af5 100644 --- a/test/components/structures/MatrixChat-test.tsx +++ b/test/components/structures/MatrixChat-test.tsx @@ -136,9 +136,9 @@ describe("", () => { getUserVerificationStatus: jest.fn().mockResolvedValue(new UserVerificationStatus(false, false, false)), getVersion: jest.fn().mockReturnValue("1"), setDeviceIsolationMode: jest.fn(), + // This needs to not finish immediately because we need to test the screen appears + bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise), }), - // This needs to not finish immediately because we need to test the screen appears - bootstrapCrossSigning: jest.fn().mockImplementation(() => bootstrapDeferred.promise), secretStorage: { isStored: jest.fn().mockReturnValue(null), }, @@ -1002,17 +1002,7 @@ describe("", () => { describe("post login setup", () => { beforeEach(() => { - const mockCrypto = { - getVersion: jest.fn().mockReturnValue("Version 0"), - getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]), - getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()), - getUserVerificationStatus: jest - .fn() - .mockResolvedValue(new UserVerificationStatus(false, false, false)), - setDeviceIsolationMode: jest.fn(), - }; loginClient.isCryptoEnabled.mockReturnValue(true); - loginClient.getCrypto.mockReturnValue(mockCrypto as any); loginClient.userHasCrossSigningKeys.mockClear().mockResolvedValue(false); }); From 94fd7831c6ea044cc1b9ece924cc0574a60b5956 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 10 Oct 2024 15:24:24 +0100 Subject: [PATCH 4/6] Add test for createCrossSigning --- test/CreateCrossSigning-test.ts | 96 +++++++++++++++++++++++++++++++++ test/test-utils/test-utils.ts | 1 + 2 files changed, 97 insertions(+) create mode 100644 test/CreateCrossSigning-test.ts diff --git a/test/CreateCrossSigning-test.ts b/test/CreateCrossSigning-test.ts new file mode 100644 index 0000000000..1fc64ddcbc --- /dev/null +++ b/test/CreateCrossSigning-test.ts @@ -0,0 +1,96 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2018-2022 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import { MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; + +import { createCrossSigning } from "../src/CreateCrossSigning"; +import { createTestClient } from "./test-utils"; +import Modal from "../src/Modal"; + +describe("CreateCrossSigning", () => { + let client: MatrixClient; + + beforeEach(() => { + client = createTestClient(); + }); + + afterEach(() => {}); + + it("should call bootstrapCrossSigning with an authUploadDeviceSigningKeys function", async () => { + await createCrossSigning(client, false, "password"); + + expect(client.getCrypto()?.bootstrapCrossSigning).toHaveBeenCalledWith({ + authUploadDeviceSigningKeys: expect.any(Function), + }); + }); + + it("should upload with password auth if possible", async () => { + client.uploadDeviceSigningKeys = jest.fn().mockRejectedValueOnce( + new MatrixError({ + flows: [ + { + stages: ["m.login.password"], + }, + ], + }), + ); + + await createCrossSigning(client, false, "password"); + + const { authUploadDeviceSigningKeys } = mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0]; + + const makeRequest = jest.fn(); + await authUploadDeviceSigningKeys!(makeRequest); + expect(makeRequest).toHaveBeenCalledWith({ + type: "m.login.password", + identifier: { + type: "m.id.user", + user: client.getUserId(), + }, + password: "password", + }); + }); + + it("should attempt to upload keys without auth if using token login", async () => { + await createCrossSigning(client, true, undefined); + + const { authUploadDeviceSigningKeys } = mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0]; + + const makeRequest = jest.fn(); + await authUploadDeviceSigningKeys!(makeRequest); + expect(makeRequest).toHaveBeenCalledWith({}); + }); + + it("should prompt user if password upload not possible", async () => { + const createDialog = jest.spyOn(Modal, "createDialog").mockReturnValue({ + finished: Promise.resolve([true]), + close: jest.fn(), + }); + jest.mock; + + client.uploadDeviceSigningKeys = jest.fn().mockRejectedValueOnce( + new MatrixError({ + flows: [ + { + stages: ["dummy.mystery_flow_nobody_knows"], + }, + ], + }), + ); + + await createCrossSigning(client, false, "password"); + + const { authUploadDeviceSigningKeys } = mocked(client.getCrypto()!).bootstrapCrossSigning.mock.calls[0][0]; + + const makeRequest = jest.fn(); + await authUploadDeviceSigningKeys!(makeRequest); + expect(makeRequest).not.toHaveBeenCalledWith(); + expect(createDialog).toHaveBeenCalled(); + }); +}); diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index ebfc6b221b..fac0a51e61 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -132,6 +132,7 @@ export function createTestClient(): MatrixClient { isEncryptionEnabledInRoom: jest.fn(), getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]), setDeviceIsolationMode: jest.fn(), + bootstrapCrossSigning: jest.fn(), }), getPushActionsForEvent: jest.fn(), From 1ad8f1e7accd059939a73138774963840e8b44c3 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 10 Oct 2024 17:33:15 +0100 Subject: [PATCH 5/6] More tests --- test/CreateCrossSigning-test.ts | 2 - .../CreateCrossSigningDialog-test.tsx | 126 ++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx diff --git a/test/CreateCrossSigning-test.ts b/test/CreateCrossSigning-test.ts index 1fc64ddcbc..69c00da7b8 100644 --- a/test/CreateCrossSigning-test.ts +++ b/test/CreateCrossSigning-test.ts @@ -20,8 +20,6 @@ describe("CreateCrossSigning", () => { client = createTestClient(); }); - afterEach(() => {}); - it("should call bootstrapCrossSigning with an authUploadDeviceSigningKeys function", async () => { await createCrossSigning(client, false, "password"); diff --git a/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx b/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx new file mode 100644 index 0000000000..a3ff35ff59 --- /dev/null +++ b/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx @@ -0,0 +1,126 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2018-2022 The Matrix.org Foundation C.I.C. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only +Please see LICENSE files in the repository root for full details. +*/ + +import React from "react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { mocked } from "jest-mock"; +import { MatrixClient } from "matrix-js-sdk/src/matrix"; + +import { createCrossSigning } from "../../../../../src/CreateCrossSigning"; +import CreateCrossSigningDialog from "../../../../../src/components/views/dialogs/security/CreateCrossSigningDialog"; +import { createTestClient } from "../../../../test-utils"; + +jest.mock("../../../../../src/CreateCrossSigning", () => ({ + createCrossSigning: jest.fn(), +})); + +describe("CreateCrossSigningDialog", () => { + let client: MatrixClient; + let createCrossSigningResolve: () => void; + let createCrossSigningReject: (e: Error) => void; + + beforeEach(() => { + client = createTestClient(); + mocked(createCrossSigning).mockImplementation(() => { + return new Promise((resolve, reject) => { + createCrossSigningResolve = resolve; + createCrossSigningReject = reject; + }); + }); + }); + + it("should call createCrossSigning and show a spinner while it runs", async () => { + const onFinished = jest.fn(); + + render( + , + ); + + expect(createCrossSigning).toHaveBeenCalledWith(client, false, "hunter2"); + expect(screen.getByTestId("spinner")).toBeInTheDocument(); + + createCrossSigningResolve!(); + + await waitFor(() => expect(onFinished).toHaveBeenCalledWith(true)); + }); + + it("should display an error if createCrossSigning fails", async () => { + render( + , + ); + + createCrossSigningReject!(new Error("generic error message")); + + await expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument(); + }); + + it("ignores failures when tokenLogin is true", async () => { + const onFinished = jest.fn(); + + render( + , + ); + + createCrossSigningReject!(new Error("generic error message")); + + await waitFor(() => expect(onFinished).toHaveBeenCalledWith(false)); + }); + + it("cancels the dialog when the cancel button is clicked", async () => { + const onFinished = jest.fn(); + + render( + , + ); + + createCrossSigningReject!(new Error("generic error message")); + + const cancelButton = await screen.findByRole("button", { name: "Cancel" }); + cancelButton.click(); + + expect(onFinished).toHaveBeenCalledWith(false); + }); + + it("should retry when the retry button is clicked", async () => { + render( + , + ); + + createCrossSigningReject!(new Error("generic error message")); + + const retryButton = await screen.findByRole("button", { name: "Retry" }); + retryButton.click(); + + expect(createCrossSigning).toHaveBeenCalledTimes(2); + }); +}); From 62da8abdb831f7c9d0ffb3c0dcf5fae63157ef75 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 10 Oct 2024 17:42:09 +0100 Subject: [PATCH 6/6] reset mocks --- .../views/dialogs/security/CreateCrossSigningDialog-test.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx b/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx index a3ff35ff59..286b415b34 100644 --- a/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx +++ b/test/components/views/dialogs/security/CreateCrossSigningDialog-test.tsx @@ -34,6 +34,11 @@ describe("CreateCrossSigningDialog", () => { }); }); + afterEach(() => { + jest.resetAllMocks(); + jest.restoreAllMocks(); + }); + it("should call createCrossSigning and show a spinner while it runs", async () => { const onFinished = jest.fn();