mv element.io @types __mocks__/ debian docker module_system/ playwright res src test webapp Dockerfile .dockerignore .eslintignore .stylelintrc.cjs babel.config.cjs recorder-worklet-loader.cjs .modernizr.json components.json config.json config.sample.json package.json project.json tsconfig.json tsconfig.module_system.json jest.config.ts playwright.config.ts webpack.config.ts build_config.sample.yaml apps/web/

mkdir apps/web/scripts
mv scripts/{cleanup.sh,ci_package.sh,copy-res.ts,deploy.py,package.sh} apps/web/scripts

And a couple of gitignore tweaks

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski
2026-02-24 15:43:58 +00:00
parent e7509c92a1
commit 91a3cb03c1
3408 changed files with 28 additions and 32 deletions
@@ -0,0 +1,100 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import { render, type RenderResult, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { mocked, type MockedObject } from "jest-mock";
import { type MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
import { sleep } from "matrix-js-sdk/src/utils";
import { filterConsole, stubClient } from "../../../../../test-utils";
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
describe("CreateSecretStorageDialog", () => {
let mockClient: MockedObject<MatrixClient>;
beforeEach(() => {
mockClient = mocked(stubClient());
mockClient.uploadDeviceSigningKeys.mockImplementation(async () => {
await sleep(0); // CreateSecretStorageDialog doesn't expect this to resolve immediately
throw new MatrixError({ flows: [] });
});
// Mock the clipboard API
document.execCommand = jest.fn().mockReturnValue(true);
});
afterEach(() => {
jest.restoreAllMocks();
});
function renderComponent(
props: Partial<React.ComponentProps<typeof CreateSecretStorageDialog>> = {},
): RenderResult {
const onFinished = jest.fn();
return render(<CreateSecretStorageDialog onFinished={onFinished} {...props} />);
}
it("handles the happy path", async () => {
const result = renderComponent();
await result.findByText(
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
);
expect(result.container).toMatchSnapshot();
await userEvent.click(result.getByRole("button", { name: "Continue" }));
await screen.findByText("Save your Recovery Key");
expect(result.container).toMatchSnapshot();
// Copy the key to enable the continue button
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
expect(result.queryByText("Copied!")).not.toBeNull();
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
await screen.findByText("Your keys are now being backed up from this device.");
});
it("when there is an error when bootstraping the secret storage, it shows an error", async () => {
jest.spyOn(mockClient.getCrypto()!, "bootstrapSecretStorage").mockRejectedValue(new Error("error"));
renderComponent();
await screen.findByText(
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
);
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
await screen.findByText("Save your Recovery Key");
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
await screen.findByText("Unable to set up secret storage");
});
describe("when there is an error fetching the backup version", () => {
filterConsole("Error fetching backup data from server");
it("shows an error", async () => {
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockImplementation(async () => {
throw new Error("bleh bleh");
});
const result = renderComponent();
// We go though the dialog until we have to get the key backup
await userEvent.click(result.getByRole("button", { name: "Continue" }));
await userEvent.click(screen.getByRole("button", { name: "Copy" }));
await userEvent.click(screen.getByRole("button", { name: "Continue" }));
// XXX the error message is... misleading.
await screen.findByText("Unable to query secret storage status");
expect(result.container).toMatchSnapshot();
// Now we can get the backup and we retry
jest.spyOn(mockClient.getCrypto()!, "getKeyBackupInfo").mockRestore();
await userEvent.click(screen.getByRole("button", { name: "Retry" }));
await screen.findByText("Your keys are now being backed up from this device.");
});
});
});
@@ -0,0 +1,86 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { screen, fireEvent, render, waitFor, act } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { type IMegolmSessionData } from "matrix-js-sdk/src/matrix";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
import ExportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ExportE2eKeysDialog";
import { createTestClient } from "../../../../../test-utils";
describe("ExportE2eKeysDialog", () => {
it("renders", () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { asFragment } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
expect(asFragment()).toMatchSnapshot();
});
it("should have disabled submit button initially", async () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
expect(screen.getByLabelText("Enter passphrase")).toBeInTheDocument();
});
it("should complain about weak passphrases", async () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
const input = screen.getByLabelText("Enter passphrase");
await userEvent.type(input, "password");
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
await expect(screen.findByText("This is a top-10 common password")).resolves.toBeInTheDocument();
});
it("should complain if passphrases don't match", async () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
await userEvent.type(screen.getByLabelText("Enter passphrase"), "ThisIsAMoreSecurePW123$$");
await userEvent.type(screen.getByLabelText("Confirm passphrase"), "ThisIsAMoreSecurePW124$$");
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
await expect(screen.findByText("Passphrases must match")).resolves.toBeInTheDocument();
});
it("should export if everything is fine", async () => {
// Given a client able to export keys
const cli = createTestClient();
const keys: IMegolmSessionData[] = [];
const passphrase = "ThisIsAMoreSecurePW123$$";
const exportRoomKeysAsJson = jest.fn().mockResolvedValue(JSON.stringify(keys));
cli.getCrypto = () => {
return {
exportRoomKeysAsJson,
} as unknown as CryptoApi;
};
// Mock the result of encrypting the sessions. If we don't do this, the
// encryption process fails, possibly because we didn't initialise
// something.
jest.spyOn(MegolmExportEncryption, "encryptMegolmKeyFile").mockResolvedValue(new ArrayBuffer(3));
// When we tell the dialog to export
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={jest.fn()} />);
await userEvent.type(screen.getByLabelText("Enter passphrase"), passphrase);
await userEvent.type(screen.getByLabelText("Confirm passphrase"), passphrase);
await act(() => fireEvent.click(container.querySelector("[type=submit]")!));
// Then it exports keys and encrypts them
await waitFor(() => expect(exportRoomKeysAsJson).toHaveBeenCalled());
await waitFor(() =>
expect(MegolmExportEncryption.encryptMegolmKeyFile).toHaveBeenCalledWith(JSON.stringify(keys), passphrase),
);
});
});
@@ -0,0 +1,87 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { fireEvent, render, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import ImportE2eKeysDialog from "../../../../../../src/async-components/views/dialogs/security/ImportE2eKeysDialog";
import * as MegolmExportEncryption from "../../../../../../src/utils/MegolmExportEncryption";
import { createTestClient } from "../../../../../test-utils";
describe("ImportE2eKeysDialog", () => {
it("renders", () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { asFragment } = render(<ImportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
expect(asFragment()).toMatchSnapshot();
});
it("should have disabled submit button initially", () => {
const cli = createTestClient();
const onFinished = jest.fn();
const { container } = render(<ImportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
expect(container.querySelector("[type=submit]")!).toBeDisabled();
});
it("should enable submit once file is uploaded and passphrase typed in", () => {
const cli = createTestClient();
const onFinished = jest.fn();
const file = new File(["test"], "file.txt", { type: "text/plain" });
const { container } = render(<ImportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
fireEvent.change(container.querySelector("[type=file]")!, {
target: { files: [file] },
});
fireEvent.change(container.querySelector("[type=password]")!, {
target: { value: "passphrase" },
});
expect(container.querySelector("[type=submit]")!).toBeEnabled();
});
it("should enable submit once file is uploaded and passphrase pasted in", async () => {
const cli = createTestClient();
const onFinished = jest.fn();
const file = new File(["test"], "file.txt", { type: "text/plain" });
const { container } = render(<ImportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
fireEvent.change(container.querySelector("[type=file]")!, {
target: { files: [file] },
});
await userEvent.click(container.querySelector("[type=password]")!);
await userEvent.paste("passphrase");
expect(container.querySelector("[type=submit]")!).toBeEnabled();
});
it("should import exported keys on submit", async () => {
const cli = createTestClient();
const onFinished = jest.fn();
const file = new File(["test"], "file.txt", { type: "text/plain" });
const importRoomKeysAsJson = jest.fn();
cli.getCrypto = () => {
return {
importRoomKeysAsJson,
} as unknown as CryptoApi;
};
// Mock the result of decrypting the sessions, to avoid needing to
// create encrypted input data.
jest.spyOn(MegolmExportEncryption, "decryptMegolmKeyFile").mockResolvedValue("[]");
const { container } = render(<ImportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
fireEvent.change(container.querySelector("[type=file]")!, {
target: { files: [file] },
});
await userEvent.click(container.querySelector("[type=password]")!);
await userEvent.paste("passphrase");
fireEvent.click(container.querySelector("[type=submit]")!);
await waitFor(() => expect(importRoomKeysAsJson).toHaveBeenCalled());
});
});
@@ -0,0 +1,58 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { render, screen } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { InitialCryptoSetupDialog } from "../../../../../../src/components/views/dialogs/security/InitialCryptoSetupDialog";
import { InitialCryptoSetupStore } from "../../../../../../src/stores/InitialCryptoSetupStore";
describe("InitialCryptoSetupDialog", () => {
const storeMock = {
getStatus: jest.fn(),
retry: jest.fn(),
on: jest.fn(),
off: jest.fn(),
};
beforeEach(() => {
jest.spyOn(InitialCryptoSetupStore, "sharedInstance").mockReturnValue(storeMock as any);
});
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
it("should show a spinner while the setup is in progress", async () => {
storeMock.getStatus.mockReturnValue("in_progress");
render(<InitialCryptoSetupDialog onCancelled={jest.fn()} />);
expect(screen.getByTestId("spinner")).toBeInTheDocument();
});
it("should display an error if setup has failed", async () => {
storeMock.getStatus.mockReturnValue("error");
render(<InitialCryptoSetupDialog onCancelled={jest.fn()} />);
await expect(await screen.findByRole("button", { name: "Retry" })).toBeInTheDocument();
});
it("calls retry when retry button pressed", async () => {
storeMock.getStatus.mockReturnValue("error");
render(<InitialCryptoSetupDialog onCancelled={jest.fn()} />);
await userEvent.click(await screen.findByRole("button", { name: "Retry" }));
expect(storeMock.retry).toHaveBeenCalled();
});
});
@@ -0,0 +1,63 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { act } from "react";
import { render } from "jest-matrix-react";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import { type Mocked } from "jest-mock";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { getMockClientWithEventEmitter } from "../../../../../test-utils";
import { ResetIdentityDialog } from "../../../../../../src/components/views/dialogs/ResetIdentityDialog";
describe("ResetIdentityDialog", () => {
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
it("should call onReset and onFinished when we click Continue", async () => {
const client = mockClient();
const onFinished = jest.fn();
const onReset = jest.fn();
const dialog = render(<ResetIdentityDialog onFinished={onFinished} onReset={onReset} variant="compromised" />);
await act(async () => dialog.getByRole("button", { name: "Continue" }).click());
expect(onReset).toHaveBeenCalled();
expect(onFinished).toHaveBeenCalled();
expect(client.getCrypto()?.resetEncryption).toHaveBeenCalled();
});
it("should call onFinished when we click Cancel", async () => {
const client = mockClient();
const onFinished = jest.fn();
const onReset = jest.fn();
const dialog = render(<ResetIdentityDialog onFinished={onFinished} onReset={onReset} variant="compromised" />);
await act(async () => dialog.getByRole("button", { name: "Cancel" }).click());
expect(onFinished).toHaveBeenCalled();
expect(onReset).not.toHaveBeenCalled();
expect(client.getCrypto()?.resetEncryption).not.toHaveBeenCalled();
});
});
function mockClient(): Mocked<MatrixClient> {
const mockCrypto = {
resetEncryption: jest.fn().mockResolvedValue(null),
} as unknown as Mocked<CryptoApi>;
return getMockClientWithEventEmitter({
getCrypto: jest.fn().mockReturnValue(mockCrypto),
});
}
@@ -0,0 +1,126 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
import React from "react";
import { screen, render, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import { type MatrixClient } from "matrix-js-sdk/src/matrix";
import { type KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
// Needed to be able to mock decodeRecoveryKey
// eslint-disable-next-line no-restricted-imports
import * as recoveryKeyModule from "matrix-js-sdk/src/crypto-api/recovery-key";
import RestoreKeyBackupDialog from "../../../../../../src/components/views/dialogs/security/RestoreKeyBackupDialog.tsx";
import { stubClient } from "../../../../../test-utils";
describe("<RestoreKeyBackupDialog />", () => {
const keyBackupRestoreResult = {
total: 2,
imported: 1,
};
let matrixClient: MatrixClient;
beforeEach(() => {
matrixClient = stubClient();
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({ version: "1" } as KeyBackupInfo);
});
it("should render", async () => {
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should display an error when recovery key is invalid", async () => {
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockImplementation(() => {
throw new Error("Invalid recovery key");
});
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
await userEvent.type(screen.getByRole("textbox"), "invalid key");
await waitFor(() => expect(screen.getByText("👎 Not a valid Recovery Key")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should not raise an error when recovery is valid", async () => {
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
await userEvent.type(screen.getByRole("textbox"), "valid key");
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Recovery Key!")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should restore key backup when the key is cached", async () => {
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup").mockResolvedValue(keyBackupRestoreResult);
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should restore key backup when the key is in secret storage", async () => {
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
// Reject when trying to restore from cache
.mockRejectedValueOnce(new Error("key backup not found"))
// Resolve when trying to restore from secret storage
.mockResolvedValue(keyBackupRestoreResult);
jest.spyOn(matrixClient.secretStorage, "hasKey").mockResolvedValue(true);
jest.spyOn(matrixClient, "isKeyBackupKeyStored").mockResolvedValue({});
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
it("should restore key backup when Recovery key is filled by user", async () => {
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
// Reject when trying to restore from cache
.mockRejectedValueOnce(new Error("key backup not found"))
// Resolve when trying to restore from recovery key
.mockResolvedValue(keyBackupRestoreResult);
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Recovery Key")).toBeInTheDocument());
await userEvent.type(screen.getByRole("textbox"), "my recovery key");
await userEvent.click(screen.getByRole("button", { name: "Next" }));
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
test("should restore key backup when passphrase is filled", async () => {
// Determine that the passphrase is required
jest.spyOn(matrixClient.getCrypto()!, "getKeyBackupInfo").mockResolvedValue({
version: "1",
auth_data: {
private_key_salt: "salt",
private_key_iterations: 1,
},
} as KeyBackupInfo);
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackup")
// Reject when trying to restore from cache
.mockRejectedValue(new Error("key backup not found"));
jest.spyOn(matrixClient.getCrypto()!, "restoreKeyBackupWithPassphrase").mockResolvedValue(
keyBackupRestoreResult,
);
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
await waitFor(() => expect(screen.getByText("Enter Security Phrase")).toBeInTheDocument());
// Not role for password https://github.com/w3c/aria/issues/935
await userEvent.type(screen.getByTestId("passphraseInput"), "my passphrase");
await userEvent.click(screen.getByRole("button", { name: "Next" }));
await waitFor(() => expect(screen.getByText("Successfully restored 1 keys")).toBeInTheDocument());
expect(asFragment()).toMatchSnapshot();
});
});
@@ -0,0 +1,88 @@
/*
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 OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
import React, { act } from "react";
import { render, screen } from "jest-matrix-react";
import { type Mocked } from "jest-mock";
import { type CryptoApi } from "matrix-js-sdk/src/crypto-api";
import SetupEncryptionDialog from "../../../../../../src/components/views/dialogs/security/SetupEncryptionDialog";
import { getMockClientWithEventEmitter } from "../../../../../test-utils";
import { Phase, SetupEncryptionStore } from "../../../../../../src/stores/SetupEncryptionStore";
import Modal from "../../../../../../src/Modal";
describe("SetupEncryptionDialog", () => {
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
it("should launch a dialog when I say Proceed, then be finished when I reset", async () => {
mockClient();
const store = new SetupEncryptionStore();
jest.spyOn(SetupEncryptionStore, "sharedInstance").mockReturnValue(store);
// Given when you open the reset dialog we immediately reset
jest.spyOn(Modal, "createDialog").mockImplementation((_, props) => {
// Simulate doing the reset in the dialog
props?.onReset();
return {
close: jest.fn(),
finished: Promise.resolve([]),
};
});
// When we launch the dialog and set it ready to start
const onFinished = jest.fn();
render(<SetupEncryptionDialog onFinished={onFinished} />);
await act(async () => await store.fetchKeyInfo());
expect(store.phase).toBe(Phase.Intro);
// And we hit the Proceed with reset button.
// (The createDialog mock above simulates the user doing the reset)
await act(async () => screen.getByRole("button", { name: "Can't confirm?" }).click());
// Then the phase has been set to Finished
expect(store.phase).toBe(Phase.Finished);
});
});
function mockClient() {
const mockCrypto = {
getDeviceVerificationStatus: jest.fn().mockResolvedValue({
crossSigningVerified: false,
}),
getUserDeviceInfo: jest.fn().mockResolvedValue(new Map()),
isCrossSigningReady: jest.fn().mockResolvedValue(true),
isSecretStorageReady: jest.fn().mockResolvedValue(true),
userHasCrossSigningKeys: jest.fn(),
getActiveSessionBackupVersion: jest.fn(),
getCrossSigningStatus: jest.fn().mockReturnValue({
publicKeysOnDevice: true,
privateKeysInSecretStorage: true,
privateKeysCachedLocally: {
masterKey: true,
selfSigningKey: true,
userSigningKey: true,
},
}),
getSessionBackupPrivateKey: jest.fn(),
isEncryptionEnabledInRoom: jest.fn(),
getKeyBackupInfo: jest.fn().mockResolvedValue(null),
getVerificationRequestsToDeviceInProgress: jest.fn().mockReturnValue([]),
} as unknown as Mocked<CryptoApi>;
const userId = "@user:server";
getMockClientWithEventEmitter({
getCrypto: jest.fn().mockReturnValue(mockCrypto),
getUserId: jest.fn().mockReturnValue(userId),
secretStorage: { isStored: jest.fn().mockReturnValue({}) },
});
}
@@ -0,0 +1,274 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`CreateSecretStorageDialog handles the happy path 1`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title mx_CreateSecretStorageDialog_centeredTitle"
id="mx_BaseDialog_title"
>
Set up Secure Backup
</h1>
</div>
<div>
<form>
<p
class="mx_CreateSecretStorageDialog_centeredBody"
>
Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.
</p>
<div
class="mx_CreateSecretStorageDialog_primaryContainer"
role="radiogroup"
>
<label
class="mx_StyledRadioButton mx_StyledRadioButton_enabled mx_StyledRadioButton_checked mx_StyledRadioButton_outlined"
>
<input
checked=""
name="keyPassphrase"
type="radio"
value="key"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
<div
class="mx_CreateSecretStorageDialog_optionTitle"
>
Generate a Recovery Key
</div>
<div>
We'll generate a Recovery Key for you to store somewhere safe, like a password manager or a safe.
</div>
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
<label
class="mx_StyledRadioButton mx_StyledRadioButton_enabled mx_StyledRadioButton_outlined"
>
<input
name="keyPassphrase"
type="radio"
value="passphrase"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
<div
class="mx_CreateSecretStorageDialog_optionTitle"
>
Enter a Security Phrase
</div>
<div>
Use a secret phrase only you know, and optionally save a Recovery Key to use for backup.
</div>
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Continue
</button>
</span>
</div>
</form>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
exports[`CreateSecretStorageDialog handles the happy path 2`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Save your Recovery Key
</h1>
</div>
<div>
<div>
<p>
Store your Recovery Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.
</p>
<div
class="mx_CreateSecretStorageDialog_primaryContainer mx_CreateSecretStorageDialog_recoveryKeyPrimarycontainer"
>
<div
class="mx_CreateSecretStorageDialog_recoveryKeyContainer"
>
<div
class="mx_CreateSecretStorageDialog_recoveryKey"
>
<code>
encoded private key
</code>
</div>
<div
class="mx_CreateSecretStorageDialog_recoveryKeyButtons"
>
<div
class="mx_AccessibleButton mx_Dialog_primary mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Download
</div>
<span>
or
</span>
<div
class="mx_AccessibleButton mx_Dialog_primary mx_CreateSecretStorageDialog_recoveryKeyButtons_copyBtn mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Copy
</div>
</div>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Continue
</button>
</span>
</div>
</div>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
exports[`CreateSecretStorageDialog when there is an error fetching the backup version shows an error 1`] = `
<div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_CreateSecretStorageDialog"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
/>
<div>
<div>
<p>
Unable to query secret storage status
</p>
<div
class="mx_Dialog_buttons"
>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Retry
</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</div>
`;
@@ -0,0 +1,125 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`ExportE2eKeysDialog renders 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_exportE2eKeysDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Export room keys
</h1>
</div>
<form>
<div
class="mx_Dialog_content"
>
<p>
This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.
</p>
<p>
The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a unique passphrase below, which will only be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.
</p>
<div
class="error"
/>
<div
class="mx_E2eKeysDialog_inputTable"
>
<div
class="mx_E2eKeysDialog_inputRow"
>
<div
class="mx_Field mx_Field_input mx_PassphraseField"
>
<input
autocomplete="new-password"
id="mx_Field_1"
label="Enter passphrase"
placeholder="Enter passphrase"
type="password"
value=""
/>
<label
for="mx_Field_1"
>
Enter passphrase
</label>
</div>
</div>
<div
class="mx_E2eKeysDialog_inputRow"
>
<div
class="mx_Field mx_Field_input"
>
<input
autocomplete="new-password"
id="mx_Field_2"
label="Confirm passphrase"
placeholder="Confirm passphrase"
type="password"
value=""
/>
<label
for="mx_Field_2"
>
Confirm passphrase
</label>
</div>
</div>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<input
class="mx_Dialog_primary"
type="submit"
value="Export"
/>
<button>
Cancel
</button>
</div>
</form>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
@@ -0,0 +1,126 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`ImportE2eKeysDialog renders 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_importE2eKeysDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Import room keys
</h1>
</div>
<form>
<div
class="mx_Dialog_content"
>
<p>
This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.
</p>
<p>
The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.
</p>
<div
class="error"
/>
<div
class="mx_E2eKeysDialog_inputTable"
>
<div
class="mx_E2eKeysDialog_inputRow"
>
<div
class="mx_E2eKeysDialog_inputLabel"
>
<label
for="importFile"
>
File to import
</label>
</div>
<div
class="mx_E2eKeysDialog_inputCell"
>
<input
id="importFile"
type="file"
/>
</div>
</div>
<div
class="mx_E2eKeysDialog_inputRow"
>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_Field_1"
label="Enter passphrase"
placeholder="Enter passphrase"
size="64"
type="password"
value=""
/>
<label
for="mx_Field_1"
>
Enter passphrase
</label>
</div>
</div>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<input
class="mx_Dialog_primary"
disabled=""
type="submit"
value="Import"
/>
<button>
Cancel
</button>
</div>
</form>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
@@ -0,0 +1,652 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`<RestoreKeyBackupDialog /> should display an error when recovery key is invalid 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Enter Recovery Key
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
<span>
<strong>
Warning
</strong>
: you should only set up key backup from a trusted computer.
</span>
</p>
<p>
Access your secure message history and set up secure messaging by entering your Recovery Key.
</p>
<div
class="mx_RestoreKeyBackupDialog_primaryContainer"
>
<input
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
value="invalid key"
/>
<div
class="mx_RestoreKeyBackupDialog_keyStatus"
>
👎 Not a valid Recovery Key
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Next
</button>
</span>
</div>
</div>
<span>
If you've forgotten your Recovery Key you can
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
set up new recovery options
</div>
</span>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should not raise an error when recovery is valid 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Enter Recovery Key
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
<span>
<strong>
Warning
</strong>
: you should only set up key backup from a trusted computer.
</span>
</p>
<p>
Access your secure message history and set up secure messaging by entering your Recovery Key.
</p>
<div
class="mx_RestoreKeyBackupDialog_primaryContainer"
>
<input
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
value="valid key"
/>
<div
class="mx_RestoreKeyBackupDialog_keyStatus"
>
👍 This looks like a valid Recovery Key!
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
Next
</button>
</span>
</div>
</div>
<span>
If you've forgotten your Recovery Key you can
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
set up new recovery options
</div>
</span>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should render 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Enter Recovery Key
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
<span>
<strong>
Warning
</strong>
: you should only set up key backup from a trusted computer.
</span>
</p>
<p>
Access your secure message history and set up secure messaging by entering your Recovery Key.
</p>
<div
class="mx_RestoreKeyBackupDialog_primaryContainer"
>
<input
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
value=""
/>
<div
class="mx_RestoreKeyBackupDialog_keyStatus"
/>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-testid="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
disabled=""
type="button"
>
Next
</button>
</span>
</div>
</div>
<span>
If you've forgotten your Recovery Key you can
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
set up new recovery options
</div>
</span>
</div>
</div>
<div
aria-describedby="_r_2_"
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should restore key backup when Recovery key is filled by user 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should restore key backup when passphrase is filled 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should restore key backup when the key is cached 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-describedby="_r_k_"
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;
exports[`<RestoreKeyBackupDialog /> should restore key backup when the key is in secret storage 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-labelledby="mx_BaseDialog_title"
class="mx_RestoreKeyBackupDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
tabindex="-1"
>
<div
class="mx_Dialog_header"
>
<h1
class="mx_Heading_h3 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Keys restored
</h1>
</div>
<div
class="mx_RestoreKeyBackupDialog_content"
>
<div>
<p>
Successfully restored 1 keys
</p>
<p>
Failed to decrypt 1 sessions!
</p>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
class="mx_Dialog_primary"
data-testid="dialog-primary-button"
type="button"
>
OK
</button>
</span>
</div>
</div>
</div>
<div
aria-describedby="_r_q_"
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414"
/>
</svg>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;