From fb57924350881d82ec77e7b64894c6120551bc4d Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 20 Feb 2025 16:40:19 +0100 Subject: [PATCH] First step to add header to new room list (#29320) * feat: create new header * test: add tests to view model * test: add tests to view * feat: add header to new room list * test(e2e): update RoomListView snapshot * test(e2e): add tests for room list header * refactor: minor code improvement --- .../room-list-view/room-list-header.spec.ts | 58 ++++++++ .../room-list-header-compose-menu-linux.png | Bin 0 -> 4816 bytes .../room-list-header-linux.png | Bin 0 -> 1657 bytes .../room-list-view-linux.png | Bin 6399 -> 7866 bytes res/css/_components.pcss | 1 + .../RoomListView/_RoomListHeaderView.pcss | 20 +++ .../roomlist/RoomListHeaderViewModel.tsx | 128 ++++++++++++++++++ .../rooms/RoomListView/RoomListHeaderView.tsx | 77 +++++++++++ .../views/rooms/RoomListView/RoomListView.tsx | 6 +- src/i18n/strings/en_EN.json | 2 + .../roomlist/RoomListHeaderViewModel-test.tsx | 101 ++++++++++++++ .../RoomListView/RoomListHeaderView-test.tsx | 87 ++++++++++++ .../RoomListHeaderView-test.tsx.snap | 71 ++++++++++ .../__snapshots__/RoomListView-test.tsx.snap | 69 +++++++++- 14 files changed, 614 insertions(+), 6 deletions(-) create mode 100644 playwright/e2e/left-panel/room-list-view/room-list-header.spec.ts create mode 100644 playwright/snapshots/left-panel/room-list-view/room-list-header.spec.ts/room-list-header-compose-menu-linux.png create mode 100644 playwright/snapshots/left-panel/room-list-view/room-list-header.spec.ts/room-list-header-linux.png create mode 100644 res/css/views/rooms/RoomListView/_RoomListHeaderView.pcss create mode 100644 src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx create mode 100644 src/components/views/rooms/RoomListView/RoomListHeaderView.tsx create mode 100644 test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx create mode 100644 test/unit-tests/components/views/rooms/RoomListView/RoomListHeaderView-test.tsx create mode 100644 test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListHeaderView-test.tsx.snap diff --git a/playwright/e2e/left-panel/room-list-view/room-list-header.spec.ts b/playwright/e2e/left-panel/room-list-view/room-list-header.spec.ts new file mode 100644 index 00000000000..bda87b04621 --- /dev/null +++ b/playwright/e2e/left-panel/room-list-view/room-list-header.spec.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { test, expect } from "../../../element-web-test"; +import type { Page } from "@playwright/test"; + +test.describe("Header section of the room list", () => { + test.use({ + labsFlags: ["feature_new_room_list"], + }); + + /** + * Get the header section of the room list + * @param page + */ + function getHeaderSection(page: Page) { + return page.getByTestId("room-list-header"); + } + + test.beforeEach(async ({ page, app, user }) => { + // The notification toast is displayed above the search section + await app.closeNotificationToast(); + }); + + test("should render the header section", { tag: "@screenshot" }, async ({ page, app, user }) => { + const roomListHeader = getHeaderSection(page); + await expect(roomListHeader).toMatchScreenshot("room-list-header.png"); + + const composeMenu = roomListHeader.getByRole("button", { name: "Add" }); + await composeMenu.click(); + + await expect(page.getByRole("menu")).toMatchScreenshot("room-list-header-compose-menu.png"); + + // New message should open the direct messages dialog + await page.getByRole("menuitem", { name: "New message" }).click(); + await expect(page.getByRole("heading", { name: "Direct Messages" })).toBeVisible(); + await app.closeDialog(); + + // New room should open the room creation dialog + await composeMenu.click(); + await page.getByRole("menuitem", { name: "New room" }).click(); + await expect(page.getByRole("heading", { name: "Create a private room" })).toBeVisible(); + await app.closeDialog(); + }); + + test("should render the header section for a space", async ({ page, app, user }) => { + await app.client.createSpace({ name: "MySpace" }); + await page.getByRole("button", { name: "MySpace" }).click(); + + const roomListHeader = getHeaderSection(page); + await expect(roomListHeader.getByRole("heading", { name: "MySpace" })).toBeVisible(); + await expect(roomListHeader.getByRole("button", { name: "Add" })).not.toBeVisible(); + }); +}); diff --git a/playwright/snapshots/left-panel/room-list-view/room-list-header.spec.ts/room-list-header-compose-menu-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-header.spec.ts/room-list-header-compose-menu-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8f53c47d53c0252f104e995e574fd3314a50d948 GIT binary patch literal 4816 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/left-panel/room-list-view/room-list-header.spec.ts/room-list-header-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-header.spec.ts/room-list-header-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..c139730915a40b63d6fb5a11f4d7f6b52294e3e8 GIT binary patch literal 1657 zcmbuAYdF&j9LN7Aw=HT!x!>lvJRQSQ?kB9~5~8S{rb0-=Xl*2yp0qIKWWyY{+HyI_ zB=<14%G`#K%ZO?=S~ikGF2^~q&%56IzQ5=5;`{vG{J!6m)9xr)unHIe09j`zq!$2) z`R?vE(xBbxV!mCzD`Ge=lmozEt1SS4B*GbKe>OUAaSD5CBuGtaX{K~gNKBHuJ?uH? zY5u8CsolT`GAln6N)L6vPqNwxjDaDBL!tTsY~+L6wO8+?yr@bxxk;LRoN|>NU^Up- zM$hQG5QrY?rrq=Zs3>e`v1n{pz(-8c=YJ^3Px3<&;-ZJJ1tlS-4b$NuVBU}QNn`iA z$VU*M&A}J|AdLWUmlqA& z$i>}2_9)Z(z+_DmRj>o2FICa8=351j$++-;LQ z3O)|^_4SoX%R9?B_SNRcB^R=@G;??|>)cYyyZ9*jk?=Po-OS8YkyXc~%vv+^`b+vc z0a_*RsB_^4l?=miifJd;w(#g}S0X*KFzWAjo939@kE^5`_=e+!hI~lfv$HiFEm-`W!9-Gsmi@L-Q=+~?188!vHUd-4?WJ*`z|fdl-oQ;Nphfh?{}gN`2`#@x`e_?tTRz0W7R3Kx=pJco zcKFTgO@(VJC0fVsh;|tMS-Ml52BqZ~BDjE}wT#?cn~z?J&>AD`;TmPgPP)NL1Q}Mk%GfLD;o+=;^C`%u1BGZ0sJDd$S z8d^=GcD&Mq@MYDlo?L875J<1^o(3u*MAK2M2@%0VSuMw{N+I1s*J0ky!35=s4t%(k z)6YP~+RwTH(ffZ=8$2<*#0jB@23V&JrWML4Li0(rY2ii zTkUvZEqobqQZClk`84eSC3q}u{5ehhuyBA6+32*^m|UH|j$Yd=s?r&&zK{*mJ}`SB zs)}cJ>%G&F!EVh2Iq5^Zn=c(4PnHg*|@pV)=etJMjw2Vu2ft0p4p?; zqX3HIKnb)+1p^h>kLL#O0Z$!4pxm z&s9ev!|*eOY?2$gy1ZMiOx084svy<4GzN64p(WvkFVl!i2)ephS&Laj!jQ~-rmpTSG8`gyCibL#)!IZI zyqAjsLzNwQ=j(ziNQmV)3+36;@7=}t9ZX!Thm47I?Dh$^F7wYrYs-6MHN}9#|G@`f zjfopgqX@|SPeZ%Qaw`&d?6vHICRtGXxIgToxyftUA-j0sAaIAc{%cA4ziIp|0T^c~ VS|ViKRd%}sa6aLVWH?;8`ws$l4<`Ts literal 0 HcmV?d00001 diff --git a/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png b/playwright/snapshots/left-panel/room-list-view/room-list-view.spec.ts/room-list-view-linux.png index f114eff64c9f84317d20cb7117d89c175e0845ae..b1c7960e3b22f583988a354909e0162886af8e05 100644 GIT binary patch literal 7866 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 qS1+1$M~+9LG2x<=>G$|CQ_UL literal 6399 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 + SpaceStore.instance, + UPDATE_SELECTED_SPACE, + () => [SpaceStore.instance.activeSpace, SpaceStore.instance.activeSpaceRoom], + ); + const spaceName = useTypedEventEmitterState(activeSpace ?? undefined, RoomEvent.Name, () => activeSpace?.name); + const allRoomsInHome = useEventEmitterState( + SpaceStore.instance, + UPDATE_HOME_BEHAVIOUR, + () => SpaceStore.instance.allRoomsInHome, + ); + + const title = spaceName ?? getMetaSpaceName(spaceKey as MetaSpace, allRoomsInHome); + + return { + activeSpace, + title, + }; +} + +export interface RoomListHeaderViewState { + /** + * The title of the room list + */ + title: string; + /** + * Whether to display the compose menu + * True if the user can create rooms and is not in a Space + */ + displayComposeMenu: boolean; + /** + * Whether the user can create rooms + */ + canCreateRoom: boolean; + /** + * Whether the user can create video rooms + */ + canCreateVideoRoom: boolean; + /** + * Create a chat room + * @param e - The click event + */ + createChatRoom: (e: Event) => void; + /** + * Create a room + * @param e - The click event + */ + createRoom: (e: Event) => void; + /** + * Create a video room + */ + createVideoRoom: () => void; +} + +/** + * View model for the RoomListHeader. + * The actions don't work when called in a space yet. + */ +export function useRoomListHeaderViewModel(): RoomListHeaderViewState { + const { activeSpace, title } = useSpace(); + + const canCreateRoom = shouldShowComponent(UIComponent.CreateRooms); + const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms"); + // Temporary: don't display the compose menu when in a Space + const displayComposeMenu = canCreateRoom && !activeSpace; + + /* Actions */ + + const createChatRoom = useCallback((e: Event) => { + defaultDispatcher.fire(Action.CreateChat); + PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e); + }, []); + + const createRoom = useCallback((e: Event) => { + defaultDispatcher.fire(Action.CreateRoom); + PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e); + }, []); + + const elementCallVideoRoomsEnabled = useFeatureEnabled("feature_element_call_video_rooms"); + const createVideoRoom = useCallback( + () => + defaultDispatcher.dispatch({ + action: Action.CreateRoom, + type: elementCallVideoRoomsEnabled ? RoomType.UnstableCall : RoomType.ElementVideo, + }), + [elementCallVideoRoomsEnabled], + ); + + return { + title, + displayComposeMenu, + canCreateRoom, + canCreateVideoRoom, + createChatRoom, + createRoom, + createVideoRoom, + }; +} diff --git a/src/components/views/rooms/RoomListView/RoomListHeaderView.tsx b/src/components/views/rooms/RoomListView/RoomListHeaderView.tsx new file mode 100644 index 00000000000..478b9b7e704 --- /dev/null +++ b/src/components/views/rooms/RoomListView/RoomListHeaderView.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ +import React, { type JSX, useState } from "react"; +import { IconButton, Menu, MenuItem } from "@vector-im/compound-web"; +import ComposeIcon from "@vector-im/compound-design-tokens/assets/web/icons/compose"; +import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user-add"; +import RoomIcon from "@vector-im/compound-design-tokens/assets/web/icons/room"; +import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call"; + +import { _t } from "../../../../languageHandler"; +import { Flex } from "../../../utils/Flex"; +import { + type RoomListHeaderViewState, + useRoomListHeaderViewModel, +} from "../../../viewmodels/roomlist/RoomListHeaderViewModel"; + +/** + * The header view for the room list + * The space name is displayed and a compose menu is shown if the user can create rooms + */ +export function RoomListHeaderView(): JSX.Element { + const vm = useRoomListHeaderViewModel(); + + return ( + +

{vm.title}

+ {vm.displayComposeMenu && } +
+ ); +} + +interface ComposeMenuProps { + /** + * The view model for the room list header + */ + vm: RoomListHeaderViewState; +} + +/** + * The compose menu for the room list header + */ +function ComposeMenu({ vm }: ComposeMenuProps): JSX.Element { + const [open, setOpen] = useState(false); + + return ( + + + + } + > + + {vm.canCreateRoom && } + {vm.canCreateVideoRoom && ( + + )} + + ); +} diff --git a/src/components/views/rooms/RoomListView/RoomListView.tsx b/src/components/views/rooms/RoomListView/RoomListView.tsx index f733078a2cf..2aa11269ffb 100644 --- a/src/components/views/rooms/RoomListView/RoomListView.tsx +++ b/src/components/views/rooms/RoomListView/RoomListView.tsx @@ -10,6 +10,7 @@ import React from "react"; import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../../settings/UIFeature"; import { RoomListSearch } from "./RoomListSearch"; +import { RoomListHeaderView } from "./RoomListHeaderView"; type RoomListViewProps = { /** @@ -26,8 +27,9 @@ export const RoomListView: React.FC = ({ activeSpace }) => { const displayRoomSearch = shouldShowComponent(UIComponent.FilterContainer); return ( -
+
{displayRoomSearch && } -
+ + ); }; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2428efc72fa..1b8a504d5ec 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -80,12 +80,14 @@ "maximise": "Maximise", "mention": "Mention", "minimise": "Minimise", + "new_message": "New message", "new_room": "New room", "new_video_room": "New video room", "next": "Next", "no": "No", "ok": "OK", "open": "Open", + "open_menu": "Open menu", "pause": "Pause", "pin": "Pin", "play": "Play", diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx new file mode 100644 index 00000000000..95a594e3b65 --- /dev/null +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import { renderHook } from "jest-matrix-react"; +import { type MatrixClient, RoomType } from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; + +import { useRoomListHeaderViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel"; +import SpaceStore from "../../../../../src/stores/spaces/SpaceStore"; +import { mkStubRoom, stubClient } from "../../../../test-utils"; +import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents"; +import SettingsStore from "../../../../../src/settings/SettingsStore"; +import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; + +jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({ + shouldShowComponent: jest.fn(), +})); + +describe("useRoomListHeaderViewModel", () => { + let matrixClient: MatrixClient; + + beforeEach(() => { + matrixClient = stubClient(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe("title", () => { + it("should return Home as title", () => { + const { result } = renderHook(() => useRoomListHeaderViewModel()); + expect(result.current.title).toStrictEqual("Home"); + }); + + it("should return the current space name as title", () => { + const room = mkStubRoom("spaceId", "spaceName", matrixClient); + jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(room); + const { result } = renderHook(() => useRoomListHeaderViewModel()); + + expect(result.current.title).toStrictEqual("spaceName"); + }); + }); + + it("should be displayComposeMenu=true and canCreateRoom=true if the user can creates room", () => { + mocked(shouldShowComponent).mockReturnValue(false); + const { result, rerender } = renderHook(() => useRoomListHeaderViewModel()); + expect(result.current.displayComposeMenu).toBe(false); + expect(result.current.canCreateRoom).toBe(false); + + mocked(shouldShowComponent).mockReturnValue(true); + rerender(); + expect(result.current.displayComposeMenu).toBe(true); + expect(result.current.canCreateRoom).toBe(true); + }); + + it("should be canCreateVideoRoom=true if feature_video_rooms is enabled", () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + const { result } = renderHook(() => useRoomListHeaderViewModel()); + expect(result.current.canCreateVideoRoom).toBe(true); + }); + + it("should fire Action.CreateChat when createChatRoom is called", () => { + const spy = jest.spyOn(defaultDispatcher, "fire"); + const { result } = renderHook(() => useRoomListHeaderViewModel()); + result.current.createChatRoom(new Event("click")); + + expect(spy).toHaveBeenCalledWith(Action.CreateChat); + }); + + it("should fire Action.CreateRoom when createRoom is called", () => { + const spy = jest.spyOn(defaultDispatcher, "fire"); + const { result } = renderHook(() => useRoomListHeaderViewModel()); + result.current.createRoom(new Event("click")); + + expect(spy).toHaveBeenCalledWith(Action.CreateRoom); + }); + + it("should fire Action.CreateRoom with RoomType.UnstableCall when createVideoRoom is called and feature_element_call_video_rooms is enabled", () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); + const spy = jest.spyOn(defaultDispatcher, "dispatch"); + const { result } = renderHook(() => useRoomListHeaderViewModel()); + result.current.createVideoRoom(); + + expect(spy).toHaveBeenCalledWith({ action: Action.CreateRoom, type: RoomType.UnstableCall }); + }); + + it("should fire Action.CreateRoom with RoomType.ElementVideo when createVideoRoom is called and feature_element_call_video_rooms is disabled", () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); + const spy = jest.spyOn(defaultDispatcher, "dispatch"); + const { result } = renderHook(() => useRoomListHeaderViewModel()); + result.current.createVideoRoom(); + + expect(spy).toHaveBeenCalledWith({ action: Action.CreateRoom, type: RoomType.ElementVideo }); + }); +}); diff --git a/test/unit-tests/components/views/rooms/RoomListView/RoomListHeaderView-test.tsx b/test/unit-tests/components/views/rooms/RoomListView/RoomListHeaderView-test.tsx new file mode 100644 index 00000000000..fa81db04b0d --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListView/RoomListHeaderView-test.tsx @@ -0,0 +1,87 @@ +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +import React from "react"; +import { mocked } from "jest-mock"; +import { render, screen } from "jest-matrix-react"; +import userEvent from "@testing-library/user-event"; + +import { + type RoomListHeaderViewState, + useRoomListHeaderViewModel, +} from "../../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel"; +import { RoomListHeaderView } from "../../../../../../src/components/views/rooms/RoomListView/RoomListHeaderView"; + +jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListHeaderViewModel", () => ({ + useRoomListHeaderViewModel: jest.fn(), +})); + +describe("", () => { + const defaultValue: RoomListHeaderViewState = { + title: "title", + displayComposeMenu: true, + canCreateRoom: true, + canCreateVideoRoom: true, + createRoom: jest.fn(), + createVideoRoom: jest.fn(), + createChatRoom: jest.fn(), + }; + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should display the compose menu", () => { + mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue); + + const { asFragment } = render(); + expect(screen.queryByRole("button", { name: "Add" })).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should not display the compose menu", () => { + mocked(useRoomListHeaderViewModel).mockReturnValue({ ...defaultValue, displayComposeMenu: false }); + + const { asFragment } = render(); + expect(screen.queryByRole("button", { name: "Add" })).toBeNull(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should display all the buttons when the menu is opened", async () => { + const user = userEvent.setup(); + mocked(useRoomListHeaderViewModel).mockReturnValue(defaultValue); + render(); + const openMenu = screen.getByRole("button", { name: "Add" }); + await user.click(openMenu); + + await user.click(screen.getByRole("menuitem", { name: "New message" })); + expect(defaultValue.createChatRoom).toHaveBeenCalled(); + + await user.click(openMenu); + await user.click(screen.getByRole("menuitem", { name: "New room" })); + expect(defaultValue.createRoom).toHaveBeenCalled(); + + await user.click(openMenu); + await user.click(screen.getByRole("menuitem", { name: "New video room" })); + expect(defaultValue.createVideoRoom).toHaveBeenCalled(); + }); + + it("should display only the new message button", async () => { + const user = userEvent.setup(); + mocked(useRoomListHeaderViewModel).mockReturnValue({ + ...defaultValue, + canCreateRoom: false, + canCreateVideoRoom: false, + }); + + render(); + await user.click(screen.getByRole("button", { name: "Add" })); + + expect(screen.queryByRole("menuitem", { name: "New room" })).toBeNull(); + expect(screen.queryByRole("menuitem", { name: "New video room" })).toBeNull(); + }); +}); diff --git a/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListHeaderView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListHeaderView-test.tsx.snap new file mode 100644 index 00000000000..02b806f456d --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListHeaderView-test.tsx.snap @@ -0,0 +1,71 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should display the compose menu 1`] = ` + +
+

+ title +

+ +
+
+`; + +exports[` should not display the compose menu 1`] = ` + +
+

+ title +

+
+
+`; diff --git a/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap index 4ddc9ac5ece..54336c96e2f 100644 --- a/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListView/__snapshots__/RoomListView-test.tsx.snap @@ -2,16 +2,27 @@ exports[` should not render the RoomListSearch component when UIComponent.FilterContainer is at false 1`] = ` -
+ > +
+

+ Home +

+
+ `; exports[` should render the RoomListSearch component when UIComponent.FilterContainer is at true 1`] = ` -
@@ -71,6 +82,56 @@ exports[` should render the RoomListSearch component when UIComp
-
+
+

+ Home +

+ +
+
`;