diff --git a/src/components/views/settings/encryption/RecoveryPanel.tsx b/src/components/views/settings/encryption/RecoveryPanel.tsx
index b047b528e3..df555a583a 100644
--- a/src/components/views/settings/encryption/RecoveryPanel.tsx
+++ b/src/components/views/settings/encryption/RecoveryPanel.tsx
@@ -68,7 +68,7 @@ export function RecoveryPanel({ onSetUpRecoveryClick, onChangingRecoveryKeyClick
let content: JSX.Element;
switch (state) {
case "loading":
- content = ;
+ content = ;
break;
case "missing_backup":
content = (
diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts
index f9aee512a3..902dc32e6e 100644
--- a/test/test-utils/test-utils.ts
+++ b/test/test-utils/test-utils.ts
@@ -127,7 +127,10 @@ export function createTestClient(): MatrixClient {
bootstrapCrossSigning: jest.fn(),
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
isKeyBackupTrusted: jest.fn().mockResolvedValue({}),
- createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({}),
+ createRecoveryKeyFromPassphrase: jest.fn().mockResolvedValue({
+ privateKey: new Uint8Array(32),
+ encodedPrivateKey: "encoded private key",
+ }),
bootstrapSecretStorage: jest.fn(),
isDehydrationSupported: jest.fn().mockResolvedValue(false),
restoreKeyBackup: jest.fn(),
@@ -136,6 +139,17 @@ export function createTestClient(): MatrixClient {
storeSessionBackupPrivateKey: jest.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
getEncryptionInfoForEvent: jest.fn().mockResolvedValue(null),
+ getCrossSigningStatus: jest.fn().mockResolvedValue({
+ publicKeysOnDevice: false,
+ privateKeysInSecretStorage: false,
+ privateKeysCachedLocally: {
+ masterKey: false,
+ selfSigningKey: false,
+ userSigningKey: false,
+ },
+ }),
+ isCrossSigningReady: jest.fn().mockResolvedValue(false),
+ checkKeyBackupAndEnable: jest.fn().mockResolvedValue(null),
}),
getPushActionsForEvent: jest.fn(),
diff --git a/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx b/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx
new file mode 100644
index 0000000000..ce81b51b6f
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/RecoveryPanel-test.tsx
@@ -0,0 +1,97 @@
+/*
+ * 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 { MatrixClient } from "matrix-js-sdk/src/matrix";
+import { render, screen } from "jest-matrix-react";
+import { waitFor } from "@testing-library/dom";
+import userEvent from "@testing-library/user-event";
+import { mocked } from "jest-mock";
+import { KeyBackupCheck } from "matrix-js-sdk/src/crypto-api";
+
+import { createTestClient, withClientContextRenderOptions } from "../../../../../test-utils";
+import { RecoveryPanel } from "../../../../../../src/components/views/settings/encryption/RecoveryPanel";
+import { accessSecretStorage } from "../../../../../../src/SecurityManager";
+
+jest.mock("../../../../../../src/SecurityManager", () => ({
+ accessSecretStorage: jest.fn(),
+}));
+
+describe("", () => {
+ let matrixClient: MatrixClient;
+
+ beforeEach(() => {
+ matrixClient = createTestClient();
+ mocked(accessSecretStorage).mockClear().mockResolvedValue();
+ });
+
+ function renderRecoverPanel(
+ props = {
+ onSetUpRecoveryClick: jest.fn(),
+ onChangingRecoveryKeyClick: jest.fn(),
+ },
+ ) {
+ return render(, withClientContextRenderOptions(matrixClient));
+ }
+
+ it("should be in loading state when checking backup and the cached keys", () => {
+ jest.spyOn(matrixClient.getCrypto()!, "checkKeyBackupAndEnable").mockImplementation(
+ () => new Promise(() => {}),
+ );
+
+ const { asFragment } = renderRecoverPanel();
+ expect(screen.getByLabelText("Loading…")).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
+ it("should ask to set up a recovery key when there is no key backup", async () => {
+ const user = userEvent.setup();
+
+ const onSetUpRecoveryClick = jest.fn();
+ const { asFragment } = renderRecoverPanel({ onSetUpRecoveryClick, onChangingRecoveryKeyClick: jest.fn() });
+
+ await waitFor(() => screen.getByRole("button", { name: "Set up recovery" }));
+ expect(asFragment()).toMatchSnapshot();
+
+ await user.click(screen.getByRole("button", { name: "Set up recovery" }));
+ expect(onSetUpRecoveryClick).toHaveBeenCalled();
+ });
+
+ it("should ask to enter the recovery key when secrets are not cached", async () => {
+ jest.spyOn(matrixClient.getCrypto()!, "checkKeyBackupAndEnable").mockResolvedValue({} as KeyBackupCheck);
+ const user = userEvent.setup();
+ const { asFragment } = renderRecoverPanel();
+
+ await waitFor(() => screen.getByRole("button", { name: "Enter recovery key" }));
+ expect(asFragment()).toMatchSnapshot();
+
+ await user.click(screen.getByRole("button", { name: "Enter recovery key" }));
+ expect(accessSecretStorage).toHaveBeenCalled();
+ });
+
+ it("should allow to change the recovery key when everything is good", async () => {
+ jest.spyOn(matrixClient.getCrypto()!, "checkKeyBackupAndEnable").mockResolvedValue({} as KeyBackupCheck);
+ jest.spyOn(matrixClient.getCrypto()!, "getCrossSigningStatus").mockResolvedValue({
+ privateKeysInSecretStorage: true,
+ publicKeysOnDevice: true,
+ privateKeysCachedLocally: {
+ masterKey: true,
+ selfSigningKey: true,
+ userSigningKey: true,
+ },
+ });
+ const user = userEvent.setup();
+
+ const onChangingRecoveryKeyClick = jest.fn();
+ const { asFragment } = renderRecoverPanel({ onSetUpRecoveryClick: jest.fn(), onChangingRecoveryKeyClick });
+ await waitFor(() => screen.getByRole("button", { name: "Change recovery key" }));
+ expect(asFragment()).toMatchSnapshot();
+
+ await user.click(screen.getByRole("button", { name: "Change recovery key" }));
+ expect(onChangingRecoveryKeyClick).toHaveBeenCalled();
+ });
+});
diff --git a/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap
new file mode 100644
index 0000000000..864116a9c2
--- /dev/null
+++ b/test/unit-tests/components/views/settings/encryption/__snapshots__/RecoveryPanel-test.tsx.snap
@@ -0,0 +1,179 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[` should allow to change the recovery key when everything is good 1`] = `
+
+
+
+
+ Recovery
+
+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
+
+
+
+
+`;
+
+exports[` should ask to enter the recovery key when secrets are not cached 1`] = `
+
+
+
+
+ Recovery
+
+
+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
+
+
+ Your key storage is out of sync. Click the button below to fix the problem.
+
+
+
+
+
+
+`;
+
+exports[` should ask to set up a recovery key when there is no key backup 1`] = `
+
+
+
+
+ Recovery
+
+ Recommended
+
+
+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
+
+
+
+
+`;
+
+exports[` should be in loading state when checking backup and the cached keys 1`] = `
+
+
+
+
+ Recovery
+
+ Recover your cryptographic identity and message history with a recovery key if you’ve lost all your existing devices.
+