Files
element-web/test/unit-tests/components/views/settings/tabs/user/EncryptionUserSettingsTab-test.tsx
David Baker be3778bef0 Add key storage toggle to Encryption settings (#29310)
* Add key storage toggle to Encryption settings

* Keys in the acceptable order

* Fix some tests

* Fix import

* Fix toast showing condition

* Fix import order

* Fix playwright tests

* Fix bits lost in merge

* Add key storage delete confirm screen

* Fix hardcoded Element string

* Fix type imports

* Fix tests

* Tests for key storage delete panel

* Fix test

* Type import

* Test for the view model

* Fix type import

* Actually fix type imports

* Test updating

* Add playwright test & clarify slightly confusing comment

* Show the advnced section whatever the state of key storage

* Update screenshots

* Copy css to its own file

* Add missing doc & merge loading states

* Add tsdoc & loading alt text to spinner

* Turn comments into proper tsdoc

* Switch to TypedEventEmitter and remove unnecessary loading state

* Add screenshot

* Use higher level interface

* Merge the two hooks in EncryptionUserSettingsTab

* Remove unused import

* Don't check key backup enabled state separately

as we don't need it for all the screens

* Update snapshot

* Use fixed recovery key function

* Amalgamate duplicated CSS files

* Have "key storage disabled" as a separate state

* Update snapshot

* Fix... bad merge?

* Add backup enabled mock to more tests

* More snapshots

* Use defer util

* Update to use EncryptionCardButtons

* Update snapshots

* Use EncryptionCardEmphasisedContent

* Update snapshots

* Update snapshot

* Try screenshot from CI playwright

* Try playwright screenshots again

* More screenshots

* Rename to match files

* Test that 4S secrets are deleted

* Make description clearer

* Fix typo & move related states together

* Add comment

* More comments

* Fix hook docs

* restoreAllMocks

* Update snapshot

because pulling in upstream has caused IDs to shift

* Switch icon

as apparenty the error icon has changed

* Update snapshot

* Missing copyright

* Re-order states

and also sort out indenting

* Remove phantom space

* Clarify 'button'

* Clarify docs more

* Explain thinking behind updating

* Switch to getActiveBackupVersion

which checks that key backup is happining on this device, which is
consistent with EX.

* Add use of Key Storage Panel

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Change key storage panel to be consistent

ie. using getActiveBackupVersion(), and add comment

* Add tsdoc

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Use BACKUP_DISABLED_ACCOUNT_DATA_KEY in more places

* Expand doc

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Undo random yarn lock change

* Use aggregate method for disabling key storage

in https://github.com/matrix-org/matrix-js-sdk/pull/4742

* Fix tests

* Use key backup status event to update

* Comment formatting

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>

* Fix comment & put check inside if statement

* Add comment

* Prettier

* Fix comment

* Update snapshot

Which has gained nowrap due to 917d53a56f

---------

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2025-03-14 08:52:41 +00:00

212 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright 2024 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 React from "react";
import { act, render, screen } from "jest-matrix-react";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { waitFor } from "@testing-library/dom";
import userEvent from "@testing-library/user-event";
import { CryptoEvent } from "matrix-js-sdk/src/crypto-api";
import {
EncryptionUserSettingsTab,
type State,
} from "../../../../../../../src/components/views/settings/tabs/user/EncryptionUserSettingsTab";
import { createTestClient, withClientContextRenderOptions } from "../../../../../../test-utils";
import Modal from "../../../../../../../src/Modal";
describe("<EncryptionUserSettingsTab />", () => {
let matrixClient: MatrixClient;
beforeEach(() => {
matrixClient = createTestClient();
jest.spyOn(matrixClient.getCrypto()!, "isCrossSigningReady").mockResolvedValue(true);
// Recovery key is available
jest.spyOn(matrixClient.secretStorage, "getDefaultKeyId").mockResolvedValue("default key");
// Secrets are cached
jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
privateKeysInSecretStorage: true,
publicKeysOnDevice: true,
privateKeysCachedLocally: {
masterKey: true,
selfSigningKey: true,
userSigningKey: true,
},
});
});
function renderComponent(props: { initialState?: State } = {}) {
return render(<EncryptionUserSettingsTab {...props} />, withClientContextRenderOptions(matrixClient));
}
it("should display a loading state when the encryption state is computed", () => {
jest.spyOn(matrixClient.getCrypto()!, "isCrossSigningReady").mockImplementation(() => new Promise(() => {}));
renderComponent();
expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
});
it("should display a verify button when the encryption is not set up", async () => {
const user = userEvent.setup();
jest.spyOn(matrixClient.getCrypto()!, "isCrossSigningReady").mockResolvedValue(false);
const { asFragment } = renderComponent();
await waitFor(() =>
expect(
screen.getByText("You need to verify this device in order to view your encryption settings."),
).toBeInTheDocument(),
);
expect(asFragment()).toMatchSnapshot();
const spy = jest.spyOn(Modal, "createDialog").mockReturnValue({} as any);
await user.click(screen.getByText("Verify this device"));
expect(spy).toHaveBeenCalled();
});
it("should display the recovery panel when key storage is enabled", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
renderComponent();
await waitFor(() => expect(screen.getByText("Recovery")).toBeInTheDocument());
});
it("should not display the recovery panel when key storage is not enabled", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue(null);
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue(null);
renderComponent();
await expect(screen.queryByText("Recovery")).not.toBeInTheDocument();
});
it("should display the recovery out of sync panel when secrets are not cached", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
// Secrets are not cached
jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
privateKeysInSecretStorage: true,
publicKeysOnDevice: true,
privateKeysCachedLocally: {
masterKey: false,
selfSigningKey: true,
userSigningKey: true,
},
});
const user = userEvent.setup();
const { asFragment } = renderComponent();
await waitFor(() => screen.getByRole("button", { name: "Enter recovery key" }));
expect(asFragment()).toMatchSnapshot();
await user.click(screen.getByRole("button", { name: "Forgot recovery key?" }));
expect(
screen.getByRole("heading", { name: "Forgot your recovery key? Youll need to reset your identity." }),
).toBeVisible();
});
it("should display the change recovery key panel when the user clicks on the change recovery button", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
const user = userEvent.setup();
const { asFragment } = renderComponent();
await waitFor(() => {
const button = screen.getByRole("button", { name: "Change recovery key" });
expect(button).toBeInTheDocument();
user.click(button);
});
await waitFor(() => expect(screen.getByText("Change recovery key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should display the set up recovery key when the user clicks on the set up recovery key button", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
jest.spyOn(matrixClient.secretStorage, "getDefaultKeyId").mockResolvedValue(null);
const user = userEvent.setup();
const { asFragment } = renderComponent();
await waitFor(() => {
const button = screen.getByRole("button", { name: "Set up recovery" });
expect(button).toBeInTheDocument();
user.click(button);
});
await waitFor(() => expect(screen.getByText("Set up recovery")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should display the reset identity panel when the user clicks on the reset cryptographic identity panel", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
const user = userEvent.setup();
const { asFragment } = renderComponent();
await waitFor(() => {
const button = screen.getByRole("button", { name: "Reset cryptographic identity" });
expect(button).toBeInTheDocument();
user.click(button);
});
await waitFor(() =>
expect(screen.getByText("Are you sure you want to reset your identity?")).toBeInTheDocument(),
);
expect(asFragment()).toMatchSnapshot();
});
it("should enter reset flow when showResetIdentity is set", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
renderComponent({ initialState: "reset_identity_forgot" });
await expect(
await screen.findByRole("heading", {
name: "Forgot your recovery key? Youll need to reset your identity.",
}),
).toBeVisible();
});
it("should update when key backup status event is fired", async () => {
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
renderComponent();
await expect(await screen.findByRole("heading", { name: "Recovery" })).toBeVisible();
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue(null);
act(() => {
matrixClient.emit(CryptoEvent.KeyBackupStatus, false);
});
await waitFor(() => {
expect(screen.queryByRole("heading", { name: "Recovery" })).toBeNull();
});
});
it("should re-check the encryption state and displays the correct panel when the user clicks cancel the reset identity flow", async () => {
const user = userEvent.setup();
jest.spyOn(matrixClient.getCrypto()!, "getActiveSessionBackupVersion").mockResolvedValue("1");
// Secrets are not cached
jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
privateKeysInSecretStorage: true,
publicKeysOnDevice: true,
privateKeysCachedLocally: {
masterKey: false,
selfSigningKey: true,
userSigningKey: true,
},
});
renderComponent({ initialState: "reset_identity_forgot" });
expect(
screen.getByRole("heading", { name: "Forgot your recovery key? Youll need to reset your identity." }),
).toBeVisible();
await user.click(screen.getByRole("button", { name: "Back" }));
await waitFor(() =>
screen.getByText("Your key storage is out of sync. Click one of the buttons below to fix the problem."),
);
});
});