diff --git a/packages/shared-components/src/i18n/strings/en_EN.json b/packages/shared-components/src/i18n/strings/en_EN.json
index 016b996b6fb..96e0f045310 100644
--- a/packages/shared-components/src/i18n/strings/en_EN.json
+++ b/packages/shared-components/src/i18n/strings/en_EN.json
@@ -44,8 +44,10 @@
}
},
"room_list": {
+ "appearance": "Appearance",
"open_space_menu": "Open space menu",
"room_options": "Room Options",
+ "show_message_previews": "Show message previews",
"sort": "Sort",
"sort_type": {
"activity": "Activity",
diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx
index 83babc68d16..18351d907f1 100644
--- a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx
+++ b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx
@@ -28,6 +28,7 @@ const RoomListHeaderViewWrapper = ({
inviteInSpace,
openSpacePreferences,
sort,
+ toggleMessagePreview,
...rest
}: RoomListHeaderProps): JSX.Element => {
const vm = useMockedViewModel(rest, {
@@ -39,6 +40,7 @@ const RoomListHeaderViewWrapper = ({
inviteInSpace,
sort,
openSpacePreferences,
+ toggleMessagePreview,
});
return ;
};
@@ -57,6 +59,7 @@ export default {
inviteInSpace: fn(),
sort: fn(),
openSpacePreferences: fn(),
+ toggleMessagePreview: fn(),
},
parameters: {
design: {
diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx
index 59cd6909e5b..4659625555c 100644
--- a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx
+++ b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx
@@ -56,6 +56,10 @@ export interface RoomListHeaderViewSnapshot {
* The currently active sort option.
*/
activeSortOption: SortOption;
+ /**
+ * Whether message previews are enabled in the room list.
+ */
+ isMessagePreviewEnabled: boolean;
}
export interface RoomListHeaderViewActions {
@@ -91,6 +95,10 @@ export interface RoomListHeaderViewActions {
* Change the sort order of the room-list.
*/
sort: (option: SortOption) => void;
+ /**
+ * Toggle message preview display in the room list.
+ */
+ toggleMessagePreview: () => void;
}
/**
diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.test.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.test.tsx
index c18c99b2d33..f067c1db3b6 100644
--- a/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.test.tsx
+++ b/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.test.tsx
@@ -77,4 +77,17 @@ describe("", () => {
expect(vm.sort).toHaveBeenCalledWith("recent");
});
+
+ it("should toggle message preview", async () => {
+ const user = userEvent.setup();
+
+ const vm = new MockedViewModel({ ...defaultSnapshot, isMessagePreviewEnabled: true });
+ render();
+
+ await user.click(screen.getByRole("button", { name: "Room Options" }));
+ expect(screen.getByRole("menuitemcheckbox", { name: "Show message previews" })).toBeChecked();
+
+ await user.click(screen.getByRole("menuitemcheckbox", { name: "Show message previews" }));
+ expect(vm.toggleMessagePreview).toHaveBeenCalled();
+ });
});
diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.tsx
index ba21222d696..bf6a5d80c2d 100644
--- a/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.tsx
+++ b/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.tsx
@@ -5,7 +5,7 @@
* Please see LICENSE files in the repository root for full details.
*/
-import { IconButton, Menu, MenuTitle, RadioMenuItem } from "@vector-im/compound-web";
+import { CheckboxMenuItem, IconButton, Menu, MenuTitle, RadioMenuItem } from "@vector-im/compound-web";
import React, { type JSX, useState } from "react";
import OverflowHorizontalIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
@@ -33,7 +33,7 @@ interface OptionMenuViewProps {
export function OptionMenuView({ vm }: OptionMenuViewProps): JSX.Element {
const { translate: _t } = useI18n();
const [open, setOpen] = useState(false);
- const { activeSortOption } = useViewModel(vm);
+ const { activeSortOption, isMessagePreviewEnabled } = useViewModel(vm);
return (
);
}
diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts b/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts
index 995e4fd7750..aedcf32ad6b 100644
--- a/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts
+++ b/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts
@@ -20,6 +20,7 @@ export class MockedViewModel extends MockViewModel i
public inviteInSpace = jest.fn();
public sort = jest.fn();
public openSpacePreferences = jest.fn();
+ public toggleMessagePreview = jest.fn();
}
export const defaultSnapshot: RoomListHeaderViewSnapshot = {
@@ -31,4 +32,5 @@ export const defaultSnapshot: RoomListHeaderViewSnapshot = {
canInviteInSpace: true,
canAccessSpaceSettings: true,
activeSortOption: "recent",
+ isMessagePreviewEnabled: true,
};
diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
index 5cc78eca693..ff1710091c9 100644
--- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
+++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
@@ -8,6 +8,8 @@
import { type Page } from "@playwright/test";
import { expect, test } from "../../../element-web-test";
+import { type Bot } from "../../../pages/bot";
+import { type ElementAppPage } from "../../../pages/ElementAppPage";
test.describe("Room list", () => {
test.use({
@@ -392,13 +394,8 @@ test.describe("Room list", () => {
await expect(room).toMatchScreenshot("room-list-item-mention.png");
});
- test("should render a message preview", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
- await app.settings.openUserSettings("Preferences");
- await page.getByRole("switch", { name: "Show message previews" }).click();
- await app.closeDialog();
-
+ async function checkMessagePreview(page: Page, app: ElementAppPage, bot: Bot) {
const roomListView = getRoomList(page);
-
const roomId = await app.client.createRoom({ name: "activity" });
// focus the user menu to avoid to have hover decoration
@@ -411,7 +408,30 @@ test.describe("Room list", () => {
const room = roomListView.getByRole("option", { name: "activity" });
await expect(room.getByText("I am a robot. Beep.")).toBeVisible();
await expect(room).toMatchScreenshot("room-list-item-message-preview.png");
- });
+ }
+
+ test(
+ "should render a message preview when enable in settings",
+ { tag: "@screenshot" },
+ async ({ page, app, user, bot }) => {
+ await app.settings.openUserSettings("Preferences");
+ await page.getByRole("switch", { name: "Show message previews" }).click();
+ await app.closeDialog();
+
+ await checkMessagePreview(page, app, bot);
+ },
+ );
+
+ test(
+ "should render a message preview when enabled in header",
+ { tag: "@screenshot" },
+ async ({ page, app, user, bot }) => {
+ await page.getByRole("button", { name: "Room Options" }).click();
+ await page.getByRole("menuitemcheckbox", { name: "Show message previews" }).click();
+
+ await checkMessagePreview(page, app, bot);
+ },
+ );
test("should render an activity decoration", { tag: "@screenshot" }, async ({ page, app, user, bot }) => {
const roomListView = getRoomList(page);
diff --git a/src/viewmodels/room-list/RoomListHeaderViewModel.ts b/src/viewmodels/room-list/RoomListHeaderViewModel.ts
index d5587a5629a..6d81df100c3 100644
--- a/src/viewmodels/room-list/RoomListHeaderViewModel.ts
+++ b/src/viewmodels/room-list/RoomListHeaderViewModel.ts
@@ -30,12 +30,16 @@ import { createRoom, hasCreateRoomRights } from "../../components/viewmodels/roo
import SettingsStore from "../../settings/SettingsStore";
import RoomListStoreV3 from "../../stores/room-list-v3/RoomListStoreV3";
import { SortingAlgorithm } from "../../stores/room-list-v3/skip-list/sorters";
+import { SettingLevel } from "../../settings/SettingLevel";
export interface Props {
/**
* The Matrix client instance.
*/
matrixClient: MatrixClient;
+ /**
+ * The space store instance.
+ */
spaceStore: SpaceStoreClass;
}
@@ -170,6 +174,12 @@ export class RoomListHeaderViewModel
RoomListStoreV3.instance.resort(sortingAlgorithm);
this.snapshot.merge({ activeSortOption: option });
};
+
+ public toggleMessagePreview = (): void => {
+ const isMessagePreviewEnabled = SettingsStore.getValue("RoomList.showMessagePreview");
+ SettingsStore.setValue("RoomList.showMessagePreview", null, SettingLevel.DEVICE, !isMessagePreviewEnabled);
+ this.snapshot.merge({ isMessagePreviewEnabled });
+ };
}
/**
@@ -182,9 +192,11 @@ function getInitialSnapshot(spaceStore: SpaceStoreClass, matrixClient: MatrixCli
const sortingAlgorithm = SettingsStore.getValue("RoomList.preferredSorting");
const activeSortOption =
sortingAlgorithm === SortingAlgorithm.Recency ? ("recent" as const) : ("alphabetical" as const);
+ const isMessagePreviewEnabled = SettingsStore.getValue("RoomList.showMessagePreview");
return {
activeSortOption,
+ isMessagePreviewEnabled,
...computeHeaderSpaceState(spaceStore, matrixClient),
};
}
@@ -216,7 +228,7 @@ function getCanCreateVideoRoom(canCreateRoom: boolean): boolean {
function computeHeaderSpaceState(
spaceStore: SpaceStoreClass,
matrixClient: MatrixClient,
-): Omit {
+): Omit {
const activeSpace = spaceStore.activeSpaceRoom;
const title = getHeaderTitle(spaceStore);
diff --git a/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts b/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts
index f9d65441302..3d6083bc366 100644
--- a/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts
+++ b/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts
@@ -148,6 +148,16 @@ describe("RoomListHeaderViewModel", () => {
vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
expect(vm.getSnapshot().canAccessSpaceSettings).toBe(false);
});
+
+ it("should show message preview when RoomList.showMessagePreview is enabled", () => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
+ if (settingName === "RoomList.showMessagePreview") return true;
+ return false;
+ });
+
+ vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
+ expect(vm.getSnapshot().isMessagePreviewEnabled).toBe(true);
+ });
});
describe("event listeners", () => {
@@ -268,5 +278,20 @@ describe("RoomListHeaderViewModel", () => {
expect(resortSpy).toHaveBeenCalledWith(expectedAlgorithm);
});
+
+ it("should toggle message preview from enabled to disabled", () => {
+ jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => {
+ if (settingName === "RoomList.showMessagePreview") return true;
+ return false;
+ });
+ const setValueSpy = jest.spyOn(SettingsStore, "setValue").mockImplementation(jest.fn());
+
+ vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance });
+ expect(vm.getSnapshot().isMessagePreviewEnabled).toBe(true);
+
+ vm.toggleMessagePreview();
+
+ expect(setValueSpy).toHaveBeenCalledWith("RoomList.showMessagePreview", null, expect.anything(), false);
+ });
});
});