Merge matrix-react-sdk into element-web
Merge remote-tracking branch 'repomerge/t3chguy/repomerge' into t3chguy/repo-merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
commit
f0ee7f7905
3265 changed files with 484599 additions and 699 deletions
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020-2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ComponentProps } from "react";
|
||||
import { SecretStorage, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { act, fireEvent, render, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { mockPlatformPeg, stubClient } from "../../../../test-utils";
|
||||
import AccessSecretStorageDialog from "../../../../../src/components/views/dialogs/security/AccessSecretStorageDialog";
|
||||
|
||||
const securityKey = "EsTc WKmb ivvk jLS7 Y1NH 5CcQ mP1E JJwj B3Fd pFWm t4Dp dbyu";
|
||||
|
||||
describe("AccessSecretStorageDialog", () => {
|
||||
let mockClient: MatrixClient;
|
||||
|
||||
const defaultProps: ComponentProps<typeof AccessSecretStorageDialog> = {
|
||||
keyInfo: {} as any,
|
||||
onFinished: jest.fn(),
|
||||
checkPrivateKey: jest.fn(),
|
||||
};
|
||||
|
||||
const renderComponent = (props = {}): void => {
|
||||
render(<AccessSecretStorageDialog {...defaultProps} {...props} />);
|
||||
};
|
||||
|
||||
const enterSecurityKey = (placeholder = "Security Key"): void => {
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByPlaceholderText(placeholder), {
|
||||
target: {
|
||||
value: securityKey,
|
||||
},
|
||||
});
|
||||
// wait for debounce
|
||||
jest.advanceTimersByTime(250);
|
||||
});
|
||||
};
|
||||
|
||||
const submitDialog = async (): Promise<void> => {
|
||||
await userEvent.click(screen.getByText("Continue"), { delay: null });
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
mockPlatformPeg();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = stubClient();
|
||||
});
|
||||
|
||||
it("Closes the dialog when the form is submitted with a valid key", async () => {
|
||||
jest.spyOn(mockClient.secretStorage, "checkKey").mockResolvedValue(true);
|
||||
|
||||
const onFinished = jest.fn();
|
||||
const checkPrivateKey = jest.fn().mockResolvedValue(true);
|
||||
renderComponent({ onFinished, checkPrivateKey });
|
||||
|
||||
// check that the input field is focused
|
||||
expect(screen.getByPlaceholderText("Security Key")).toHaveFocus();
|
||||
|
||||
await enterSecurityKey();
|
||||
await submitDialog();
|
||||
|
||||
expect(screen.getByText("Looks good!")).toBeInTheDocument();
|
||||
expect(checkPrivateKey).toHaveBeenCalledWith({ recoveryKey: securityKey });
|
||||
expect(onFinished).toHaveBeenCalledWith({ recoveryKey: securityKey });
|
||||
});
|
||||
|
||||
it("Notifies the user if they input an invalid Security Key", async () => {
|
||||
const onFinished = jest.fn();
|
||||
const checkPrivateKey = jest.fn().mockResolvedValue(true);
|
||||
renderComponent({ onFinished, checkPrivateKey });
|
||||
|
||||
jest.spyOn(mockClient.secretStorage, "checkKey").mockImplementation(() => {
|
||||
throw new Error("invalid key");
|
||||
});
|
||||
|
||||
await enterSecurityKey();
|
||||
await submitDialog();
|
||||
|
||||
expect(screen.getByText("Continue")).toBeDisabled();
|
||||
expect(screen.getByText("Invalid Security Key")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Notifies the user if they input an invalid passphrase", async function () {
|
||||
const keyInfo = {
|
||||
name: "test",
|
||||
algorithm: "test",
|
||||
iv: "test",
|
||||
mac: "1:2:3:4",
|
||||
passphrase: {
|
||||
// this type is weird in js-sdk
|
||||
// cast 'm.pbkdf2' to itself
|
||||
algorithm: "m.pbkdf2" as SecretStorage.PassphraseInfo["algorithm"],
|
||||
iterations: 2,
|
||||
salt: "nonempty",
|
||||
},
|
||||
};
|
||||
const checkPrivateKey = jest.fn().mockResolvedValue(false);
|
||||
renderComponent({ checkPrivateKey, keyInfo });
|
||||
|
||||
await enterSecurityKey("Security Phrase");
|
||||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveValue(securityKey);
|
||||
await submitDialog();
|
||||
|
||||
await expect(
|
||||
screen.findByText(
|
||||
"👎 Unable to access secret storage. Please verify that you entered the correct Security Phrase.",
|
||||
),
|
||||
).resolves.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByPlaceholderText("Security Phrase")).toHaveFocus();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
|
||||
import { AppDownloadDialog } from "../../../../../src/components/views/dialogs/AppDownloadDialog";
|
||||
import SdkConfig, { ConfigOptions } from "../../../../../src/SdkConfig";
|
||||
|
||||
describe("AppDownloadDialog", () => {
|
||||
afterEach(() => {
|
||||
SdkConfig.reset();
|
||||
});
|
||||
|
||||
it("should render with desktop, ios, android, fdroid buttons by default", () => {
|
||||
const { asFragment } = render(<AppDownloadDialog onFinished={jest.fn()} />);
|
||||
expect(screen.queryByRole("button", { name: "Download Element Desktop" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Download on the App Store" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on Google Play" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on F-Droid" })).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should allow disabling fdroid build", () => {
|
||||
SdkConfig.add({
|
||||
mobile_builds: {
|
||||
fdroid: null,
|
||||
},
|
||||
} as ConfigOptions);
|
||||
const { asFragment } = render(<AppDownloadDialog onFinished={jest.fn()} />);
|
||||
expect(screen.queryByRole("button", { name: "Download Element Desktop" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Download on the App Store" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on Google Play" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on F-Droid" })).not.toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should allow disabling desktop build", () => {
|
||||
SdkConfig.add({
|
||||
desktop_builds: {
|
||||
available: false,
|
||||
},
|
||||
} as ConfigOptions);
|
||||
const { asFragment } = render(<AppDownloadDialog onFinished={jest.fn()} />);
|
||||
expect(screen.queryByRole("button", { name: "Download Element Desktop" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Download on the App Store" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on Google Play" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on F-Droid" })).toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should allow disabling mobile builds", () => {
|
||||
SdkConfig.add({
|
||||
mobile_builds: {
|
||||
ios: null,
|
||||
android: null,
|
||||
fdroid: null,
|
||||
},
|
||||
} as ConfigOptions);
|
||||
const { asFragment } = render(<AppDownloadDialog onFinished={jest.fn()} />);
|
||||
expect(screen.queryByRole("button", { name: "Download Element Desktop" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Download on the App Store" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on Google Play" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "Get it on F-Droid" })).not.toBeInTheDocument();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { getByText, render, RenderResult } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
|
||||
import AskInviteAnywayDialog, {
|
||||
AskInviteAnywayDialogProps,
|
||||
} from "../../../../../src/components/views/dialogs/AskInviteAnywayDialog";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("AskInviteaAnywayDialog", () => {
|
||||
const onFinished: jest.Mock<any, any> = jest.fn();
|
||||
const onGiveUp: jest.Mock<any, any> = jest.fn();
|
||||
const onInviteAnyways: jest.Mock<any, any> = jest.fn();
|
||||
|
||||
function renderComponent(props: Partial<AskInviteAnywayDialogProps> = {}): RenderResult {
|
||||
return render(
|
||||
<AskInviteAnywayDialog
|
||||
onFinished={onFinished}
|
||||
onGiveUp={onGiveUp}
|
||||
onInviteAnyways={onInviteAnyways}
|
||||
unknownProfileUsers={[
|
||||
{
|
||||
userId: "@alice:localhost",
|
||||
errorText: "🤷♂️",
|
||||
},
|
||||
]}
|
||||
{...props}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("remembers to not warn again", async () => {
|
||||
const { container } = renderComponent();
|
||||
|
||||
jest.spyOn(SettingsStore, "setValue").mockImplementation(async (): Promise<void> => {});
|
||||
|
||||
const neverWarnAgainBtn = getByText(container, /never warn/);
|
||||
await userEvent.click(neverWarnAgainBtn);
|
||||
|
||||
expect(SettingsStore.setValue).toHaveBeenCalledWith(
|
||||
"promptBeforeInviteUnknownUsers",
|
||||
null,
|
||||
expect.any(String),
|
||||
false,
|
||||
);
|
||||
expect(onInviteAnyways).toHaveBeenCalledTimes(1);
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("invites anyway", async () => {
|
||||
const { container } = renderComponent();
|
||||
|
||||
jest.spyOn(SettingsStore, "setValue");
|
||||
|
||||
const inviteAnywayBtn = getByText(container, "Invite anyway");
|
||||
await userEvent.click(inviteAnywayBtn);
|
||||
|
||||
expect(onInviteAnyways).toHaveBeenCalledTimes(1);
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it("gives up", async () => {
|
||||
const { container } = renderComponent();
|
||||
|
||||
jest.spyOn(SettingsStore, "setValue");
|
||||
|
||||
const closeBtn = getByText(container, /Close/);
|
||||
await userEvent.click(closeBtn);
|
||||
|
||||
expect(onGiveUp).toHaveBeenCalledTimes(1);
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
import { render, screen, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
|
||||
import ChangelogDialog from "../../../../../src/components/views/dialogs/ChangelogDialog";
|
||||
|
||||
describe("<ChangelogDialog />", () => {
|
||||
it("should fetch github proxy url for each repo with old and new version strings", async () => {
|
||||
const webUrl = "https://riot.im/github/repos/element-hq/element-web/compare/oldsha1...newsha1";
|
||||
fetchMock.get(webUrl, {
|
||||
url: "https://api.github.com/repos/element-hq/element-web/compare/master...develop",
|
||||
html_url: "https://github.com/element-hq/element-web/compare/master...develop",
|
||||
permalink_url: "https://github.com/element-hq/element-web/compare/vector-im:72ca95e...vector-im:8891698",
|
||||
diff_url: "https://github.com/element-hq/element-web/compare/master...develop.diff",
|
||||
patch_url: "https://github.com/element-hq/element-web/compare/master...develop.patch",
|
||||
base_commit: {},
|
||||
merge_base_commit: {},
|
||||
status: "ahead",
|
||||
ahead_by: 24,
|
||||
behind_by: 0,
|
||||
total_commits: 24,
|
||||
commits: [
|
||||
{
|
||||
sha: "commit-sha",
|
||||
html_url: "https://api.github.com/repos/element-hq/element-web/commit/commit-sha",
|
||||
commit: { message: "This is the first commit message" },
|
||||
},
|
||||
],
|
||||
files: [],
|
||||
});
|
||||
const jsUrl = "https://riot.im/github/repos/matrix-org/matrix-js-sdk/compare/oldsha3...newsha3";
|
||||
fetchMock.get(jsUrl, {
|
||||
url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/compare/master...develop",
|
||||
html_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop",
|
||||
permalink_url: "https://github.com/matrix-org/matrix-js-sdk/compare/matrix-org:6166a8f...matrix-org:fec350",
|
||||
diff_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop.diff",
|
||||
patch_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop.patch",
|
||||
base_commit: {},
|
||||
merge_base_commit: {},
|
||||
status: "ahead",
|
||||
ahead_by: 48,
|
||||
behind_by: 0,
|
||||
total_commits: 48,
|
||||
commits: [
|
||||
{
|
||||
sha: "commit-sha1",
|
||||
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
|
||||
commit: { message: "This is a commit message" },
|
||||
},
|
||||
{
|
||||
sha: "commit-sha2",
|
||||
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
|
||||
commit: { message: "This is another commit message" },
|
||||
},
|
||||
],
|
||||
files: [],
|
||||
});
|
||||
|
||||
const newVersion = "newsha1-react-newsha2-js-newsha3";
|
||||
const oldVersion = "oldsha1-react-oldsha2-js-oldsha3";
|
||||
const { asFragment } = render(
|
||||
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />,
|
||||
);
|
||||
|
||||
// Wait for spinners to go away
|
||||
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
|
||||
|
||||
expect(fetchMock).toHaveFetched(webUrl);
|
||||
expect(fetchMock).toHaveFetched(jsUrl);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
|
||||
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { flushPromises, mkEvent, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createRedactEventDialog } from "../../../../../src/components/views/dialogs/ConfirmRedactDialog";
|
||||
|
||||
describe("ConfirmRedactDialog", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let mxEvent: MatrixEvent;
|
||||
|
||||
const setUpVoiceBroadcastStartedEvent = () => {
|
||||
mxEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
};
|
||||
|
||||
const confirmDeleteVoiceBroadcastStartedEvent = async () => {
|
||||
createRedactEventDialog({ mxEvent });
|
||||
// double-flush promises required for the dialog to show up
|
||||
await flushPromises();
|
||||
await flushPromises();
|
||||
|
||||
await userEvent.click(screen.getByTestId("dialog-primary-button"));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
it("should raise an error for an event without ID", async () => {
|
||||
mxEvent = mkEvent({
|
||||
event: true,
|
||||
type: "m.room.message",
|
||||
room: roomId,
|
||||
content: {},
|
||||
user: client.getSafeUserId(),
|
||||
});
|
||||
jest.spyOn(mxEvent, "getId").mockReturnValue(undefined);
|
||||
await expect(confirmDeleteVoiceBroadcastStartedEvent()).rejects.toThrow("cannot redact event without ID");
|
||||
});
|
||||
|
||||
it("should raise an error for an event without room-ID", async () => {
|
||||
mxEvent = mkEvent({
|
||||
event: true,
|
||||
type: "m.room.message",
|
||||
room: roomId,
|
||||
content: {},
|
||||
user: client.getSafeUserId(),
|
||||
});
|
||||
jest.spyOn(mxEvent, "getRoomId").mockReturnValue(undefined);
|
||||
await expect(confirmDeleteVoiceBroadcastStartedEvent()).rejects.toThrow(
|
||||
`cannot redact event ${mxEvent.getId()} without room ID`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when redacting a voice broadcast started event", () => {
|
||||
beforeEach(() => {
|
||||
setUpVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
describe("and the server does not support relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unsupported);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact without `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the server supports relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unstable);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact with `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {
|
||||
with_rel_types: [RelationType.Reference],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import ConfirmUserActionDialog from "../../../../../src/components/views/dialogs/ConfirmUserActionDialog";
|
||||
import { mkRoomMember } from "../../../../test-utils";
|
||||
|
||||
describe("ConfirmUserActionDialog", () => {
|
||||
it("renders", () => {
|
||||
const { asFragment } = render(
|
||||
<ConfirmUserActionDialog
|
||||
onFinished={jest.fn()}
|
||||
member={mkRoomMember("123", "@user:test.com", KnownMembership.Join)}
|
||||
action="Ban"
|
||||
title="Ban this " // eg. 'Ban this user?'
|
||||
/>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,347 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, within } from "jest-matrix-react";
|
||||
import { JoinRule, MatrixError, Preset, Visibility } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import CreateRoomDialog from "../../../../../src/components/views/dialogs/CreateRoomDialog";
|
||||
import { flushPromises, getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
describe("<CreateRoomDialog />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
getDomain: jest.fn().mockReturnValue("server.org"),
|
||||
getClientWellKnown: jest.fn(),
|
||||
doesServerForceEncryptionForPreset: jest.fn(),
|
||||
// make every alias available
|
||||
getRoomIdForAlias: jest.fn().mockRejectedValue(new MatrixError({ errcode: "M_NOT_FOUND" })),
|
||||
});
|
||||
|
||||
const getE2eeEnableToggleInputElement = () => screen.getByLabelText("Enable end-to-end encryption");
|
||||
// labelled toggle switch doesn't set the disabled attribute, only aria-disabled
|
||||
const getE2eeEnableToggleIsDisabled = () =>
|
||||
getE2eeEnableToggleInputElement().getAttribute("aria-disabled") === "true";
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.doesServerForceEncryptionForPreset.mockResolvedValue(false);
|
||||
mockClient.getClientWellKnown.mockReturnValue({});
|
||||
});
|
||||
|
||||
const getComponent = (props = {}) => render(<CreateRoomDialog onFinished={jest.fn()} {...props} />);
|
||||
|
||||
it("should default to private room", async () => {
|
||||
getComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText("Create a private room")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should use defaultName from props", async () => {
|
||||
const defaultName = "My test room";
|
||||
getComponent({ defaultName });
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByLabelText("Name")).toHaveDisplayValue(defaultName);
|
||||
});
|
||||
|
||||
describe("for a private room", () => {
|
||||
// default behaviour is a private room
|
||||
|
||||
it("should use server .well-known default for encryption setting", async () => {
|
||||
// default to off
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
getComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(getE2eeEnableToggleInputElement()).not.toBeChecked();
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeFalsy();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it("should use server .well-known force_disable for encryption setting", async () => {
|
||||
// force to off
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
default: true,
|
||||
force_disable: true,
|
||||
},
|
||||
});
|
||||
getComponent();
|
||||
await flushPromises();
|
||||
|
||||
expect(getE2eeEnableToggleInputElement()).not.toBeChecked();
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it("should use defaultEncrypted prop", async () => {
|
||||
// default to off in server wk
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
// but pass defaultEncrypted prop
|
||||
getComponent({ defaultEncrypted: true });
|
||||
await flushPromises();
|
||||
// encryption enabled
|
||||
expect(getE2eeEnableToggleInputElement()).toBeChecked();
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should use defaultEncrypted prop when it is false", async () => {
|
||||
// default to off in server wk
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
// but pass defaultEncrypted prop
|
||||
getComponent({ defaultEncrypted: false });
|
||||
await flushPromises();
|
||||
// encryption disabled
|
||||
expect(getE2eeEnableToggleInputElement()).not.toBeChecked();
|
||||
// not forced to off
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should override defaultEncrypted when server .well-known forces disabled encryption", async () => {
|
||||
// force to off
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
force_disable: true,
|
||||
},
|
||||
});
|
||||
getComponent({ defaultEncrypted: true });
|
||||
await flushPromises();
|
||||
|
||||
// server forces encryption to disabled, even though defaultEncrypted is false
|
||||
expect(getE2eeEnableToggleInputElement()).not.toBeChecked();
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeTruthy();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
),
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
it("should override defaultEncrypted when server forces enabled encryption", async () => {
|
||||
mockClient.doesServerForceEncryptionForPreset.mockResolvedValue(true);
|
||||
getComponent({ defaultEncrypted: false });
|
||||
await flushPromises();
|
||||
|
||||
// server forces encryption to enabled, even though defaultEncrypted is true
|
||||
expect(getE2eeEnableToggleInputElement()).toBeChecked();
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeTruthy();
|
||||
expect(screen.getByText("Your server requires encryption to be enabled in private rooms.")).toBeDefined();
|
||||
});
|
||||
|
||||
it("should enable encryption toggle and disable field when server forces encryption", async () => {
|
||||
mockClient.doesServerForceEncryptionForPreset.mockResolvedValue(true);
|
||||
getComponent();
|
||||
|
||||
await flushPromises();
|
||||
expect(getE2eeEnableToggleInputElement()).toBeChecked();
|
||||
expect(getE2eeEnableToggleIsDisabled()).toBeTruthy();
|
||||
|
||||
expect(screen.getByText("Your server requires encryption to be enabled in private rooms.")).toBeDefined();
|
||||
});
|
||||
|
||||
it("should warn when trying to create a room with an invalid form", async () => {
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
await flushPromises();
|
||||
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
|
||||
// didn't submit room
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should create a private room", async () => {
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
await flushPromises();
|
||||
|
||||
const roomName = "Test Room Name";
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } });
|
||||
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(true, {
|
||||
createOpts: {
|
||||
name: roomName,
|
||||
},
|
||||
encryption: true,
|
||||
parentSpace: undefined,
|
||||
roomType: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for a knock room", () => {
|
||||
describe("when feature is disabled", () => {
|
||||
it("should not have the option to create a knock room", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
getComponent();
|
||||
fireEvent.click(screen.getByLabelText("Room visibility"));
|
||||
expect(screen.queryByRole("option", { name: "Ask to join" })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when feature is enabled", () => {
|
||||
const onFinished = jest.fn();
|
||||
const roomName = "Test Room Name";
|
||||
|
||||
beforeEach(async () => {
|
||||
onFinished.mockReset();
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(setting) => setting === "feature_ask_to_join",
|
||||
);
|
||||
getComponent({ onFinished });
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } });
|
||||
fireEvent.click(screen.getByLabelText("Room visibility"));
|
||||
fireEvent.click(screen.getByRole("option", { name: "Ask to join" }));
|
||||
});
|
||||
|
||||
it("should have a heading", () => {
|
||||
expect(screen.getByRole("heading")).toHaveTextContent("Create a room");
|
||||
});
|
||||
|
||||
it("should have a hint", () => {
|
||||
expect(
|
||||
screen.getByText(
|
||||
"Anyone can request to join, but admins or moderators need to grant access. You can change this later.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should create a knock room with private visibility", async () => {
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
expect(onFinished).toHaveBeenCalledWith(true, {
|
||||
createOpts: {
|
||||
name: roomName,
|
||||
visibility: Visibility.Private,
|
||||
},
|
||||
encryption: true,
|
||||
joinRule: JoinRule.Knock,
|
||||
parentSpace: undefined,
|
||||
roomType: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a knock room with public visibility", async () => {
|
||||
fireEvent.click(
|
||||
screen.getByRole("checkbox", { name: "Make this room visible in the public room directory." }),
|
||||
);
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
expect(onFinished).toHaveBeenCalledWith(true, {
|
||||
createOpts: {
|
||||
name: roomName,
|
||||
visibility: Visibility.Public,
|
||||
},
|
||||
encryption: true,
|
||||
joinRule: JoinRule.Knock,
|
||||
parentSpace: undefined,
|
||||
roomType: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for a public room", () => {
|
||||
it("should set join rule to public defaultPublic is truthy", async () => {
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ defaultPublic: true, onFinished });
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText("Create a public room")).toBeInTheDocument();
|
||||
|
||||
// e2e section is not rendered
|
||||
expect(screen.queryByText("Enable end-to-end encryption")).not.toBeInTheDocument();
|
||||
|
||||
const roomName = "Test Room Name";
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } });
|
||||
});
|
||||
|
||||
it("should not create a public room without an alias", async () => {
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
await flushPromises();
|
||||
|
||||
// set to public
|
||||
fireEvent.click(screen.getByLabelText("Room visibility"));
|
||||
fireEvent.click(screen.getByText("Public room"));
|
||||
expect(within(screen.getByLabelText("Room visibility")).findByText("Public room")).toBeTruthy();
|
||||
expect(screen.getByText("Create a public room")).toBeInTheDocument();
|
||||
|
||||
// set name
|
||||
const roomName = "Test Room Name";
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } });
|
||||
|
||||
// try to create the room
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
|
||||
// alias field invalid
|
||||
expect(screen.getByLabelText("Room address").parentElement!).toHaveClass("mx_Field_invalid");
|
||||
|
||||
// didn't submit
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should create a public room", async () => {
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished, defaultPublic: true });
|
||||
await flushPromises();
|
||||
|
||||
// set name
|
||||
const roomName = "Test Room Name";
|
||||
fireEvent.change(screen.getByLabelText("Name"), { target: { value: roomName } });
|
||||
|
||||
const roomAlias = "test";
|
||||
|
||||
fireEvent.change(screen.getByLabelText("Room address"), { target: { value: roomAlias } });
|
||||
|
||||
// try to create the room
|
||||
fireEvent.click(screen.getByText("Create room"));
|
||||
await flushPromises();
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(true, {
|
||||
createOpts: {
|
||||
name: roomName,
|
||||
preset: Preset.PublicChat,
|
||||
room_alias_name: roomAlias,
|
||||
visibility: Visibility.Public,
|
||||
},
|
||||
guestAccess: false,
|
||||
parentSpace: undefined,
|
||||
roomType: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { getByLabelText, getAllByLabelText, render } from "jest-matrix-react";
|
||||
import { Room, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import DevtoolsDialog from "../../../../../src/components/views/dialogs/DevtoolsDialog";
|
||||
|
||||
describe("DevtoolsDialog", () => {
|
||||
let cli: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
function getComponent(roomId: string, threadRootId: string | null = null, onFinished = () => true) {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<DevtoolsDialog roomId={roomId} threadRootId={threadRootId} onFinished={onFinished} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
room = new Room("!id", cli, "@alice:matrix.org");
|
||||
|
||||
jest.spyOn(cli, "getRoom").mockReturnValue(room);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders the devtools dialog", () => {
|
||||
const { asFragment } = getComponent(room.roomId);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("copies the roomid", async () => {
|
||||
const user = userEvent.setup();
|
||||
jest.spyOn(navigator.clipboard, "writeText");
|
||||
|
||||
const { container } = getComponent(room.roomId);
|
||||
|
||||
const copyBtn = getByLabelText(container, "Copy");
|
||||
await user.click(copyBtn);
|
||||
const copiedBtn = getByLabelText(container, "Copied!");
|
||||
|
||||
expect(copiedBtn).toBeInTheDocument();
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalled();
|
||||
await expect(navigator.clipboard.readText()).resolves.toBe(room.roomId);
|
||||
});
|
||||
|
||||
it("copies the thread root id when provided", async () => {
|
||||
const user = userEvent.setup();
|
||||
jest.spyOn(navigator.clipboard, "writeText");
|
||||
|
||||
const threadRootId = "$test_event_id_goes_here";
|
||||
const { container } = getComponent(room.roomId, threadRootId);
|
||||
|
||||
const copyBtn = getAllByLabelText(container, "Copy")[1];
|
||||
await user.click(copyBtn);
|
||||
const copiedBtn = getByLabelText(container, "Copied!");
|
||||
|
||||
expect(copiedBtn).toBeInTheDocument();
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalled();
|
||||
await expect(navigator.clipboard.readText()).resolves.toBe(threadRootId);
|
||||
});
|
||||
});
|
326
test/unit-tests/components/views/dialogs/ExportDialog-test.tsx
Normal file
326
test/unit-tests/components/views/dialogs/ExportDialog-test.tsx
Normal file
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, RenderResult, waitFor } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog";
|
||||
import { ExportType, ExportFormat } from "../../../../../src/utils/exportUtils/exportUtils";
|
||||
import { createTestClient, mkStubRoom } from "../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import HTMLExporter from "../../../../../src/utils/exportUtils/HtmlExport";
|
||||
import ChatExport from "../../../../../src/customisations/ChatExport";
|
||||
import PlainTextExporter from "../../../../../src/utils/exportUtils/PlainTextExport";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
const htmlExporterInstance = {
|
||||
export: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
const plainTextExporterInstance = {
|
||||
export: jest.fn().mockResolvedValue({}),
|
||||
};
|
||||
jest.mock("../../../../../src/utils/exportUtils/HtmlExport", () => jest.fn());
|
||||
jest.mock("../../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn());
|
||||
|
||||
jest.mock("../../../../../src/customisations/ChatExport", () => ({
|
||||
getForceChatExportParameters: jest.fn().mockReturnValue({}),
|
||||
}));
|
||||
|
||||
const ChatExportMock = mocked(ChatExport);
|
||||
const HTMLExporterMock = mocked(HTMLExporter);
|
||||
const PlainTextExporterMock = mocked(PlainTextExporter);
|
||||
|
||||
describe("<ExportDialog />", () => {
|
||||
const mockClient = createTestClient();
|
||||
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
|
||||
|
||||
const roomId = "test:test.org";
|
||||
const defaultProps = {
|
||||
room: mkStubRoom(roomId, "test", mockClient) as unknown as Room,
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
|
||||
const getComponent = (props = {}) => render(<ExportDialog {...defaultProps} {...props} />);
|
||||
|
||||
const getSizeInput = ({ container }: RenderResult) => container.querySelector('input[id="size-limit"]')!;
|
||||
const getExportTypeInput = ({ container }: RenderResult) => container.querySelector('select[id="export-type"]')!;
|
||||
const getAttachmentsCheckbox = ({ container }: RenderResult) =>
|
||||
container.querySelector('input[id="include-attachments"]')!;
|
||||
const getMessageCountInput = ({ container }: RenderResult) => container.querySelector('input[id="message-count"]')!;
|
||||
const getExportFormatInput = ({ container }: RenderResult, format: ExportFormat) =>
|
||||
container.querySelector(`input[id="exportFormat-${format}"]`)!;
|
||||
const getPrimaryButton = ({ getByTestId }: RenderResult) => getByTestId("dialog-primary-button")!;
|
||||
const getSecondaryButton = ({ getByTestId }: RenderResult) => getByTestId("dialog-cancel-button")!;
|
||||
|
||||
const submitForm = async (component: RenderResult) => fireEvent.click(getPrimaryButton(component));
|
||||
const selectExportFormat = async (component: RenderResult, format: ExportFormat) =>
|
||||
fireEvent.click(getExportFormatInput(component, format));
|
||||
const selectExportType = async (component: RenderResult, type: ExportType) =>
|
||||
fireEvent.change(getExportTypeInput(component), { target: { value: type } });
|
||||
const setMessageCount = async (component: RenderResult, count: number) =>
|
||||
fireEvent.change(getMessageCountInput(component), { target: { value: count } });
|
||||
|
||||
const setSizeLimit = async (component: RenderResult, limit: number) =>
|
||||
fireEvent.change(getSizeInput(component), { target: { value: limit } });
|
||||
|
||||
beforeEach(() => {
|
||||
HTMLExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(htmlExporterInstance));
|
||||
PlainTextExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(plainTextExporterInstance));
|
||||
htmlExporterInstance.export.mockClear();
|
||||
plainTextExporterInstance.export.mockClear();
|
||||
|
||||
// default setting value
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockClear().mockReturnValue({});
|
||||
});
|
||||
|
||||
it("renders export dialog", () => {
|
||||
const component = getComponent();
|
||||
expect(component.container.querySelector(".mx_ExportDialog")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calls onFinished when cancel button is clicked", () => {
|
||||
const onFinished = jest.fn();
|
||||
const component = getComponent({ onFinished });
|
||||
fireEvent.click(getSecondaryButton(component));
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
it("exports room on submit", async () => {
|
||||
const component = getComponent();
|
||||
await submitForm(component);
|
||||
|
||||
await waitFor(() => {
|
||||
// 4th arg is an component function
|
||||
const exportConstructorProps = HTMLExporterMock.mock.calls[0].slice(0, 3);
|
||||
expect(exportConstructorProps).toEqual([
|
||||
defaultProps.room,
|
||||
ExportType.Timeline,
|
||||
{
|
||||
attachmentsIncluded: false,
|
||||
maxSize: 8388608, // 8MB to bytes
|
||||
numberOfMessages: 100,
|
||||
},
|
||||
]);
|
||||
});
|
||||
expect(htmlExporterInstance.export).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exports room using values set from ForceRoomExportParameters", async () => {
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||
format: ExportFormat.PlainText,
|
||||
range: ExportType.Beginning,
|
||||
sizeMb: 7000,
|
||||
numberOfMessages: 30,
|
||||
includeAttachments: true,
|
||||
});
|
||||
const component = getComponent();
|
||||
await submitForm(component);
|
||||
|
||||
// 4th arg is an component function
|
||||
const exportConstructorProps = PlainTextExporterMock.mock.calls[0].slice(0, 3);
|
||||
expect(exportConstructorProps).toEqual([
|
||||
defaultProps.room,
|
||||
ExportType.Beginning,
|
||||
{
|
||||
attachmentsIncluded: true,
|
||||
maxSize: 7000 * 1024 * 1024,
|
||||
numberOfMessages: 30,
|
||||
},
|
||||
]);
|
||||
expect(plainTextExporterInstance.export).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("renders success screen when export is finished", async () => {
|
||||
const component = getComponent();
|
||||
await submitForm(component);
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(component.container.querySelector(".mx_InfoDialog .mx_Dialog_content")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("export format", () => {
|
||||
it("renders export format with html selected by default", () => {
|
||||
const component = getComponent();
|
||||
expect(getExportFormatInput(component, ExportFormat.Html)).toBeChecked();
|
||||
});
|
||||
|
||||
it("sets export format on radio button click", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportFormat(component, ExportFormat.PlainText);
|
||||
expect(getExportFormatInput(component, ExportFormat.PlainText)).toBeChecked();
|
||||
expect(getExportFormatInput(component, ExportFormat.Html)).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("hides export format input when format is valid in ForceRoomExportParameters", () => {
|
||||
const component = getComponent();
|
||||
expect(getExportFormatInput(component, ExportFormat.Html)).toBeChecked();
|
||||
});
|
||||
|
||||
it("does not render export format when set in ForceRoomExportParameters", () => {
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||
format: ExportFormat.PlainText,
|
||||
});
|
||||
const component = getComponent();
|
||||
expect(getExportFormatInput(component, ExportFormat.Html)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("export type", () => {
|
||||
it("renders export type with timeline selected by default", () => {
|
||||
const component = getComponent();
|
||||
expect(getExportTypeInput(component)).toHaveValue(ExportType.Timeline);
|
||||
});
|
||||
|
||||
it("sets export type on change", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportType(component, ExportType.Beginning);
|
||||
expect(getExportTypeInput(component)).toHaveValue(ExportType.Beginning);
|
||||
});
|
||||
|
||||
it("does not render export type when set in ForceRoomExportParameters", () => {
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||
range: ExportType.Beginning,
|
||||
});
|
||||
const component = getComponent();
|
||||
expect(getExportTypeInput(component)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("does not render message count input", async () => {
|
||||
const component = getComponent();
|
||||
expect(getMessageCountInput(component)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("renders message count input with default value 100 when export type is lastNMessages", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportType(component, ExportType.LastNMessages);
|
||||
expect(getMessageCountInput(component)).toHaveValue(100);
|
||||
});
|
||||
|
||||
it("sets message count on change", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportType(component, ExportType.LastNMessages);
|
||||
await setMessageCount(component, 10);
|
||||
expect(getMessageCountInput(component)).toHaveValue(10);
|
||||
});
|
||||
|
||||
it("does not export when export type is lastNMessages and message count is falsy", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportType(component, ExportType.LastNMessages);
|
||||
await setMessageCount(component, 0);
|
||||
await submitForm(component);
|
||||
|
||||
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not export when export type is lastNMessages and message count is more than max", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportType(component, ExportType.LastNMessages);
|
||||
await setMessageCount(component, 99999999999);
|
||||
await submitForm(component);
|
||||
|
||||
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exports when export type is NOT lastNMessages and message count is falsy", async () => {
|
||||
const component = getComponent();
|
||||
await selectExportType(component, ExportType.LastNMessages);
|
||||
await setMessageCount(component, 0);
|
||||
await selectExportType(component, ExportType.Timeline);
|
||||
await submitForm(component);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(htmlExporterInstance.export).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("size limit", () => {
|
||||
it("renders size limit input with default value", () => {
|
||||
const component = getComponent();
|
||||
expect(getSizeInput(component)).toHaveValue(8);
|
||||
});
|
||||
|
||||
it("updates size limit on change", async () => {
|
||||
const component = getComponent();
|
||||
await setSizeLimit(component, 20);
|
||||
expect(getSizeInput(component)).toHaveValue(20);
|
||||
});
|
||||
|
||||
it("does not export when size limit is falsy", async () => {
|
||||
const component = getComponent();
|
||||
await setSizeLimit(component, 0);
|
||||
await submitForm(component);
|
||||
|
||||
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not export when size limit is larger than max", async () => {
|
||||
const component = getComponent();
|
||||
await setSizeLimit(component, 2001);
|
||||
await submitForm(component);
|
||||
|
||||
expect(htmlExporterInstance.export).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("exports when size limit is max", async () => {
|
||||
const component = getComponent();
|
||||
await setSizeLimit(component, 2000);
|
||||
await submitForm(component);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(htmlExporterInstance.export).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("does not render size limit input when set in ForceRoomExportParameters", () => {
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||
sizeMb: 10000,
|
||||
});
|
||||
const component = getComponent();
|
||||
expect(getSizeInput(component)).toBeFalsy();
|
||||
});
|
||||
|
||||
/**
|
||||
* 2000mb size limit does not apply when higher limit is configured in config
|
||||
*/
|
||||
it("exports when size limit set in ForceRoomExportParameters is larger than 2000", async () => {
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||
sizeMb: 10000,
|
||||
});
|
||||
const component = getComponent();
|
||||
await submitForm(component);
|
||||
|
||||
expect(htmlExporterInstance.export).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("include attachments", () => {
|
||||
it("renders input with default value of false", () => {
|
||||
const component = getComponent();
|
||||
expect(getAttachmentsCheckbox(component)).not.toBeChecked();
|
||||
});
|
||||
|
||||
it("updates include attachments on change", async () => {
|
||||
const component = getComponent();
|
||||
fireEvent.click(getAttachmentsCheckbox(component));
|
||||
expect(getAttachmentsCheckbox(component)).toBeChecked();
|
||||
});
|
||||
|
||||
it("does not render input when set in ForceRoomExportParameters", () => {
|
||||
mocked(ChatExportMock.getForceChatExportParameters!).mockReturnValue({
|
||||
includeAttachments: false,
|
||||
});
|
||||
const component = getComponent();
|
||||
expect(getAttachmentsCheckbox(component)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
|
||||
import SdkConfig from "../../../../../src/SdkConfig";
|
||||
import FeedbackDialog from "../../../../../src/components/views/dialogs/FeedbackDialog";
|
||||
|
||||
describe("FeedbackDialog", () => {
|
||||
it("should respect feedback config", () => {
|
||||
SdkConfig.put({
|
||||
feedback: {
|
||||
existing_issues_url: "http://existing?foo=bar",
|
||||
new_issue_url: "https://new.issue.url?foo=bar",
|
||||
},
|
||||
});
|
||||
|
||||
const { asFragment } = render(<FeedbackDialog onFinished={jest.fn()} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
395
test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx
Normal file
395
test/unit-tests/components/views/dialogs/ForwardDialog-test.tsx
Normal file
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2021 Robin Townsend <robin@robin.town>
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
MatrixEvent,
|
||||
EventType,
|
||||
LocationAssetType,
|
||||
M_ASSET,
|
||||
M_LOCATION,
|
||||
M_TIMESTAMP,
|
||||
M_TEXT,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { act, fireEvent, getByTestId, render, RenderResult, screen, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import ForwardDialog from "../../../../../src/components/views/dialogs/ForwardDialog";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
makeBeaconEvent,
|
||||
makeLegacyLocationEvent,
|
||||
makeLocationEvent,
|
||||
mkEvent,
|
||||
mkMessage,
|
||||
mkStubRoom,
|
||||
mockPlatformPeg,
|
||||
} from "../../../../test-utils";
|
||||
import { TILE_SERVER_WK_KEY } from "../../../../../src/utils/WellKnownUtils";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
// mock offsetParent
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
|
||||
get() {
|
||||
return this.parentNode;
|
||||
},
|
||||
});
|
||||
|
||||
describe("ForwardDialog", () => {
|
||||
const sourceRoom = "!111111111111111111:example.org";
|
||||
const aliceId = "@alice:example.org";
|
||||
const defaultMessage = mkMessage({
|
||||
room: sourceRoom,
|
||||
user: aliceId,
|
||||
msg: "Hello world!",
|
||||
event: true,
|
||||
});
|
||||
const accountDataEvent = new MatrixEvent({
|
||||
type: EventType.Direct,
|
||||
sender: aliceId,
|
||||
content: {},
|
||||
});
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
getUserId: jest.fn().mockReturnValue(aliceId),
|
||||
getSafeUserId: jest.fn().mockReturnValue(aliceId),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||
getRoom: jest.fn(),
|
||||
getAccountData: jest.fn().mockReturnValue(accountDataEvent),
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue(""),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
getProfileInfo: jest.fn().mockResolvedValue({
|
||||
displayname: "Alice",
|
||||
}),
|
||||
decryptEventIfNeeded: jest.fn(),
|
||||
sendEvent: jest.fn(),
|
||||
getClientWellKnown: jest.fn().mockReturnValue({
|
||||
[TILE_SERVER_WK_KEY.name]: { map_style_url: "maps.com" },
|
||||
}),
|
||||
});
|
||||
const defaultRooms = ["a", "A", "b"].map((name) => mkStubRoom(name, name, mockClient));
|
||||
|
||||
const mountForwardDialog = (message = defaultMessage, rooms = defaultRooms) => {
|
||||
mockClient.getVisibleRooms.mockReturnValue(rooms);
|
||||
mockClient.getRoom.mockImplementation((roomId) => rooms.find((room) => room.roomId === roomId) || null);
|
||||
|
||||
const wrapper: RenderResult = render(
|
||||
<ForwardDialog
|
||||
matrixClient={mockClient}
|
||||
event={message}
|
||||
permalinkCreator={new RoomPermalinkCreator(undefined!, sourceRoom)}
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
DMRoomMap.makeShared(mockClient);
|
||||
jest.clearAllMocks();
|
||||
mockClient.getUserId.mockReturnValue("@bob:example.org");
|
||||
mockClient.getSafeUserId.mockReturnValue("@bob:example.org");
|
||||
mockClient.sendEvent.mockReset();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(MatrixClientPeg, "get").mockRestore();
|
||||
});
|
||||
|
||||
it("shows a preview with us as the sender", async () => {
|
||||
const { container } = mountForwardDialog();
|
||||
|
||||
expect(screen.queryByText("Hello world!")).toBeInTheDocument();
|
||||
|
||||
// We would just test SenderProfile for the user ID, but it's stubbed
|
||||
const previewAvatar = container.querySelector(".mx_EventTile_avatar .mx_BaseAvatar");
|
||||
expect(previewAvatar?.getAttribute("title")).toBe("@bob:example.org");
|
||||
});
|
||||
|
||||
it("filters the rooms", async () => {
|
||||
const { container } = mountForwardDialog();
|
||||
|
||||
expect(container.querySelectorAll(".mx_ForwardList_entry")).toHaveLength(3);
|
||||
|
||||
const searchInput = getByTestId(container, "searchbox-input");
|
||||
await userEvent.type(searchInput, "a");
|
||||
|
||||
expect(container.querySelectorAll(".mx_ForwardList_entry")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should be navigable using arrow keys", async () => {
|
||||
const { container } = mountForwardDialog();
|
||||
|
||||
const searchBox = getByTestId(container, "searchbox-input");
|
||||
searchBox.focus();
|
||||
await waitFor(() =>
|
||||
expect(container.querySelectorAll(".mx_ForwardList_entry")[0]).toHaveClass("mx_ForwardList_entry_active"),
|
||||
);
|
||||
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
await waitFor(() =>
|
||||
expect(container.querySelectorAll(".mx_ForwardList_entry")[1]).toHaveClass("mx_ForwardList_entry_active"),
|
||||
);
|
||||
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
await waitFor(() =>
|
||||
expect(container.querySelectorAll(".mx_ForwardList_entry")[2]).toHaveClass("mx_ForwardList_entry_active"),
|
||||
);
|
||||
|
||||
await userEvent.keyboard("[ArrowUp]");
|
||||
await waitFor(() =>
|
||||
expect(container.querySelectorAll(".mx_ForwardList_entry")[1]).toHaveClass("mx_ForwardList_entry_active"),
|
||||
);
|
||||
|
||||
await userEvent.keyboard("[Enter]");
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith("A", "m.room.message", {
|
||||
body: "Hello world!",
|
||||
msgtype: "m.text",
|
||||
});
|
||||
});
|
||||
|
||||
it("tracks message sending progress across multiple rooms", async () => {
|
||||
mockPlatformPeg();
|
||||
const { container } = mountForwardDialog();
|
||||
|
||||
// Make sendEvent require manual resolution so we can see the sending state
|
||||
let finishSend: (arg?: any) => void;
|
||||
let cancelSend: () => void;
|
||||
mockClient.sendEvent.mockImplementation(
|
||||
<T extends {}>() =>
|
||||
new Promise<T>((resolve, reject) => {
|
||||
finishSend = resolve;
|
||||
cancelSend = reject;
|
||||
}),
|
||||
);
|
||||
|
||||
let firstButton!: Element;
|
||||
let secondButton!: Element;
|
||||
const update = () => {
|
||||
[firstButton, secondButton] = container.querySelectorAll(".mx_ForwardList_sendButton");
|
||||
};
|
||||
update();
|
||||
|
||||
expect(firstButton.className).toContain("mx_ForwardList_canSend");
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(firstButton);
|
||||
});
|
||||
update();
|
||||
expect(firstButton.className).toContain("mx_ForwardList_sending");
|
||||
|
||||
await act(async () => {
|
||||
cancelSend();
|
||||
// Wait one tick for the button to realize the send failed
|
||||
await sleep(0);
|
||||
});
|
||||
update();
|
||||
expect(firstButton.className).toContain("mx_ForwardList_sendFailed");
|
||||
|
||||
expect(secondButton.className).toContain("mx_ForwardList_canSend");
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(secondButton);
|
||||
});
|
||||
update();
|
||||
expect(secondButton.className).toContain("mx_ForwardList_sending");
|
||||
|
||||
await act(async () => {
|
||||
finishSend();
|
||||
// Wait one tick for the button to realize the send succeeded
|
||||
await sleep(0);
|
||||
});
|
||||
update();
|
||||
expect(secondButton.className).toContain("mx_ForwardList_sent");
|
||||
});
|
||||
|
||||
it("can render replies", async () => {
|
||||
const replyMessage = mkEvent({
|
||||
type: "m.room.message",
|
||||
room: "!111111111111111111:example.org",
|
||||
user: "@alice:example.org",
|
||||
content: {
|
||||
"msgtype": "m.text",
|
||||
"body": "> <@bob:example.org> Hi Alice!\n\nHi Bob!",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
event_id: "$2222222222222222222222222222222222222222222",
|
||||
},
|
||||
},
|
||||
},
|
||||
event: true,
|
||||
});
|
||||
|
||||
mountForwardDialog(replyMessage);
|
||||
|
||||
expect(screen.queryByText("Hi Alice!", { exact: false })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("disables buttons for rooms without send permissions", async () => {
|
||||
const readOnlyRoom = mkStubRoom("a", "a", mockClient);
|
||||
readOnlyRoom.maySendMessage = jest.fn().mockReturnValue(false);
|
||||
const rooms = [readOnlyRoom, mkStubRoom("b", "b", mockClient)];
|
||||
|
||||
const { container } = mountForwardDialog(undefined, rooms);
|
||||
|
||||
const [firstButton, secondButton] = container.querySelectorAll<HTMLButtonElement>(".mx_ForwardList_sendButton");
|
||||
|
||||
expect(firstButton.getAttribute("aria-disabled")).toBeTruthy();
|
||||
expect(secondButton.getAttribute("aria-disabled")).toBeFalsy();
|
||||
});
|
||||
|
||||
describe("Location events", () => {
|
||||
// 14.03.2022 16:15
|
||||
const now = 1647270879403;
|
||||
const roomId = "a";
|
||||
const geoUri = "geo:51.5076,-0.1276";
|
||||
const legacyLocationEvent = makeLegacyLocationEvent(geoUri);
|
||||
const modernLocationEvent = makeLocationEvent(geoUri);
|
||||
const pinDropLocationEvent = makeLocationEvent(geoUri, LocationAssetType.Pin);
|
||||
|
||||
beforeEach(() => {
|
||||
// legacy events will default timestamp to Date.now()
|
||||
// mock a stable now for easy assertion
|
||||
jest.spyOn(Date, "now").mockReturnValue(now);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(Date, "now").mockRestore();
|
||||
});
|
||||
|
||||
const sendToFirstRoom = (container: HTMLElement): void =>
|
||||
act(() => {
|
||||
const sendToFirstRoomButton = container.querySelector(".mx_ForwardList_sendButton");
|
||||
fireEvent.click(sendToFirstRoomButton!);
|
||||
});
|
||||
|
||||
it("converts legacy location events to pin drop shares", async () => {
|
||||
const { container } = mountForwardDialog(legacyLocationEvent);
|
||||
|
||||
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
|
||||
sendToFirstRoom(container);
|
||||
|
||||
// text and description from original event are removed
|
||||
// text gets new default message from event values
|
||||
// timestamp is defaulted to now
|
||||
const text = `Location ${geoUri} at ${new Date(now).toISOString()}`;
|
||||
const expectedStrippedContent = {
|
||||
...modernLocationEvent.getContent(),
|
||||
body: text,
|
||||
[M_TEXT.name]: text,
|
||||
[M_TIMESTAMP.name]: now,
|
||||
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||
[M_LOCATION.name]: {
|
||||
uri: geoUri,
|
||||
},
|
||||
};
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
legacyLocationEvent.getType(),
|
||||
expectedStrippedContent,
|
||||
);
|
||||
});
|
||||
|
||||
it("removes personal information from static self location shares", async () => {
|
||||
const { container } = mountForwardDialog(modernLocationEvent);
|
||||
|
||||
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
|
||||
sendToFirstRoom(container);
|
||||
|
||||
const timestamp = M_TIMESTAMP.findIn<number>(modernLocationEvent.getContent())!;
|
||||
// text and description from original event are removed
|
||||
// text gets new default message from event values
|
||||
const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`;
|
||||
const expectedStrippedContent = {
|
||||
...modernLocationEvent.getContent(),
|
||||
body: text,
|
||||
[M_TEXT.name]: text,
|
||||
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||
[M_LOCATION.name]: {
|
||||
uri: geoUri,
|
||||
},
|
||||
};
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
modernLocationEvent.getType(),
|
||||
expectedStrippedContent,
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards beacon location as a pin drop event", async () => {
|
||||
const timestamp = 123456;
|
||||
const beaconEvent = makeBeaconEvent("@alice:server.org", { geoUri, timestamp });
|
||||
const text = `Location ${geoUri} at ${new Date(timestamp).toISOString()}`;
|
||||
const expectedContent = {
|
||||
msgtype: "m.location",
|
||||
body: text,
|
||||
[M_TEXT.name]: text,
|
||||
[M_ASSET.name]: { type: LocationAssetType.Pin },
|
||||
[M_LOCATION.name]: {
|
||||
uri: geoUri,
|
||||
},
|
||||
geo_uri: geoUri,
|
||||
[M_TIMESTAMP.name]: timestamp,
|
||||
};
|
||||
const { container } = mountForwardDialog(beaconEvent);
|
||||
|
||||
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
|
||||
|
||||
sendToFirstRoom(container);
|
||||
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(roomId, EventType.RoomMessage, expectedContent);
|
||||
});
|
||||
|
||||
it("forwards pin drop event", async () => {
|
||||
const { container } = mountForwardDialog(pinDropLocationEvent);
|
||||
|
||||
expect(container.querySelector(".mx_MLocationBody")).toBeTruthy();
|
||||
|
||||
sendToFirstRoom(container);
|
||||
|
||||
expect(mockClient.sendEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
pinDropLocationEvent.getType(),
|
||||
pinDropLocationEvent.getContent(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("If the feature_dynamic_room_predecessors is not enabled", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
|
||||
});
|
||||
|
||||
it("Passes through the dynamic predecessor setting", async () => {
|
||||
mockClient.getVisibleRooms.mockClear();
|
||||
mountForwardDialog();
|
||||
expect(mockClient.getVisibleRooms).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("If the feature_dynamic_room_predecessors is enabled", () => {
|
||||
beforeEach(() => {
|
||||
// Turn on feature_dynamic_room_predecessors setting
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_dynamic_room_predecessors",
|
||||
);
|
||||
});
|
||||
|
||||
it("Passes through the dynamic predecessor setting", async () => {
|
||||
mockClient.getVisibleRooms.mockClear();
|
||||
mountForwardDialog();
|
||||
expect(mockClient.getVisibleRooms).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { act, render } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
import { Mocked } from "jest-mock";
|
||||
import {
|
||||
EmojiMapping,
|
||||
ShowSasCallbacks,
|
||||
Verifier,
|
||||
VerifierEvent,
|
||||
VerifierEventHandlerMap,
|
||||
} from "matrix-js-sdk/src/crypto-api";
|
||||
import { TypedEventEmitter } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import IncomingSasDialog from "../../../../../src/components/views/dialogs/IncomingSasDialog";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
|
||||
describe("IncomingSasDialog", () => {
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
});
|
||||
|
||||
it("shows a spinner at first", () => {
|
||||
const mockVerifier = makeMockVerifier();
|
||||
const { container } = renderComponent(mockVerifier);
|
||||
expect(container.getElementsByClassName("mx_Spinner").length).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should show some emojis once keys are exchanged", () => {
|
||||
const mockVerifier = makeMockVerifier();
|
||||
const { container } = renderComponent(mockVerifier);
|
||||
|
||||
// fire the ShowSas event
|
||||
const sasEvent = makeMockSasCallbacks();
|
||||
act(() => {
|
||||
mockVerifier.emit(VerifierEvent.ShowSas, sasEvent);
|
||||
});
|
||||
|
||||
const emojis = container.getElementsByClassName("mx_VerificationShowSas_emojiSas_block");
|
||||
expect(emojis.length).toEqual(7);
|
||||
for (const emoji of emojis) {
|
||||
expect(emoji).toHaveTextContent("🦄Unicorn");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function renderComponent(verifier: Verifier, onFinished = () => true) {
|
||||
return render(<IncomingSasDialog verifier={verifier} onFinished={onFinished} />);
|
||||
}
|
||||
|
||||
function makeMockVerifier(): Mocked<Verifier> {
|
||||
const verifier = new TypedEventEmitter<VerifierEvent, VerifierEventHandlerMap>();
|
||||
Object.assign(verifier, {
|
||||
cancel: jest.fn(),
|
||||
});
|
||||
return verifier as unknown as Mocked<Verifier>;
|
||||
}
|
||||
|
||||
function makeMockSasCallbacks(): ShowSasCallbacks {
|
||||
const unicorn: EmojiMapping = ["🦄", "unicorn"];
|
||||
return {
|
||||
sas: {
|
||||
emoji: new Array<EmojiMapping>(7).fill(unicorn),
|
||||
},
|
||||
cancel: jest.fn(),
|
||||
confirm: jest.fn(),
|
||||
mismatch: jest.fn(),
|
||||
};
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import InteractiveAuthDialog from "../../../../../src/components/views/dialogs/InteractiveAuthDialog";
|
||||
import { clearAllModals, flushPromises, getMockClientWithEventEmitter, unmockClientPeg } from "../../../../test-utils";
|
||||
|
||||
describe("InteractiveAuthDialog", function () {
|
||||
const homeserverUrl = "https://matrix.org";
|
||||
const authUrl = "https://auth.com";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
generateClientSecret: jest.fn().mockReturnValue("t35tcl1Ent5ECr3T"),
|
||||
getFallbackAuthUrl: jest.fn().mockReturnValue(authUrl),
|
||||
getHomeserverUrl: jest.fn().mockReturnValue(homeserverUrl),
|
||||
});
|
||||
|
||||
const defaultProps = {
|
||||
matrixClient: mockClient,
|
||||
makeRequest: jest.fn().mockResolvedValue(undefined),
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
|
||||
const renderComponent = (props = {}) => render(<InteractiveAuthDialog {...defaultProps} {...props} />);
|
||||
const getPasswordField = () => screen.getByLabelText("Password");
|
||||
const getSubmitButton = () => screen.getByRole("button", { name: "Continue" });
|
||||
|
||||
beforeEach(async function () {
|
||||
jest.clearAllMocks();
|
||||
mockClient.credentials = { userId: null };
|
||||
await clearAllModals();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
unmockClientPeg();
|
||||
await clearAllModals();
|
||||
});
|
||||
|
||||
it("Should successfully complete a password flow", async () => {
|
||||
const onFinished = jest.fn();
|
||||
const makeRequest = jest.fn().mockResolvedValue({ a: 1 });
|
||||
|
||||
mockClient.credentials = { userId: "@user:id" };
|
||||
const authData = {
|
||||
session: "sess",
|
||||
flows: [{ stages: ["m.login.password"] }],
|
||||
};
|
||||
|
||||
renderComponent({ makeRequest, onFinished, authData });
|
||||
|
||||
const passwordField = getPasswordField();
|
||||
const submitButton = getSubmitButton();
|
||||
|
||||
expect(passwordField).toBeTruthy();
|
||||
expect(submitButton).toBeTruthy();
|
||||
|
||||
// submit should be disabled
|
||||
expect(submitButton).toBeDisabled();
|
||||
|
||||
// put something in the password box
|
||||
await userEvent.type(passwordField, "s3kr3t");
|
||||
|
||||
expect(submitButton).not.toBeDisabled();
|
||||
|
||||
// hit enter; that should trigger a request
|
||||
await userEvent.click(submitButton);
|
||||
|
||||
// wait for auth request to resolve
|
||||
await flushPromises();
|
||||
|
||||
expect(makeRequest).toHaveBeenCalledTimes(1);
|
||||
expect(makeRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
session: "sess",
|
||||
type: "m.login.password",
|
||||
password: "s3kr3t",
|
||||
identifier: {
|
||||
type: "m.id.user",
|
||||
user: "@user:id",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(onFinished).toHaveBeenCalledTimes(1);
|
||||
expect(onFinished).toHaveBeenCalledWith(true, { a: 1 });
|
||||
});
|
||||
|
||||
describe("SSO flow", () => {
|
||||
it("should close on cancel", () => {
|
||||
const onFinished = jest.fn();
|
||||
const makeRequest = jest.fn().mockResolvedValue({ a: 1 });
|
||||
|
||||
mockClient.credentials = { userId: "@user:id" };
|
||||
const authData = {
|
||||
session: "sess",
|
||||
flows: [{ stages: ["m.login.sso"] }],
|
||||
};
|
||||
|
||||
renderComponent({ makeRequest, onFinished, authData });
|
||||
|
||||
expect(screen.getByText("To continue, use Single Sign On to prove your identity.")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByText("Cancel"));
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(false, null);
|
||||
});
|
||||
|
||||
it("should complete an sso flow", async () => {
|
||||
jest.spyOn(global.window, "addEventListener");
|
||||
// @ts-ignore
|
||||
jest.spyOn(global.window, "open").mockImplementation(() => {});
|
||||
const onFinished = jest.fn();
|
||||
const successfulResult = { test: 1 };
|
||||
const makeRequest = jest
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new MatrixError({ flows: [{ stages: ["m.login.sso"] }] }, 401))
|
||||
.mockResolvedValue(successfulResult);
|
||||
|
||||
mockClient.credentials = { userId: "@user:id" };
|
||||
const authData = {
|
||||
session: "sess",
|
||||
flows: [{ stages: ["m.login.sso"] }],
|
||||
};
|
||||
|
||||
renderComponent({ makeRequest, onFinished, authData });
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(screen.getByText("To continue, use Single Sign On to prove your identity.")).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByText("Single Sign On"));
|
||||
|
||||
// no we're on the sso auth screen
|
||||
expect(screen.getByText("Click the button below to confirm your identity.")).toBeInTheDocument();
|
||||
|
||||
// launch sso
|
||||
fireEvent.click(screen.getByText("Confirm"));
|
||||
expect(global.window.open).toHaveBeenCalledWith(authUrl, "_blank");
|
||||
|
||||
const onWindowReceiveMessageCall = mocked(window.addEventListener).mock.calls.find(
|
||||
(args) => args[0] === "message",
|
||||
);
|
||||
expect(onWindowReceiveMessageCall).toBeTruthy();
|
||||
// get the handle from SSO auth component
|
||||
// so we can pretend sso auth was completed
|
||||
const onWindowReceiveMessage = onWindowReceiveMessageCall![1];
|
||||
|
||||
// complete sso successfully
|
||||
act(() => {
|
||||
// @ts-ignore
|
||||
onWindowReceiveMessage({ data: "authDone", origin: homeserverUrl });
|
||||
});
|
||||
|
||||
// expect(makeRequest).toHaveBeenCalledWith({ session: authData.session })
|
||||
|
||||
// spinner displayed
|
||||
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
||||
// cancel/confirm buttons hidden while request in progress
|
||||
expect(screen.queryByText("Confirm")).not.toBeInTheDocument();
|
||||
|
||||
await flushPromises();
|
||||
await flushPromises();
|
||||
|
||||
// nothing in progress
|
||||
expect(screen.queryByRole("progressbar")).not.toBeInTheDocument();
|
||||
|
||||
// auth completed, now make the request again with auth
|
||||
fireEvent.click(screen.getByText("Confirm"));
|
||||
// loading while making request
|
||||
expect(screen.getByRole("progressbar")).toBeInTheDocument();
|
||||
|
||||
expect(makeRequest).toHaveBeenCalledTimes(2);
|
||||
|
||||
await flushPromises();
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith(true, successfulResult);
|
||||
});
|
||||
});
|
||||
});
|
488
test/unit-tests/components/views/dialogs/InviteDialog-test.tsx
Normal file
488
test/unit-tests/components/views/dialogs/InviteDialog-test.tsx
Normal file
|
@ -0,0 +1,488 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { RoomType, MatrixClient, MatrixError, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
|
||||
import InviteDialog from "../../../../../src/components/views/dialogs/InviteDialog";
|
||||
import { InviteKind } from "../../../../../src/components/views/dialogs/InviteDialogTypes";
|
||||
import {
|
||||
clearAllModals,
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
mkMembership,
|
||||
mkMessage,
|
||||
mkRoomCreateEvent,
|
||||
} from "../../../../test-utils";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import SdkConfig from "../../../../../src/SdkConfig";
|
||||
import { ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
|
||||
import { IConfigOptions } from "../../../../../src/IConfigOptions";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { IProfileInfo } from "../../../../../src/hooks/useProfileInfo";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
|
||||
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
|
||||
jest.mock("../../../../../src/IdentityAuthClient", () =>
|
||||
jest.fn().mockImplementation(() => ({
|
||||
getAccessToken: mockGetAccessToken,
|
||||
})),
|
||||
);
|
||||
|
||||
jest.mock("../../../../../src/utils/direct-messages", () => ({
|
||||
...jest.requireActual("../../../../../src/utils/direct-messages"),
|
||||
__esModule: true,
|
||||
startDmOnFirstMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
const getSearchField = () => screen.getByTestId("invite-dialog-input");
|
||||
|
||||
const enterIntoSearchField = async (value: string) => {
|
||||
const searchField = getSearchField();
|
||||
await userEvent.clear(searchField);
|
||||
await userEvent.type(searchField, value + "{enter}");
|
||||
};
|
||||
|
||||
const pasteIntoSearchField = async (value: string) => {
|
||||
const searchField = getSearchField();
|
||||
await userEvent.clear(searchField);
|
||||
searchField.focus();
|
||||
await userEvent.paste(value);
|
||||
};
|
||||
|
||||
const expectPill = (value: string) => {
|
||||
expect(screen.getByText(value)).toBeInTheDocument();
|
||||
expect(getSearchField()).toHaveValue("");
|
||||
};
|
||||
|
||||
const expectNoPill = (value: string) => {
|
||||
expect(screen.queryByText(value)).not.toBeInTheDocument();
|
||||
expect(getSearchField()).toHaveValue(value);
|
||||
};
|
||||
|
||||
const roomId = "!111111111111111111:example.org";
|
||||
const aliceId = "@alice:example.org";
|
||||
const aliceEmail = "foobar@email.com";
|
||||
const bobId = "@bob:example.org";
|
||||
const bobEmail = "bobbob@example.com"; // bob@example.com is already used as an example in the invite dialog
|
||||
const carolId = "@carol:example.com";
|
||||
const bobbob = "bobbob";
|
||||
|
||||
const aliceProfileInfo: IProfileInfo = {
|
||||
user_id: aliceId,
|
||||
display_name: "Alice",
|
||||
};
|
||||
|
||||
const bobProfileInfo: IProfileInfo = {
|
||||
user_id: bobId,
|
||||
display_name: "Bob",
|
||||
};
|
||||
|
||||
describe("InviteDialog", () => {
|
||||
let mockClient: Mocked<MatrixClient>;
|
||||
let room: Room;
|
||||
|
||||
filterConsole(
|
||||
"Error retrieving profile for userId @carol:example.com",
|
||||
"Error retrieving profile for userId @localpart:server.tld",
|
||||
"Error retrieving profile for userId @localpart:server:tld",
|
||||
"[Invite:Recents] Excluding @alice:example.org from recents",
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
getUserId: jest.fn().mockReturnValue(bobId),
|
||||
getSafeUserId: jest.fn().mockReturnValue(bobId),
|
||||
isGuest: jest.fn().mockReturnValue(false),
|
||||
getVisibleRooms: jest.fn().mockReturnValue([]),
|
||||
getRoom: jest.fn(),
|
||||
getRooms: jest.fn(),
|
||||
getAccountData: jest.fn(),
|
||||
getPushActionsForEvent: jest.fn(),
|
||||
mxcUrlToHttp: jest.fn().mockReturnValue(""),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
getProfileInfo: jest.fn().mockImplementation(async (userId: string) => {
|
||||
if (userId === aliceId) return aliceProfileInfo;
|
||||
if (userId === bobId) return bobProfileInfo;
|
||||
|
||||
throw new MatrixError({
|
||||
errcode: "M_NOT_FOUND",
|
||||
error: "Profile not found",
|
||||
});
|
||||
}),
|
||||
getIdentityServerUrl: jest.fn(),
|
||||
searchUserDirectory: jest.fn().mockResolvedValue({}),
|
||||
lookupThreePid: jest.fn(),
|
||||
registerWithIdentityServer: jest.fn().mockResolvedValue({
|
||||
access_token: "access_token",
|
||||
token: "token",
|
||||
}),
|
||||
getOpenIdToken: jest.fn().mockResolvedValue({}),
|
||||
getIdentityAccount: jest.fn().mockResolvedValue({}),
|
||||
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
|
||||
supportsThreads: jest.fn().mockReturnValue(false),
|
||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||
getClientWellKnown: jest.fn().mockResolvedValue({}),
|
||||
});
|
||||
SdkConfig.put({ validated_server_config: {} as ValidatedServerConfig } as IConfigOptions);
|
||||
DMRoomMap.makeShared(mockClient);
|
||||
jest.clearAllMocks();
|
||||
|
||||
room = new Room(roomId, mockClient, mockClient.getSafeUserId());
|
||||
room.addLiveEvents([
|
||||
mkMessage({
|
||||
msg: "Hello",
|
||||
relatesTo: undefined,
|
||||
event: true,
|
||||
room: roomId,
|
||||
user: mockClient.getSafeUserId(),
|
||||
ts: Date.now(),
|
||||
}),
|
||||
]);
|
||||
room.currentState.setStateEvents([
|
||||
mkRoomCreateEvent(bobId, roomId),
|
||||
mkMembership({
|
||||
event: true,
|
||||
room: roomId,
|
||||
mship: KnownMembership.Join,
|
||||
user: aliceId,
|
||||
skey: aliceId,
|
||||
}),
|
||||
]);
|
||||
jest.spyOn(DMRoomMap.shared(), "getUniqueRoomsWithIndividuals").mockReturnValue({
|
||||
[aliceId]: room,
|
||||
});
|
||||
mockClient.getRooms.mockReturnValue([room]);
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
|
||||
SdkContextClass.instance.client = mockClient;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await clearAllModals();
|
||||
SdkContextClass.instance.onLoggedOut();
|
||||
SdkContextClass.instance.client = undefined;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should label with space name", () => {
|
||||
room.isSpaceRoom = jest.fn().mockReturnValue(true);
|
||||
room.getType = jest.fn().mockReturnValue(RoomType.Space);
|
||||
room.name = "Space";
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
expect(screen.queryByText("Invite to Space")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should label with room name", () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
expect(screen.getByText(`Invite to ${roomId}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not suggest valid unknown MXIDs", async () => {
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="@localpart:server.tld"
|
||||
/>,
|
||||
);
|
||||
await flushPromises();
|
||||
expect(screen.queryByText("@localpart:server.tld")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not suggest invalid MXIDs", () => {
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="@localpart:server:tld"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
|
||||
});
|
||||
|
||||
it.each([[InviteKind.Dm], [InviteKind.Invite]] as [typeof InviteKind.Dm | typeof InviteKind.Invite][])(
|
||||
"should lookup inputs which look like email addresses (%s)",
|
||||
async (kind: typeof InviteKind.Dm | typeof InviteKind.Invite) => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({
|
||||
address: aliceEmail,
|
||||
medium: "email",
|
||||
mxid: aliceId,
|
||||
});
|
||||
mockClient.getProfileInfo.mockResolvedValue({
|
||||
displayname: "Mrs Alice",
|
||||
avatar_url: "mxc://foo/bar",
|
||||
});
|
||||
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={kind}
|
||||
roomId={kind === InviteKind.Invite ? roomId : ""}
|
||||
onFinished={jest.fn()}
|
||||
initialText={aliceEmail}
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByText("Mrs Alice");
|
||||
// expect the email and MXID to be visible
|
||||
await screen.findByText(aliceId);
|
||||
await screen.findByText(aliceEmail);
|
||||
expect(mockClient.lookupThreePid).toHaveBeenCalledWith("email", aliceEmail, expect.anything());
|
||||
expect(mockClient.getProfileInfo).toHaveBeenCalledWith(aliceId);
|
||||
},
|
||||
);
|
||||
|
||||
it("should suggest e-mail even if lookup fails", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="foobar@email.com"
|
||||
/>,
|
||||
);
|
||||
|
||||
await screen.findByText("foobar@email.com");
|
||||
await screen.findByText("Invite by email");
|
||||
});
|
||||
|
||||
it("should add pasted values", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.paste(`${bobId} ${aliceEmail}`);
|
||||
|
||||
await screen.findAllByText(bobId);
|
||||
await screen.findByText(aliceEmail);
|
||||
expect(input).toHaveValue("");
|
||||
});
|
||||
it("should support pasting one username that is not a mx id or email", async () => {
|
||||
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
|
||||
mockClient.lookupThreePid.mockResolvedValue({});
|
||||
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.paste(`${bobbob}`);
|
||||
|
||||
await screen.findAllByText(bobId);
|
||||
expect(input).toHaveValue(`${bobbob}`);
|
||||
});
|
||||
|
||||
it("should allow to invite multiple emails to a room", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
await enterIntoSearchField(aliceEmail);
|
||||
expectPill(aliceEmail);
|
||||
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expectPill(bobEmail);
|
||||
});
|
||||
|
||||
describe("when encryption by default is disabled", () => {
|
||||
beforeEach(() => {
|
||||
mockClient.getClientWellKnown.mockReturnValue({
|
||||
"io.element.e2ee": {
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow to invite more than one email to a DM", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
|
||||
await enterIntoSearchField(aliceEmail);
|
||||
expectPill(aliceEmail);
|
||||
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expectPill(bobEmail);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not allow to invite more than one email to a DM", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
|
||||
// Start with an email → should convert to a pill
|
||||
await enterIntoSearchField(aliceEmail);
|
||||
expect(screen.getByText("Invites by email can only be sent one at a time")).toBeInTheDocument();
|
||||
expectPill(aliceEmail);
|
||||
|
||||
// Everything else from now on should not convert to a pill
|
||||
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expectNoPill(bobEmail);
|
||||
|
||||
await enterIntoSearchField(aliceId);
|
||||
expectNoPill(aliceId);
|
||||
|
||||
await pasteIntoSearchField(bobEmail);
|
||||
expectNoPill(bobEmail);
|
||||
});
|
||||
|
||||
it("should not allow to invite a MXID and an email to a DM", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
|
||||
// Start with a MXID → should convert to a pill
|
||||
await enterIntoSearchField(carolId);
|
||||
expect(screen.queryByText("Invites by email can only be sent one at a time")).not.toBeInTheDocument();
|
||||
expectPill(carolId);
|
||||
|
||||
// Add an email → should not convert to a pill
|
||||
await enterIntoSearchField(bobEmail);
|
||||
expect(screen.getByText("Invites by email can only be sent one at a time")).toBeInTheDocument();
|
||||
expectNoPill(bobEmail);
|
||||
});
|
||||
|
||||
it("should start a DM if the profile is available", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
await enterIntoSearchField(aliceId);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
||||
new DirectoryMember({
|
||||
user_id: aliceId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not allow pasting the same user multiple times", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.paste(`${bobId}`);
|
||||
await userEvent.paste(`${bobId}`);
|
||||
await userEvent.paste(`${bobId}`);
|
||||
|
||||
expect(input).toHaveValue("");
|
||||
await expect(screen.findAllByText(bobId, { selector: "a" })).resolves.toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should add to selection on click of user tile", async () => {
|
||||
render(<InviteDialog kind={InviteKind.Invite} roomId={roomId} onFinished={jest.fn()} />);
|
||||
|
||||
const input = screen.getByTestId("invite-dialog-input");
|
||||
input.focus();
|
||||
await userEvent.keyboard(`${aliceId}`);
|
||||
|
||||
const btn = await screen.findByText(aliceId, {
|
||||
selector: ".mx_InviteDialog_tile_nameStack_userId .mx_InviteDialog_tile--room_highlight",
|
||||
});
|
||||
fireEvent.click(btn);
|
||||
|
||||
const tile = await screen.findByText(aliceId, { selector: ".mx_InviteDialog_userTile_name" });
|
||||
expect(tile).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when inviting a user with an unknown profile", () => {
|
||||
beforeEach(async () => {
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
await enterIntoSearchField(carolId);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
||||
// Wait for the »invite anyway« modal to show up
|
||||
await screen.findByText("The following users may not exist");
|
||||
});
|
||||
|
||||
it("should not start the DM", () => {
|
||||
expect(startDmOnFirstMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show the »invite anyway« dialog if the profile is not available", () => {
|
||||
expect(screen.getByText("The following users may not exist")).toBeInTheDocument();
|
||||
expect(screen.getByText(`${carolId}: Profile not found`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when clicking »Start DM anyway«", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByRole("button", { name: "Start DM anyway" }));
|
||||
});
|
||||
|
||||
it("should start the DM", () => {
|
||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
||||
new DirectoryMember({
|
||||
user_id: carolId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when clicking »Close«", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(startDmOnFirstMessage).mockClear();
|
||||
await userEvent.click(screen.getByRole("button", { name: "Close" }));
|
||||
});
|
||||
|
||||
it("should not start the DM", () => {
|
||||
expect(startDmOnFirstMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when inviting a user with an unknown profile and »promptBeforeInviteUnknownUsers« setting = false", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(startDmOnFirstMessage).mockClear();
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName !== "promptBeforeInviteUnknownUsers",
|
||||
);
|
||||
render(<InviteDialog kind={InviteKind.Dm} onFinished={jest.fn()} />);
|
||||
await enterIntoSearchField(carolId);
|
||||
await userEvent.click(screen.getByRole("button", { name: "Go" }));
|
||||
// modal rendering has some weird sleeps - fake timers will mess up the entire test
|
||||
await sleep(100);
|
||||
});
|
||||
|
||||
it("should not show the »invite anyway« dialog", () => {
|
||||
expect(screen.queryByText("The following users may not exist")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should start the DM directly", () => {
|
||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockClient, [
|
||||
new DirectoryMember({
|
||||
user_id: carolId,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not suggest users from other server when room has m.federate=false", async () => {
|
||||
room.currentState.setStateEvents([mkRoomCreateEvent(bobId, roomId, { "m.federate": false })]);
|
||||
|
||||
render(
|
||||
<InviteDialog
|
||||
kind={InviteKind.Invite}
|
||||
roomId={roomId}
|
||||
onFinished={jest.fn()}
|
||||
initialText="@localpart:server.tld"
|
||||
/>,
|
||||
);
|
||||
await flushPromises();
|
||||
expect(screen.queryByText("@localpart:server.tld")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
|
||||
import { filterConsole, getMockClientWithEventEmitter, mockClientMethodsCrypto } from "../../../../test-utils";
|
||||
import LogoutDialog from "../../../../../src/components/views/dialogs/LogoutDialog";
|
||||
|
||||
describe("LogoutDialog", () => {
|
||||
let mockClient: MockedObject<MatrixClient>;
|
||||
let mockCrypto: MockedObject<CryptoApi>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsCrypto(),
|
||||
getKeyBackupVersion: jest.fn(),
|
||||
});
|
||||
|
||||
mockCrypto = mocked(mockClient.getCrypto()!);
|
||||
Object.assign(mockCrypto, {
|
||||
getActiveSessionBackupVersion: jest.fn().mockResolvedValue(null),
|
||||
});
|
||||
});
|
||||
|
||||
function renderComponent(props: Partial<React.ComponentProps<typeof LogoutDialog>> = {}): RenderResult {
|
||||
const onFinished = jest.fn();
|
||||
return render(<LogoutDialog onFinished={onFinished} {...props} />);
|
||||
}
|
||||
|
||||
it("shows a regular dialog when crypto is disabled", async () => {
|
||||
mocked(mockClient.getCrypto).mockReturnValue(undefined);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Are you sure you want to sign out?");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows a regular dialog if backups are working", async () => {
|
||||
mockCrypto.getActiveSessionBackupVersion.mockResolvedValue("1");
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Are you sure you want to sign out?");
|
||||
});
|
||||
|
||||
it("Prompts user to connect backup if there is a backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Connect this session to Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("Prompts user to set up backup if there is no backup on the server", async () => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue(null);
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Start using Key Backup");
|
||||
expect(rendered.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when there is an error fetching backups", () => {
|
||||
filterConsole("Unable to fetch key backup status");
|
||||
it("prompts user to set up backup", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
throw new Error("beep");
|
||||
});
|
||||
const rendered = renderComponent();
|
||||
await rendered.findByText("Start using Key Backup");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
|
||||
import ManageRestrictedJoinRuleDialog from "../../../../../src/components/views/dialogs/ManageRestrictedJoinRuleDialog";
|
||||
import SpaceStore from "../../../../../src/stores/spaces/SpaceStore";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
|
||||
describe("<ManageRestrictedJoinRuleDialog />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
getRoom: jest.fn(),
|
||||
});
|
||||
const room = new Room("!roomId:server", mockClient, userId);
|
||||
mockClient.getRoom.mockReturnValue(room);
|
||||
DMRoomMap.makeShared(mockClient);
|
||||
|
||||
const onFinished = jest.fn();
|
||||
const getComponent = (props = {}) =>
|
||||
render(<ManageRestrictedJoinRuleDialog room={room} onFinished={onFinished} {...props} />);
|
||||
|
||||
it("should render empty state", () => {
|
||||
expect(getComponent().asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should list spaces which are not parents of the room", () => {
|
||||
const space1 = new Room("!space:server", mockClient, userId);
|
||||
space1.name = "Other Space";
|
||||
jest.spyOn(SpaceStore.instance, "spacePanelSpaces", "get").mockReturnValue([space1]);
|
||||
|
||||
expect(getComponent().asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { Device, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { ManualDeviceKeyVerificationDialog } from "../../../../../src/components/views/dialogs/ManualDeviceKeyVerificationDialog";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
|
||||
describe("ManualDeviceKeyVerificationDialog", () => {
|
||||
let mockClient: MatrixClient;
|
||||
|
||||
function renderDialog(userId: string, device: Device, onLegacyFinished: (confirm: boolean) => void) {
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
<ManualDeviceKeyVerificationDialog userId={userId} device={device} onFinished={onLegacyFinished} />
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = stubClient();
|
||||
});
|
||||
|
||||
it("should display the device", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const { container } = renderDialog(mockClient.getUserId()!, device, jest.fn());
|
||||
|
||||
// Then
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display the device of another user", () => {
|
||||
// When
|
||||
const userId = "@alice:example.com";
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const { container } = renderDialog(userId, device, jest.fn());
|
||||
|
||||
// Then
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should call onFinished and matrixClient.setDeviceVerified", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(mockClient.getUserId()!, device, onFinished);
|
||||
|
||||
screen.getByRole("button", { name: "Verify session" }).click();
|
||||
|
||||
// Then
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
expect(mockClient.setDeviceVerified).toHaveBeenCalledWith(mockClient.getUserId(), deviceId, true);
|
||||
});
|
||||
|
||||
it("should call onFinished and not matrixClient.setDeviceVerified", () => {
|
||||
// When
|
||||
const deviceId = "XYZ";
|
||||
const device = new Device({
|
||||
userId: mockClient.getUserId()!,
|
||||
deviceId,
|
||||
displayName: "my device",
|
||||
algorithms: [],
|
||||
keys: new Map([[`ed25519:${deviceId}`, "ABCDEFGH"]]),
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(mockClient.getUserId()!, device, onFinished);
|
||||
|
||||
screen.getByRole("button", { name: "Cancel" }).click();
|
||||
|
||||
// Then
|
||||
expect(onFinished).toHaveBeenCalledWith(false);
|
||||
expect(mockClient.setDeviceVerified).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, RenderResult, waitForElementToBeRemoved } from "jest-matrix-react";
|
||||
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { flushPromises, mkMessage, stubClient } from "../../../../test-utils";
|
||||
import MessageEditHistoryDialog from "../../../../../src/components/views/dialogs/MessageEditHistoryDialog";
|
||||
|
||||
describe("<MessageEditHistory />", () => {
|
||||
const roomId = "!aroom:example.com";
|
||||
let client: jest.Mocked<MatrixClient>;
|
||||
let event: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient() as jest.Mocked<MatrixClient>;
|
||||
event = mkMessage({
|
||||
event: true,
|
||||
user: "@user:example.com",
|
||||
room: "!room:example.com",
|
||||
msg: "My Great Message",
|
||||
});
|
||||
});
|
||||
|
||||
async function renderComponent(): Promise<RenderResult> {
|
||||
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
|
||||
await waitForElementToBeRemoved(() => result.queryByRole("progressbar"));
|
||||
await flushPromises();
|
||||
return result;
|
||||
}
|
||||
|
||||
function mockEdits(...edits: { msg: string; ts?: number }[]) {
|
||||
client.relations.mockImplementation(() =>
|
||||
Promise.resolve({
|
||||
events: edits.map(
|
||||
(e) =>
|
||||
new MatrixEvent({
|
||||
type: EventType.RoomMessage,
|
||||
room_id: roomId,
|
||||
origin_server_ts: e.ts ?? 0,
|
||||
content: {
|
||||
body: e.msg,
|
||||
},
|
||||
}),
|
||||
),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
it("should match the snapshot", async () => {
|
||||
mockEdits({ msg: "My Great Massage", ts: 1234 });
|
||||
|
||||
const { container } = await renderComponent();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should support events with", async () => {
|
||||
mockEdits(
|
||||
{ msg: "My Great Massage", ts: undefined },
|
||||
{ msg: "My Great Massage?", ts: undefined },
|
||||
{ msg: "My Great Missage", ts: undefined },
|
||||
);
|
||||
|
||||
const { container } = await renderComponent();
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import {
|
||||
EventTimeline,
|
||||
EventType,
|
||||
JoinRule,
|
||||
MatrixEvent,
|
||||
Room,
|
||||
RoomStateEvent,
|
||||
Visibility,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../test-utils";
|
||||
import RoomSettingsDialog from "../../../../../src/components/views/dialogs/RoomSettingsDialog";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
|
||||
describe("<RoomSettingsDialog />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
isRoomEncrypted: jest.fn().mockReturnValue(false),
|
||||
getRoom: jest.fn(),
|
||||
getDomain: jest.fn().mockReturnValue("server.org"),
|
||||
getLocalAliases: jest.fn().mockResolvedValue({ aliases: [] }),
|
||||
getRoomDirectoryVisibility: jest.fn().mockResolvedValue({ visibility: Visibility.Private }),
|
||||
getOrCreateFilter: jest.fn(),
|
||||
});
|
||||
|
||||
const roomId = "!room:server.org";
|
||||
const room = new Room(roomId, mockClient, userId);
|
||||
room.name = "Test Room";
|
||||
const room2 = new Room("!room2:server.org", mockClient, userId);
|
||||
room2.name = "Another Room";
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockClient.getRoom.mockImplementation((roomId) => {
|
||||
if (roomId === room.roomId) return room;
|
||||
if (roomId === room2.roomId) return room2;
|
||||
return null;
|
||||
});
|
||||
|
||||
jest.spyOn(SettingsStore, "getValue").mockReset().mockReturnValue(false);
|
||||
|
||||
const dmRoomMap = {
|
||||
getUserIdForRoomId: jest.fn(),
|
||||
} as unknown as DMRoomMap;
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue(dmRoomMap);
|
||||
});
|
||||
|
||||
const getComponent = (onFinished = jest.fn(), propRoomId = roomId) =>
|
||||
render(<RoomSettingsDialog roomId={propRoomId} onFinished={onFinished} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
});
|
||||
|
||||
it("catches errors when room is not found", () => {
|
||||
getComponent(undefined, "!room-that-does-not-exist");
|
||||
expect(screen.getByText("Something went wrong!")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("updates when roomId prop changes", () => {
|
||||
const { rerender, getByText } = getComponent(undefined, roomId);
|
||||
|
||||
expect(getByText(`Room Settings - ${room.name}`)).toBeInTheDocument();
|
||||
|
||||
rerender(<RoomSettingsDialog roomId={room2.roomId} onFinished={jest.fn()} />);
|
||||
|
||||
expect(getByText(`Room Settings - ${room2.name}`)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("Settings tabs", () => {
|
||||
it("renders default tabs correctly", () => {
|
||||
const { container } = getComponent();
|
||||
expect(container.querySelectorAll(".mx_TabbedView_tabLabel")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("people settings tab", () => {
|
||||
it("does not render when disabled and room join rule is not knock", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
|
||||
getComponent();
|
||||
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render when disabled and room join rule is knock", () => {
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
getComponent();
|
||||
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render when enabled and room join rule is not knock", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(setting) => setting === "feature_ask_to_join",
|
||||
);
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
|
||||
getComponent();
|
||||
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders when enabled and room join rule is knock", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(setting) => setting === "feature_ask_to_join",
|
||||
);
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
getComponent();
|
||||
expect(screen.getByTestId("settings-tab-ROOM_PEOPLE_TAB")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("re-renders on room join rule changes", async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(setting) => setting === "feature_ask_to_join",
|
||||
);
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Knock);
|
||||
getComponent();
|
||||
jest.spyOn(room, "getJoinRule").mockReturnValue(JoinRule.Invite);
|
||||
mockClient.emit(
|
||||
RoomStateEvent.Events,
|
||||
new MatrixEvent({ content: {}, type: EventType.RoomJoinRules }),
|
||||
room.getLiveTimeline().getState(EventTimeline.FORWARDS)!,
|
||||
null,
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByTestId("settings-tab-ROOM_PEOPLE_TAB")).not.toBeInTheDocument(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("renders voip settings tab when enabled", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_group_calls",
|
||||
);
|
||||
getComponent();
|
||||
expect(screen.getByTestId("settings-tab-ROOM_VOIP_TAB")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders bridges settings tab when enabled", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === "feature_bridge_state",
|
||||
);
|
||||
getComponent();
|
||||
expect(screen.getByTestId("settings-tab-ROOM_BRIDGES_TAB")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders advanced settings tab when enabled", () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName) => settingName === UIFeature.AdvancedSettings,
|
||||
);
|
||||
getComponent();
|
||||
expect(screen.getByTestId("settings-tab-ROOM_ADVANCED_TAB")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("poll history", () => {
|
||||
beforeEach(() => {
|
||||
mockClient.getOrCreateFilter.mockResolvedValue("filterId");
|
||||
});
|
||||
it("renders poll history tab", () => {
|
||||
getComponent();
|
||||
expect(screen.getByTestId("settings-tab-ROOM_POLL_HISTORY_TAB")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays poll history when tab clicked", () => {
|
||||
const { container } = getComponent();
|
||||
|
||||
fireEvent.click(screen.getByText("Polls"));
|
||||
|
||||
expect(container.querySelector(".mx_SettingsTab")).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import ServerPickerDialog from "../../../../../src/components/views/dialogs/ServerPickerDialog";
|
||||
import SdkConfig from "../../../../../src/SdkConfig";
|
||||
import { flushPromises } from "../../../../test-utils";
|
||||
import { ValidatedServerConfig } from "../../../../../src/utils/ValidatedServerConfig";
|
||||
|
||||
/** The matrix versions our mock server claims to support */
|
||||
const SERVER_SUPPORTED_MATRIX_VERSIONS = ["v1.1", "v1.5", "v1.6", "v1.8", "v1.9"];
|
||||
|
||||
describe("<ServerPickerDialog />", () => {
|
||||
const defaultServerConfig = {
|
||||
hsUrl: "https://matrix.org",
|
||||
hsName: "matrix.org",
|
||||
hsNameIsDifferent: true,
|
||||
isUrl: "https://is.org",
|
||||
isDefault: true,
|
||||
isNameResolvable: true,
|
||||
warning: "",
|
||||
};
|
||||
const wkHsUrl = "https://hsbaseurlfrom.wk";
|
||||
const wkIsUrl = "https://isbaseurlfrom.wk";
|
||||
const validWellKnown = {
|
||||
"m.homeserver": {
|
||||
base_url: wkHsUrl,
|
||||
},
|
||||
"m.identity_server": {
|
||||
base_url: wkIsUrl,
|
||||
},
|
||||
};
|
||||
const defaultProps = {
|
||||
serverConfig: defaultServerConfig,
|
||||
onFinished: jest.fn(),
|
||||
};
|
||||
const getComponent = (
|
||||
props: Partial<{
|
||||
onFinished: any;
|
||||
serverConfig: ValidatedServerConfig;
|
||||
}> = {},
|
||||
) => render(<ServerPickerDialog {...defaultProps} {...props} />);
|
||||
|
||||
beforeEach(() => {
|
||||
SdkConfig.add({
|
||||
validated_server_config: defaultServerConfig,
|
||||
});
|
||||
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.catch({
|
||||
status: 404,
|
||||
body: '{"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}',
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
});
|
||||
|
||||
it("should render dialog", () => {
|
||||
const { container } = getComponent();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// checkbox and text input have the same aria-label
|
||||
const getOtherHomeserverCheckBox = () =>
|
||||
screen.getAllByLabelText("Other homeserver").find((node) => node.getAttribute("type") === "radio")!;
|
||||
const getOtherHomeserverInput = () =>
|
||||
screen.getAllByLabelText("Other homeserver").find((node) => node.getAttribute("type") === "text")!;
|
||||
|
||||
describe("when default server config is selected", () => {
|
||||
it("should select other homeserver field on open", () => {
|
||||
getComponent();
|
||||
expect(getOtherHomeserverCheckBox()).toBeChecked();
|
||||
// empty field
|
||||
expect(getOtherHomeserverInput()).toHaveDisplayValue("");
|
||||
});
|
||||
|
||||
it("should display an error when trying to continue with an empty homeserver field", async () => {
|
||||
const onFinished = jest.fn();
|
||||
const { container } = getComponent({ onFinished });
|
||||
|
||||
fireEvent.click(screen.getByText("Continue"));
|
||||
|
||||
await flushPromises();
|
||||
|
||||
// error on field
|
||||
expect(container.querySelector(".mx_ServerPickerDialog_otherHomeserver.mx_Field_invalid")).toBeTruthy();
|
||||
|
||||
// didn't close dialog
|
||||
expect(onFinished).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should close when selecting default homeserver and clicking continue", async () => {
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
|
||||
fireEvent.click(screen.getByTestId("defaultHomeserver"));
|
||||
expect(screen.getByTestId("defaultHomeserver")).toBeChecked();
|
||||
|
||||
fireEvent.click(screen.getByText("Continue"));
|
||||
|
||||
// closed dialog with default server
|
||||
expect(onFinished).toHaveBeenCalledWith(defaultServerConfig);
|
||||
});
|
||||
|
||||
it("should allow user to revert from a custom server to the default", async () => {
|
||||
fetchMock.get(`https://custom.org/_matrix/client/versions`, {
|
||||
unstable_features: {},
|
||||
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||
});
|
||||
|
||||
const onFinished = jest.fn();
|
||||
const serverConfig = {
|
||||
hsUrl: "https://custom.org",
|
||||
hsName: "custom.org",
|
||||
hsNameIsDifferent: true,
|
||||
isUrl: "https://is.org",
|
||||
isDefault: false,
|
||||
isNameResolvable: true,
|
||||
warning: "",
|
||||
};
|
||||
getComponent({ onFinished, serverConfig });
|
||||
|
||||
fireEvent.click(screen.getByTestId("defaultHomeserver"));
|
||||
expect(screen.getByTestId("defaultHomeserver")).toBeChecked();
|
||||
|
||||
fireEvent.click(screen.getByText("Continue"));
|
||||
await flushPromises();
|
||||
|
||||
// closed dialog with default server and nothing else
|
||||
expect(onFinished).toHaveBeenCalledWith(defaultServerConfig);
|
||||
expect(onFinished).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should submit successfully with a valid custom homeserver", async () => {
|
||||
const homeserver = "https://myhomeserver.site";
|
||||
fetchMock.get(`${homeserver}/_matrix/client/versions`, {
|
||||
unstable_features: {},
|
||||
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
|
||||
fireEvent.change(getOtherHomeserverInput(), { target: { value: homeserver } });
|
||||
expect(getOtherHomeserverInput()).toHaveDisplayValue(homeserver);
|
||||
|
||||
fireEvent.click(screen.getByText("Continue"));
|
||||
|
||||
// validation on submit is async
|
||||
await flushPromises();
|
||||
|
||||
// closed dialog with validated custom server
|
||||
expect(onFinished).toHaveBeenCalledWith({
|
||||
hsName: "myhomeserver.site",
|
||||
hsUrl: homeserver,
|
||||
hsNameIsDifferent: false,
|
||||
warning: null,
|
||||
isDefault: false,
|
||||
isNameResolvable: false,
|
||||
isUrl: defaultServerConfig.isUrl,
|
||||
});
|
||||
});
|
||||
|
||||
describe("validates custom homeserver", () => {
|
||||
it("should lookup .well-known for homeserver without protocol", async () => {
|
||||
const homeserver = "myhomeserver1.site";
|
||||
const wellKnownUrl = `https://${homeserver}/.well-known/matrix/client`;
|
||||
fetchMock.get(wellKnownUrl, {});
|
||||
getComponent();
|
||||
|
||||
fireEvent.change(getOtherHomeserverInput(), { target: { value: homeserver } });
|
||||
expect(getOtherHomeserverInput()).toHaveDisplayValue(homeserver);
|
||||
// trigger validation
|
||||
fireEvent.blur(getOtherHomeserverInput());
|
||||
|
||||
// validation on submit is async
|
||||
await flushPromises();
|
||||
|
||||
expect(fetchMock).toHaveFetched(wellKnownUrl);
|
||||
});
|
||||
|
||||
it("should submit using validated config from a valid .well-known", async () => {
|
||||
const homeserver = "myhomeserver2.site";
|
||||
const wellKnownUrl = `https://${homeserver}/.well-known/matrix/client`;
|
||||
|
||||
// urls from homeserver well-known
|
||||
const versionsUrl = `${wkHsUrl}/_matrix/client/versions`;
|
||||
const isWellKnownUrl = `${wkIsUrl}/_matrix/identity/v2`;
|
||||
|
||||
fetchMock.getOnce(wellKnownUrl, validWellKnown);
|
||||
fetchMock.getOnce(versionsUrl, {
|
||||
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||
});
|
||||
fetchMock.getOnce(isWellKnownUrl, {});
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
|
||||
fireEvent.change(getOtherHomeserverInput(), { target: { value: homeserver } });
|
||||
fireEvent.click(screen.getByText("Continue"));
|
||||
|
||||
// validation on submit is async
|
||||
await flushPromises();
|
||||
|
||||
expect(fetchMock).toHaveFetched(wellKnownUrl);
|
||||
// fetched using urls from .well-known
|
||||
expect(fetchMock).toHaveFetched(versionsUrl);
|
||||
expect(fetchMock).toHaveFetched(isWellKnownUrl);
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith({
|
||||
hsName: homeserver,
|
||||
hsUrl: wkHsUrl,
|
||||
hsNameIsDifferent: true,
|
||||
warning: null,
|
||||
isDefault: false,
|
||||
isNameResolvable: true,
|
||||
isUrl: wkIsUrl,
|
||||
});
|
||||
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
it("should fall back to static config when well-known lookup fails", async () => {
|
||||
const homeserver = "myhomeserver3.site";
|
||||
// this server returns 404 for well-known
|
||||
const wellKnownUrl = `https://${homeserver}/.well-known/matrix/client`;
|
||||
fetchMock.get(wellKnownUrl, { status: 404 });
|
||||
// but is otherwise live (happy versions response)
|
||||
fetchMock.get(`https://${homeserver}/_matrix/client/versions`, {
|
||||
versions: SERVER_SUPPORTED_MATRIX_VERSIONS,
|
||||
});
|
||||
const onFinished = jest.fn();
|
||||
getComponent({ onFinished });
|
||||
|
||||
fireEvent.change(getOtherHomeserverInput(), { target: { value: homeserver } });
|
||||
fireEvent.click(screen.getByText("Continue"));
|
||||
|
||||
// validation on submit is async
|
||||
await flushPromises();
|
||||
|
||||
expect(fetchMock).toHaveFetched(wellKnownUrl);
|
||||
expect(fetchMock).toHaveFetched(`https://${homeserver}/_matrix/client/versions`);
|
||||
|
||||
expect(onFinished).toHaveBeenCalledWith({
|
||||
hsName: homeserver,
|
||||
hsUrl: "https://" + homeserver,
|
||||
hsNameIsDifferent: false,
|
||||
warning: null,
|
||||
isDefault: false,
|
||||
isNameResolvable: false,
|
||||
isUrl: defaultServerConfig.isUrl,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
117
test/unit-tests/components/views/dialogs/ShareDialog-test.tsx
Normal file
117
test/unit-tests/components/views/dialogs/ShareDialog-test.tsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { EventTimeline, MatrixEvent, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderOptions } from "jest-matrix-react";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { _t } from "../../../../../src/languageHandler";
|
||||
import ShareDialog from "../../../../../src/components/views/dialogs/ShareDialog";
|
||||
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
jest.mock("../../../../../src/utils/ShieldUtils");
|
||||
|
||||
function getWrapper(): RenderOptions {
|
||||
return {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={MatrixClientPeg.safeGet()}>{children}</MatrixClientContext.Provider>
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
describe("ShareDialog", () => {
|
||||
let room: Room;
|
||||
|
||||
const ROOM_ID = "!1:example.org";
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
room = new Room(ROOM_ID, MatrixClientPeg.get()!, "@alice:example.org");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders room share dialog", () => {
|
||||
const { container: withoutEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withoutEvents).toHaveTextContent(_t("share|title_room"));
|
||||
|
||||
jest.spyOn(room, "getLiveTimeline").mockReturnValue({ getEvents: () => [{} as MatrixEvent] } as EventTimeline);
|
||||
const { container: withEvents } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
expect(withEvents).toHaveTextContent(_t("share|permalink_most_recent"));
|
||||
});
|
||||
|
||||
it("renders user share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={room.getJoinedMembers()[0]} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_user"));
|
||||
});
|
||||
|
||||
it("renders link share dialog", () => {
|
||||
mockRoomMembers(room, 1);
|
||||
const { container } = render(
|
||||
<ShareDialog target={new URL("https://matrix.org")} onFinished={jest.fn()} />,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent(_t("share|title_link"));
|
||||
});
|
||||
|
||||
it("renders the QR code if configured", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareQRCode) return true;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_qrcode_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
});
|
||||
|
||||
it("renders the social button if configured", () => {
|
||||
const originalGetValue = SettingsStore.getValue;
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((feature) => {
|
||||
if (feature === UIFeature.ShareSocial) return true;
|
||||
return originalGetValue(feature);
|
||||
});
|
||||
const { container } = render(<ShareDialog target={room} onFinished={jest.fn()} />, getWrapper());
|
||||
const qrCodesVisible = container.getElementsByClassName("mx_ShareDialog_social_container").length > 0;
|
||||
expect(qrCodesVisible).toBe(true);
|
||||
});
|
||||
it("renders custom title and subtitle", () => {
|
||||
const { container } = render(
|
||||
<ShareDialog
|
||||
target={room}
|
||||
customTitle="test_title_123"
|
||||
subtitle="custom_subtitle_1234"
|
||||
onFinished={jest.fn()}
|
||||
/>,
|
||||
getWrapper(),
|
||||
);
|
||||
expect(container).toHaveTextContent("test_title_123");
|
||||
expect(container).toHaveTextContent("custom_subtitle_1234");
|
||||
});
|
||||
});
|
||||
/**
|
||||
*
|
||||
* @param count the number of users to create
|
||||
*/
|
||||
function mockRoomMembers(room: Room, count: number) {
|
||||
const members = Array(count)
|
||||
.fill(0)
|
||||
.map((_, index) => new RoomMember(room.roomId, "@alice:example.org"));
|
||||
|
||||
room.currentState.setJoinedMemberCount(members.length);
|
||||
room.getJoinedMembers = jest.fn().mockReturnValue(members);
|
||||
}
|
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
import {
|
||||
ConnectionError,
|
||||
IProtocol,
|
||||
IPublicRoomsChunkRoom,
|
||||
JoinRule,
|
||||
MatrixClient,
|
||||
Room,
|
||||
RoomMember,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
import { fireEvent, render, screen } from "jest-matrix-react";
|
||||
|
||||
import SpotlightDialog from "../../../../../src/components/views/dialogs/spotlight/SpotlightDialog";
|
||||
import { Filter } from "../../../../../src/components/views/dialogs/spotlight/Filter";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../../src/models/LocalRoom";
|
||||
import { DirectoryMember, startDmOnFirstMessage } from "../../../../../src/utils/direct-messages";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { flushPromisesWithFakeTimers, mkRoom, stubClient } from "../../../../test-utils";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import SdkConfig from "../../../../../src/SdkConfig";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
|
||||
jest.useFakeTimers();
|
||||
|
||||
jest.mock("../../../../../src/utils/Feedback");
|
||||
|
||||
jest.mock("../../../../../src/utils/direct-messages", () => ({
|
||||
// @ts-ignore
|
||||
...jest.requireActual("../../../../../src/utils/direct-messages"),
|
||||
startDmOnFirstMessage: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher", () => ({
|
||||
register: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
}));
|
||||
|
||||
interface IUserChunkMember {
|
||||
user_id: string;
|
||||
display_name?: string;
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
interface MockClientOptions {
|
||||
userId?: string;
|
||||
homeserver?: string;
|
||||
thirdPartyProtocols?: Record<string, IProtocol>;
|
||||
rooms?: IPublicRoomsChunkRoom[];
|
||||
members?: RoomMember[];
|
||||
users?: IUserChunkMember[];
|
||||
}
|
||||
|
||||
function mockClient({
|
||||
userId = "testuser",
|
||||
homeserver = "example.tld",
|
||||
thirdPartyProtocols = {},
|
||||
rooms = [],
|
||||
members = [],
|
||||
users = [],
|
||||
}: MockClientOptions = {}): MatrixClient {
|
||||
stubClient();
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
cli.getUserId = jest.fn(() => userId);
|
||||
cli.getDomain = jest.fn(() => homeserver);
|
||||
cli.getHomeserverUrl = jest.fn(() => homeserver);
|
||||
cli.getThirdpartyProtocols = jest.fn(() => Promise.resolve(thirdPartyProtocols));
|
||||
cli.publicRooms = jest.fn((options) => {
|
||||
const searchTerm = options?.filter?.generic_search_term?.toLowerCase();
|
||||
const chunk = rooms.filter(
|
||||
(it) =>
|
||||
!searchTerm ||
|
||||
it.room_id.toLowerCase().includes(searchTerm) ||
|
||||
it.name?.toLowerCase().includes(searchTerm) ||
|
||||
sanitizeHtml(it?.topic || "", { allowedTags: [] })
|
||||
.toLowerCase()
|
||||
.includes(searchTerm) ||
|
||||
it.canonical_alias?.toLowerCase().includes(searchTerm) ||
|
||||
it.aliases?.find((alias) => alias.toLowerCase().includes(searchTerm)),
|
||||
);
|
||||
return Promise.resolve({
|
||||
chunk,
|
||||
total_room_count_estimate: chunk.length,
|
||||
});
|
||||
});
|
||||
cli.searchUserDirectory = jest.fn(({ term, limit }) => {
|
||||
const searchTerm = term?.toLowerCase();
|
||||
const results = users.filter(
|
||||
(it) =>
|
||||
!searchTerm ||
|
||||
it.user_id.toLowerCase().includes(searchTerm) ||
|
||||
it.display_name?.toLowerCase().includes(searchTerm),
|
||||
);
|
||||
return Promise.resolve({
|
||||
results: results.slice(0, limit ?? +Infinity),
|
||||
limited: !!limit && limit < results.length,
|
||||
});
|
||||
});
|
||||
cli.getProfileInfo = jest.fn(async (userId) => {
|
||||
const member = members.find((it) => it.userId === userId);
|
||||
if (member) {
|
||||
return Promise.resolve({
|
||||
displayname: member.rawDisplayName,
|
||||
avatar_url: member.getMxcAvatarUrl(),
|
||||
});
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
});
|
||||
return cli;
|
||||
}
|
||||
|
||||
describe("Spotlight Dialog", () => {
|
||||
const testPerson: IUserChunkMember = {
|
||||
user_id: "@janedoe:matrix.org",
|
||||
display_name: "Jane Doe",
|
||||
avatar_url: undefined,
|
||||
};
|
||||
|
||||
const testPublicRoom: IPublicRoomsChunkRoom = {
|
||||
room_id: "!room247:matrix.org",
|
||||
name: "Room #247",
|
||||
topic: "We hope you'll have a <b>shining</b> experience!",
|
||||
world_readable: false,
|
||||
num_joined_members: 1,
|
||||
guest_can_join: false,
|
||||
};
|
||||
|
||||
const testDMRoomId = "!testDM:example.com";
|
||||
const testDMUserId = "@alice:matrix.org";
|
||||
|
||||
let testRoom: Room;
|
||||
let testDM: Room;
|
||||
let testLocalRoom: LocalRoom;
|
||||
|
||||
let mockedClient: MatrixClient;
|
||||
|
||||
beforeEach(() => {
|
||||
mockedClient = mockClient({ rooms: [testPublicRoom], users: [testPerson] });
|
||||
testRoom = mkRoom(mockedClient, "!test23:example.com");
|
||||
mocked(testRoom.getMyMembership).mockReturnValue(KnownMembership.Join);
|
||||
testLocalRoom = new LocalRoom(LOCAL_ROOM_ID_PREFIX + "test23", mockedClient, mockedClient.getUserId()!);
|
||||
testLocalRoom.updateMyMembership(KnownMembership.Join);
|
||||
mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom]);
|
||||
|
||||
jest.spyOn(DMRoomMap, "shared").mockReturnValue({
|
||||
getUserIdForRoomId: jest.fn(),
|
||||
} as unknown as DMRoomMap);
|
||||
|
||||
testDM = mkRoom(mockedClient, testDMRoomId);
|
||||
testDM.name = "Chat with Alice";
|
||||
mocked(testDM.getMyMembership).mockReturnValue(KnownMembership.Join);
|
||||
|
||||
mocked(DMRoomMap.shared().getUserIdForRoomId).mockImplementation((roomId: string) => {
|
||||
if (roomId === testDMRoomId) {
|
||||
return testDMUserId;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
mocked(mockedClient.getVisibleRooms).mockReturnValue([testRoom, testLocalRoom, testDM]);
|
||||
});
|
||||
|
||||
describe("should apply filters supplied via props", () => {
|
||||
it("without filter", async () => {
|
||||
render(<SpotlightDialog onFinished={() => null} />);
|
||||
|
||||
const filterChip = document.querySelector("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("with public room filter", async () => {
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
|
||||
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const filterChip = document.querySelector("div.mx_SpotlightDialog_filter")!;
|
||||
expect(filterChip).toBeInTheDocument();
|
||||
expect(filterChip.innerHTML).toContain("Public rooms");
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBe(1);
|
||||
expect(options[0].innerHTML).toContain(testPublicRoom.name);
|
||||
});
|
||||
|
||||
it("with people filter", async () => {
|
||||
render(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.People}
|
||||
initialText={testPerson.display_name}
|
||||
onFinished={() => null}
|
||||
/>,
|
||||
);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const filterChip = document.querySelector("div.mx_SpotlightDialog_filter")!;
|
||||
expect(filterChip).toBeInTheDocument();
|
||||
expect(filterChip.innerHTML).toContain("People");
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(1);
|
||||
expect(options[0]!.innerHTML).toContain(testPerson.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when MSC3946 dynamic room predecessors is enabled", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, excludeDefault) => {
|
||||
if (settingName === "feature_dynamic_room_predecessors") {
|
||||
return true;
|
||||
} else {
|
||||
return []; // SpotlightSearch.recentSearches
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should call getVisibleRooms with MSC3946 dynamic room predecessors", async () => {
|
||||
render(<SpotlightDialog onFinished={() => null} />);
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
expect(mockedClient.getVisibleRooms).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("should apply manually selected filter", () => {
|
||||
it("with public rooms", async () => {
|
||||
render(<SpotlightDialog onFinished={() => null} />);
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(screen.getByText("Public rooms"));
|
||||
// wrapper.find("#mx_SpotlightDialog_button_explorePublicRooms").first().simulate("click");
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const filterChip = document.querySelector("div.mx_SpotlightDialog_filter")!;
|
||||
expect(filterChip).toBeInTheDocument();
|
||||
expect(filterChip.innerHTML).toContain("Public rooms");
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBe(1);
|
||||
expect(options[0]!.innerHTML).toContain(testPublicRoom.name);
|
||||
|
||||
// assert that getVisibleRooms is called without MSC3946 dynamic room predecessors
|
||||
expect(mockedClient.getVisibleRooms).toHaveBeenCalledWith(false);
|
||||
});
|
||||
it("with people", async () => {
|
||||
render(<SpotlightDialog initialText={testPerson.display_name} onFinished={() => null} />);
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(screen.getByText("People"));
|
||||
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const filterChip = document.querySelector("div.mx_SpotlightDialog_filter")!;
|
||||
expect(filterChip).toBeInTheDocument();
|
||||
expect(filterChip.innerHTML).toContain("People");
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(1);
|
||||
expect(options[0]!.innerHTML).toContain(testPerson.display_name);
|
||||
});
|
||||
});
|
||||
|
||||
describe("should allow clearing filter manually", () => {
|
||||
it("with public room filter", async () => {
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
let filterChip = document.querySelector("div.mx_SpotlightDialog_filter")!;
|
||||
expect(filterChip).toBeInTheDocument();
|
||||
expect(filterChip.innerHTML).toContain("Public rooms");
|
||||
|
||||
fireEvent.click(filterChip.querySelector("div.mx_SpotlightDialog_filter--close")!);
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
filterChip = document.querySelector("div.mx_SpotlightDialog_filter")!;
|
||||
expect(filterChip).not.toBeInTheDocument();
|
||||
});
|
||||
it("with people filter", async () => {
|
||||
render(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.People}
|
||||
initialText={testPerson.display_name}
|
||||
onFinished={() => null}
|
||||
/>,
|
||||
);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
let filterChip = document.querySelector("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip).toBeInTheDocument();
|
||||
expect(filterChip!.innerHTML).toContain("People");
|
||||
|
||||
fireEvent.click(filterChip!.querySelector("div.mx_SpotlightDialog_filter--close")!);
|
||||
jest.advanceTimersByTime(1);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
filterChip = document.querySelector("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("searching for rooms", () => {
|
||||
let options: NodeListOf<Element>;
|
||||
|
||||
beforeAll(async () => {
|
||||
render(<SpotlightDialog initialText="test23" onFinished={() => null} />);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
});
|
||||
|
||||
it("should find Rooms", () => {
|
||||
expect(options).toHaveLength(4);
|
||||
expect(options[0]!.innerHTML).toContain(testRoom.name);
|
||||
});
|
||||
|
||||
it("should not find LocalRooms", () => {
|
||||
expect(options).toHaveLength(4);
|
||||
expect(options[0]!.innerHTML).not.toContain(testLocalRoom.name);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not filter out users sent by the server", async () => {
|
||||
mocked(mockedClient.searchUserDirectory).mockResolvedValue({
|
||||
results: [
|
||||
{ user_id: "@user1:server", display_name: "User Alpha", avatar_url: "mxc://1/avatar" },
|
||||
{ user_id: "@user2:server", display_name: "User Beta", avatar_url: "mxc://2/avatar" },
|
||||
],
|
||||
limited: false,
|
||||
});
|
||||
|
||||
render(<SpotlightDialog initialFilter={Filter.People} initialText="Alpha" onFinished={() => null} />);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(2);
|
||||
expect(options[0]).toHaveTextContent("User Alpha");
|
||||
expect(options[1]).toHaveTextContent("User Beta");
|
||||
});
|
||||
|
||||
it("should not filter out users sent by the server even if a local suggestion gets filtered out", async () => {
|
||||
const member = new RoomMember(testRoom.roomId, testPerson.user_id);
|
||||
member.name = member.rawDisplayName = testPerson.display_name!;
|
||||
member.getMxcAvatarUrl = jest.fn().mockReturnValue("mxc://0/avatar");
|
||||
mocked(testRoom.getJoinedMembers).mockReturnValue([member]);
|
||||
mocked(mockedClient.searchUserDirectory).mockResolvedValue({
|
||||
results: [
|
||||
{ user_id: "@janedoe:matrix.org", display_name: "User Alpha", avatar_url: "mxc://1/avatar" },
|
||||
{ user_id: "@johndoe:matrix.org", display_name: "User Beta", avatar_url: "mxc://2/avatar" },
|
||||
],
|
||||
limited: false,
|
||||
});
|
||||
|
||||
render(<SpotlightDialog initialFilter={Filter.People} initialText="Beta" onFinished={() => null} />);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(2);
|
||||
expect(options[0]).toHaveTextContent(testPerson.display_name!);
|
||||
expect(options[1]).toHaveTextContent("User Beta");
|
||||
});
|
||||
|
||||
it("show non-matching query members with DMs if they are present in the server search results", async () => {
|
||||
mocked(mockedClient.searchUserDirectory).mockResolvedValue({
|
||||
results: [
|
||||
{ user_id: testDMUserId, display_name: "Alice Wonder", avatar_url: "mxc://1/avatar" },
|
||||
{ user_id: "@bob:matrix.org", display_name: "Bob Wonder", avatar_url: "mxc://2/avatar" },
|
||||
],
|
||||
limited: false,
|
||||
});
|
||||
render(
|
||||
<SpotlightDialog initialFilter={Filter.People} initialText="Something Wonder" onFinished={() => null} />,
|
||||
);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(2);
|
||||
expect(options[0]).toHaveTextContent(testDMUserId);
|
||||
expect(options[1]).toHaveTextContent("Bob Wonder");
|
||||
});
|
||||
|
||||
it("don't sort the order of users sent by the server", async () => {
|
||||
const serverList = [
|
||||
{ user_id: "@user2:server", display_name: "User Beta", avatar_url: "mxc://2/avatar" },
|
||||
{ user_id: "@user1:server", display_name: "User Alpha", avatar_url: "mxc://1/avatar" },
|
||||
];
|
||||
mocked(mockedClient.searchUserDirectory).mockResolvedValue({
|
||||
results: serverList,
|
||||
limited: false,
|
||||
});
|
||||
|
||||
render(<SpotlightDialog initialFilter={Filter.People} initialText="User" onFinished={() => null} />);
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(2);
|
||||
expect(options[0]).toHaveTextContent("User Beta");
|
||||
expect(options[1]).toHaveTextContent("User Alpha");
|
||||
});
|
||||
|
||||
it("should start a DM when clicking a person", async () => {
|
||||
render(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.People}
|
||||
initialText={testPerson.display_name}
|
||||
onFinished={() => null}
|
||||
/>,
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const options = document.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(1);
|
||||
expect(options[0]!.innerHTML).toContain(testPerson.display_name);
|
||||
|
||||
fireEvent.click(options[0]!);
|
||||
expect(startDmOnFirstMessage).toHaveBeenCalledWith(mockedClient, [new DirectoryMember(testPerson)]);
|
||||
});
|
||||
|
||||
it("should pass via of the server being explored when joining room from directory", async () => {
|
||||
SdkConfig.put({
|
||||
room_directory: {
|
||||
servers: ["example.tld"],
|
||||
},
|
||||
});
|
||||
localStorage.setItem("mx_last_room_directory_server", "example.tld");
|
||||
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
const content = document.querySelector("#mx_SpotlightDialog_content")!;
|
||||
const options = content.querySelectorAll("li.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBe(1);
|
||||
expect(options[0].innerHTML).toContain(testPublicRoom.name);
|
||||
|
||||
fireEvent.click(options[0].querySelector("[role='button']")!);
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "view_room",
|
||||
room_id: testPublicRoom.room_id,
|
||||
via_servers: ["example.tld"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe("nsfw public rooms filter", () => {
|
||||
const nsfwNameRoom: IPublicRoomsChunkRoom = {
|
||||
room_id: "@room1:matrix.org",
|
||||
name: "Room 1 [NSFW]",
|
||||
topic: undefined,
|
||||
world_readable: false,
|
||||
num_joined_members: 1,
|
||||
guest_can_join: false,
|
||||
};
|
||||
|
||||
const nsfwTopicRoom: IPublicRoomsChunkRoom = {
|
||||
room_id: "@room2:matrix.org",
|
||||
name: "Room 2",
|
||||
topic: "A room with a topic that includes nsfw",
|
||||
world_readable: false,
|
||||
num_joined_members: 1,
|
||||
guest_can_join: false,
|
||||
};
|
||||
|
||||
const potatoRoom: IPublicRoomsChunkRoom = {
|
||||
room_id: "@room3:matrix.org",
|
||||
name: "Potato Room 3",
|
||||
topic: "Room where we discuss potatoes",
|
||||
world_readable: false,
|
||||
num_joined_members: 1,
|
||||
guest_can_join: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockedClient = mockClient({ rooms: [nsfwNameRoom, nsfwTopicRoom, potatoRoom], users: [testPerson] });
|
||||
SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, false);
|
||||
});
|
||||
|
||||
it("does not display rooms with nsfw keywords in results when showNsfwPublicRooms is falsy", async () => {
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
|
||||
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(screen.getByText(potatoRoom.name!)).toBeInTheDocument();
|
||||
expect(screen.queryByText(nsfwTopicRoom.name!)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(nsfwTopicRoom.name!)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("displays rooms with nsfw keywords in results when showNsfwPublicRooms is truthy", async () => {
|
||||
SettingsStore.setValue("SpotlightSearch.showNsfwPublicRooms", null, SettingLevel.DEVICE, true);
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
|
||||
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(screen.getByText(nsfwTopicRoom.name!)).toBeInTheDocument();
|
||||
expect(screen.getByText(nsfwNameRoom.name!)).toBeInTheDocument();
|
||||
expect(screen.getByText(potatoRoom.name!)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show error if /publicRooms API failed", async () => {
|
||||
mocked(mockedClient.publicRooms).mockRejectedValue(new ConnectionError("Failed to fetch"));
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
|
||||
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("knock rooms", () => {
|
||||
const knockRoom: IPublicRoomsChunkRoom = {
|
||||
guest_can_join: false,
|
||||
join_rule: JoinRule.Knock,
|
||||
num_joined_members: 0,
|
||||
room_id: "some-room-id",
|
||||
world_readable: false,
|
||||
};
|
||||
|
||||
const viewRoomParams = {
|
||||
action: Action.ViewRoom,
|
||||
metricsTrigger: "WebUnifiedSearch",
|
||||
metricsViaKeyboard: false,
|
||||
room_alias: undefined,
|
||||
room_id: knockRoom.room_id,
|
||||
should_peek: false,
|
||||
via_servers: ["example.tld"],
|
||||
};
|
||||
|
||||
beforeEach(() => (mockedClient = mockClient({ rooms: [knockRoom] })));
|
||||
|
||||
describe("when disabling feature", () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) =>
|
||||
setting === "feature_ask_to_join" ? false : [],
|
||||
);
|
||||
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />);
|
||||
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "View" }));
|
||||
});
|
||||
|
||||
it("should not skip to auto join", async () => {
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: true });
|
||||
});
|
||||
|
||||
it("should not prompt ask to join", async () => {
|
||||
expect(defaultDispatcher.dispatch).not.toHaveBeenCalledWith({ action: Action.PromptAskToJoin });
|
||||
});
|
||||
});
|
||||
|
||||
describe("when enabling feature", () => {
|
||||
beforeEach(async () => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) =>
|
||||
setting === "feature_ask_to_join" ? true : [],
|
||||
);
|
||||
jest.spyOn(mockedClient, "getRoom").mockReturnValue(null);
|
||||
|
||||
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />);
|
||||
|
||||
// search is debounced
|
||||
jest.advanceTimersByTime(200);
|
||||
await flushPromisesWithFakeTimers();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Ask to join" }));
|
||||
});
|
||||
|
||||
it("should skip to auto join", async () => {
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: false });
|
||||
});
|
||||
|
||||
it("should prompt ask to join", async () => {
|
||||
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.PromptAskToJoin });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { EventType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { UnpinAllDialog } from "../../../../../src/components/views/dialogs/UnpinAllDialog";
|
||||
import { createTestClient } from "../../../../test-utils";
|
||||
|
||||
describe("<UnpinAllDialog />", () => {
|
||||
const client = createTestClient();
|
||||
const roomId = "!room:example.org";
|
||||
|
||||
function renderDialog(onFinished = jest.fn()) {
|
||||
return render(<UnpinAllDialog matrixClient={client} roomId={roomId} onFinished={onFinished} />);
|
||||
}
|
||||
|
||||
it("should render", () => {
|
||||
const { asFragment } = renderDialog();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should remove all pinned events when clicked on Continue", async () => {
|
||||
const onFinished = jest.fn();
|
||||
renderDialog(onFinished);
|
||||
|
||||
await userEvent.click(screen.getByText("Continue"));
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPinnedEvents, { pinned: [] }, "");
|
||||
expect(onFinished).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { ReactElement } from "react";
|
||||
import { render, screen } from "jest-matrix-react";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import SettingsStore, { CallbackFn } from "../../../../../src/settings/SettingsStore";
|
||||
import SdkConfig from "../../../../../src/SdkConfig";
|
||||
import { UserTab } from "../../../../../src/components/views/dialogs/UserTab";
|
||||
import UserSettingsDialog from "../../../../../src/components/views/dialogs/UserSettingsDialog";
|
||||
import {
|
||||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsUser,
|
||||
mockClientMethodsServer,
|
||||
mockPlatformPeg,
|
||||
mockClientMethodsCrypto,
|
||||
mockClientMethodsRooms,
|
||||
useMockMediaDevices,
|
||||
} from "../../../../test-utils";
|
||||
import { UIFeature } from "../../../../../src/settings/UIFeature";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
|
||||
mockPlatformPeg({
|
||||
supportsSpellCheckSettings: jest.fn().mockReturnValue(false),
|
||||
getAppVersion: jest.fn().mockResolvedValue("1"),
|
||||
});
|
||||
|
||||
jest.mock("../../../../../src/settings/SettingsStore", () => ({
|
||||
getValue: jest.fn(),
|
||||
getValueAt: jest.fn(),
|
||||
canSetValue: jest.fn(),
|
||||
monitorSetting: jest.fn(),
|
||||
watchSetting: jest.fn(),
|
||||
unwatchSetting: jest.fn(),
|
||||
getFeatureSettingNames: jest.fn(),
|
||||
getBetaInfo: jest.fn(),
|
||||
getDisplayName: jest.fn(),
|
||||
getDescription: jest.fn(),
|
||||
shouldHaveWarning: jest.fn(),
|
||||
disabledMessage: jest.fn(),
|
||||
settingIsOveriddenAtConfigLevel: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("<UserSettingsDialog />", () => {
|
||||
const userId = "@alice:server.org";
|
||||
const mockSettingsStore = mocked(SettingsStore);
|
||||
let mockClient!: MockedObject<MatrixClient>;
|
||||
|
||||
let sdkContext: SdkContextClass;
|
||||
const defaultProps = { onFinished: jest.fn() };
|
||||
const getComponent = (props: Partial<typeof defaultProps & { initialTabId?: UserTab }> = {}): ReactElement => (
|
||||
<UserSettingsDialog sdkContext={sdkContext} {...defaultProps} {...props} />
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsUser(userId),
|
||||
...mockClientMethodsServer(),
|
||||
...mockClientMethodsCrypto(),
|
||||
...mockClientMethodsRooms(),
|
||||
getIgnoredUsers: jest.fn().mockResolvedValue([]),
|
||||
getPushers: jest.fn().mockResolvedValue([]),
|
||||
getProfileInfo: jest.fn().mockResolvedValue({}),
|
||||
});
|
||||
sdkContext = new SdkContextClass();
|
||||
sdkContext.client = mockClient;
|
||||
mockSettingsStore.getValue.mockReturnValue(false);
|
||||
mockSettingsStore.getValueAt.mockReturnValue(false);
|
||||
mockSettingsStore.getFeatureSettingNames.mockReturnValue([]);
|
||||
SdkConfig.reset();
|
||||
SdkConfig.put({ brand: "Test" });
|
||||
});
|
||||
|
||||
const getActiveTabLabel = (container: Element) =>
|
||||
container.querySelector(".mx_TabbedView_tabLabel_active")?.textContent;
|
||||
|
||||
it("should render general settings tab when no initialTabId", () => {
|
||||
const { container } = render(getComponent());
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Account");
|
||||
});
|
||||
|
||||
it("should render initial tab when initialTabId is set", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Help }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Help & About");
|
||||
});
|
||||
|
||||
it("should render general tab if initialTabId tab cannot be rendered", () => {
|
||||
// mjolnir tab is only rendered in some configs
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Mjolnir }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Account");
|
||||
});
|
||||
|
||||
it("renders tabs correctly", () => {
|
||||
SdkConfig.add({
|
||||
show_labs_settings: true,
|
||||
});
|
||||
const { container } = render(getComponent());
|
||||
expect(container.querySelectorAll(".mx_TabbedView_tabLabel")).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders ignored users tab when feature_mjolnir is enabled", () => {
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === "feature_mjolnir");
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders voip tab when voip is enabled", () => {
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === UIFeature.Voip);
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Voice}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders with session manager tab selected", () => {
|
||||
const { getByTestId } = render(getComponent({ initialTabId: UserTab.SessionManager }));
|
||||
expect(getByTestId(`settings-tab-${UserTab.SessionManager}`)).toBeTruthy();
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Sessions");
|
||||
});
|
||||
|
||||
it("renders with appearance tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Appearance }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Appearance");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Appearance");
|
||||
});
|
||||
|
||||
it("renders with notifications tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Notifications }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Notifications");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Notifications");
|
||||
});
|
||||
|
||||
it("renders with preferences tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Preferences }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Preferences");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Preferences");
|
||||
});
|
||||
|
||||
it("renders with keyboard tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Keyboard }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Keyboard");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Keyboard");
|
||||
});
|
||||
|
||||
it("renders with sidebar tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Sidebar }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Sidebar");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Sidebar");
|
||||
});
|
||||
|
||||
it("renders with voip tab selected", () => {
|
||||
useMockMediaDevices();
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === UIFeature.Voip);
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Voice }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Voice & Video");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Voice & Video");
|
||||
});
|
||||
|
||||
it("renders with security tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Security }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Security & Privacy");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Security & Privacy");
|
||||
});
|
||||
|
||||
it("renders with labs tab selected", () => {
|
||||
SdkConfig.add({
|
||||
show_labs_settings: true,
|
||||
});
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Labs }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Labs");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Labs");
|
||||
});
|
||||
|
||||
it("renders with mjolnir tab selected", () => {
|
||||
mockSettingsStore.getValue.mockImplementation((settingName): any => settingName === "feature_mjolnir");
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Mjolnir }));
|
||||
expect(getActiveTabLabel(container)).toEqual("Ignored users");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Ignored Users");
|
||||
});
|
||||
|
||||
it("renders with help tab selected", () => {
|
||||
const { container } = render(getComponent({ initialTabId: UserTab.Help }));
|
||||
|
||||
expect(getActiveTabLabel(container)).toEqual("Help & About");
|
||||
expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Settings: Help & About");
|
||||
});
|
||||
|
||||
it("renders labs tab when show_labs_settings is enabled in config", () => {
|
||||
SdkConfig.add({
|
||||
show_labs_settings: true,
|
||||
});
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("renders labs tab when some feature is in beta", () => {
|
||||
mockSettingsStore.getFeatureSettingNames.mockReturnValue(["feature_beta_setting", "feature_just_normal_labs"]);
|
||||
mockSettingsStore.getBetaInfo.mockImplementation((settingName) =>
|
||||
settingName === "feature_beta_setting" ? ({} as any) : undefined,
|
||||
);
|
||||
const { getByTestId } = render(getComponent());
|
||||
expect(getByTestId(`settings-tab-${UserTab.Labs}`)).toBeTruthy();
|
||||
});
|
||||
|
||||
it("watches settings", async () => {
|
||||
const watchSettingCallbacks: Record<string, CallbackFn> = {};
|
||||
|
||||
mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => {
|
||||
watchSettingCallbacks[settingName] = callback;
|
||||
return `mock-watcher-id-${settingName}`;
|
||||
});
|
||||
mockSettingsStore.getValue.mockReturnValue(false);
|
||||
|
||||
const { queryByTestId, findByTestId, unmount } = render(getComponent());
|
||||
expect(queryByTestId(`settings-tab-${UserTab.Mjolnir}`)).toBeFalsy();
|
||||
|
||||
expect(mockSettingsStore.watchSetting).toHaveBeenCalledWith("feature_mjolnir", null, expect.anything());
|
||||
|
||||
// call the watch setting callback
|
||||
mockSettingsStore.getValue.mockReturnValue(true);
|
||||
watchSettingCallbacks["feature_mjolnir"]("feature_mjolnir", "", SettingLevel.ACCOUNT, true, true);
|
||||
|
||||
// tab is rendered now
|
||||
await expect(findByTestId(`settings-tab-${UserTab.Mjolnir}`)).resolves.toBeTruthy();
|
||||
|
||||
unmount();
|
||||
|
||||
// unwatches settings on unmount
|
||||
expect(mockSettingsStore.unwatchSetting).toHaveBeenCalledWith("mock-watcher-id-feature_mjolnir");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,540 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`AppDownloadDialog should allow disabling desktop build 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_AppDownloadDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Download Element
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_mobile"
|
||||
>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_app"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
iOS
|
||||
</h3>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_info"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_links"
|
||||
>
|
||||
<a
|
||||
aria-label="Download on the App Store"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://apps.apple.com/app/vector/id1083446067"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_app"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
Android
|
||||
</h3>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_info"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_links"
|
||||
>
|
||||
<a
|
||||
aria-label="Get it on Google Play"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://play.google.com/store/apps/details?id=im.vector.app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
<a
|
||||
aria-label="Get it on F-Droid"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://f-droid.org/repository/browse/?fdid=im.vector.app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_legal"
|
||||
>
|
||||
<p>
|
||||
App Store® and the Apple logo® are trademarks of Apple Inc.
|
||||
</p>
|
||||
<p>
|
||||
Google Play and the Google Play logo are trademarks of Google LLC.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`AppDownloadDialog should allow disabling fdroid build 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_AppDownloadDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Download Element
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_desktop"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
Download Element Desktop
|
||||
</h3>
|
||||
<a
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
href="https://element.io/download"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
Download Element Desktop
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_mobile"
|
||||
>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_app"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
iOS
|
||||
</h3>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_info"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_links"
|
||||
>
|
||||
<a
|
||||
aria-label="Download on the App Store"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://apps.apple.com/app/vector/id1083446067"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_app"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
Android
|
||||
</h3>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_info"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_links"
|
||||
>
|
||||
<a
|
||||
aria-label="Get it on Google Play"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://play.google.com/store/apps/details?id=im.vector.app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_legal"
|
||||
>
|
||||
<p>
|
||||
App Store® and the Apple logo® are trademarks of Apple Inc.
|
||||
</p>
|
||||
<p>
|
||||
Google Play and the Google Play logo are trademarks of Google LLC.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`AppDownloadDialog should allow disabling mobile builds 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_AppDownloadDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Download Element
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_desktop"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
Download Element Desktop
|
||||
</h3>
|
||||
<a
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
href="https://element.io/download"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
Download Element Desktop
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_mobile"
|
||||
/>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_legal"
|
||||
>
|
||||
<p>
|
||||
App Store® and the Apple logo® are trademarks of Apple Inc.
|
||||
</p>
|
||||
<p>
|
||||
Google Play and the Google Play logo are trademarks of Google LLC.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`AppDownloadDialog should render with desktop, ios, android, fdroid buttons by default 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_AppDownloadDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Download Element
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_desktop"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
Download Element Desktop
|
||||
</h3>
|
||||
<a
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
href="https://element.io/download"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
Download Element Desktop
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_mobile"
|
||||
>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_app"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
iOS
|
||||
</h3>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_info"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_links"
|
||||
>
|
||||
<a
|
||||
aria-label="Download on the App Store"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://apps.apple.com/app/vector/id1083446067"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_app"
|
||||
>
|
||||
<h3
|
||||
class="mx_Heading_h3"
|
||||
>
|
||||
Android
|
||||
</h3>
|
||||
<div
|
||||
class="mx_QRCode"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_info"
|
||||
>
|
||||
or
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_links"
|
||||
>
|
||||
<a
|
||||
aria-label="Get it on Google Play"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://play.google.com/store/apps/details?id=im.vector.app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
<a
|
||||
aria-label="Get it on F-Droid"
|
||||
class="mx_AccessibleButton"
|
||||
href="https://f-droid.org/repository/browse/?fdid=im.vector.app"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
target="_blank"
|
||||
>
|
||||
<div />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AppDownloadDialog_legal"
|
||||
>
|
||||
<p>
|
||||
App Store® and the Apple logo® are trademarks of Apple Inc.
|
||||
</p>
|
||||
<p>
|
||||
Google Play and the Google Play logo are trademarks of Google LLC.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,141 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ChangelogDialog /> should fetch github proxy url for each repo with old and new version strings 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Changelog
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ChangelogDialog_content"
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
class="mx_Heading_h4"
|
||||
>
|
||||
element-hq/element-web
|
||||
</h2>
|
||||
<ul>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/element-hq/element-web/commit/commit-sha"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is the first commit message
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2
|
||||
class="mx_Heading_h4"
|
||||
>
|
||||
element-hq/matrix-react-sdk
|
||||
</h2>
|
||||
<ul>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/element-hq/matrix-react-sdk/commit/commit-sha"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is a commit message
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h2
|
||||
class="mx_Heading_h4"
|
||||
>
|
||||
matrix-org/matrix-js-sdk
|
||||
</h2>
|
||||
<ul>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is a commit message
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
class="mx_ChangelogDialog_li"
|
||||
>
|
||||
<a
|
||||
href="https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
This is another commit message
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</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"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,95 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfirmUserActionDialog 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ConfirmUserActionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Ban this
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_ConfirmUserActionDialog_user"
|
||||
>
|
||||
<div
|
||||
class="mx_ConfirmUserActionDialog_avatar"
|
||||
>
|
||||
<span
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="3"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 48px;"
|
||||
title="@user:test.com"
|
||||
>
|
||||
u
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ConfirmUserActionDialog_name"
|
||||
>
|
||||
@user:test.com
|
||||
</div>
|
||||
<div
|
||||
class="mx_ConfirmUserActionDialog_userId"
|
||||
>
|
||||
@user:test.com
|
||||
</div>
|
||||
</div>
|
||||
</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"
|
||||
>
|
||||
Ban
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,243 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`DevtoolsDialog renders the devtools dialog 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_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Developer Tools
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DevTools_label_left"
|
||||
>
|
||||
Toolbox
|
||||
</div>
|
||||
<div
|
||||
class="mx_CopyableText mx_DevTools_label_right"
|
||||
>
|
||||
Room ID: !id
|
||||
<div
|
||||
aria-describedby="floating-ui-2"
|
||||
aria-label="Copy"
|
||||
class="mx_AccessibleButton mx_CopyableText_copyButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_DevTools_label_bottom"
|
||||
/>
|
||||
<div
|
||||
class="mx_DevTools_content"
|
||||
>
|
||||
<div>
|
||||
<h3>
|
||||
Room
|
||||
</h3>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Send custom timeline event
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Explore room state
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Explore room account data
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
View servers in room
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Notifications debug
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Verification explorer
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Active Widgets
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
Other
|
||||
</h3>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Explore account data
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Settings explorer
|
||||
</button>
|
||||
<button
|
||||
class="mx_DevTools_button"
|
||||
>
|
||||
Server info
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h3>
|
||||
Options
|
||||
</h3>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Developer mode
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Developer mode"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_vY7Q4uEh9K38"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_QgU2PomxwKpa"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Show hidden events in timeline
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Show hidden events in timeline"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_QgU2PomxwKpa"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_6hpi3YEetmBG"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Enable widget screenshots on supported widgets
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Enable widget screenshots on supported widgets"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_6hpi3YEetmBG"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Force 15s voice broadcast chunk length
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Force 15s voice broadcast chunk length"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<button>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,206 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ExportDialog /> renders export dialog 1`] = `
|
||||
<div
|
||||
aria-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ExportDialog false mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Export Chat
|
||||
</h1>
|
||||
</div>
|
||||
<p>
|
||||
Select from the options below to export chats from your timeline
|
||||
</p>
|
||||
<div
|
||||
class="mx_ExportDialog_options"
|
||||
>
|
||||
<span
|
||||
class="mx_ExportDialog_subheading"
|
||||
>
|
||||
Format
|
||||
</span>
|
||||
<label
|
||||
class="mx_StyledRadioButton mx_StyledRadioButton_enabled mx_StyledRadioButton_checked"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
id="exportFormat-Html"
|
||||
name="exportFormat"
|
||||
type="radio"
|
||||
value="Html"
|
||||
/>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_content"
|
||||
>
|
||||
HTML
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
|
||||
>
|
||||
<input
|
||||
id="exportFormat-PlainText"
|
||||
name="exportFormat"
|
||||
type="radio"
|
||||
value="PlainText"
|
||||
/>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_content"
|
||||
>
|
||||
Plain Text
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
|
||||
>
|
||||
<input
|
||||
id="exportFormat-Json"
|
||||
name="exportFormat"
|
||||
type="radio"
|
||||
value="Json"
|
||||
/>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_content"
|
||||
>
|
||||
JSON
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</label>
|
||||
<span
|
||||
class="mx_ExportDialog_subheading"
|
||||
>
|
||||
Messages
|
||||
</span>
|
||||
<div
|
||||
class="mx_Field mx_Field_select"
|
||||
>
|
||||
<select
|
||||
id="export-type"
|
||||
type="text"
|
||||
>
|
||||
<option
|
||||
value="Timeline"
|
||||
>
|
||||
Current Timeline
|
||||
</option>
|
||||
<option
|
||||
value="Beginning"
|
||||
>
|
||||
From the beginning
|
||||
</option>
|
||||
<option
|
||||
value="LastNMessages"
|
||||
>
|
||||
Specify a number of messages
|
||||
</option>
|
||||
</select>
|
||||
<label
|
||||
for="export-type"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="mx_ExportDialog_subheading"
|
||||
>
|
||||
Size Limit
|
||||
</span>
|
||||
<div
|
||||
class="mx_Field mx_Field_input"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
id="size-limit"
|
||||
type="number"
|
||||
value="8"
|
||||
/>
|
||||
<label
|
||||
for="size-limit"
|
||||
/>
|
||||
<span
|
||||
class="mx_Field_postfix"
|
||||
>
|
||||
<span>
|
||||
MB
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<span
|
||||
class="mx_Checkbox mx_ExportDialog_attachments-checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
id="include-attachments"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="include-attachments"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_checkmark"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
Include Attachments
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
</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"
|
||||
>
|
||||
Export
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<ExportDialog /> renders success screen when export is finished 1`] = `null`;
|
|
@ -0,0 +1,90 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`FeedbackDialog should respect feedback config 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_FeedbackDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Feedback
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div
|
||||
class="mx_FeedbackDialog_section mx_FeedbackDialog_reportBug"
|
||||
>
|
||||
<h3>
|
||||
Report a bug
|
||||
</h3>
|
||||
<p>
|
||||
<span>
|
||||
Please view
|
||||
<a
|
||||
class="mx_ExternalLink"
|
||||
href="http://existing?foo=bar"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
existing bugs on Github
|
||||
<i
|
||||
class="mx_ExternalLink_icon"
|
||||
/>
|
||||
</a>
|
||||
first. No match?
|
||||
<a
|
||||
class="mx_ExternalLink"
|
||||
href="https://new.issue.url?foo=bar"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
Start a new one
|
||||
<i
|
||||
class="mx_ExternalLink_icon"
|
||||
/>
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Go back
|
||||
</button>
|
||||
</span>
|
||||
</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,243 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LogoutDialog Prompts user to connect backup if there is a backup on the server 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
You'll lose access to your encrypted messages
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.
|
||||
</p>
|
||||
<p>
|
||||
When you sign out, these keys will be deleted from this device, which means you won't be able to read encrypted messages unless you have the keys for them on your other devices, or backed them up to the server.
|
||||
</p>
|
||||
<p>
|
||||
Back up your keys before signing out to avoid losing them.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button>
|
||||
I don't want my encrypted messages
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Connect this session to Key Backup
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<details>
|
||||
<summary
|
||||
class="mx_LogoutDialog_ExportKeyAdvanced"
|
||||
>
|
||||
Advanced
|
||||
</summary>
|
||||
<p>
|
||||
<button>
|
||||
Manually export keys
|
||||
</button>
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
<div
|
||||
aria-describedby="floating-ui-22"
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`LogoutDialog Prompts user to set up backup if there is no backup on the server 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
You'll lose access to your encrypted messages
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Encrypted messages are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.
|
||||
</p>
|
||||
<p>
|
||||
When you sign out, these keys will be deleted from this device, which means you won't be able to read encrypted messages unless you have the keys for them on your other devices, or backed them up to the server.
|
||||
</p>
|
||||
<p>
|
||||
Back up your keys before signing out to avoid losing them.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button>
|
||||
I don't want my encrypted messages
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Start using Key Backup
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<details>
|
||||
<summary
|
||||
class="mx_LogoutDialog_ExportKeyAdvanced"
|
||||
>
|
||||
Advanced
|
||||
</summary>
|
||||
<p>
|
||||
<button>
|
||||
Manually export keys
|
||||
</button>
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
<div
|
||||
aria-describedby="floating-ui-28"
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`LogoutDialog shows a regular dialog when crypto is disabled 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Sign out
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
Are you sure you want to sign out?
|
||||
</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"
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,252 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ManageRestrictedJoinRuleDialog /> should list spaces which are not parents of the room 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_ManageRestrictedJoinRuleDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Select spaces
|
||||
</h1>
|
||||
</div>
|
||||
<p>
|
||||
<span>
|
||||
Decide which spaces can access this room. If a space is selected, its members can find and join
|
||||
<strong>
|
||||
!roomId:server
|
||||
</strong>
|
||||
.
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
class="mx_SearchBox mx_textinput"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="mx_textinput_icon mx_textinput_search mx_textinput_icon mx_textinput_search"
|
||||
data-testid="searchbox-input"
|
||||
placeholder="Search spaces"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_SearchBox_closeButton"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ManageRestrictedJoinRuleDialog_content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_section"
|
||||
>
|
||||
<h3>
|
||||
Other spaces you know
|
||||
</h3>
|
||||
<label
|
||||
class="mx_ManageRestrictedJoinRuleDialog_entry"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<span
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 20px;"
|
||||
>
|
||||
O
|
||||
</span>
|
||||
<span
|
||||
class="mx_ManageRestrictedJoinRuleDialog_entry_name"
|
||||
>
|
||||
Other Space
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_entry_description"
|
||||
>
|
||||
0 members
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
|
||||
>
|
||||
<input
|
||||
id="checkbox_vY7Q4uEh9K"
|
||||
type="checkbox"
|
||||
/>
|
||||
<label
|
||||
for="checkbox_vY7Q4uEh9K"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_background"
|
||||
>
|
||||
<div
|
||||
class="mx_Checkbox_checkmark"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_footer"
|
||||
>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_section_info"
|
||||
>
|
||||
You're removing all spaces. Access will default to invite only
|
||||
</div>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_footer_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Confirm
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`<ManageRestrictedJoinRuleDialog /> should render empty state 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_ManageRestrictedJoinRuleDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Select spaces
|
||||
</h1>
|
||||
</div>
|
||||
<p>
|
||||
<span>
|
||||
Decide which spaces can access this room. If a space is selected, its members can find and join
|
||||
<strong>
|
||||
!roomId:server
|
||||
</strong>
|
||||
.
|
||||
</span>
|
||||
</p>
|
||||
<div
|
||||
class="mx_SearchBox mx_textinput"
|
||||
>
|
||||
<input
|
||||
autocomplete="off"
|
||||
class="mx_textinput_icon mx_textinput_search mx_textinput_icon mx_textinput_search"
|
||||
data-testid="searchbox-input"
|
||||
placeholder="Search spaces"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_SearchBox_closeButton"
|
||||
role="button"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ManageRestrictedJoinRuleDialog_content"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="mx_ManageRestrictedJoinRuleDialog_noResults"
|
||||
>
|
||||
No results
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_footer"
|
||||
>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_section_info"
|
||||
>
|
||||
You're removing all spaces. Access will default to invite only
|
||||
</div>
|
||||
<div
|
||||
class="mx_ManageRestrictedJoinRuleDialog_footer_buttons"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Confirm
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,231 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ManualDeviceKeyVerificationDialog should display the device 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify session
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Confirm by comparing the following with the User Settings in your other session:
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceVerifyDialog_cryptoSection"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
Session name
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
my device
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session ID
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
XYZ
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session key
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
<strong>
|
||||
ABCD EFGH
|
||||
</strong>
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If they don't match, the security of your communication may be compromised.
|
||||
</p>
|
||||
</div>
|
||||
</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"
|
||||
>
|
||||
Verify session
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`ManualDeviceKeyVerificationDialog should display the device of another user 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-describedby="mx_Dialog_content"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_QuestionDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Verify session
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_content"
|
||||
id="mx_Dialog_content"
|
||||
>
|
||||
<div>
|
||||
<p>
|
||||
Confirm this user's session by comparing the following with their User Settings:
|
||||
</p>
|
||||
<div
|
||||
class="mx_DeviceVerifyDialog_cryptoSection"
|
||||
>
|
||||
<ul>
|
||||
<li>
|
||||
<label>
|
||||
Session name
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
my device
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session ID
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
XYZ
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<label>
|
||||
Session key
|
||||
:
|
||||
</label>
|
||||
|
||||
<span>
|
||||
<code>
|
||||
<strong>
|
||||
ABCD EFGH
|
||||
</strong>
|
||||
</code>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<p>
|
||||
If they don't match, the security of your communication may be compromised.
|
||||
</p>
|
||||
</div>
|
||||
</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"
|
||||
>
|
||||
Verify session
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,332 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<MessageEditHistory /> should match the snapshot 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_MessageEditHistoryDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Message edits
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ScrollPanel mx_MessageEditHistoryDialog_scrollPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_messageListWrapper"
|
||||
>
|
||||
<ol
|
||||
aria-live="polite"
|
||||
class="mx_RoomView_MessageList"
|
||||
>
|
||||
<ul
|
||||
class="mx_MessageEditHistoryDialog_edits"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
aria-label="Thu, Jan 1, 1970"
|
||||
class="mx_TimelineSeparator"
|
||||
role="separator"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
<div
|
||||
class="mx_DateSeparator_dateContent"
|
||||
>
|
||||
<h2
|
||||
aria-hidden="true"
|
||||
class="mx_DateSeparator_dateHeading"
|
||||
>
|
||||
Thu, Jan 1, 1970
|
||||
</h2>
|
||||
</div>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body translate"
|
||||
dir="auto"
|
||||
>
|
||||
My Great Massage
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-describedby="floating-ui-2"
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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[`<MessageEditHistory /> should support events with 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_MessageEditHistoryDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Message edits
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AutoHideScrollbar mx_ScrollPanel mx_MessageEditHistoryDialog_scrollPanel"
|
||||
tabindex="-1"
|
||||
>
|
||||
<div
|
||||
class="mx_RoomView_messageListWrapper"
|
||||
>
|
||||
<ol
|
||||
aria-live="polite"
|
||||
class="mx_RoomView_MessageList"
|
||||
>
|
||||
<ul
|
||||
class="mx_MessageEditHistoryDialog_edits"
|
||||
>
|
||||
<li>
|
||||
<div
|
||||
aria-label="Thu, Jan 1, 1970"
|
||||
class="mx_TimelineSeparator"
|
||||
role="separator"
|
||||
>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
<div
|
||||
class="mx_DateSeparator_dateContent"
|
||||
>
|
||||
<h2
|
||||
aria-hidden="true"
|
||||
class="mx_DateSeparator_dateHeading"
|
||||
>
|
||||
Thu, Jan 1, 1970
|
||||
</h2>
|
||||
</div>
|
||||
<hr
|
||||
role="none"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body markdown-body"
|
||||
dir="auto"
|
||||
>
|
||||
<span>
|
||||
My Great Massage
|
||||
<span
|
||||
class="mx_EditHistoryMessage_deletion"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body markdown-body"
|
||||
dir="auto"
|
||||
>
|
||||
<span>
|
||||
My Great M
|
||||
<span
|
||||
class="mx_EditHistoryMessage_deletion"
|
||||
>
|
||||
i
|
||||
</span>
|
||||
<span
|
||||
class="mx_EditHistoryMessage_insertion"
|
||||
>
|
||||
a
|
||||
</span>
|
||||
ssage
|
||||
<span
|
||||
class="mx_EditHistoryMessage_insertion"
|
||||
>
|
||||
?
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div
|
||||
class="mx_EventTile"
|
||||
>
|
||||
<div
|
||||
class="mx_EventTile_line"
|
||||
>
|
||||
<span
|
||||
class="mx_MessageTimestamp"
|
||||
>
|
||||
00:00
|
||||
</span>
|
||||
<div
|
||||
class="mx_EventTile_content"
|
||||
>
|
||||
<span
|
||||
class="mx_EventTile_body translate"
|
||||
dir="auto"
|
||||
>
|
||||
My Great Missage
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_MessageActionBar"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Remove
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-describedby="floating-ui-8"
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,159 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RoomSettingsDialog /> Settings tabs renders default tabs correctly 1`] = `
|
||||
NodeList [
|
||||
<li
|
||||
aria-controls="mx_tabpanel_ROOM_GENERAL_TAB"
|
||||
aria-selected="true"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active"
|
||||
data-testid="settings-tab-ROOM_GENERAL_TAB"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<span
|
||||
class="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_settingsIcon"
|
||||
/>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_ROOM_GENERAL_TAB_label"
|
||||
>
|
||||
General
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_ROOM_SECURITY_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-ROOM_SECURITY_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_securityIcon"
|
||||
/>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_ROOM_SECURITY_TAB_label"
|
||||
>
|
||||
Security & Privacy
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_ROOM_ROLES_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-ROOM_ROLES_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_rolesIcon"
|
||||
/>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_ROOM_ROLES_TAB_label"
|
||||
>
|
||||
Roles & Permissions
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_ROOM_NOTIFICATIONS_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-ROOM_NOTIFICATIONS_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_notificationsIcon"
|
||||
/>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_ROOM_NOTIFICATIONS_TAB_label"
|
||||
>
|
||||
Notifications
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_ROOM_POLL_HISTORY_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-ROOM_POLL_HISTORY_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="mx_TabbedView_maskedIcon mx_RoomSettingsDialog_pollsIcon"
|
||||
/>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_ROOM_POLL_HISTORY_TAB_label"
|
||||
>
|
||||
Polls
|
||||
</span>
|
||||
</li>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`<RoomSettingsDialog /> poll history displays poll history when tab clicked 1`] = `
|
||||
<div
|
||||
class="mx_SettingsTab"
|
||||
>
|
||||
<div
|
||||
class="mx_PollHistory_content"
|
||||
>
|
||||
<h2
|
||||
class="mx_Heading_h2 mx_PollHistory_header"
|
||||
>
|
||||
Polls
|
||||
</h2>
|
||||
<div
|
||||
class="mx_PollHistoryList"
|
||||
>
|
||||
<fieldset
|
||||
class="mx_FilterTabGroup"
|
||||
>
|
||||
<label
|
||||
data-testid="filter-tab-PollHistory_filter-ACTIVE"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
name="PollHistory_filter"
|
||||
type="radio"
|
||||
value="ACTIVE"
|
||||
/>
|
||||
<span>
|
||||
Active polls
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
data-testid="filter-tab-PollHistory_filter-ENDED"
|
||||
>
|
||||
<input
|
||||
name="PollHistory_filter"
|
||||
type="radio"
|
||||
value="ENDED"
|
||||
/>
|
||||
<span>
|
||||
Past polls
|
||||
</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<div
|
||||
class="mx_PollHistoryList_loading mx_PollHistoryList_noResultsYet"
|
||||
>
|
||||
<div
|
||||
class="mx_InlineSpinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_InlineSpinner_icon mx_Spinner_icon"
|
||||
style="width: 16px; height: 16px;"
|
||||
/>
|
||||
</div>
|
||||
Loading polls
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,142 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ServerPickerDialog /> should render dialog 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-describedby="mx_ServerPickerDialog"
|
||||
aria-labelledby="mx_BaseDialog_title"
|
||||
class="mx_ServerPickerDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Sign into your homeserver
|
||||
</h1>
|
||||
</div>
|
||||
<form
|
||||
class="mx_Dialog_content"
|
||||
id="mx_ServerPickerDialog"
|
||||
>
|
||||
<p>
|
||||
We call the places where you can host your account 'homeservers'.
|
||||
|
||||
Matrix.org is the biggest public homeserver in the world, so it's a good place for many.
|
||||
</p>
|
||||
<label
|
||||
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
data-testid="defaultHomeserver"
|
||||
name="defaultChosen"
|
||||
type="radio"
|
||||
value="true"
|
||||
/>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_content"
|
||||
>
|
||||
<span
|
||||
aria-labelledby="floating-ui-1"
|
||||
class="mx_Login_underlinedServerName"
|
||||
tabindex="0"
|
||||
>
|
||||
matrix.org
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</label>
|
||||
<div
|
||||
class="mx_StyledRadioButton mx_ServerPickerDialog_otherHomeserverRadio mx_StyledRadioButton_enabled mx_StyledRadioButton_checked"
|
||||
>
|
||||
<label
|
||||
class="mx_StyledRadioButton_innerLabel"
|
||||
>
|
||||
<input
|
||||
aria-label="Other homeserver"
|
||||
name="defaultChosen"
|
||||
type="radio"
|
||||
value="false"
|
||||
/>
|
||||
<div>
|
||||
<div />
|
||||
</div>
|
||||
</label>
|
||||
<div
|
||||
class="mx_StyledRadioButton_content"
|
||||
>
|
||||
<div
|
||||
class="mx_Field mx_Field_input mx_ServerPickerDialog_otherHomeserver"
|
||||
>
|
||||
<input
|
||||
id="mx_homeserverInput"
|
||||
label="Other homeserver"
|
||||
placeholder="Other homeserver"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_homeserverInput"
|
||||
>
|
||||
Other homeserver
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_StyledRadioButton_spacer"
|
||||
/>
|
||||
</div>
|
||||
<p>
|
||||
Use your preferred Matrix homeserver if you have one, or host your own.
|
||||
</p>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_ServerPickerDialog_continue mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Continue
|
||||
</div>
|
||||
<h2>
|
||||
Learn more
|
||||
</h2>
|
||||
<a
|
||||
class="mx_ExternalLink"
|
||||
href="https://matrix.org/docs/matrix-concepts/elements-of-matrix/#homeserver"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
About homeservers
|
||||
<i
|
||||
class="mx_ExternalLink_icon"
|
||||
/>
|
||||
</a>
|
||||
</form>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,66 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UnpinAllDialog /> 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_UnpinAllDialog"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title mx_UnpinAllDialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Unpin all messages?
|
||||
</h1>
|
||||
</div>
|
||||
<span
|
||||
class="_typography_yh5dq_162 _font-body-md-regular_yh5dq_59"
|
||||
>
|
||||
Make sure that you really want to remove all pinned messages. This action can’t be undone.
|
||||
</span>
|
||||
<div
|
||||
class="mx_UnpinAllDialog_buttons"
|
||||
>
|
||||
<button
|
||||
class="_button_i91xf_17 _destructive_i91xf_116"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Continue
|
||||
</button>
|
||||
<button
|
||||
class="_button_i91xf_17"
|
||||
data-kind="tertiary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Close dialog"
|
||||
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
/>
|
||||
</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,292 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UserSettingsDialog /> renders tabs correctly 1`] = `
|
||||
NodeList [
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_ACCOUNT_TAB"
|
||||
aria-selected="true"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel mx_TabbedView_tabLabel_active"
|
||||
data-testid="settings-tab-USER_ACCOUNT_TAB"
|
||||
role="tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.175 13.825C9.958 14.608 10.9 15 12 15s2.042-.392 2.825-1.175C15.608 13.042 16 12.1 16 11s-.392-2.042-1.175-2.825C14.042 7.392 13.1 7 12 7s-2.042.392-2.825 1.175C8.392 8.958 8 9.9 8 11s.392 2.042 1.175 2.825Zm4.237-1.412A1.926 1.926 0 0 1 12 13c-.55 0-1.02-.196-1.412-.588A1.926 1.926 0 0 1 10 11c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 12 9c.55 0 1.02.196 1.412.588.392.391.588.862.588 1.412 0 .55-.196 1.02-.588 1.412Z"
|
||||
/>
|
||||
<path
|
||||
d="M22 12c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2s10 4.477 10 10Zm-2 0a8 8 0 1 0-16 0 8 8 0 0 0 16 0Z"
|
||||
/>
|
||||
<path
|
||||
d="M16.23 18.792a12.47 12.47 0 0 0-1.455-.455 11.6 11.6 0 0 0-5.55 0c-.487.12-.972.271-1.455.455a8.04 8.04 0 0 1-1.729-1.454c.89-.412 1.794-.729 2.709-.95A13.76 13.76 0 0 1 12 16c1.1 0 2.183.13 3.25.387a14.78 14.78 0 0 1 2.709.95 8.042 8.042 0 0 1-1.73 1.455Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_ACCOUNT_TAB_label"
|
||||
>
|
||||
Account
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_SESSION_MANAGER_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_SESSION_MANAGER_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3.5 20c-.417 0-.77-.146-1.063-.438A1.447 1.447 0 0 1 2 18.5c0-.417.146-.77.438-1.063A1.446 1.446 0 0 1 3.5 17H4V6c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 4h14a.97.97 0 0 1 .712.287c.192.192.288.43.288.713s-.096.52-.288.713A.968.968 0 0 1 20 6H6v11h4.5c.417 0 .77.146 1.063.438.291.291.437.645.437 1.062 0 .417-.146.77-.438 1.063A1.446 1.446 0 0 1 10.5 20h-7ZM15 20a.968.968 0 0 1-.713-.288A.968.968 0 0 1 14 19V9c0-.283.096-.52.287-.713A.968.968 0 0 1 15 8h6a.97.97 0 0 1 .712.287c.192.192.288.43.288.713v10c0 .283-.096.52-.288.712A.968.968 0 0 1 21 20h-6Zm1-3h4v-7h-4v7Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_SESSION_MANAGER_TAB_label"
|
||||
>
|
||||
Sessions
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_APPEARANCE_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_APPEARANCE_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 16c1.25 0 2.313-.438 3.188-1.313.874-.874 1.312-1.937 1.312-3.187 0-1.25-.438-2.313-1.313-3.188C14.313 7.439 13.25 7 12 7c-1.25 0-2.312.438-3.187 1.313C7.938 9.187 7.5 10.25 7.5 11.5c0 1.25.438 2.313 1.313 3.188C9.688 15.562 10.75 16 12 16Zm0-1.8c-.75 0-1.387-.262-1.912-.787A2.604 2.604 0 0 1 9.3 11.5c0-.75.263-1.387.787-1.912A2.604 2.604 0 0 1 12 8.8c.75 0 1.387.262 1.912.787.525.526.788 1.163.788 1.913s-.262 1.387-.787 1.912A2.604 2.604 0 0 1 12 14.2Zm0 4.8c-2.317 0-4.433-.613-6.35-1.837-1.917-1.226-3.367-2.88-4.35-4.963a.812.812 0 0 1-.1-.313 2.93 2.93 0 0 1 0-.774.812.812 0 0 1 .1-.313c.983-2.083 2.433-3.738 4.35-4.963C7.567 4.614 9.683 4 12 4c2.317 0 4.433.612 6.35 1.838 1.917 1.224 3.367 2.879 4.35 4.962a.81.81 0 0 1 .1.313 2.925 2.925 0 0 1 0 .774.81.81 0 0 1-.1.313c-.983 2.083-2.433 3.738-4.35 4.963C16.433 18.387 14.317 19 12 19Zm0-2a9.544 9.544 0 0 0 5.188-1.488A9.773 9.773 0 0 0 20.8 11.5a9.773 9.773 0 0 0-3.613-4.013A9.544 9.544 0 0 0 12 6a9.545 9.545 0 0 0-5.187 1.487A9.773 9.773 0 0 0 3.2 11.5a9.773 9.773 0 0 0 3.613 4.012A9.544 9.544 0 0 0 12 17Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_APPEARANCE_TAB_label"
|
||||
>
|
||||
Appearance
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_NOTIFICATIONS_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_NOTIFICATIONS_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 3c7 0 7 7 7 7v6l1.293 1.293c.63.63.184 1.707-.707 1.707H4.414c-.89 0-1.337-1.077-.707-1.707L5 16v-6s0-7 7-7Zm5 7.01v-.022l-.009-.146a6.591 6.591 0 0 0-.073-.607 6.608 6.608 0 0 0-.582-1.84c-.319-.638-.766-1.215-1.398-1.637C14.318 5.344 13.4 5 12 5c-1.4 0-2.317.344-2.937.758-.633.422-1.08.999-1.4 1.636a6.606 6.606 0 0 0-.58 1.841A6.596 6.596 0 0 0 7 9.988v6.84L6.828 17h10.344L17 16.828V10.01ZM12 22a2 2 0 0 1-2-2h4a2 2 0 0 1-2 2Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_NOTIFICATIONS_TAB_label"
|
||||
>
|
||||
Notifications
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_PREFERENCES_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_PREFERENCES_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M6.5 2h11a4.5 4.5 0 1 1 0 9h-11a4.5 4.5 0 0 1 0-9Zm0 2h7.258A4.479 4.479 0 0 0 13 6.5c0 .925.28 1.785.758 2.5H6.5a2.5 2.5 0 0 1 0-5ZM15 6.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0Zm-13 11A4.5 4.5 0 0 1 6.5 13h11a4.5 4.5 0 1 1 0 9h-11A4.5 4.5 0 0 1 2 17.5Zm8.242-2.5H17.5a2.5 2.5 0 0 1 0 5h-7.258A4.478 4.478 0 0 0 11 17.5c0-.925-.28-1.785-.758-2.5ZM6.5 15a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_PREFERENCES_TAB_label"
|
||||
>
|
||||
Preferences
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_KEYBOARD_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_KEYBOARD_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5.188 8v2h2V8h-2Zm3.875 0v2h2V8h-2Zm3.875 0v2h2V8h-2Zm3.875 0v2h2V8h-2ZM5.188 11.531v2h2v-2h-2Zm3.875 0v2h2v-2h-2Zm3.875 0v2h2v-2h-2Zm3.875 0v2h2v-2h-2ZM9 15a1 1 0 1 0 0 2h6a1 1 0 1 0 0-2H9Z"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M2 6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6Zm2 0h16v12H4V6Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_KEYBOARD_TAB_label"
|
||||
>
|
||||
Keyboard
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_SIDEBAR_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_SIDEBAR_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M18 3a4 4 0 0 1 4 4v10a4 4 0 0 1-4 4H6a4 4 0 0 1-4-4V7a4 4 0 0 1 4-4h12Zm-8 2h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2h-8V5ZM8 19H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h2v14Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_SIDEBAR_TAB_label"
|
||||
>
|
||||
Sidebar
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_SECURITY_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_SECURITY_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6 22c-.55 0-1.02-.196-1.412-.587A1.926 1.926 0 0 1 4 20V10c0-.55.196-1.02.588-1.412A1.926 1.926 0 0 1 6 8h1V6c0-1.383.487-2.563 1.463-3.538C9.438 1.487 10.617 1 12 1s2.563.488 3.537 1.462C16.512 3.438 17 4.617 17 6v2h1c.55 0 1.02.196 1.413.588.391.391.587.862.587 1.412v10c0 .55-.196 1.02-.587 1.413A1.926 1.926 0 0 1 18 22H6Zm0-2h12V10H6v10ZM9 8h6V6c0-.833-.292-1.542-.875-2.125A2.893 2.893 0 0 0 12 3c-.833 0-1.542.292-2.125.875A2.893 2.893 0 0 0 9 6v2Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_SECURITY_TAB_label"
|
||||
>
|
||||
Security & Privacy
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_LABS_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_LABS_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 5a1 1 0 0 1-1-1V3a1 1 0 1 1 2 0v1a1 1 0 0 1-1 1Zm-7.071-.071a1 1 0 0 1 1.414 0l.707.707A1 1 0 0 1 5.636 7.05l-.707-.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M15.734 15.325C15.316 15.795 15 16.371 15 17v2a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2v-2c0-.63-.316-1.205-.734-1.675a5 5 0 1 1 7.468 0Zm-1.493-1.33a3 3 0 1 0-4.482 0c.433.486.894 1.166 1.112 2.005h2.258c.218-.84.679-1.52 1.112-2.005ZM13 18h-2v1h2v-1Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
<path
|
||||
d="M2 12a1 1 0 0 1 1-1h1a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm18-1a1 1 0 1 0 0 2h1a1 1 0 1 0 0-2h-1Zm-3.05-5.364a1 1 0 0 0 1.414 1.414l.707-.707a1 1 0 0 0-1.414-1.414l-.707.707Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_LABS_TAB_label"
|
||||
>
|
||||
Labs
|
||||
</span>
|
||||
</li>,
|
||||
<li
|
||||
aria-controls="mx_tabpanel_USER_HELP_TAB"
|
||||
aria-selected="false"
|
||||
class="mx_AccessibleButton mx_TabbedView_tabLabel"
|
||||
data-testid="settings-tab-USER_HELP_TAB"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12 8a1.5 1.5 0 0 0-1.5 1.5 1 1 0 1 1-2 0 3.5 3.5 0 1 1 6.01 2.439c-.122.126-.24.243-.352.355-.287.288-.54.54-.76.824-.293.375-.398.651-.398.882a1 1 0 1 1-2 0c0-.874.407-1.58.819-2.11.305-.392.688-.775 1-1.085l.257-.26A1.5 1.5 0 0 0 12 8Zm1 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"
|
||||
/>
|
||||
<path
|
||||
d="M8.1 21.212A9.738 9.738 0 0 0 12 22a9.738 9.738 0 0 0 3.9-.788 10.098 10.098 0 0 0 3.175-2.137c.9-.9 1.613-1.958 2.137-3.175A9.738 9.738 0 0 0 22 12a9.738 9.738 0 0 0-.788-3.9 10.099 10.099 0 0 0-2.137-3.175c-.9-.9-1.958-1.612-3.175-2.137A9.738 9.738 0 0 0 12 2a9.738 9.738 0 0 0-3.9.788 10.099 10.099 0 0 0-3.175 2.137c-.9.9-1.612 1.958-2.137 3.175A9.738 9.738 0 0 0 2 12a9.74 9.74 0 0 0 .788 3.9 10.098 10.098 0 0 0 2.137 3.175c.9.9 1.958 1.613 3.175 2.137Zm9.575-3.537C16.125 19.225 14.233 20 12 20c-2.233 0-4.125-.775-5.675-2.325C4.775 16.125 4 14.233 4 12c0-2.233.775-4.125 2.325-5.675C7.875 4.775 9.767 4 12 4c2.233 0 4.125.775 5.675 2.325C19.225 7.875 20 9.767 20 12c0 2.233-.775 4.125-2.325 5.675Z"
|
||||
/>
|
||||
</svg>
|
||||
<span
|
||||
class="mx_TabbedView_tabLabel_text"
|
||||
id="mx_tabpanel_USER_HELP_TAB_label"
|
||||
>
|
||||
Help & About
|
||||
</span>
|
||||
</li>,
|
||||
]
|
||||
`;
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { Room, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../../../../test-utils";
|
||||
import { DevtoolsContext } from "../../../../../../src/components/views/dialogs/devtools/BaseTool";
|
||||
import { TimelineEventEditor } from "../../../../../../src/components/views/dialogs/devtools/Event";
|
||||
|
||||
describe("<EventEditor />", () => {
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const { asFragment } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<DevtoolsContext.Provider
|
||||
value={{
|
||||
room: new Room("!roomId", cli, "@alice:example.com", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<TimelineEventEditor onBack={() => {}} />
|
||||
</DevtoolsContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("thread context", () => {
|
||||
it("should pre-populate a thread relationship", () => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const { asFragment } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<DevtoolsContext.Provider
|
||||
value={{
|
||||
room: new Room("!roomId", cli, "@alice:example.com", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
}),
|
||||
threadRootId: "$this_is_a_thread_id",
|
||||
}}
|
||||
>
|
||||
<TimelineEventEditor onBack={() => {}} />
|
||||
</DevtoolsContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { Room, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import RoomNotifications from "../../../../../../src/components/views/dialogs/devtools/RoomNotifications";
|
||||
import MatrixClientContext from "../../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../../../../test-utils";
|
||||
import { DevtoolsContext } from "../../../../../../src/components/views/dialogs/devtools/BaseTool";
|
||||
|
||||
describe("<RoomNotifications />", () => {
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const { asFragment } = render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<DevtoolsContext.Provider
|
||||
value={{
|
||||
room: new Room("!roomId", cli, "@alice:example.com", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RoomNotifications onBack={() => {}} setTool={() => {}} />
|
||||
</DevtoolsContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<EventEditor /> should render 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_DevTools_content"
|
||||
>
|
||||
<div
|
||||
class="mx_DevTools_eventTypeStateKeyGroup"
|
||||
>
|
||||
<div
|
||||
class="mx_Field mx_Field_input"
|
||||
>
|
||||
<input
|
||||
autocomplete="on"
|
||||
id="eventType"
|
||||
label="Event Type"
|
||||
placeholder="Event Type"
|
||||
size="42"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="eventType"
|
||||
>
|
||||
Event Type
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Field mx_Field_textarea mx_DevTools_textarea"
|
||||
>
|
||||
<textarea
|
||||
autocomplete="off"
|
||||
id="evContent"
|
||||
label="Event Content"
|
||||
placeholder="Event Content"
|
||||
type="text"
|
||||
>
|
||||
{
|
||||
|
||||
}
|
||||
</textarea>
|
||||
<label
|
||||
for="evContent"
|
||||
>
|
||||
Event Content
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<button>
|
||||
Back
|
||||
</button>
|
||||
<button>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`<EventEditor /> thread context should pre-populate a thread relationship 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_DevTools_content"
|
||||
>
|
||||
<div
|
||||
class="mx_DevTools_eventTypeStateKeyGroup"
|
||||
>
|
||||
<div
|
||||
class="mx_Field mx_Field_input"
|
||||
>
|
||||
<input
|
||||
autocomplete="on"
|
||||
id="eventType"
|
||||
label="Event Type"
|
||||
placeholder="Event Type"
|
||||
size="42"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="eventType"
|
||||
>
|
||||
Event Type
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Field mx_Field_textarea mx_DevTools_textarea"
|
||||
>
|
||||
<textarea
|
||||
autocomplete="off"
|
||||
id="evContent"
|
||||
label="Event Content"
|
||||
placeholder="Event Content"
|
||||
type="text"
|
||||
>
|
||||
{
|
||||
"m.relates_to": {
|
||||
"rel_type": "m.thread",
|
||||
"event_id": "$this_is_a_thread_id"
|
||||
}
|
||||
}
|
||||
</textarea>
|
||||
<label
|
||||
for="evContent"
|
||||
>
|
||||
Event Content
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<button>
|
||||
Back
|
||||
</button>
|
||||
<button>
|
||||
Send
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
|
@ -0,0 +1,72 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<RoomNotifications /> should render 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_DevTools_content"
|
||||
>
|
||||
<section>
|
||||
<h2>
|
||||
Room status
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<span>
|
||||
Room unread status:
|
||||
<strong>
|
||||
None
|
||||
</strong>
|
||||
, count:
|
||||
<strong>
|
||||
0
|
||||
</strong>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
Notification state is
|
||||
<strong />
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span>
|
||||
Room is
|
||||
<strong>
|
||||
not encrypted 🚨
|
||||
</strong>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>
|
||||
Main timeline
|
||||
</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Total: 0
|
||||
</li>
|
||||
<li>
|
||||
Highlight: 0
|
||||
</li>
|
||||
<li>
|
||||
Dot: false
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
<h2>
|
||||
Threads timeline
|
||||
</h2>
|
||||
<ul />
|
||||
</section>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<button>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import CreateKeyBackupDialog from "../../../../../../src/async-components/views/dialogs/security/CreateKeyBackupDialog";
|
||||
import { createTestClient } from "../../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
|
||||
jest.mock("../../../../../../src/SecurityManager", () => ({
|
||||
accessSecretStorage: jest.fn().mockResolvedValue(undefined),
|
||||
withSecretStorageKeyCache: jest.fn().mockImplementation((fn) => fn()),
|
||||
}));
|
||||
|
||||
describe("CreateKeyBackupDialog", () => {
|
||||
beforeEach(() => {
|
||||
MatrixClientPeg.safeGet = MatrixClientPeg.get = () => createTestClient();
|
||||
});
|
||||
|
||||
it("should display the spinner when creating backup", () => {
|
||||
const { asFragment } = render(<CreateKeyBackupDialog onFinished={jest.fn()} />);
|
||||
|
||||
// Check if the spinner is displayed
|
||||
expect(screen.getByTestId("spinner")).toBeDefined();
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display an error message when backup creation failed", async () => {
|
||||
const matrixClient = createTestClient();
|
||||
mocked(matrixClient.hasSecretStorageKey).mockResolvedValue(true);
|
||||
mocked(matrixClient.getCrypto()!.resetKeyBackup).mockImplementation(() => {
|
||||
throw new Error("failed");
|
||||
});
|
||||
MatrixClientPeg.safeGet = MatrixClientPeg.get = () => matrixClient;
|
||||
|
||||
const { asFragment } = render(<CreateKeyBackupDialog onFinished={jest.fn()} />);
|
||||
|
||||
// Check if the error message is displayed
|
||||
await waitFor(() => expect(screen.getByText("Unable to create key backup")).toBeDefined());
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should display an error message when there is no Crypto available", async () => {
|
||||
const matrixClient = createTestClient();
|
||||
mocked(matrixClient.hasSecretStorageKey).mockResolvedValue(true);
|
||||
mocked(matrixClient.getCrypto).mockReturnValue(undefined);
|
||||
MatrixClientPeg.safeGet = MatrixClientPeg.get = () => matrixClient;
|
||||
|
||||
render(<CreateKeyBackupDialog onFinished={jest.fn()} />);
|
||||
|
||||
// Check if the error message is displayed
|
||||
await waitFor(() => expect(screen.getByText("Unable to create key backup")).toBeDefined());
|
||||
});
|
||||
|
||||
it("should display the success dialog when the key backup is finished", async () => {
|
||||
const onFinished = jest.fn();
|
||||
const { asFragment } = render(<CreateKeyBackupDialog onFinished={onFinished} />);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
screen.getByText("Your keys are being backed up (the first backup could take a few minutes)."),
|
||||
).toBeDefined(),
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
|
||||
// Click on the OK button
|
||||
screen.getByRole("button", { name: "OK" }).click();
|
||||
expect(onFinished).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React from "react";
|
||||
import { mocked, MockedObject } from "jest-mock";
|
||||
import { Crypto, MatrixClient, MatrixError } from "matrix-js-sdk/src/matrix";
|
||||
import { defer, IDeferred, sleep } from "matrix-js-sdk/src/utils";
|
||||
import { BackupTrustInfo, KeyBackupInfo } from "matrix-js-sdk/src/crypto-api";
|
||||
|
||||
import {
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
getMockClientWithEventEmitter,
|
||||
mockClientMethodsCrypto,
|
||||
mockClientMethodsServer,
|
||||
} from "../../../../../test-utils";
|
||||
import CreateSecretStorageDialog from "../../../../../../src/async-components/views/dialogs/security/CreateSecretStorageDialog";
|
||||
import Modal from "../../../../../../src/Modal";
|
||||
import RestoreKeyBackupDialog from "../../../../../../src/components/views/dialogs/security/RestoreKeyBackupDialog";
|
||||
|
||||
describe("CreateSecretStorageDialog", () => {
|
||||
let mockClient: MockedObject<MatrixClient>;
|
||||
let mockCrypto: MockedObject<Crypto.CryptoApi>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = getMockClientWithEventEmitter({
|
||||
...mockClientMethodsServer(),
|
||||
...mockClientMethodsCrypto(),
|
||||
uploadDeviceSigningKeys: jest.fn().mockImplementation(async () => {
|
||||
await sleep(0); // CreateSecretStorageDialog doesn't expect this to resolve immediately
|
||||
throw new MatrixError({ flows: [] });
|
||||
}),
|
||||
});
|
||||
|
||||
mockCrypto = mocked(mockClient.getCrypto()!);
|
||||
Object.assign(mockCrypto, {
|
||||
isKeyBackupTrusted: jest.fn(),
|
||||
isDehydrationSupported: jest.fn(() => false),
|
||||
bootstrapCrossSigning: jest.fn(),
|
||||
bootstrapSecretStorage: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
function renderComponent(
|
||||
props: Partial<React.ComponentProps<typeof CreateSecretStorageDialog>> = {},
|
||||
): RenderResult {
|
||||
const onFinished = jest.fn();
|
||||
return render(<CreateSecretStorageDialog onFinished={onFinished} {...props} />);
|
||||
}
|
||||
|
||||
it("shows a loading spinner initially", async () => {
|
||||
const { container } = renderComponent();
|
||||
expect(screen.getByTestId("spinner")).toBeDefined();
|
||||
expect(container).toMatchSnapshot();
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
describe("when there is an error fetching the backup version", () => {
|
||||
filterConsole("Error fetching backup data from server");
|
||||
|
||||
it("shows an error", async () => {
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
throw new Error("bleh bleh");
|
||||
});
|
||||
|
||||
const result = renderComponent();
|
||||
// XXX the error message is... misleading.
|
||||
await result.findByText("Unable to query secret storage status");
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("shows 'Generate a Security Key' text if no key backup is present", async () => {
|
||||
const result = renderComponent();
|
||||
await flushPromises();
|
||||
expect(result.container).toMatchSnapshot();
|
||||
result.getByText("Generate a Security Key");
|
||||
});
|
||||
|
||||
describe("when canUploadKeysWithPasswordOnly", () => {
|
||||
// spy on Modal.createDialog
|
||||
let modalSpy: jest.SpyInstance;
|
||||
|
||||
// deferred which should be resolved to indicate that the created dialog has completed
|
||||
let restoreDialogFinishedDefer: IDeferred<[done?: boolean]>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
mockClient.uploadDeviceSigningKeys.mockImplementation(async () => {
|
||||
await sleep(0);
|
||||
throw new MatrixError({
|
||||
flows: [{ stages: ["m.login.password"] }],
|
||||
});
|
||||
});
|
||||
|
||||
restoreDialogFinishedDefer = defer<[done?: boolean]>();
|
||||
modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({
|
||||
finished: restoreDialogFinishedDefer.promise,
|
||||
close: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
it("prompts for a password and then shows RestoreKeyBackupDialog", async () => {
|
||||
const result = renderComponent();
|
||||
await result.findByText(/Enter your account password to confirm the upgrade/);
|
||||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
await userEvent.type(result.getByPlaceholderText("Password"), "my pass");
|
||||
result.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
expect(modalSpy).toHaveBeenCalledWith(
|
||||
RestoreKeyBackupDialog,
|
||||
{
|
||||
keyCallback: expect.any(Function),
|
||||
showSummary: false,
|
||||
},
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
restoreDialogFinishedDefer.resolve([]);
|
||||
});
|
||||
|
||||
it("calls bootstrapSecretStorage once keys are restored if the backup is now trusted", async () => {
|
||||
mockClient.isCryptoEnabled.mockReturnValue(true);
|
||||
|
||||
const result = renderComponent();
|
||||
await result.findByText(/Enter your account password to confirm the upgrade/);
|
||||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
await userEvent.type(result.getByPlaceholderText("Password"), "my pass");
|
||||
result.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
|
||||
// While we restore the key backup, its signature becomes accepted
|
||||
mockCrypto.isKeyBackupTrusted.mockResolvedValue({ trusted: true } as BackupTrustInfo);
|
||||
|
||||
restoreDialogFinishedDefer.resolve([]);
|
||||
await flushPromises();
|
||||
|
||||
// XXX no idea why this is a sensible thing to do. I just work here.
|
||||
expect(mockCrypto.bootstrapCrossSigning).toHaveBeenCalled();
|
||||
expect(mockCrypto.bootstrapSecretStorage).toHaveBeenCalled();
|
||||
|
||||
await result.findByText("Your keys are now being backed up from this device.");
|
||||
});
|
||||
|
||||
describe("when there is an error fetching the backup version after RestoreKeyBackupDialog", () => {
|
||||
filterConsole("Error fetching backup data from server");
|
||||
|
||||
it("handles the error sensibly", async () => {
|
||||
const result = renderComponent();
|
||||
await result.findByText(/Enter your account password to confirm the upgrade/);
|
||||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
await userEvent.type(result.getByPlaceholderText("Password"), "my pass");
|
||||
result.getByRole("button", { name: "Next" }).click();
|
||||
|
||||
expect(modalSpy).toHaveBeenCalled();
|
||||
|
||||
mockClient.getKeyBackupVersion.mockImplementation(async () => {
|
||||
throw new Error("bleh bleh");
|
||||
});
|
||||
restoreDialogFinishedDefer.resolve([]);
|
||||
await result.findByText("Unable to query secret storage status");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when backup is present but not trusted", () => {
|
||||
beforeEach(() => {
|
||||
mockClient.getKeyBackupVersion.mockResolvedValue({} as KeyBackupInfo);
|
||||
});
|
||||
|
||||
it("shows migrate text, then 'RestoreKeyBackupDialog' if 'Restore' is clicked", async () => {
|
||||
const result = renderComponent();
|
||||
await result.findByText("Restore your key backup to upgrade your encryption");
|
||||
expect(result.container).toMatchSnapshot();
|
||||
|
||||
// before we click "Restore", set up a spy on createDialog
|
||||
const restoreDialogFinishedDefer = defer<[done?: boolean]>();
|
||||
const modalSpy = jest.spyOn(Modal, "createDialog").mockReturnValue({
|
||||
finished: restoreDialogFinishedDefer.promise,
|
||||
close: jest.fn(),
|
||||
});
|
||||
|
||||
result.getByRole("button", { name: "Restore" }).click();
|
||||
|
||||
expect(modalSpy).toHaveBeenCalledWith(
|
||||
RestoreKeyBackupDialog,
|
||||
{
|
||||
keyCallback: expect.any(Function),
|
||||
showSummary: false,
|
||||
},
|
||||
undefined,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
// simulate RestoreKeyBackupDialog completing, to run that code path
|
||||
restoreDialogFinishedDefer.resolve([]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
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
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { screen, fireEvent, render, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { Crypto, IMegolmSessionData } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
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", () => {
|
||||
const cli = createTestClient();
|
||||
const onFinished = jest.fn();
|
||||
const { container } = render(<ExportE2eKeysDialog matrixClient={cli} onFinished={onFinished} />);
|
||||
fireEvent.click(container.querySelector("[type=submit]")!);
|
||||
expect(screen.getByText("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");
|
||||
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$$");
|
||||
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 Crypto.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);
|
||||
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
|
||||
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 { Crypto } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
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 Crypto.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,51 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { screen, render, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
// 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 />", () => {
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
jest.spyOn(recoveryKeyModule, "decodeRecoveryKey").mockReturnValue(new Uint8Array(32));
|
||||
});
|
||||
|
||||
it("should render", async () => {
|
||||
const { asFragment } = render(<RestoreKeyBackupDialog onFinished={jest.fn()} />);
|
||||
await waitFor(() => expect(screen.getByText("Enter Security 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 Security Key")).toBeInTheDocument());
|
||||
|
||||
await userEvent.type(screen.getByRole("textbox"), "invalid key");
|
||||
await waitFor(() => expect(screen.getByText("👎 Not a valid Security 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 Security Key")).toBeInTheDocument());
|
||||
|
||||
await userEvent.type(screen.getByRole("textbox"), "valid key");
|
||||
await waitFor(() => expect(screen.getByText("👍 This looks like a valid Security Key!")).toBeInTheDocument());
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,168 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreateKeyBackupDialog should display an error message when backup creation failed 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_CreateKeyBackupDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Starting backup…
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
Unable to create key backup
|
||||
</p>
|
||||
<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
|
||||
data-focus-guard="true"
|
||||
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||
tabindex="0"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`CreateKeyBackupDialog should display the spinner when creating backup 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_CreateKeyBackupDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Starting backup…
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`CreateKeyBackupDialog should display the success dialog when the key backup is finished 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_CreateKeyBackupDialog mx_Dialog_fixedWidth"
|
||||
data-focus-lock-disabled="false"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Success!
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
Your keys are being backed up (the first backup could take a few minutes).
|
||||
</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"
|
||||
/>
|
||||
</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,551 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CreateSecretStorageDialog shows 'Generate a Security Key' text if no key backup is present 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"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<span
|
||||
class="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup"
|
||||
/>
|
||||
Generate a Security Key
|
||||
</div>
|
||||
<div>
|
||||
We'll generate a Security 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"
|
||||
>
|
||||
<span
|
||||
class="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase"
|
||||
/>
|
||||
Enter a Security Phrase
|
||||
</div>
|
||||
<div>
|
||||
Use a secret phrase only you know, and optionally save a Security 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 shows a loading spinner initially 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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
/>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 32px; height: 32px;"
|
||||
/>
|
||||
</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 backup is present but not trusted shows migrate text, then 'RestoreKeyBackupDialog' if 'Restore' is clicked 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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Upgrade your encryption
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<form>
|
||||
<p>
|
||||
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
Restore your key backup to upgrade your encryption
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
class="danger"
|
||||
type="button"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
type="button"
|
||||
>
|
||||
Restore
|
||||
</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 when canUploadKeysWithPasswordOnly calls bootstrapSecretStorage once keys are restored if the backup is now trusted 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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Upgrade your encryption
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<form>
|
||||
<p>
|
||||
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
Enter your account password to confirm the upgrade:
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Field mx_Field_input"
|
||||
>
|
||||
<input
|
||||
id="mx_CreateSecretStorageDialog_password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_CreateSecretStorageDialog_password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
class="danger"
|
||||
type="button"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Next
|
||||
</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 when canUploadKeysWithPasswordOnly prompts for a password and then shows RestoreKeyBackupDialog 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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Upgrade your encryption
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<form>
|
||||
<p>
|
||||
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
Enter your account password to confirm the upgrade:
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Field mx_Field_input"
|
||||
>
|
||||
<input
|
||||
id="mx_CreateSecretStorageDialog_password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_CreateSecretStorageDialog_password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
class="danger"
|
||||
type="button"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Next
|
||||
</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 when canUploadKeysWithPasswordOnly when there is an error fetching the backup version after RestoreKeyBackupDialog handles the error sensibly 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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Upgrade your encryption
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<form>
|
||||
<p>
|
||||
Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.
|
||||
</p>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
Enter your account password to confirm the upgrade:
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mx_Field mx_Field_input"
|
||||
>
|
||||
<input
|
||||
id="mx_CreateSecretStorageDialog_password"
|
||||
label="Password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<label
|
||||
for="mx_CreateSecretStorageDialog_password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_Dialog_buttons"
|
||||
>
|
||||
<span
|
||||
class="mx_Dialog_buttons_row"
|
||||
>
|
||||
<button
|
||||
class="danger"
|
||||
type="button"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
<button
|
||||
class="mx_Dialog_primary"
|
||||
data-testid="dialog-primary-button"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
Next
|
||||
</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 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"
|
||||
>
|
||||
<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,112 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</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,113 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</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,298 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Enter Security 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 Security Key.
|
||||
</p>
|
||||
<div
|
||||
class="mx_RestoreKeyBackupDialog_primaryContainer"
|
||||
>
|
||||
<input
|
||||
class="mx_RestoreKeyBackupDialog_recoveryKeyInput"
|
||||
value="invalid key"
|
||||
/>
|
||||
<div
|
||||
class="mx_RestoreKeyBackupDialog_keyStatus"
|
||||
>
|
||||
👎 Not a valid Security 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 Security 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"
|
||||
/>
|
||||
</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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Enter Security 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 Security 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 Security 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 Security 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"
|
||||
/>
|
||||
</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"
|
||||
>
|
||||
<div
|
||||
class="mx_Dialog_header"
|
||||
>
|
||||
<h1
|
||||
class="mx_Heading_h3 mx_Dialog_title"
|
||||
id="mx_BaseDialog_title"
|
||||
>
|
||||
Enter Security 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 Security 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 Security 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"
|
||||
/>
|
||||
</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,65 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { IPublicRoomsChunkRoom } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { PublicRoomResultDetails } from "../../../../../../src/components/views/dialogs/spotlight/PublicRoomResultDetails";
|
||||
|
||||
describe("PublicRoomResultDetails", () => {
|
||||
it("renders", () => {
|
||||
const { asFragment } = render(
|
||||
<PublicRoomResultDetails
|
||||
room={{
|
||||
room_id: "room-id",
|
||||
name: "hello?",
|
||||
canonical_alias: "canonical-alias",
|
||||
world_readable: true,
|
||||
guest_can_join: false,
|
||||
num_joined_members: 666,
|
||||
}}
|
||||
labelId="label-id"
|
||||
descriptionId="description-id"
|
||||
detailsId="details-id"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ canonical_alias: "canonical-alias" },
|
||||
{ aliases: ["alias-from-aliases"] },
|
||||
{ name: "name over alias", canonical_alias: "canonical-alias" },
|
||||
{
|
||||
name: "with an overly long name that will be truncated for sure, you can't say anything about it",
|
||||
topic: "with a topic!",
|
||||
},
|
||||
{ topic: "Very long topic " + new Array(1337).join("a") },
|
||||
])("Public room results", (partialPublicRoomChunk: Partial<IPublicRoomsChunkRoom>) => {
|
||||
const roomChunk: IPublicRoomsChunkRoom = {
|
||||
room_id: "room-id",
|
||||
world_readable: true,
|
||||
guest_can_join: false,
|
||||
num_joined_members: 666,
|
||||
...partialPublicRoomChunk,
|
||||
};
|
||||
|
||||
const { asFragment } = render(
|
||||
<PublicRoomResultDetails
|
||||
room={roomChunk}
|
||||
labelId="label-id"
|
||||
descriptionId="description-id"
|
||||
detailsId="details-id"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 Mikhail Aheichyk
|
||||
Copyright 2023 Nordeck IT + Consulting GmbH.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, screen, RenderResult } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { Room, MatrixClient, PendingEventOrdering } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { RoomResultContextMenus } from "../../../../../../src/components/views/dialogs/spotlight/RoomResultContextMenus";
|
||||
import { filterConsole, stubClient } from "../../../../../test-utils";
|
||||
import { shouldShowComponent } from "../../../../../../src/customisations/helpers/UIComponents";
|
||||
import { UIComponent } from "../../../../../../src/settings/UIFeature";
|
||||
|
||||
jest.mock("../../../../../../src/customisations/helpers/UIComponents", () => ({
|
||||
shouldShowComponent: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("RoomResultContextMenus", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const renderRoomResultContextMenus = (): RenderResult => {
|
||||
return render(<RoomResultContextMenus room={room} />);
|
||||
};
|
||||
|
||||
filterConsole(
|
||||
// irrelevant for this test
|
||||
"Room !1:example.org does not have an m.room.create event",
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room("!1:example.org", client, "@alice:example.org", {
|
||||
pendingEventOrdering: PendingEventOrdering.Detached,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not render the room options context menu when UIComponent customisations disable room options", () => {
|
||||
mocked(shouldShowComponent).mockReturnValue(false);
|
||||
renderRoomResultContextMenus();
|
||||
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu);
|
||||
expect(screen.queryByRole("button", { name: "Room options" })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the room options context menu when UIComponent customisations enable room options", () => {
|
||||
mocked(shouldShowComponent).mockReturnValue(true);
|
||||
renderRoomResultContextMenus();
|
||||
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.RoomOptionsMenu);
|
||||
expect(screen.queryByRole("button", { name: "Room options" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,223 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PublicRoomResultDetails Public room results 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||
>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomName"
|
||||
id="label-id"
|
||||
>
|
||||
canonical-alias
|
||||
</span>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||
id="description-id"
|
||||
>
|
||||
canonical-alias
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||
id="details-id"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||
>
|
||||
666 Members
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PublicRoomResultDetails Public room results 2`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||
>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomName"
|
||||
id="label-id"
|
||||
>
|
||||
alias-from-aliases
|
||||
</span>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||
id="description-id"
|
||||
>
|
||||
room-id
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||
id="details-id"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||
>
|
||||
666 Members
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PublicRoomResultDetails Public room results 3`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||
>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomName"
|
||||
id="label-id"
|
||||
>
|
||||
name over alias
|
||||
</span>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||
id="description-id"
|
||||
>
|
||||
canonical-alias
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||
id="details-id"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||
>
|
||||
666 Members
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PublicRoomResultDetails Public room results 4`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||
>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomName"
|
||||
id="label-id"
|
||||
>
|
||||
with an overly long name that will be truncated for sure, you can't say anything...
|
||||
</span>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||
id="description-id"
|
||||
>
|
||||
room-id
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||
id="details-id"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||
>
|
||||
666 Members
|
||||
</span>
|
||||
·
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomTopic"
|
||||
>
|
||||
with a topic!
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PublicRoomResultDetails Public room results 5`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||
>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomName"
|
||||
id="label-id"
|
||||
>
|
||||
Unnamed Room
|
||||
</span>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||
id="description-id"
|
||||
>
|
||||
room-id
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||
id="details-id"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||
>
|
||||
666 Members
|
||||
</span>
|
||||
·
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomTopic"
|
||||
>
|
||||
Very long topic aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`PublicRoomResultDetails renders 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDetails"
|
||||
>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomHeader"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomName"
|
||||
id="label-id"
|
||||
>
|
||||
hello?
|
||||
</span>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomAlias"
|
||||
id="description-id"
|
||||
>
|
||||
canonical-alias
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SpotlightDialog_result_publicRoomDescription"
|
||||
id="details-id"
|
||||
>
|
||||
<span
|
||||
class="mx_SpotlightDialog_result_publicRoomMemberCount"
|
||||
>
|
||||
666 Members
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</DocumentFragment>
|
||||
`;
|
Loading…
Add table
Add a link
Reference in a new issue