diff --git a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts index efbdb777fd0..79c78b71f71 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts @@ -33,43 +33,6 @@ test.describe("Appearance user settings tab", () => { await expect(tab).toMatchScreenshot("appearance-tab.png"); }); - test("should support switching layouts", async ({ page, user, app }) => { - // Create and view a room first - await app.client.createRoom({ name: "Test Room" }); - await app.viewRoomByName("Test Room"); - - await app.settings.openUserSettings("Appearance"); - - const buttons = page.locator(".mx_LayoutSwitcher_RadioButton"); - - // Assert that the layout selected by default is "Modern" - await expect( - buttons.locator(".mx_StyledRadioButton_enabled", { - hasText: "Modern", - }), - ).toBeVisible(); - - // Assert that the room layout is set to group (modern) layout - await expect(page.locator(".mx_RoomView_body[data-layout='group']")).toBeVisible(); - - // Select the first layout - await buttons.first().click(); - // Assert that the layout selected is "IRC (Experimental)" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "IRC (Experimental)" })).toBeVisible(); - - // Assert that the room layout is set to IRC layout - await expect(page.locator(".mx_RoomView_body[data-layout='irc']")).toBeVisible(); - - // Select the last layout - await buttons.last().click(); - - // Assert that the layout selected is "Message bubbles" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "Message bubbles" })).toBeVisible(); - - // Assert that the room layout is set to bubble layout - await expect(page.locator(".mx_RoomView_body[data-layout='bubble']")).toBeVisible(); - }); - test("should support changing font size by using the font size dropdown", async ({ page, app, user }) => { await app.settings.openUserSettings("Appearance"); @@ -84,69 +47,61 @@ test.describe("Appearance user settings tab", () => { await expect(page).toMatchScreenshot("window-12px.png"); }); - test("should support enabling compact group (modern) layout", async ({ page, app, user }) => { - // Create and view a room first - await app.client.createRoom({ name: "Test Room" }); - await app.viewRoomByName("Test Room"); - - await app.settings.openUserSettings("Appearance"); - - // Click "Show advanced" link button - const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); - await tab.getByRole("button", { name: "Show advanced" }).click(); - - await tab.locator("label", { hasText: "Use a more compact 'Modern' layout" }).click(); - - // Assert that the room layout is set to compact group (modern) layout - await expect(page.locator("#matrixchat .mx_MatrixChat_wrapper.mx_MatrixChat_useCompactLayout")).toBeVisible(); - }); - - test("should disable compact group (modern) layout option on IRC layout and bubble layout", async ({ - page, - app, - user, - }) => { + test("should support enabling system font", async ({ page, app, user }) => { await app.settings.openUserSettings("Appearance"); const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); - const checkDisabled = async () => { - await expect(tab.getByRole("checkbox", { name: "Use a more compact 'Modern' layout" })).toBeDisabled(); - }; - // Click "Show advanced" link button await tab.getByRole("button", { name: "Show advanced" }).click(); - const buttons = page.locator(".mx_LayoutSwitcher_RadioButton"); - - // Enable IRC layout - await buttons.first().click(); + await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click(); + await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click(); - // Assert that the layout selected is "IRC (Experimental)" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "IRC (Experimental)" })).toBeVisible(); + // Assert that the font-family value was removed + await expect(page.locator("body")).toHaveCSS("font-family", '""'); + }); - await checkDisabled(); + test.describe("Message Layout Panel", () => { + test.beforeEach(async ({ app, user, util }) => { + await util.createAndDisplayRoom(); + await util.assertModernLayout(); + await util.openAppearanceTab(); + }); - // Enable bubble layout - await buttons.last().click(); + test("should change the message layout from modern to bubble", async ({ page, app, user, util }) => { + await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-modern.png"); - // Assert that the layout selected is "IRC (Experimental)" - await expect(buttons.locator(".mx_StyledRadioButton_enabled", { hasText: "Message bubbles" })).toBeVisible(); + await util.getBubbleLayout().click(); - await checkDisabled(); - }); + // Assert that modern are irc layout are not selected + await expect(util.getBubbleLayout()).toBeChecked(); + await expect(util.getModernLayout()).not.toBeChecked(); + await expect(util.getIRCLayout()).not.toBeChecked(); - test("should support enabling system font", async ({ page, app, user }) => { - await app.settings.openUserSettings("Appearance"); - const tab = page.getByTestId("mx_AppearanceUserSettingsTab"); + // Assert that the room layout is set to bubble layout + await util.assertBubbleLayout(); + await util.assertScreenshot(util.getMessageLayoutPanel(), "message-layout-panel-bubble.png"); + }); - // Click "Show advanced" link button - await tab.getByRole("button", { name: "Show advanced" }).click(); + test("should enable compact layout when the modern layout is selected", async ({ page, app, user, util }) => { + await expect(util.getCompactLayoutCheckbox()).not.toBeChecked(); - await tab.locator(".mx_Checkbox", { hasText: "Use bundled emoji font" }).click(); - await tab.locator(".mx_Checkbox", { hasText: "Use a system font" }).click(); + await util.getCompactLayoutCheckbox().click(); + await util.assertCompactLayout(); + }); - // Assert that the font-family value was removed - await expect(page.locator("body")).toHaveCSS("font-family", '""'); + test("should disable compact layout when the modern layout is not selected", async ({ + page, + app, + user, + util, + }) => { + await expect(util.getCompactLayoutCheckbox()).not.toBeDisabled(); + + // Select the bubble layout, which should disable the compact layout checkbox + await util.getBubbleLayout().click(); + await expect(util.getCompactLayoutCheckbox()).toBeDisabled(); + }); }); test.describe("Theme Choice Panel", () => { diff --git a/playwright/e2e/settings/appearance-user-settings-tab/index.ts b/playwright/e2e/settings/appearance-user-settings-tab/index.ts index 7181e5c9b42..e8641306ed5 100644 --- a/playwright/e2e/settings/appearance-user-settings-tab/index.ts +++ b/playwright/e2e/settings/appearance-user-settings-tab/index.ts @@ -14,11 +14,12 @@ * limitations under the License. */ -import { Page } from "@playwright/test"; +import { Locator, Page } from "@playwright/test"; import { ElementAppPage } from "../../../pages/ElementAppPage"; import { test as base, expect } from "../../../element-web-test"; import { SettingLevel } from "../../../../src/settings/SettingLevel"; +import { Layout } from "../../../../src/settings/enums/Layout"; export { expect }; @@ -57,6 +58,21 @@ class Helpers { return this.app.settings.openUserSettings("Appearance"); } + /** + * Compare screenshot and hide the matrix chat + * @param locator + * @param screenshot + */ + assertScreenshot(locator: Locator, screenshot: `${string}.png`) { + return expect(locator).toMatchScreenshot(screenshot, { + css: ` + #matrixchat { + display: none; + } + `, + }); + } + // Theme Panel /** @@ -136,4 +152,90 @@ class Helpers { removeCustomTheme() { return this.getThemePanel().getByRole("listitem", { name: this.CUSTOM_THEME.name }).getByRole("button").click(); } + + // Message layout Panel + + /** + * Create and display a room named Test Room + */ + async createAndDisplayRoom() { + await this.app.client.createRoom({ name: "Test Room" }); + await this.app.viewRoomByName("Test Room"); + } + + /** + * Assert the room layout + * @param layout + * @private + */ + private assertRoomLayout(layout: Layout) { + return expect(this.page.locator(`.mx_RoomView_body[data-layout=${layout}]`)).toBeVisible(); + } + + /** + * Assert the room layout is modern + */ + assertModernLayout() { + return this.assertRoomLayout(Layout.Group); + } + + /** + * Assert the room layout is bubble + */ + assertBubbleLayout() { + return this.assertRoomLayout(Layout.Bubble); + } + + /** + * Return the layout panel + */ + getMessageLayoutPanel() { + return this.page.getByTestId("layoutPanel"); + } + + /** + * Return the layout radio button + * @param layoutName + * @private + */ + private getLayout(layoutName: string) { + return this.getMessageLayoutPanel().getByRole("radio", { name: layoutName }); + } + + /** + * Return the message bubbles layout radio button + */ + getBubbleLayout() { + return this.getLayout("Message bubbles"); + } + + /** + * Return the modern layout radio button + */ + getModernLayout() { + return this.getLayout("Modern"); + } + + /** + * Return the IRC layout radio button + */ + getIRCLayout() { + return this.getLayout("IRC (experimental)"); + } + + /** + * Return the compact layout checkbox + */ + getCompactLayoutCheckbox() { + return this.getMessageLayoutPanel().getByRole("checkbox", { name: "Show compact text and messages" }); + } + + /** + * Assert the compact layout is enabled + */ + assertCompactLayout() { + return expect( + this.page.locator("#matrixchat .mx_MatrixChat_wrapper.mx_MatrixChat_useCompactLayout"), + ).toBeVisible(); + } } diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png index 98ece4d4d92..12ea3aa847f 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/message-layout-panel-bubble-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/message-layout-panel-bubble-linux.png new file mode 100644 index 00000000000..3f39a3b01d0 Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/message-layout-panel-bubble-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/message-layout-panel-modern-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/message-layout-panel-modern-linux.png new file mode 100644 index 00000000000..74aaf9a763b Binary files /dev/null and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/message-layout-panel-modern-linux.png differ diff --git a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png index a1ca72b398a..1ce0ffd520a 100644 Binary files a/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png and b/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/window-12px-linux.png differ diff --git a/res/css/views/settings/_LayoutSwitcher.pcss b/res/css/views/settings/_LayoutSwitcher.pcss index 571b9a1cf1c..96fe27b420c 100644 --- a/res/css/views/settings/_LayoutSwitcher.pcss +++ b/res/css/views/settings/_LayoutSwitcher.pcss @@ -15,79 +15,80 @@ See the License for the specific language governing permissions and limitations under the License. */ -.mx_LayoutSwitcher_RadioButtons { +.mx_LayoutSwitcher_LayoutSelector { display: flex; - flex-direction: row; - gap: 24px; - width: 100%; - - color: $primary-content; - - > .mx_LayoutSwitcher_RadioButton { - flex-grow: 0; - flex-shrink: 1; - display: flex; - flex-direction: column; - overflow: hidden; - - flex-basis: 33%; - min-width: 0; - - border: 1px solid $quinary-content; - border-radius: 10px; - - .mx_EventTile_msgOption, - .mx_MessageActionBar { - display: none; - } - - .mx_LayoutSwitcher_RadioButton_preview { - flex-grow: 1; + flex-direction: column; + /** + * The settings form has a default gap of 10px + * We want to have a bigger gap between the layout options + */ + gap: var(--cpd-space-4x) !important; + + .mxLayoutSwitcher_LayoutSelector_LayoutRadio { + border: 1px solid var(--cpd-color-border-interactive-primary); + border-radius: var(--cpd-space-2x); + + .mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline { display: flex; + /* + * 10px + */ + gap: calc(var(--cpd-space-2x) + var(--cpd-space-0-5x)); align-items: center; - padding: 10px; - pointer-events: none; - - .mx_EventTile[data-layout="bubble"] .mx_EventTile_line { - padding-right: 11px; - } } - .mx_StyledRadioButton { - flex-grow: 0; - padding: 10px; + .mxLayoutSwitcher_LayoutSelector_LayoutRadio_inline, + .mxLayoutSwitcher_LayoutSelector_LayoutRadio_EventTilePreview { + margin: var(--cpd-space-3x); } - .mx_EventTile_content { - margin-right: 0; - } - - &.mx_LayoutSwitcher_RadioButton_selected { - border-color: var(--cpd-color-bg-accent-rest); - } - } - - .mx_StyledRadioButton { - border-top: 1px solid $quinary-content; - } - - .mx_StyledRadioButton_checked { - background-color: var(--cpd-color-bg-subtle-secondary); - } + /** + * Override the event tile style to make it fit in the selector + * Tweak also hover style and remove action bar + */ + .mxLayoutSwitcher_LayoutSelector_LayoutRadio_EventTilePreview { + pointer-events: none; - .mx_EventTile { - margin: 0; - &[data-layout="bubble"] { - margin-right: 40px; - flex-shrink: 1; - } - &[data-layout="irc"] { - > a { - display: none; + .mx_EventTile { + margin: 0; + + /** + * Hide the message options and message action bar in the preview + */ + .mx_EventTile_msgOption, + .mx_MessageActionBar { + display: none; + } + + .mx_EventTile_content { + margin-right: 0; + } + + &[data-layout="group"] { + margin-top: calc(var(--cpd-space-3x) * -1); + } + + /** + * Add margin to center the bubble + */ + &[data-layout="bubble"] { + /** + * Add the layout margin and the margin to vertically center the bubble + */ + margin-top: var(--cpd-space-6x); + margin-right: 34px; + flex-shrink: 1; + } + + .mx_EventTile_line { + max-width: 100%; + } } } - .mx_EventTile_line { - max-width: 90%; + + .mxLayoutSwitcher_LayoutSelector_LayoutRadio_separator { + border-top: 0; + border-bottom: 1px solid var(--cpd-color-border-interactive-secondary); } } } diff --git a/src/components/views/elements/EventTilePreview.tsx b/src/components/views/elements/EventTilePreview.tsx index fd11d372dc3..4b8ccb0ed90 100644 --- a/src/components/views/elements/EventTilePreview.tsx +++ b/src/components/views/elements/EventTilePreview.tsx @@ -37,7 +37,7 @@ interface IProps { /** * classnames to apply to the wrapper of the preview */ - className: string; + className?: string; /** * The ID of the displayed user diff --git a/src/components/views/settings/LayoutSwitcher.tsx b/src/components/views/settings/LayoutSwitcher.tsx index 101a75fbe89..de3827b63df 100644 --- a/src/components/views/settings/LayoutSwitcher.tsx +++ b/src/components/views/settings/LayoutSwitcher.tsx @@ -1,131 +1,170 @@ /* -Copyright 2019 New Vector Ltd -Copyright 2019 - 2021 The Matrix.org Foundation C.I.C. -Copyright 2021 Šimon Brandner + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +import React, { JSX, useEffect, useState } from "react"; +import { Field, HelpMessage, InlineField, Label, RadioControl, Root, ToggleControl } from "@vector-im/compound-web"; - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import React from "react"; -import classNames from "classnames"; - -import SettingsStore from "../../../settings/SettingsStore"; -import EventTilePreview from "../elements/EventTilePreview"; -import StyledRadioButton from "../elements/StyledRadioButton"; +import SettingsSubsection from "./shared/SettingsSubsection"; import { _t } from "../../../languageHandler"; -import { Layout } from "../../../settings/enums/Layout"; +import SettingsStore from "../../../settings/SettingsStore"; import { SettingLevel } from "../../../settings/SettingLevel"; -import SettingsSubsection from "./shared/SettingsSubsection"; +import { useSettingValue } from "../../../hooks/useSettings"; +import { Layout } from "../../../settings/enums/Layout"; +import EventTilePreview from "../elements/EventTilePreview"; +import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; -interface IProps { - userId?: string; - displayName?: string; - avatarUrl?: string; - messagePreviewText: string; - onLayoutChanged: (layout: Layout) => void; +/** + * A section to switch between different message layouts. + */ +export function LayoutSwitcher(): JSX.Element { + return ( + + + + + ); } -interface IState { +/** + * A selector to choose the layout of the messages. + */ +function LayoutSelector(): JSX.Element { + return ( + { + // We don't have any file in the form, we can cast it as string safely + const newLayout = new FormData(evt.currentTarget).get("layout") as string | null; + await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, newLayout); + }} + > + + + + + ); +} + +/** + * A radio button to select a layout. + */ +interface LayoutRadioProps { + /** + * The value of the layout. + */ layout: Layout; + /** + * The label to display for the layout. + */ + label: string; } -export default class LayoutSwitcher extends React.Component { - public constructor(props: IProps) { - super(props); +/** + * A radio button to select a layout. + * @param layout + * @param label + */ +function LayoutRadio({ layout, label }: LayoutRadioProps): JSX.Element { + const currentLayout = useSettingValue("layout"); + const eventTileInfo = useEventTileInfo(); - this.state = { - layout: SettingsStore.getValue("layout"), - }; - } + return ( + + + + ); +} + +type EventTileInfo = { + /** + * The ID of the user to display. + */ + userId: string; + /** + * The display name of the user to display. + */ + displayName?: string; + /** + * The avatar URL of the user to display. + */ + avatarUrl?: string; +}; + +/** + * Fetch the information to display in the event tile preview. + */ +function useEventTileInfo(): EventTileInfo { + const matrixClient = useMatrixClientContext(); + const userId = matrixClient.getSafeUserId(); + const [eventTileInfo, setEventTileInfo] = useState({ userId }); - private onLayoutChange = (e: React.ChangeEvent): void => { - const layout = e.target.value as Layout; + useEffect(() => { + const run = async (): Promise => { + const profileInfo = await matrixClient.getProfileInfo(userId); + setEventTileInfo({ + userId, + displayName: profileInfo.displayname, + avatarUrl: profileInfo.avatar_url, + }); + }; - this.setState({ layout: layout }); - SettingsStore.setValue("layout", null, SettingLevel.DEVICE, layout); - this.props.onLayoutChanged(layout); - }; + run(); + }, [userId, matrixClient, setEventTileInfo]); + return eventTileInfo; +} - public render(): React.ReactNode { - const ircClasses = classNames("mx_LayoutSwitcher_RadioButton", { - mx_LayoutSwitcher_RadioButton_selected: this.state.layout == Layout.IRC, - }); - const groupClasses = classNames("mx_LayoutSwitcher_RadioButton", { - mx_LayoutSwitcher_RadioButton_selected: this.state.layout == Layout.Group, - }); - const bubbleClasses = classNames("mx_LayoutSwitcher_RadioButton", { - mx_LayoutSwitcher_RadioButton_selected: this.state.layout === Layout.Bubble, - }); +/** + * A toggleable setting to enable or disable the compact layout. + */ +function ToggleCompactLayout(): JSX.Element { + const compactLayoutEnabled = useSettingValue("useCompactLayout"); + const layout = useSettingValue("layout"); - return ( - -
- - - -
-
- ); - } + return ( + { + const checked = new FormData(evt.currentTarget).get("compactLayout") === "on"; + await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, checked); + }} + > + + } + > + + {_t("settings|appearance|compact_layout_description")} + + + ); } diff --git a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx index cc76a7b2c1e..cf3db0f5ad9 100644 --- a/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/AppearanceUserSettingsTab.tsx @@ -25,15 +25,13 @@ import Field from "../../../elements/Field"; import AccessibleButton from "../../../elements/AccessibleButton"; import { SettingLevel } from "../../../../../settings/SettingLevel"; import { UIFeature } from "../../../../../settings/UIFeature"; -import { Layout } from "../../../../../settings/enums/Layout"; -import LayoutSwitcher from "../../LayoutSwitcher"; +import { LayoutSwitcher } from "../../LayoutSwitcher"; import FontScalingPanel from "../../FontScalingPanel"; import { ThemeChoicePanel } from "../../ThemeChoicePanel"; import ImageSizePanel from "../../ImageSizePanel"; import SettingsTab from "../SettingsTab"; import { SettingsSection } from "../../shared/SettingsSection"; import SettingsSubsection from "../../shared/SettingsSubsection"; -import MatrixClientContext from "../../../../../contexts/MatrixClientContext"; interface IProps {} @@ -42,21 +40,9 @@ interface IState { useSystemFont: boolean; systemFont: string; showAdvanced: boolean; - layout: Layout; - // User profile data for the message preview - userId?: string; - displayName?: string; - avatarUrl?: string; } export default class AppearanceUserSettingsTab extends React.Component { - public static contextType = MatrixClientContext; - public context!: React.ContextType; - - private readonly MESSAGE_PREVIEW_TEXT = _t("common|preview_message"); - - private unmounted = false; - public constructor(props: IProps) { super(props); @@ -65,32 +51,9 @@ export default class AppearanceUserSettingsTab extends React.Component { - // Fetch the current user profile for the message preview - const client = this.context; - const userId = client.getUserId()!; - const profileInfo = await client.getProfileInfo(userId); - if (this.unmounted) return; - - this.setState({ - userId, - displayName: profileInfo.displayname, - avatarUrl: profileInfo.avatar_url, - }); - } - - public componentWillUnmount(): void { - this.unmounted = true; - } - - private onLayoutChanged = (layout: Layout): void => { - this.setState({ layout: layout }); - }; - private renderAdvancedSection(): ReactNode { if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; @@ -156,13 +119,7 @@ export default class AppearanceUserSettingsTab extends React.Component - + {this.renderAdvancedSection()} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5a532d00789..5272e22c855 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2416,6 +2416,8 @@ "always_show_message_timestamps": "Always show message timestamps", "appearance": { "bundled_emoji_font": "Use bundled emoji font", + "compact_layout": "Show compact text and messages", + "compact_layout_description": "Modern layout must be selected to use this feature.", "custom_font": "Use a system font", "custom_font_description": "Set the name of a font installed on your system & %(brand)s will attempt to use it.", "custom_font_name": "System font name", @@ -2432,7 +2434,7 @@ "image_size_default": "Default", "image_size_large": "Large", "layout_bubbles": "Message bubbles", - "layout_irc": "IRC (Experimental)", + "layout_irc": "IRC (experimental)", "match_system_theme": "Match system theme", "timeline_image_size": "Image size in the timeline" }, diff --git a/test/components/views/settings/LayoutSwitcher-test.tsx b/test/components/views/settings/LayoutSwitcher-test.tsx new file mode 100644 index 00000000000..c5d8a455fc1 --- /dev/null +++ b/test/components/views/settings/LayoutSwitcher-test.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2024 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from "react"; +import { act, render, screen, waitFor } from "@testing-library/react"; +import { mocked } from "jest-mock"; + +import { LayoutSwitcher } from "../../../../src/components/views/settings/LayoutSwitcher"; +import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; +import { stubClient } from "../../../test-utils"; +import SettingsStore from "../../../../src/settings/SettingsStore"; +import { SettingLevel } from "../../../../src/settings/SettingLevel"; +import { Layout } from "../../../../src/settings/enums/Layout"; + +describe("", () => { + const matrixClient = stubClient(); + const profileInfo = { + displayname: "Alice", + }; + + async function renderLayoutSwitcher() { + const renderResult = render( + + + , + ); + + // Wait for the profile info to be displayed in the event tile preview + // Also avoid act warning + await waitFor(() => expect(screen.getAllByText(profileInfo.displayname).length).toBe(3)); + return renderResult; + } + + beforeEach(async () => { + await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group); + mocked(matrixClient).getProfileInfo.mockResolvedValue(profileInfo); + }); + + it("should render", async () => { + const { asFragment } = await renderLayoutSwitcher(); + expect(asFragment()).toMatchSnapshot(); + }); + + describe("layout selection", () => { + it("should display the modern layout", async () => { + await renderLayoutSwitcher(); + expect(screen.getByRole("radio", { name: "Modern" })).toBeChecked(); + }); + + it("should change the layout when selected", async () => { + await renderLayoutSwitcher(); + act(() => screen.getByRole("radio", { name: "Message bubbles" }).click()); + + expect(screen.getByRole("radio", { name: "Message bubbles" })).toBeChecked(); + await waitFor(() => expect(SettingsStore.getValue("layout")).toBe(Layout.Bubble)); + }); + }); + + describe("compact layout", () => { + beforeEach(async () => { + await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, false); + }); + + it("should be enabled", async () => { + await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true); + await renderLayoutSwitcher(); + + expect(screen.getByRole("checkbox", { name: "Show compact text and messages" })).toBeChecked(); + }); + + it("should change the setting when toggled", async () => { + await renderLayoutSwitcher(); + act(() => screen.getByRole("checkbox", { name: "Show compact text and messages" }).click()); + + await waitFor(() => expect(SettingsStore.getValue("useCompactLayout")).toBe(true)); + }); + + it("should be disabled when the modern layout is not enabled", async () => { + await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Bubble); + await renderLayoutSwitcher(); + expect(screen.getByRole("checkbox", { name: "Show compact text and messages" })).toBeDisabled(); + }); + }); +}); diff --git a/test/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap b/test/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap new file mode 100644 index 00000000000..fef33c497bf --- /dev/null +++ b/test/components/views/settings/__snapshots__/LayoutSwitcher-test.tsx.snap @@ -0,0 +1,426 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render 1`] = ` + +
+
+

+ Message layout +

+
+
+
+
+