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:
Michael Telatynski 2024-10-15 14:57:26 +01:00
commit f0ee7f7905
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
3265 changed files with 484599 additions and 699 deletions

View file

@ -0,0 +1,417 @@
/*
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 { fireEvent, render, screen, within } from "jest-matrix-react";
import React from "react";
import { MatrixClient, ThreepidMedium } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import userEvent from "@testing-library/user-event";
import { MockedObject } from "jest-mock";
import AccountUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/AccountUserSettingsTab";
import { SdkContextClass, SDKContext } from "../../../../../../../src/contexts/SDKContext";
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
import {
getMockClientWithEventEmitter,
mockClientMethodsServer,
mockClientMethodsUser,
mockPlatformPeg,
flushPromises,
} from "../../../../../../test-utils";
import { UIFeature } from "../../../../../../../src/settings/UIFeature";
import { OidcClientStore } from "../../../../../../../src/stores/oidc/OidcClientStore";
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
import Modal from "../../../../../../../src/Modal";
let changePasswordOnError: (e: Error) => void;
let changePasswordOnFinished: () => void;
jest.mock(
"../../../../../../../src/components/views/settings/ChangePassword",
() =>
({ onError, onFinished }: { onError: (e: Error) => void; onFinished: () => void }) => {
changePasswordOnError = onError;
changePasswordOnFinished = onFinished;
return <button>Mock change password</button>;
},
);
describe("<AccountUserSettingsTab />", () => {
const defaultProps = {
closeSettingsFn: jest.fn(),
};
const userId = "@alice:server.org";
let mockClient: MockedObject<MatrixClient>;
let stores: SdkContextClass;
const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}>
<SDKContext.Provider value={stores}>
<AccountUserSettingsTab {...defaultProps} />
</SDKContext.Provider>
</MatrixClientContext.Provider>
);
beforeEach(() => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
mockPlatformPeg();
jest.clearAllMocks();
jest.spyOn(SettingsStore, "getValue").mockRestore();
jest.spyOn(logger, "error").mockRestore();
mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
getCapabilities: jest.fn(),
getThreePids: jest.fn(),
getIdentityServerUrl: jest.fn(),
deleteThreePid: jest.fn(),
});
mockClient.getCapabilities.mockResolvedValue({});
mockClient.getThreePids.mockResolvedValue({
threepids: [],
});
mockClient.deleteThreePid.mockResolvedValue({
id_server_unbind_result: "success",
});
stores = new SdkContextClass();
stores.client = mockClient;
// stub out this store completely to avoid mocking initialisation
const mockOidcClientStore = {} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
});
afterEach(() => {
jest.restoreAllMocks();
});
it("does not show account management link when not available", () => {
const { queryByTestId } = render(getComponent());
expect(queryByTestId("external-account-management-outer")).toBeFalsy();
expect(queryByTestId("external-account-management-link")).toBeFalsy();
});
it("show account management link in expected format", async () => {
const accountManagementLink = "https://id.server.org/my-account";
const mockOidcClientStore = {
accountManagementEndpoint: accountManagementLink,
} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
render(getComponent());
const manageAccountLink = await screen.findByRole("button", { name: "Manage account" });
expect(manageAccountLink.getAttribute("href")).toMatch(accountManagementLink);
});
describe("deactive account", () => {
it("should not render section when account deactivation feature is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName !== UIFeature.Deactivate,
);
render(getComponent());
expect(screen.queryByText("Deactivate Account")).not.toBeInTheDocument();
expect(SettingsStore.getValue).toHaveBeenCalledWith(UIFeature.Deactivate);
});
it("should not render section when account is managed externally", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Deactivate,
);
// account is managed externally when we have delegated auth configured
const accountManagementLink = "https://id.server.org/my-account";
const mockOidcClientStore = {
accountManagementEndpoint: accountManagementLink,
} as unknown as OidcClientStore;
jest.spyOn(stores, "oidcClientStore", "get").mockReturnValue(mockOidcClientStore);
render(getComponent());
await flushPromises();
expect(screen.queryByText("Deactivate Account")).not.toBeInTheDocument();
});
it("should render section when account deactivation feature is enabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Deactivate,
);
render(getComponent());
expect(screen.getByText("Deactivate Account", { selector: "h2" }).parentElement!).toMatchSnapshot();
});
it("should display the deactivate account dialog when clicked", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Deactivate,
);
const createDialogFn = jest.fn();
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
render(getComponent());
await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
expect(createDialogFn).toHaveBeenCalled();
});
it("should close settings if account deactivated", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Deactivate,
);
const createDialogFn = jest.fn();
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
render(getComponent());
await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
createDialogFn.mock.calls[0][1].onFinished(true);
expect(defaultProps.closeSettingsFn).toHaveBeenCalled();
});
it("should not close settings if account not deactivated", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation(
(settingName) => settingName === UIFeature.Deactivate,
);
const createDialogFn = jest.fn();
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
render(getComponent());
await userEvent.click(screen.getByRole("button", { name: "Deactivate Account" }));
createDialogFn.mock.calls[0][1].onFinished(false);
expect(defaultProps.closeSettingsFn).not.toHaveBeenCalled();
});
});
describe("3pids", () => {
beforeEach(() => {
mockClient.getCapabilities.mockResolvedValue({
"m.3pid_changes": {
enabled: true,
},
});
mockClient.getThreePids.mockResolvedValue({
threepids: [
{
medium: ThreepidMedium.Email,
address: "test@test.io",
validated_at: 1685067124552,
added_at: 1685067124552,
},
{
medium: ThreepidMedium.Phone,
address: "123456789",
validated_at: 1685067124552,
added_at: 1685067124552,
},
],
});
mockClient.getIdentityServerUrl.mockReturnValue(undefined);
});
it("should show loaders while 3pids load", () => {
render(getComponent());
expect(
within(screen.getByTestId("mx_AccountEmailAddresses")).getByLabelText("Loading…"),
).toBeInTheDocument();
expect(within(screen.getByTestId("mx_AccountPhoneNumbers")).getByLabelText("Loading…")).toBeInTheDocument();
});
it("should display 3pid email addresses and phone numbers", async () => {
render(getComponent());
await flushPromises();
expect(screen.getByTestId("mx_AccountEmailAddresses")).toMatchSnapshot();
expect(screen.getByTestId("mx_AccountPhoneNumbers")).toMatchSnapshot();
});
it("should allow removing an existing email addresses", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountEmailAddresses");
fireEvent.click(within(section).getByText("Remove"));
// confirm removal
expect(screen.getByText("Remove test@test.io?")).toBeInTheDocument();
fireEvent.click(within(section).getByText("Remove"));
expect(mockClient.deleteThreePid).toHaveBeenCalledWith(ThreepidMedium.Email, "test@test.io");
});
it("should allow adding a new email address", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountEmailAddresses");
// just check the fields are enabled
expect(within(section).getByLabelText("Email Address")).not.toBeDisabled();
expect(within(section).getByText("Add")).not.toHaveAttribute("aria-disabled");
});
it("should allow removing an existing phone number", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountPhoneNumbers");
fireEvent.click(within(section).getByText("Remove"));
// confirm removal
expect(screen.getByText("Remove 123456789?")).toBeInTheDocument();
fireEvent.click(within(section).getByText("Remove"));
expect(mockClient.deleteThreePid).toHaveBeenCalledWith(ThreepidMedium.Phone, "123456789");
});
it("should allow adding a new phone number", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountPhoneNumbers");
// just check the fields are enabled
expect(within(section).getByLabelText("Phone Number")).not.toBeDisabled();
});
it("should allow 3pid changes when capabilities does not have 3pid_changes", async () => {
// We support as far back as v1.1 which doesn't have m.3pid_changes
// so the behaviour for when it is missing has to be assume true
mockClient.getCapabilities.mockResolvedValue({});
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountEmailAddresses");
// just check the fields are enabled
expect(within(section).getByLabelText("Email Address")).not.toBeDisabled();
expect(within(section).getByText("Add")).not.toHaveAttribute("aria-disabled");
});
describe("when 3pid changes capability is disabled", () => {
beforeEach(() => {
mockClient.getCapabilities.mockResolvedValue({
"m.3pid_changes": {
enabled: false,
},
});
});
it("should not allow removing email addresses", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountEmailAddresses");
expect(within(section).getByText("Remove")).toHaveAttribute("aria-disabled");
});
it("should not allow adding a new email addresses", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountEmailAddresses");
// fields are not enabled
expect(within(section).getByLabelText("Email Address")).toBeDisabled();
expect(within(section).getByText("Add")).toHaveAttribute("aria-disabled");
});
it("should not allow removing phone numbers", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountPhoneNumbers");
expect(within(section).getByText("Remove")).toHaveAttribute("aria-disabled");
});
it("should not allow adding a new phone number", async () => {
render(getComponent());
await flushPromises();
const section = screen.getByTestId("mx_AccountPhoneNumbers");
expect(within(section).getByLabelText("Phone Number")).toBeDisabled();
});
});
});
describe("Password change", () => {
beforeEach(() => {
mockClient.getCapabilities.mockResolvedValue({
"m.change_password": {
enabled: true,
},
});
});
it("should display a dialog if password change succeeded", async () => {
const createDialogFn = jest.fn();
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
render(getComponent());
const changeButton = await screen.findByRole("button", { name: "Mock change password" });
userEvent.click(changeButton);
expect(changePasswordOnFinished).toBeDefined();
changePasswordOnFinished();
expect(createDialogFn).toHaveBeenCalledWith(expect.anything(), {
title: "Success",
description: "Your password was successfully changed.",
});
});
it("should display an error if password change failed", async () => {
const ERROR_STRING =
"Your password must contain exactly 5 lowercase letters, a box drawing character and the badger emoji.";
const createDialogFn = jest.fn();
jest.spyOn(Modal, "createDialog").mockImplementation(createDialogFn);
render(getComponent());
const changeButton = await screen.findByRole("button", { name: "Mock change password" });
userEvent.click(changeButton);
expect(changePasswordOnError).toBeDefined();
changePasswordOnError(new Error(ERROR_STRING));
expect(createDialogFn).toHaveBeenCalledWith(expect.anything(), {
title: "Error changing password",
description: ERROR_STRING,
});
});
});
});

View file

@ -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 { render } from "jest-matrix-react";
import React from "react";
import { MatrixClient } from "matrix-js-sdk/src/matrix";
import AppearanceUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/AppearanceUserSettingsTab";
import { withClientContextRenderOptions, stubClient } from "../../../../../../test-utils";
describe("AppearanceUserSettingsTab", () => {
let client: MatrixClient;
beforeEach(() => {
client = stubClient();
});
it("should render", () => {
const { asFragment } = render(<AppearanceUserSettingsTab />, withClientContextRenderOptions(client));
expect(asFragment()).toMatchSnapshot();
});
});

View file

@ -0,0 +1,99 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2022 Šimon Brandner <simon.bra.ag@gmail.com>
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 } from "jest-matrix-react";
import React from "react";
import KeyboardUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/KeyboardUserSettingsTab";
import { Key } from "../../../../../../../src/Keyboard";
import { mockPlatformPeg } from "../../../../../../test-utils/platform";
const PATH_TO_KEYBOARD_SHORTCUTS = "../../../../../../../src/accessibility/KeyboardShortcuts";
const PATH_TO_KEYBOARD_SHORTCUT_UTILS = "../../../../../../../src/accessibility/KeyboardShortcutUtils";
const mockKeyboardShortcuts = (override: Record<string, any>) => {
jest.doMock(PATH_TO_KEYBOARD_SHORTCUTS, () => {
const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUTS);
return {
...original,
...override,
};
});
};
const mockKeyboardShortcutUtils = (override: Record<string, any>) => {
jest.doMock(PATH_TO_KEYBOARD_SHORTCUT_UTILS, () => {
const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUT_UTILS);
return {
...original,
...override,
};
});
};
const renderKeyboardUserSettingsTab = () => {
return render(<KeyboardUserSettingsTab />).container;
};
describe("KeyboardUserSettingsTab", () => {
beforeEach(() => {
jest.resetModules();
mockPlatformPeg();
});
it("renders list of keyboard shortcuts", () => {
mockKeyboardShortcuts({
CATEGORIES: {
Composer: {
settingNames: ["keybind1", "keybind2"],
categoryLabel: "Composer",
},
Navigation: {
settingNames: ["keybind3"],
categoryLabel: "Navigation",
},
},
});
mockKeyboardShortcutUtils({
getKeyboardShortcutValue: (name: string) => {
switch (name) {
case "keybind1":
return {
key: Key.A,
ctrlKey: true,
};
case "keybind2": {
return {
key: Key.B,
ctrlKey: true,
};
}
case "keybind3": {
return {
key: Key.ENTER,
};
}
}
},
getKeyboardShortcutDisplayName: (name: string) => {
switch (name) {
case "keybind1":
return "Cancel replying to a message";
case "keybind2":
return "Toggle Bold";
case "keybind3":
return "Select room from the room list";
}
},
});
const body = renderKeyboardUserSettingsTab();
expect(body).toMatchSnapshot();
});
});

View file

@ -0,0 +1,56 @@
/*
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, screen } from "jest-matrix-react";
import LabsUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/LabsUserSettingsTab";
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
import SdkConfig from "../../../../../../../src/SdkConfig";
describe("<LabsUserSettingsTab />", () => {
const defaultProps = {
closeSettingsFn: jest.fn(),
};
const getComponent = () => <LabsUserSettingsTab {...defaultProps} />;
const settingsValueSpy = jest.spyOn(SettingsStore, "getValue");
beforeEach(() => {
jest.clearAllMocks();
settingsValueSpy.mockReturnValue(false);
SdkConfig.reset();
SdkConfig.add({ brand: "BrandedClient" });
localStorage.clear();
});
it("renders settings marked as beta as beta cards", () => {
render(getComponent());
expect(screen.getByText("Upcoming features").parentElement!).toMatchSnapshot();
});
it("does not render non-beta labs settings when disabled in config", () => {
const sdkConfigSpy = jest.spyOn(SdkConfig, "get");
render(getComponent());
expect(sdkConfigSpy).toHaveBeenCalledWith("show_labs_settings");
// only section is beta section
expect(screen.queryByText("Early previews")).not.toBeInTheDocument();
});
it("renders non-beta labs settings when enabled in config", () => {
// enable labs
SdkConfig.add({ show_labs_settings: true });
const { container } = render(getComponent());
// non-beta labs section
expect(screen.getByText("Early previews")).toBeInTheDocument();
const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
expect(labsSections).toHaveLength(10);
});
});

View file

@ -0,0 +1,37 @@
/*
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 { getMockClientWithEventEmitter, mockClientMethodsUser } from "../../../../../../test-utils";
import MjolnirUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/MjolnirUserSettingsTab";
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
describe("<MjolnirUserSettingsTab />", () => {
const userId = "@alice:server.org";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
getRoom: jest.fn(),
});
const getComponent = () =>
render(<MjolnirUserSettingsTab />, {
wrapper: ({ children }) => (
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
),
});
it("renders correctly when user has no ignored users", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(null);
const { container } = getComponent();
expect(container).toMatchSnapshot();
});
});

View file

@ -0,0 +1,189 @@
/*
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, screen, waitFor } from "jest-matrix-react";
import userEvent from "@testing-library/user-event";
import PreferencesUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/PreferencesUserSettingsTab";
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
import { mockPlatformPeg, stubClient } from "../../../../../../test-utils";
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
import MatrixClientBackedController from "../../../../../../../src/settings/controllers/MatrixClientBackedController";
import PlatformPeg from "../../../../../../../src/PlatformPeg";
describe("PreferencesUserSettingsTab", () => {
beforeEach(() => {
mockPlatformPeg();
});
const renderTab = (): RenderResult => {
return render(<PreferencesUserSettingsTab closeSettingsFn={() => {}} />);
};
it("should render", () => {
const { asFragment } = renderTab();
expect(asFragment()).toMatchSnapshot();
});
it("should reload when changing language", async () => {
const reloadStub = jest.fn();
PlatformPeg.get()!.reload = reloadStub;
renderTab();
const languageDropdown = await screen.findByRole("button", { name: "Language Dropdown" });
expect(languageDropdown).toBeInTheDocument();
await userEvent.click(languageDropdown);
const germanOption = await screen.findByText("Deutsch");
await userEvent.click(germanOption);
expect(reloadStub).toHaveBeenCalled();
});
it("should search and select a user timezone", async () => {
renderTab();
expect(await screen.findByText(/Browser default/)).toBeInTheDocument();
const timezoneDropdown = await screen.findByRole("button", { name: "Set timezone" });
await userEvent.click(timezoneDropdown);
// Without filtering `expect(screen.queryByRole("option" ...` take over 1s.
await fireEvent.change(screen.getByRole("combobox", { name: "Set timezone" }), {
target: { value: "Africa/Abidjan" },
});
expect(screen.queryByRole("option", { name: "Africa/Abidjan" })).toBeInTheDocument();
expect(screen.queryByRole("option", { name: "Europe/Paris" })).not.toBeInTheDocument();
await fireEvent.change(screen.getByRole("combobox", { name: "Set timezone" }), {
target: { value: "Europe/Paris" },
});
expect(screen.queryByRole("option", { name: "Africa/Abidjan" })).not.toBeInTheDocument();
const option = await screen.getByRole("option", { name: "Europe/Paris" });
await userEvent.click(option);
expect(await screen.findByText("Europe/Paris")).toBeInTheDocument();
});
it("should not show spell check setting if unsupported", async () => {
PlatformPeg.get()!.supportsSpellCheckSettings = jest.fn().mockReturnValue(false);
renderTab();
expect(screen.queryByRole("switch", { name: "Allow spell check" })).not.toBeInTheDocument();
});
it("should enable spell check", async () => {
const spellCheckEnableFn = jest.fn();
PlatformPeg.get()!.supportsSpellCheckSettings = jest.fn().mockReturnValue(true);
PlatformPeg.get()!.getSpellCheckEnabled = jest.fn().mockReturnValue(false);
PlatformPeg.get()!.setSpellCheckEnabled = spellCheckEnableFn;
renderTab();
const toggle = await screen.findByRole("switch", { name: "Allow spell check" });
expect(toggle).toHaveAttribute("aria-checked", "false");
await userEvent.click(toggle);
expect(spellCheckEnableFn).toHaveBeenCalledWith(true);
});
describe("send read receipts", () => {
beforeEach(() => {
stubClient();
jest.spyOn(SettingsStore, "setValue");
jest.spyOn(window, "matchMedia").mockReturnValue({ matches: false } as MediaQueryList);
});
afterEach(() => {
jest.resetAllMocks();
});
const getToggle = () => renderTab().getByRole("switch", { name: "Send read receipts" });
const mockIsVersionSupported = (val: boolean) => {
const client = MatrixClientPeg.safeGet();
jest.spyOn(client, "doesServerSupportUnstableFeature").mockResolvedValue(false);
jest.spyOn(client, "isVersionSupported").mockImplementation(async (version: string) => {
if (version === "v1.4") return val;
return false;
});
MatrixClientBackedController.matrixClient = client;
};
const mockGetValue = (val: boolean) => {
const copyOfGetValueAt = SettingsStore.getValueAt;
SettingsStore.getValueAt = <T,>(
level: SettingLevel,
name: string,
roomId?: string,
isExplicit?: boolean,
): T => {
if (name === "sendReadReceipts") return val as T;
return copyOfGetValueAt(level, name, roomId, isExplicit);
};
};
const expectSetValueToHaveBeenCalled = (
name: string,
roomId: string | null,
level: SettingLevel,
value: boolean,
) => expect(SettingsStore.setValue).toHaveBeenCalledWith(name, roomId, level, value);
describe("with server support", () => {
beforeEach(() => {
mockIsVersionSupported(true);
});
it("can be enabled", async () => {
mockGetValue(false);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", null, SettingLevel.ACCOUNT, true);
});
it("can be disabled", async () => {
mockGetValue(true);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "false"));
fireEvent.click(toggle);
expectSetValueToHaveBeenCalled("sendReadReceipts", null, SettingLevel.ACCOUNT, false);
});
});
describe("without server support", () => {
beforeEach(() => {
mockIsVersionSupported(false);
});
it("is forcibly enabled", async () => {
const toggle = getToggle();
await waitFor(() => {
expect(toggle).toHaveAttribute("aria-checked", "true");
expect(toggle).toHaveAttribute("aria-disabled", "true");
});
});
it("cannot be disabled", async () => {
mockGetValue(true);
const toggle = getToggle();
await waitFor(() => expect(toggle).toHaveAttribute("aria-disabled", "true"));
fireEvent.click(toggle);
expect(SettingsStore.setValue).not.toHaveBeenCalled();
});
});
});
});

View file

@ -0,0 +1,61 @@
/*
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 { render } from "jest-matrix-react";
import React from "react";
import SecurityUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/SecurityUserSettingsTab";
import MatrixClientContext from "../../../../../../../src/contexts/MatrixClientContext";
import {
getMockClientWithEventEmitter,
mockClientMethodsServer,
mockClientMethodsUser,
mockClientMethodsCrypto,
mockClientMethodsDevice,
mockPlatformPeg,
} from "../../../../../../test-utils";
import { SDKContext, SdkContextClass } from "../../../../../../../src/contexts/SDKContext";
describe("<SecurityUserSettingsTab />", () => {
const defaultProps = {
closeSettingsFn: jest.fn(),
};
const userId = "@alice:server.org";
const deviceId = "alices-device";
const mockClient = getMockClientWithEventEmitter({
...mockClientMethodsUser(userId),
...mockClientMethodsServer(),
...mockClientMethodsDevice(deviceId),
...mockClientMethodsCrypto(),
getRooms: jest.fn().mockReturnValue([]),
getIgnoredUsers: jest.fn(),
getKeyBackupVersion: jest.fn(),
});
const sdkContext = new SdkContextClass();
sdkContext.client = mockClient;
const getComponent = () => (
<MatrixClientContext.Provider value={mockClient}>
<SDKContext.Provider value={sdkContext}>
<SecurityUserSettingsTab {...defaultProps} />
</SDKContext.Provider>
</MatrixClientContext.Provider>
);
beforeEach(() => {
mockPlatformPeg();
jest.clearAllMocks();
});
it("renders security section", () => {
const { container } = render(getComponent());
expect(container).toMatchSnapshot();
});
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,91 @@
/*
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 SidebarUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/SidebarUserSettingsTab";
import PosthogTrackers from "../../../../../../../src/PosthogTrackers";
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
import { MetaSpace } from "../../../../../../../src/stores/spaces";
import { SettingLevel } from "../../../../../../../src/settings/SettingLevel";
import { flushPromises } from "../../../../../../test-utils";
import SdkConfig from "../../../../../../../src/SdkConfig";
describe("<SidebarUserSettingsTab />", () => {
beforeEach(() => {
jest.spyOn(PosthogTrackers, "trackInteraction").mockClear();
jest.spyOn(SettingsStore, "getValue").mockRestore();
jest.spyOn(SettingsStore, "setValue").mockResolvedValue(undefined);
});
it("renders sidebar settings with guest spa url", () => {
const spy = jest.spyOn(SdkConfig, "get").mockReturnValue({ guest_spa_url: "https://somewhere.org" });
const originalGetValue = SettingsStore.getValue;
const spySettingsStore = jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
return setting === "feature_video_rooms" ? true : originalGetValue(setting);
});
const { container } = render(<SidebarUserSettingsTab />);
expect(container).toMatchSnapshot();
spySettingsStore.mockRestore();
spy.mockRestore();
});
it("renders sidebar settings without guest spa url", () => {
const originalGetValue = SettingsStore.getValue;
const spySettingsStore = jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
return setting === "feature_video_rooms" ? true : originalGetValue(setting);
});
const { container } = render(<SidebarUserSettingsTab />);
expect(container).toMatchSnapshot();
spySettingsStore.mockRestore();
});
it("toggles all rooms in home setting", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
if (settingName === "Spaces.enabledMetaSpaces") {
return {
[MetaSpace.Home]: true,
[MetaSpace.Favourites]: true,
[MetaSpace.People]: true,
[MetaSpace.Orphans]: true,
};
}
return false;
});
render(<SidebarUserSettingsTab />);
fireEvent.click(screen.getByTestId("mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"));
await flushPromises();
expect(SettingsStore.setValue).toHaveBeenCalledWith("Spaces.allRoomsInHome", null, SettingLevel.ACCOUNT, true);
expect(PosthogTrackers.trackInteraction).toHaveBeenCalledWith(
"WebSettingsSidebarTabSpacesCheckbox",
// synthetic event from checkbox
expect.objectContaining({ type: "change" }),
1,
);
});
it("disables all rooms in home setting when home space is disabled", () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName) => {
if (settingName === "Spaces.enabledMetaSpaces") {
return {
[MetaSpace.Home]: false,
[MetaSpace.Favourites]: true,
[MetaSpace.People]: true,
[MetaSpace.Orphans]: true,
};
}
return false;
});
render(<SidebarUserSettingsTab />);
expect(screen.getByTestId("mx_SidebarUserSettingsTab_homeAllRoomsCheckbox")).toBeDisabled();
});
});

View file

@ -0,0 +1,135 @@
/*
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 { mocked } from "jest-mock";
import { fireEvent, render, screen } from "jest-matrix-react";
import { logger } from "matrix-js-sdk/src/logger";
import VoiceUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/VoiceUserSettingsTab";
import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../../../src/MediaDeviceHandler";
import { flushPromises } from "../../../../../../test-utils";
jest.mock("../../../../../../../src/MediaDeviceHandler");
const MediaDeviceHandlerMock = mocked(MediaDeviceHandler);
describe("<VoiceUserSettingsTab />", () => {
const getComponent = (): React.ReactElement => <VoiceUserSettingsTab />;
const audioIn1 = {
deviceId: "1",
groupId: "g1",
kind: MediaDeviceKindEnum.AudioInput,
label: "Audio input test 1",
};
const videoIn1 = {
deviceId: "2",
groupId: "g1",
kind: MediaDeviceKindEnum.VideoInput,
label: "Video input test 1",
};
const videoIn2 = {
deviceId: "3",
groupId: "g1",
kind: MediaDeviceKindEnum.VideoInput,
label: "Video input test 2",
};
const defaultMediaDevices = {
[MediaDeviceKindEnum.AudioOutput]: [],
[MediaDeviceKindEnum.AudioInput]: [audioIn1],
[MediaDeviceKindEnum.VideoInput]: [videoIn1, videoIn2],
} as unknown as IMediaDevices;
beforeEach(() => {
jest.clearAllMocks();
MediaDeviceHandlerMock.hasAnyLabeledDevices.mockResolvedValue(true);
MediaDeviceHandlerMock.getDevices.mockResolvedValue(defaultMediaDevices);
MediaDeviceHandlerMock.getVideoInput.mockReturnValue(videoIn1.deviceId);
// @ts-ignore bad mocking
MediaDeviceHandlerMock.instance = { setDevice: jest.fn().mockResolvedValue(undefined) };
});
describe("devices", () => {
it("renders dropdowns for input devices", async () => {
render(getComponent());
await flushPromises();
expect(screen.getByLabelText("Microphone")).toHaveDisplayValue(audioIn1.label);
expect(screen.getByLabelText("Camera")).toHaveDisplayValue(videoIn1.label);
});
it("updates device", async () => {
render(getComponent());
await flushPromises();
fireEvent.change(screen.getByLabelText("Camera"), { target: { value: videoIn2.deviceId } });
expect(MediaDeviceHandlerMock.instance.setDevice).toHaveBeenCalledWith(
videoIn2.deviceId,
MediaDeviceKindEnum.VideoInput,
);
expect(screen.getByLabelText("Camera")).toHaveDisplayValue(videoIn2.label);
});
it("logs and resets device when update fails", async () => {
// stub to avoid littering console with expected error
jest.spyOn(logger, "error").mockImplementation(() => {});
MediaDeviceHandlerMock.instance.setDevice.mockRejectedValue("oups!");
render(getComponent());
await flushPromises();
fireEvent.change(screen.getByLabelText("Camera"), { target: { value: videoIn2.deviceId } });
expect(MediaDeviceHandlerMock.instance.setDevice).toHaveBeenCalledWith(
videoIn2.deviceId,
MediaDeviceKindEnum.VideoInput,
);
expect(screen.getByLabelText("Camera")).toHaveDisplayValue(videoIn2.label);
await flushPromises();
expect(logger.error).toHaveBeenCalledWith("Failed to set device videoinput: 3");
// reset to original
expect(screen.getByLabelText("Camera")).toHaveDisplayValue(videoIn1.label);
});
it("does not render dropdown when no devices exist for type", async () => {
render(getComponent());
await flushPromises();
expect(screen.getByText("No Audio Outputs detected")).toBeInTheDocument();
expect(screen.queryByLabelText("Audio Output")).not.toBeInTheDocument();
});
});
it("renders audio processing settings", () => {
const { getByTestId } = render(getComponent());
expect(getByTestId("voice-auto-gain")).toBeTruthy();
expect(getByTestId("voice-noise-suppression")).toBeTruthy();
expect(getByTestId("voice-echo-cancellation")).toBeTruthy();
});
it("sets and displays audio processing settings", () => {
MediaDeviceHandlerMock.getAudioAutoGainControl.mockReturnValue(false);
MediaDeviceHandlerMock.getAudioEchoCancellation.mockReturnValue(true);
MediaDeviceHandlerMock.getAudioNoiseSuppression.mockReturnValue(false);
const { getByRole } = render(getComponent());
getByRole("switch", { name: "Automatically adjust the microphone volume" }).click();
getByRole("switch", { name: "Noise suppression" }).click();
getByRole("switch", { name: "Echo cancellation" }).click();
expect(MediaDeviceHandler.setAudioAutoGainControl).toHaveBeenCalledWith(true);
expect(MediaDeviceHandler.setAudioEchoCancellation).toHaveBeenCalledWith(false);
expect(MediaDeviceHandler.setAudioNoiseSuppression).toHaveBeenCalledWith(true);
});
});

View file

@ -0,0 +1,220 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses and phone numbers 1`] = `
<div
class="mx_SettingsSubsection"
data-testid="mx_AccountEmailAddresses"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Email addresses
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_AddRemoveThreepids_existing"
>
<span
class="mx_AddRemoveThreepids_existing_address"
>
test@test.io
</span>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_sm"
role="button"
tabindex="0"
>
Remove
</div>
</div>
<form
autocomplete="off"
novalidate=""
>
<div
class="mx_Field mx_Field_input"
>
<input
autocomplete="email"
id="mx_Field_9"
label="Email Address"
placeholder="Email Address"
type="text"
value=""
/>
<label
for="mx_Field_9"
>
Email Address
</label>
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Add
</div>
</form>
</div>
</div>
`;
exports[`<AccountUserSettingsTab /> 3pids should display 3pid email addresses and phone numbers 2`] = `
<div
class="mx_SettingsSubsection"
data-testid="mx_AccountPhoneNumbers"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Phone numbers
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_AddRemoveThreepids_existing"
>
<span
class="mx_AddRemoveThreepids_existing_address"
>
123456789
</span>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_sm"
role="button"
tabindex="0"
>
Remove
</div>
</div>
<form
autocomplete="off"
novalidate=""
>
<div
class="mx_Field mx_Field_input mx_Field_labelAlwaysTopLeft"
>
<span
class="mx_Field_prefix"
>
<div
class="mx_Dropdown mx_PhoneNumbers_country mx_CountryDropdown"
>
<div
aria-describedby="mx_CountryDropdown_value"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Country Dropdown"
aria-owns="mx_CountryDropdown_input"
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
role="button"
tabindex="0"
>
<div
class="mx_Dropdown_option"
id="mx_CountryDropdown_value"
>
<span
class="mx_CountryDropdown_shortOption"
>
<div
class="mx_Dropdown_option_emoji"
>
🇺🇸
</div>
+1
</span>
</div>
<span
class="mx_Dropdown_arrow"
/>
</div>
</div>
</span>
<input
autocomplete="tel-national"
id="mx_Field_10"
label="Phone Number"
placeholder="Phone Number"
type="text"
value=""
/>
<label
for="mx_Field_10"
>
Phone Number
</label>
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Add
</div>
</form>
</div>
</div>
`;
exports[`<AccountUserSettingsTab /> deactive account should render section when account deactivation feature is enabled 1`] = `
<div
class="mx_SettingsSection"
>
<h2
class="mx_Heading_h3"
>
Deactivate Account
</h2>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection"
data-testid="account-management-section"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Account management
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
Deactivating your account is a permanent action — be careful!
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
role="button"
tabindex="0"
>
Deactivate Account
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,805 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AppearanceUserSettingsTab should render 1`] = `
<DocumentFragment>
<div
class="mx_SettingsTab"
data-testid="mx_AppearanceUserSettingsTab"
>
<div
class="mx_SettingsTab_sections"
>
<div
class="mx_SettingsSection"
>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection mx_SettingsSubsection_newUi"
data-testid="themePanel"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Theme
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
>
<form
class="_root_dgy0u_24 mx_ThemeChoicePanel_ThemeSelectors"
>
<div
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
>
<div
class="_inline-field-control_dgy0u_52"
>
<div
class="_container_1vw5h_18"
>
<input
class="_input_1vw5h_26"
disabled=""
id="radix-0"
name="themeSelector"
title=""
type="radio"
value="light"
/>
<div
class="_ui_1vw5h_27"
/>
</div>
</div>
<div
class="_inline-field-body_dgy0u_46"
>
<label
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-0"
>
Light
</label>
</div>
</div>
<div
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-dark"
>
<div
class="_inline-field-control_dgy0u_52"
>
<div
class="_container_1vw5h_18"
>
<input
class="_input_1vw5h_26"
disabled=""
id="radix-1"
name="themeSelector"
title=""
type="radio"
value="dark"
/>
<div
class="_ui_1vw5h_27"
/>
</div>
</div>
<div
class="_inline-field-body_dgy0u_46"
>
<label
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-1"
>
Dark
</label>
</div>
</div>
<div
class="_inline-field_dgy0u_40 mx_ThemeChoicePanel_themeSelector mx_ThemeChoicePanel_themeSelector_disabled cpd-theme-light"
>
<div
class="_inline-field-control_dgy0u_52"
>
<div
class="_container_1vw5h_18"
>
<input
class="_input_1vw5h_26"
disabled=""
id="radix-2"
name="themeSelector"
title=""
type="radio"
value="light-high-contrast"
/>
<div
class="_ui_1vw5h_27"
/>
</div>
</div>
<div
class="_inline-field-body_dgy0u_46"
>
<label
class="_label_dgy0u_67 mx_ThemeChoicePanel_themeSelector_Label"
for="radix-2"
>
High contrast
</label>
</div>
</div>
</form>
</div>
<div
class="_separator_144s5_17"
data-kind="primary"
data-orientation="horizontal"
role="separator"
/>
</div>
<div
class="mx_SettingsSubsection mx_SettingsSubsection_newUi"
data-testid="layoutPanel"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h3 mx_SettingsSubsectionHeading_heading"
>
Message layout
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_content_newUi"
>
<form
class="_root_dgy0u_24 mx_LayoutSwitcher_LayoutSelector"
>
<div
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
>
<label
aria-label="Modern"
class="_label_dgy0u_67"
for="radix-3"
>
<div
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
>
<div
class="_container_1vw5h_18"
>
<input
checked=""
class="_input_1vw5h_26"
id="radix-3"
name="layout"
title=""
type="radio"
value="group"
/>
<div
class="_ui_1vw5h_27"
/>
</div>
<span>
Modern
</span>
</div>
<hr
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_separator"
/>
<div
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_EventTilePreview"
role="presentation"
>
<div
aria-atomic="true"
aria-live="off"
class="mx_EventTile"
data-event-id="$9999999999999999999999999999999999999999999"
data-has-reply="false"
data-layout="group"
data-scroll-tokens="$9999999999999999999999999999999999999999999"
data-self="true"
tabindex="-1"
>
<div
class="mx_DisambiguatedProfile"
>
<span
class="mx_Username_color2 mx_DisambiguatedProfile_displayName"
dir="auto"
>
@userId:matrix.org
</span>
</div>
<div
class="mx_EventTile_avatar"
>
<span
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
data-color="2"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 30px;"
title="@userId:matrix.org"
>
u
</span>
</div>
<div
class="mx_EventTile_line"
>
<div
class="mx_MTextBody mx_EventTile_content"
>
<div
class="mx_EventTile_body translate"
dir="auto"
>
Hey you. You're the best!
</div>
</div>
<div
aria-label="Message Actions"
aria-live="off"
class="mx_MessageActionBar"
role="toolbar"
>
<div
aria-label="Edit"
class="mx_AccessibleButton mx_MessageActionBar_iconButton"
role="button"
tabindex="0"
>
<div />
</div>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_MessageActionBar_iconButton mx_MessageActionBar_optionsButton"
role="button"
tabindex="-1"
>
<div />
</div>
</div>
</div>
</div>
</div>
</label>
</div>
<div
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
>
<label
aria-label="Message bubbles"
class="_label_dgy0u_67"
for="radix-4"
>
<div
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
>
<div
class="_container_1vw5h_18"
>
<input
class="_input_1vw5h_26"
id="radix-4"
name="layout"
title=""
type="radio"
value="bubble"
/>
<div
class="_ui_1vw5h_27"
/>
</div>
<span>
Message bubbles
</span>
</div>
<hr
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_separator"
/>
<div
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_EventTilePreview"
role="presentation"
>
<div
aria-atomic="true"
aria-live="off"
class="mx_EventTile"
data-event-id="$9999999999999999999999999999999999999999999"
data-has-reply="false"
data-layout="bubble"
data-scroll-tokens="$9999999999999999999999999999999999999999999"
data-self="true"
tabindex="-1"
>
<div
class="mx_DisambiguatedProfile"
>
<span
class="mx_Username_color2 mx_DisambiguatedProfile_displayName"
dir="auto"
>
@userId:matrix.org
</span>
</div>
<div
class="mx_EventTile_avatar"
>
<span
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
data-color="2"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 30px;"
title="@userId:matrix.org"
>
u
</span>
</div>
<div
class="mx_EventTile_line"
>
<div
class="mx_MTextBody mx_EventTile_content"
>
<div
class="mx_EventTile_body translate"
dir="auto"
>
Hey you. You're the best!
</div>
</div>
<div
aria-label="Message Actions"
aria-live="off"
class="mx_MessageActionBar"
role="toolbar"
>
<div
aria-label="Edit"
class="mx_AccessibleButton mx_MessageActionBar_iconButton"
role="button"
tabindex="0"
>
<div />
</div>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_MessageActionBar_iconButton mx_MessageActionBar_optionsButton"
role="button"
tabindex="-1"
>
<div />
</div>
</div>
</div>
</div>
</div>
</label>
</div>
<div
class="_field_dgy0u_34 mxLayoutSwitcher_LayoutSelector_LayoutRadio"
>
<label
aria-label="IRC (experimental)"
class="_label_dgy0u_67"
for="radix-5"
>
<div
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline"
>
<div
class="_container_1vw5h_18"
>
<input
class="_input_1vw5h_26"
id="radix-5"
name="layout"
title=""
type="radio"
value="irc"
/>
<div
class="_ui_1vw5h_27"
/>
</div>
<span>
IRC (experimental)
</span>
</div>
<hr
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_separator"
/>
<div
class="mxLayoutSwitcher_LayoutSelector_LayoutRadio_EventTilePreview mx_IRCLayout"
role="presentation"
>
<div
aria-atomic="true"
aria-live="off"
class="mx_EventTile"
data-event-id="$9999999999999999999999999999999999999999999"
data-has-reply="false"
data-layout="irc"
data-scroll-tokens="$9999999999999999999999999999999999999999999"
data-self="true"
tabindex="-1"
>
<div
class="mx_DisambiguatedProfile"
>
<span
class="mx_Username_color2 mx_DisambiguatedProfile_displayName"
dir="auto"
>
@userId:matrix.org
</span>
</div>
<div
class="mx_EventTile_avatar"
>
<span
class="_avatar_mcap2_17 mx_BaseAvatar _avatar-imageless_mcap2_61"
data-color="2"
data-testid="avatar-img"
data-type="round"
role="presentation"
style="--cpd-avatar-size: 14px;"
title="@userId:matrix.org"
>
u
</span>
</div>
<div
class="mx_EventTile_line"
>
<div
class="mx_MTextBody mx_EventTile_content"
>
<div
class="mx_EventTile_body translate"
dir="auto"
>
Hey you. You're the best!
</div>
</div>
<div
aria-label="Message Actions"
aria-live="off"
class="mx_MessageActionBar"
role="toolbar"
>
<div
aria-label="Edit"
class="mx_AccessibleButton mx_MessageActionBar_iconButton"
role="button"
tabindex="0"
>
<div />
</div>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton mx_MessageActionBar_iconButton mx_MessageActionBar_optionsButton"
role="button"
tabindex="-1"
>
<div />
</div>
</div>
</div>
</div>
</div>
</label>
</div>
</form>
<form
class="_root_dgy0u_24"
>
<div
class="_inline-field_dgy0u_40"
>
<div
class="_inline-field-control_dgy0u_52"
>
<div
class="_container_qnvru_18"
>
<input
aria-describedby="radix-6"
class="_input_qnvru_32"
id="radix-7"
name="compactLayout"
title=""
type="checkbox"
/>
<div
class="_ui_qnvru_42"
/>
</div>
</div>
<div
class="_inline-field-body_dgy0u_46"
>
<label
class="_label_dgy0u_67"
for="radix-7"
>
Show compact text and messages
</label>
<span
class="_message_dgy0u_98 _help-message_dgy0u_104"
id="radix-6"
>
Modern layout must be selected to use this feature.
</span>
</div>
</div>
</form>
</div>
<div
class="_separator_144s5_17"
data-kind="primary"
data-orientation="horizontal"
role="separator"
/>
</div>
<div
class="mx_SettingsSubsection"
data-testid="mx_FontScalingPanel"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Font size
</h3>
</div>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_contentStretch"
>
<div
class="mx_Field mx_Field_select mx_FontScalingPanel_Dropdown"
>
<select
id="mx_Field_1"
label="Font size"
placeholder="Font size"
type="text"
>
<option
value="-7"
>
9
</option>
<option
value="-6"
>
10
</option>
<option
value="-5"
>
11
</option>
<option
value="-4"
>
12
</option>
<option
value="-3"
>
13
</option>
<option
value="-2"
>
14
</option>
<option
value="-1"
>
15
</option>
<option
value="0"
>
16 (default)
</option>
<option
value="1"
>
17
</option>
<option
value="2"
>
18
</option>
<option
value="4"
>
20
</option>
<option
value="6"
>
22
</option>
<option
value="8"
>
24
</option>
<option
value="10"
>
26
</option>
<option
value="12"
>
28
</option>
<option
value="14"
>
30
</option>
<option
value="16"
>
32
</option>
<option
value="18"
>
34
</option>
<option
value="20"
>
36
</option>
</select>
<label
for="mx_Field_1"
>
Font size
</label>
</div>
<div
class="mx_FontScalingPanel_preview mx_EventTilePreview_loader"
>
<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
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsection_content mx_SettingsSubsection_noHeading"
>
<div
aria-expanded="false"
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link"
role="button"
tabindex="0"
>
Show advanced
</div>
</div>
</div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Image size in the timeline
</h3>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_ImageSizePanel_radios"
>
<label>
<div
class="mx_ImageSizePanel_size mx_ImageSizePanel_sizeDefault"
/>
<label
class="mx_StyledRadioButton mx_StyledRadioButton_enabled mx_StyledRadioButton_checked"
>
<input
checked=""
name="image_size"
type="radio"
value="normal"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
Default
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
</label>
<label>
<div
class="mx_ImageSizePanel_size mx_ImageSizePanel_sizeLarge"
/>
<label
class="mx_StyledRadioButton mx_StyledRadioButton_enabled"
>
<input
name="image_size"
type="radio"
value="large"
/>
<div>
<div />
</div>
<div
class="mx_StyledRadioButton_content"
>
Large
</div>
<div
class="mx_StyledRadioButton_spacer"
/>
</label>
</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;

View file

@ -0,0 +1,134 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<LabsUserSettingsTab /> renders settings marked as beta as beta cards 1`] = `
<div
class="mx_SettingsSection"
>
<h2
class="mx_Heading_h3"
>
Upcoming features
</h2>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection_text"
>
What's next for BrandedClient? Labs are the best way to get things early, test out new features and help shape them before they actually launch.
</div>
<div
class="mx_BetaCard"
>
<div
class="mx_BetaCard_columns"
>
<div
class="mx_BetaCard_columns_description"
>
<h3
class="mx_BetaCard_title"
>
<span>
Video rooms
</span>
<span
class="mx_BetaCard_betaPill"
>
Beta
</span>
</h3>
<div
class="mx_BetaCard_caption"
>
<p>
A new way to chat over voice and video in BrandedClient.
</p>
<p>
Video rooms are always-on VoIP channels embedded within a room in BrandedClient.
</p>
</div>
<div
class="mx_BetaCard_buttons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Join the beta
</div>
</div>
<div
class="mx_BetaCard_refreshWarning"
>
Joining the beta will reload BrandedClient.
</div>
<div
class="mx_BetaCard_faq"
/>
</div>
<div
class="mx_BetaCard_columns_image_wrapper"
>
<img
alt=""
class="mx_BetaCard_columns_image"
src="image-file-stub"
/>
</div>
</div>
</div>
<div
class="mx_BetaCard"
>
<div
class="mx_BetaCard_columns"
>
<div
class="mx_BetaCard_columns_description"
>
<h3
class="mx_BetaCard_title"
>
<span>
Notification Settings
</span>
<span
class="mx_BetaCard_betaPill"
>
Beta
</span>
</h3>
<div
class="mx_BetaCard_caption"
>
<p>
Introducing a simpler way to change your notification settings. Customize your BrandedClient, just the way you like.
</p>
</div>
<div
class="mx_BetaCard_buttons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
>
Join the beta
</div>
</div>
</div>
<div
class="mx_BetaCard_columns_image_wrapper"
>
<img
alt=""
class="mx_BetaCard_columns_image"
/>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,165 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<MjolnirUserSettingsTab /> renders correctly when user has no ignored users 1`] = `
<div>
<div
class="mx_SettingsTab"
>
<div
class="mx_SettingsTab_sections"
>
<div
class="mx_SettingsSection"
>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection_text"
>
<strong
class="warning"
>
⚠ These settings are meant for advanced users.
</strong>
<p>
<span>
Add users and servers you want to ignore here. Use asterisks to have Element match any characters. For example,
<code>
@bot:*
</code>
would ignore all users that have the name 'bot' on any server.
</span>
</p>
<p>
Ignoring people is done through ban lists which contain rules for who to ban. Subscribing to a ban list means the users/servers blocked by that list will be hidden from you.
</p>
</div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Personal ban list
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
Your personal ban list holds all the users/servers you personally don't want to see messages from. After ignoring your first user/server, a new room will show up in your room list named 'My Ban List' - stay in this room to keep the ban list in effect.
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<i>
You have not ignored anyone.
</i>
<form
autocomplete="off"
>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_Field_1"
label="Server or user ID to ignore"
placeholder="eg: @bot:* or example.org"
type="text"
value=""
/>
<label
for="mx_Field_1"
>
Server or user ID to ignore
</label>
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
type="submit"
>
Ignore
</div>
</form>
</div>
</div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Subscribed lists
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
<strong
class="warning"
>
Subscribing to a ban list will cause you to join it!
</strong>
 
<span>
If this isn't what you want, please use a different tool to ignore users.
</span>
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<i>
You are not subscribed to any lists
</i>
<form
autocomplete="off"
>
<div
class="mx_Field mx_Field_input"
>
<input
id="mx_Field_2"
label="Room ID or address of ban list"
placeholder="Room ID or address of ban list"
type="text"
value=""
/>
<label
for="mx_Field_2"
>
Room ID or address of ban list
</label>
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
role="button"
tabindex="0"
type="submit"
>
Subscribe
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,432 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SecurityUserSettingsTab /> renders security section 1`] = `
<div>
<div
class="mx_SettingsTab"
>
<div
class="mx_SettingsTab_sections"
>
<label
class="mx_SetIntegrationManager"
data-testid="mx_SetIntegrationManager"
for="toggle_integration"
>
<div
class="mx_SettingsFlag"
>
<div
class="mx_SetIntegrationManager_heading_manager"
>
<h3
class="mx_Heading_h3"
>
Manage integrations
</h3>
<h4
class="mx_Heading_h4"
>
(scalar.vector.im)
</h4>
</div>
<div
aria-checked="true"
aria-disabled="false"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_on mx_ToggleSwitch_enabled"
id="toggle_integration"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</div>
</div>
<div
class="mx_SettingsSubsection_text"
>
<span>
Use an integration manager
<strong>
(scalar.vector.im)
</strong>
to manage bots, widgets, and sticker packs.
</span>
</div>
<div
class="mx_SettingsSubsection_text"
>
Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.
</div>
</label>
<div
class="mx_SettingsSection"
>
<h2
class="mx_Heading_h3"
>
Encryption
</h2>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Secure Backup
</h3>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_SettingsSubsection_text"
>
Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.
</div>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
<details>
<summary
class="mx_SecureBackupPanel_advanced"
>
Advanced
</summary>
<table
class="mx_SecureBackupPanel_statusList"
>
<tr>
<th
scope="row"
>
Backup key stored:
</th>
<td>
not stored
</td>
</tr>
<tr>
<th
scope="row"
>
Backup key cached:
</th>
<td>
not found locally
</td>
</tr>
<tr>
<th
scope="row"
>
Secret storage public key:
</th>
<td>
not found
</td>
</tr>
<tr>
<th
scope="row"
>
Secret storage:
</th>
<td>
not ready
</td>
</tr>
</table>
</details>
</div>
</div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Message search
</h3>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_SettingsSubsection_text"
>
<span>
Element can't securely cache encrypted messages locally while running in a web browser. Use
<a
class="mx_ExternalLink"
href="https://element.io/get-started"
rel="noreferrer noopener"
target="_blank"
>
Element Desktop
<i
class="mx_ExternalLink_icon"
/>
</a>
for encrypted messages to appear in search results.
</span>
</div>
</div>
</div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Cross-signing
</h3>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_Spinner"
>
<div
aria-label="Loading…"
class="mx_Spinner_icon"
data-testid="spinner"
role="progressbar"
style="width: 32px; height: 32px;"
/>
</div>
<details>
<summary
class="mx_CrossSigningPanel_advanced"
>
Advanced
</summary>
<table
class="mx_CrossSigningPanel_statusList"
>
<tbody>
<tr>
<th
scope="row"
>
Cross-signing public keys:
</th>
<td>
not found
</td>
</tr>
<tr>
<th
scope="row"
>
Cross-signing private keys:
</th>
<td>
not found in storage
</td>
</tr>
<tr>
<th
scope="row"
>
Master private key:
</th>
<td>
not found locally
</td>
</tr>
<tr>
<th
scope="row"
>
Self signing private key:
</th>
<td>
not found locally
</td>
</tr>
<tr>
<th
scope="row"
>
User signing private key:
</th>
<td>
not found locally
</td>
</tr>
<tr>
<th
scope="row"
>
Homeserver feature support:
</th>
<td>
not found
</td>
</tr>
</tbody>
</table>
</details>
</div>
</div>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Cryptography
</h3>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_SettingsSubsection_text"
>
<table
class="mx_CryptographyPanel_sessionInfo"
>
<tbody>
<tr>
<th
scope="row"
>
Session ID:
</th>
<td>
<code />
</td>
</tr>
<tr>
<th
scope="row"
>
Session key:
</th>
<td>
<code>
<strong>
...
</strong>
</code>
</td>
</tr>
</tbody>
</table>
</div>
<div
class="mx_CryptographyPanel_importExportButtons"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="0"
>
Export E2E room keys
</div>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary_outline"
role="button"
tabindex="0"
>
Import E2E room keys
</div>
</div>
<div
class="mx_SettingsFlag"
>
<label
class="mx_SettingsFlag_label"
for="mx_SettingsFlag_vY7Q4uEh9K38"
>
<span
class="mx_SettingsFlag_labelText"
>
Never send encrypted messages to unverified sessions from this session
</span>
</label>
<div
aria-checked="false"
aria-disabled="false"
aria-label="Never send encrypted messages to unverified sessions from this session"
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
id="mx_SettingsFlag_vY7Q4uEh9K38"
role="switch"
tabindex="0"
>
<div
class="mx_ToggleSwitch_ball"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="mx_SettingsSection"
>
<h2
class="mx_Heading_h3"
>
Advanced
</h2>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Ignored users
</h3>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_SettingsSubsection_text"
>
You have no ignored users.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,403 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SessionManagerTab /> Device verification does not allow device verification on session that do not support encryption 1`] = `
HTMLCollection [
<div
class="mx_DeviceSecurityCard"
>
<div
class="mx_DeviceSecurityCard_icon Unverified"
>
<div
height="16"
width="16"
/>
</div>
<div
class="mx_DeviceSecurityCard_content"
>
<p
class="mx_DeviceSecurityCard_heading"
>
Unverified session
</p>
<p
class="mx_DeviceSecurityCard_description"
>
This session doesn't support encryption and thus can't be verified.
<div
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</p>
</div>
</div>,
]
`;
exports[`<SessionManagerTab /> Sign out Signs out of current device 1`] = `
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_inline"
data-testid="device-detail-sign-out-cta"
role="button"
tabindex="0"
>
<span
class="mx_DeviceDetails_signOutButtonContent"
>
Sign out of this session
</span>
</div>
`;
exports[`<SessionManagerTab /> Sign out for an OIDC-aware server Signs out of current device 1`] = `
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger_inline"
data-testid="device-detail-sign-out-cta"
role="button"
tabindex="0"
>
<span
class="mx_DeviceDetails_signOutButtonContent"
>
Sign out of this session
</span>
</div>
`;
exports[`<SessionManagerTab /> current session section renders current session section with a verified session 1`] = `
<div
class="mx_SettingsSubsection"
data-testid="current-session-section"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Current session
</h3>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton"
data-testid="current-session-menu"
role="button"
tabindex="0"
>
<div
class="mx_KebabContextMenu_icon"
/>
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_DeviceTile mx_DeviceTile_interactive"
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceTypeIcon"
>
<div
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Verified"
class="mx_DeviceTypeIcon_verificationIcon verified"
role="img"
/>
</div>
<div
class="mx_DeviceTile_info"
>
<h4
class="mx_Heading_h4"
>
Alices device
</h4>
<div
class="mx_DeviceTile_metadata"
>
<span
data-testid="device-metadata-isVerified"
>
Verified
</span>
·
<span
data-testid="device-metadata-deviceId"
>
alices_device
</span>
</div>
</div>
<div
class="mx_DeviceTile_actions"
>
<div
aria-label="Show details"
class="mx_AccessibleButton mx_DeviceExpandDetailsButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon"
data-testid="current-session-toggle-details"
role="button"
tabindex="0"
>
<div
class="mx_DeviceExpandDetailsButton_icon"
/>
</div>
</div>
</div>
<br />
<div
class="mx_DeviceSecurityCard"
>
<div
class="mx_DeviceSecurityCard_icon Verified"
>
<div
height="16"
width="16"
/>
</div>
<div
class="mx_DeviceSecurityCard_content"
>
<p
class="mx_DeviceSecurityCard_heading"
>
Verified session
</p>
<p
class="mx_DeviceSecurityCard_description"
>
Your current session is ready for secure messaging.
<div
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</p>
</div>
</div>
</div>
</div>
`;
exports[`<SessionManagerTab /> current session section renders current session section with an unverified session 1`] = `
<div
class="mx_SettingsSubsection"
data-testid="current-session-section"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Current session
</h3>
<div
aria-expanded="false"
aria-haspopup="true"
aria-label="Options"
class="mx_AccessibleButton"
data-testid="current-session-menu"
role="button"
tabindex="0"
>
<div
class="mx_KebabContextMenu_icon"
/>
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<div
class="mx_DeviceTile mx_DeviceTile_interactive"
data-testid="device-tile-alices_device"
>
<div
class="mx_DeviceTypeIcon"
>
<div
class="mx_DeviceTypeIcon_deviceIconWrapper"
>
<div
aria-label="Unknown session type"
class="mx_DeviceTypeIcon_deviceIcon"
role="img"
/>
</div>
<div
aria-label="Unverified"
class="mx_DeviceTypeIcon_verificationIcon unverified"
role="img"
/>
</div>
<div
class="mx_DeviceTile_info"
>
<h4
class="mx_Heading_h4"
>
Alices device
</h4>
<div
class="mx_DeviceTile_metadata"
>
<span
data-testid="device-metadata-isVerified"
>
Unverified
</span>
·
<span
data-testid="device-metadata-deviceId"
>
alices_device
</span>
</div>
</div>
<div
class="mx_DeviceTile_actions"
>
<div
aria-label="Show details"
class="mx_AccessibleButton mx_DeviceExpandDetailsButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_icon"
data-testid="current-session-toggle-details"
role="button"
tabindex="0"
>
<div
class="mx_DeviceExpandDetailsButton_icon"
/>
</div>
</div>
</div>
<br />
<div
class="mx_DeviceSecurityCard"
>
<div
class="mx_DeviceSecurityCard_icon Unverified"
>
<div
height="16"
width="16"
/>
</div>
<div
class="mx_DeviceSecurityCard_content"
>
<p
class="mx_DeviceSecurityCard_heading"
>
Unverified session
</p>
<p
class="mx_DeviceSecurityCard_description"
>
Verify your current session for enhanced secure messaging.
<div
class="mx_AccessibleButton mx_LearnMore_button mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
role="button"
tabindex="0"
>
Learn more
</div>
</p>
<div
class="mx_DeviceSecurityCard_actions"
>
<div
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_primary"
data-testid="verification-status-button-alices_device"
role="button"
tabindex="0"
>
Verify session
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`<SessionManagerTab /> goes to filtered list from security recommendations 1`] = `
<div
class="mx_FilteredDeviceListHeader"
>
<span
tabindex="0"
>
<span
class="mx_Checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
aria-label="Select all"
aria-labelledby="floating-ui-142"
data-testid="device-select-all-checkbox"
id="device-select-all-checkbox"
type="checkbox"
/>
<label
for="device-select-all-checkbox"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
</label>
</span>
</span>
<span
class="mx_FilteredDeviceListHeader_label"
>
Sessions
</span>
<div
class="mx_Dropdown mx_FilterDropdown"
>
<div
aria-describedby="device-list-filter_value"
aria-expanded="false"
aria-haspopup="listbox"
aria-label="Filter devices"
aria-owns="device-list-filter_input"
class="mx_AccessibleButton mx_Dropdown_input mx_no_textinput"
role="button"
tabindex="0"
>
<div
class="mx_Dropdown_option"
id="device-list-filter_value"
>
Show: Unverified
</div>
<span
class="mx_Dropdown_arrow"
/>
</div>
</div>
</div>
`;

View file

@ -0,0 +1,503 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<SidebarUserSettingsTab /> renders sidebar settings with guest spa url 1`] = `
<div>
<div
class="mx_SettingsTab"
>
<div
class="mx_SettingsTab_sections"
>
<div
class="mx_SettingsSection"
>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Spaces to show
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
checked=""
disabled=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
Home
</div>
<div
class="mx_SettingsSubsection_text"
>
Home is useful for getting an overview of everything.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
id="checkbox_38QgU2Pomx"
type="checkbox"
/>
<label
for="checkbox_38QgU2Pomx"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
Show all rooms
</div>
<div
class="mx_SettingsSubsection_text"
>
Show all your rooms in Home, even if they're in a space.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_wKpa6hpi3Y"
type="checkbox"
/>
<label
for="checkbox_wKpa6hpi3Y"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
Favourites
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all your favourite rooms and people in one place.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_EetmBG4yVC"
type="checkbox"
/>
<label
for="checkbox_EetmBG4yVC"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
People
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all your people in one place.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_eEefiPqpMR"
type="checkbox"
/>
<label
for="checkbox_eEefiPqpMR"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
Rooms outside of a space
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all your rooms that aren't part of a space in one place.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_MwbPDmfGtm"
type="checkbox"
/>
<label
for="checkbox_MwbPDmfGtm"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
/>
</svg>
Video rooms and conferences
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all private video rooms and conferences. In conferences you can invite people outside of matrix.
</div>
</div>
</label>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`<SidebarUserSettingsTab /> renders sidebar settings without guest spa url 1`] = `
<div>
<div
class="mx_SettingsTab"
>
<div
class="mx_SettingsTab_sections"
>
<div
class="mx_SettingsSection"
>
<div
class="mx_SettingsSection_subSections"
>
<div
class="mx_SettingsSubsection"
>
<div
class="mx_SettingsSubsectionHeading"
>
<h3
class="mx_Heading_h4 mx_SettingsSubsectionHeading_heading"
>
Spaces to show
</h3>
</div>
<div
class="mx_SettingsSubsection_description"
>
<div
class="mx_SettingsSubsection_text"
>
Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.
</div>
</div>
<div
class="mx_SettingsSubsection_content"
>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
checked=""
disabled=""
id="checkbox_vY7Q4uEh9K"
type="checkbox"
/>
<label
for="checkbox_vY7Q4uEh9K"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
Home
</div>
<div
class="mx_SettingsSubsection_text"
>
Home is useful for getting an overview of everything.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
id="checkbox_38QgU2Pomx"
type="checkbox"
/>
<label
for="checkbox_38QgU2Pomx"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
Show all rooms
</div>
<div
class="mx_SettingsSubsection_text"
>
Show all your rooms in Home, even if they're in a space.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_wKpa6hpi3Y"
type="checkbox"
/>
<label
for="checkbox_wKpa6hpi3Y"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
Favourites
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all your favourite rooms and people in one place.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_EetmBG4yVC"
type="checkbox"
/>
<label
for="checkbox_EetmBG4yVC"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
People
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all your people in one place.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_eEefiPqpMR"
type="checkbox"
/>
<label
for="checkbox_eEefiPqpMR"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<div />
Rooms outside of a space
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all your rooms that aren't part of a space in one place.
</div>
</div>
</label>
</span>
<span
class="mx_Checkbox mx_SidebarUserSettingsTab_checkbox mx_Checkbox_hasKind mx_Checkbox_kind_solid"
>
<input
id="checkbox_MwbPDmfGtm"
type="checkbox"
/>
<label
for="checkbox_MwbPDmfGtm"
>
<div
class="mx_Checkbox_background"
>
<div
class="mx_Checkbox_checkmark"
/>
</div>
<div>
<div
class="mx_SettingsSubsection_text"
>
<svg
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6 4h10a2 2 0 0 1 2 2v4.286l3.35-2.871a1 1 0 0 1 1.65.76v7.65a1 1 0 0 1-1.65.76L18 13.715V18a2 2 0 0 1-2 2H6a4 4 0 0 1-4-4V8a4 4 0 0 1 4-4Z"
/>
</svg>
Video rooms and conferences
</div>
<div
class="mx_SettingsSubsection_text"
>
Group all private video rooms and conferences.
</div>
</div>
</label>
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;