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:
+100
@@ -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.");
|
||||
});
|
||||
});
|
||||
});
|
||||
+86
@@ -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),
|
||||
);
|
||||
});
|
||||
});
|
||||
+87
@@ -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());
|
||||
});
|
||||
});
|
||||
+58
@@ -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();
|
||||
});
|
||||
});
|
||||
+63
@@ -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),
|
||||
});
|
||||
}
|
||||
+126
@@ -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();
|
||||
});
|
||||
});
|
||||
+88
@@ -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({}) },
|
||||
});
|
||||
}
|
||||
+274
@@ -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>
|
||||
`;
|
||||
+125
@@ -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>
|
||||
`;
|
||||
+126
@@ -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>
|
||||
`;
|
||||
+652
@@ -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>
|
||||
`;
|
||||
Reference in New Issue
Block a user