From 7de54a385ebcff7af8e2ad36a605788591fe24c4 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 18 Mar 2025 11:02:33 +0100 Subject: [PATCH] New room list: add empty state (#29512) * refactor: extract room creation and right verification * refactor: update `RoomListHeaderViewModel` to use utils * feat(room list filter): add filter key to `PrimaryFilter` model * feat(room list filter): return active primary filter * feat(room list): add create room action and rights verification * test: update room list tests * feat(empty room list): add empty room list * test(empty room list): add empty room list tests * feat(room list): use empty room list in `RoomListView` * test(room list panel): update tests * test(e2e): add e2e tests for empty room list * test(e2e): update room list header snapshot --- .../room-list-filter-sort.spec.ts | 153 ++++++++----- .../Favourite-empty-room-list-linux.png | Bin 0 -> 9457 bytes .../People-empty-room-list-linux.png | Bin 0 -> 10337 bytes .../Rooms-empty-room-list-linux.png | Bin 0 -> 7703 bytes .../default-empty-room-list-linux.png | Bin 0 -> 13284 bytes .../room-panel-empty-room-list-linux.png | Bin 0 -> 24596 bytes .../unread-empty-room-list-linux.png | Bin 0 -> 8124 bytes .../room-list-header-space-menu-linux.png | Bin 11343 -> 11376 bytes res/css/_components.pcss | 1 + .../rooms/RoomListPanel/_EmptyRoomList.pcss | 33 +++ .../roomlist/RoomListHeaderViewModel.tsx | 24 +-- .../viewmodels/roomlist/RoomListViewModel.tsx | 52 ++++- .../viewmodels/roomlist/useFilteredRooms.tsx | 12 +- src/components/viewmodels/roomlist/utils.ts | 35 ++- .../rooms/RoomListPanel/EmptyRoomList.tsx | 149 +++++++++++++ .../rooms/RoomListPanel/RoomListView.tsx | 5 +- src/i18n/strings/en_EN.json | 13 ++ .../roomlist/RoomListHeaderViewModel-test.tsx | 43 ++-- .../roomlist/RoomListViewModel-test.tsx | 49 +++++ .../viewmodels/roomlist/utils-test.ts | 69 ++++++ .../RoomListPanel/EmptyRoomList-test.tsx | 93 ++++++++ .../rooms/RoomListPanel/RoomList-test.tsx | 3 + .../RoomListPrimaryFilters-test.tsx | 8 +- .../rooms/RoomListPanel/RoomListView-test.tsx | 61 ++++++ .../__snapshots__/EmptyRoomList-test.tsx.snap | 204 ++++++++++++++++++ .../__snapshots__/RoomListPanel-test.tsx.snap | 139 +++++++----- 26 files changed, 991 insertions(+), 155 deletions(-) create mode 100644 playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png create mode 100644 playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/People-empty-room-list-linux.png create mode 100644 playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png create mode 100644 playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png create mode 100644 playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/room-panel-empty-room-list-linux.png create mode 100644 playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-empty-room-list-linux.png create mode 100644 res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss create mode 100644 src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx create mode 100644 test/unit-tests/components/viewmodels/roomlist/utils-test.ts create mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx create mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx create mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap diff --git a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts index 59f5a2fcab..bb87f90f19 100644 --- a/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts +++ b/playwright/e2e/left-panel/room-list-panel/room-list-filter-sort.spec.ts @@ -18,14 +18,6 @@ test.describe("Room list filters and sort", () => { labsFlags: ["feature_new_room_list"], }); - /** - * Get the room list - * @param page - */ - function getRoomList(page: Page) { - return page.getByTestId("room-list"); - } - function getPrimaryFilters(page: Page) { return page.getByRole("listbox", { name: "Room list filters" }); } @@ -33,56 +25,113 @@ test.describe("Room list filters and sort", () => { test.beforeEach(async ({ page, app, bot, user }) => { // The notification toast is displayed above the search section await app.closeNotificationToast(); - - await app.client.createRoom({ name: "empty room" }); - - const unReadDmId = await bot.createRoom({ - name: "unread dm", - invite: [user.userId], - is_direct: true, - }); - await bot.sendMessage(unReadDmId, "I am a robot. Beep."); - - const unReadRoomId = await app.client.createRoom({ name: "unread room" }); - await app.client.inviteUser(unReadRoomId, bot.credentials.userId); - await bot.joinRoom(unReadRoomId); - await bot.sendMessage(unReadRoomId, "I am a robot. Beep."); - - const favouriteId = await app.client.createRoom({ name: "favourite room" }); - await app.client.evaluate(async (client, favouriteId) => { - await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 }); - }, favouriteId); }); - test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => { - const roomList = getRoomList(page); - const primaryFilters = getPrimaryFilters(page); - - const allFilters = await primaryFilters.locator("option").all(); - for (const filter of allFilters) { - expect(await filter.getAttribute("aria-selected")).toBe("false"); + test.describe("Room list", () => { + /** + * Get the room list + * @param page + */ + function getRoomList(page: Page) { + return page.getByTestId("room-list"); } - await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png"); - await primaryFilters.getByRole("option", { name: "Unread" }).click(); - // only one room should be visible - await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible(); - await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible(); - expect(await roomList.locator("role=gridcell").count()).toBe(2); - await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png"); + test.beforeEach(async ({ page, app, bot, user }) => { + await app.client.createRoom({ name: "empty room" }); - await primaryFilters.getByRole("option", { name: "Favourite" }).click(); - await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); - expect(await roomList.locator("role=gridcell").count()).toBe(1); + const unReadDmId = await bot.createRoom({ + name: "unread dm", + invite: [user.userId], + is_direct: true, + }); + await bot.sendMessage(unReadDmId, "I am a robot. Beep."); - await primaryFilters.getByRole("option", { name: "People" }).click(); - await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible(); - expect(await roomList.locator("role=gridcell").count()).toBe(1); + const unReadRoomId = await app.client.createRoom({ name: "unread room" }); + await app.client.inviteUser(unReadRoomId, bot.credentials.userId); + await bot.joinRoom(unReadRoomId); + await bot.sendMessage(unReadRoomId, "I am a robot. Beep."); - await primaryFilters.getByRole("option", { name: "Rooms" }).click(); - await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible(); - await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); - await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible(); - expect(await roomList.locator("role=gridcell").count()).toBe(3); + const favouriteId = await app.client.createRoom({ name: "favourite room" }); + await app.client.evaluate(async (client, favouriteId) => { + await client.setRoomTag(favouriteId, "m.favourite", { order: 0.5 }); + }, favouriteId); + }); + + test("should filter the list (with primary filters)", { tag: "@screenshot" }, async ({ page, app, user }) => { + const roomList = getRoomList(page); + const primaryFilters = getPrimaryFilters(page); + + const allFilters = await primaryFilters.locator("option").all(); + for (const filter of allFilters) { + expect(await filter.getAttribute("aria-selected")).toBe("false"); + } + await expect(primaryFilters).toMatchScreenshot("unselected-primary-filters.png"); + + await primaryFilters.getByRole("option", { name: "Unread" }).click(); + // only one room should be visible + await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible(); + await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible(); + expect(await roomList.locator("role=gridcell").count()).toBe(2); + await expect(primaryFilters).toMatchScreenshot("unread-primary-filters.png"); + + await primaryFilters.getByRole("option", { name: "Favourite" }).click(); + await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); + expect(await roomList.locator("role=gridcell").count()).toBe(1); + + await primaryFilters.getByRole("option", { name: "People" }).click(); + await expect(roomList.getByRole("gridcell", { name: "unread dm" })).toBeVisible(); + expect(await roomList.locator("role=gridcell").count()).toBe(1); + + await primaryFilters.getByRole("option", { name: "Rooms" }).click(); + await expect(roomList.getByRole("gridcell", { name: "unread room" })).toBeVisible(); + await expect(roomList.getByRole("gridcell", { name: "favourite room" })).toBeVisible(); + await expect(roomList.getByRole("gridcell", { name: "empty room" })).toBeVisible(); + expect(await roomList.locator("role=gridcell").count()).toBe(3); + }); + }); + + test.describe("Empty room list", () => { + /** + * Get the empty state + * @param page + */ + function getEmptyRoomList(page: Page) { + return page.getByTestId("empty-room-list"); + } + + test( + "should render the default placeholder when there is no filter", + { tag: "@screenshot" }, + async ({ page, app, user }) => { + const emptyRoomList = getEmptyRoomList(page); + await expect(emptyRoomList).toMatchScreenshot("default-empty-room-list.png"); + await expect(page.getByTestId("room-list-panel")).toMatchScreenshot("room-panel-empty-room-list.png"); + }, + ); + + test("should render the placeholder for unread filter", { tag: "@screenshot" }, async ({ page, app, user }) => { + const primaryFilters = getPrimaryFilters(page); + await primaryFilters.getByRole("option", { name: "Unread" }).click(); + + const emptyRoomList = getEmptyRoomList(page); + await expect(emptyRoomList).toMatchScreenshot("unread-empty-room-list.png"); + + await emptyRoomList.getByRole("button", { name: "show all chats" }).click(); + await expect(primaryFilters.getByRole("option", { name: "Unread" })).not.toBeChecked(); + }); + + ["People", "Rooms", "Favourite"].forEach((filter) => { + test( + `should render the placeholder for ${filter} filter`, + { tag: "@screenshot" }, + async ({ page, app, user }) => { + const primaryFilters = getPrimaryFilters(page); + await primaryFilters.getByRole("option", { name: filter }).click(); + + const emptyRoomList = getEmptyRoomList(page); + await expect(emptyRoomList).toMatchScreenshot(`${filter}-empty-room-list.png`); + }, + ); + }); }); }); diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Favourite-empty-room-list-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8cf4a3c97b3343d8e769223e3eb2f06e46d28876 GIT binary patch literal 9457 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 T&am$_gMgg0qExkn@%R4+qhk^T literal 0 HcmV?d00001 diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/Rooms-empty-room-list-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e01564eeb402247b7f882d96436fdbefb868be48 GIT binary patch literal 7703 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 H=Ar)sz0fn7 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/default-empty-room-list-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..45d2a775ea0a53a0d381139b79707dd3c5e27613 GIT binary patch literal 13284 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 literal 0 HcmV?d00001 diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-empty-room-list-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-filter-sort.spec.ts/unread-empty-room-list-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..94b09ac14f5fad1a0127f0d994bd0a84d07463e3 GIT binary patch literal 8124 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 Ay#N3J literal 0 HcmV?d00001 diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list-header.spec.ts/room-list-header-space-menu-linux.png index d2934c2a7695a9031a69ceee4d8a25f2a124e28d..49cf6ef08afc0993c5a46043b7307d9c0c799004 100644 GIT binary patch literal 11376 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 Gum1+lz$GyN diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 1b4dc79296..a3284fb379 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -270,6 +270,7 @@ @import "./views/right_panel/_VerificationPanel.pcss"; @import "./views/right_panel/_WidgetCard.pcss"; @import "./views/room_settings/_AliasSettings.pcss"; +@import "./views/rooms/RoomListPanel/_EmptyRoomList.pcss"; @import "./views/rooms/RoomListPanel/_RoomList.pcss"; @import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss"; @import "./views/rooms/RoomListPanel/_RoomListItemMenuView.pcss"; diff --git a/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss b/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss new file mode 100644 index 0000000000..a0fbfdaea7 --- /dev/null +++ b/res/css/views/rooms/RoomListPanel/_EmptyRoomList.pcss @@ -0,0 +1,33 @@ +/* + * 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. + */ + +.mx_EmptyRoomList_GenericPlaceholder { + align-self: center; + /** It should take 2/3 of the width **/ + width: 66%; + /** It should be positioned at 1/3 of the height **/ + padding-top: 33%; + + .mx_EmptyRoomList_GenericPlaceholder_title { + font: var(--cpd-font-body-lg-semibold); + text-align: center; + } + + .mx_EmptyRoomList_GenericPlaceholder_description { + font: var(--cpd-font-body-sm-regular); + color: var(--cpd-color-text-secondary); + text-align: center; + } + + .mx_EmptyRoomList_DefaultPlaceholder { + margin-top: var(--cpd-space-4x); + } + + button { + width: 100%; + } +} diff --git a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx index 9aa63451f3..8a1fdb1fe7 100644 --- a/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListHeaderViewModel.tsx @@ -6,10 +6,8 @@ */ import { useCallback } from "react"; -import { EventTimeline, EventType, JoinRule, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix"; +import { JoinRule, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix"; -import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; -import { UIComponent } from "../../../settings/UIFeature"; import { useFeatureEnabled } from "../../../hooks/useSettings"; import defaultDispatcher from "../../../dispatcher/dispatcher"; import PosthogTrackers from "../../../PosthogTrackers"; @@ -32,6 +30,7 @@ import { } from "../../../utils/space"; import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { createRoom, hasCreateRoomRights } from "./utils"; /** * Hook to get the active space and its title. @@ -128,14 +127,7 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { const { activeSpace, title } = useSpace(); const isSpaceRoom = Boolean(activeSpace); - const canCreateRoomInSpace = Boolean( - activeSpace - ?.getLiveTimeline() - .getState(EventTimeline.FORWARDS) - ?.maySendStateEvent(EventType.RoomAvatar, matrixClient.getSafeUserId()), - ); - // If we are in a space, we check canCreateRoomInSpace - const canCreateRoom = shouldShowComponent(UIComponent.CreateRooms) && (!isSpaceRoom || canCreateRoomInSpace); + const canCreateRoom = hasCreateRoomRights(matrixClient, activeSpace); const canCreateVideoRoom = useFeatureEnabled("feature_video_rooms"); const displayComposeMenu = canCreateRoom || canCreateVideoRoom; const displaySpaceMenu = isSpaceRoom; @@ -151,13 +143,9 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateChatItem", e); }, []); - const createRoom = useCallback( + const createRoomMemoized = useCallback( (e: Event) => { - if (activeSpace) { - showCreateNewRoom(activeSpace); - } else { - defaultDispatcher.fire(Action.CreateRoom); - } + createRoom(activeSpace); PosthogTrackers.trackInteraction("WebRoomListHeaderPlusMenuCreateRoomItem", e); }, [activeSpace], @@ -213,7 +201,7 @@ export function useRoomListHeaderViewModel(): RoomListHeaderViewState { canInviteInSpace, canAccessSpaceSettings, createChatRoom, - createRoom, + createRoom: createRoomMemoized, createVideoRoom, openSpaceHome, inviteInSpace, diff --git a/src/components/viewmodels/roomlist/RoomListViewModel.tsx b/src/components/viewmodels/roomlist/RoomListViewModel.tsx index 6c46ca6d38..2143aeae78 100644 --- a/src/components/viewmodels/roomlist/RoomListViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListViewModel.tsx @@ -5,22 +5,55 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ +import { useCallback } from "react"; + import type { Room } from "matrix-js-sdk/src/matrix"; import { type PrimaryFilter, type SecondaryFilters, useFilteredRooms } from "./useFilteredRooms"; import { type SortOption, useSorter } from "./useSorter"; import { useMessagePreviewToggle } from "./useMessagePreviewToggle"; +import { createRoom as createRoomFunc, hasCreateRoomRights } from "./utils"; +import { useEventEmitterState } from "../../../hooks/useEventEmitter"; +import { UPDATE_SELECTED_SPACE } from "../../../stores/spaces"; +import SpaceStore from "../../../stores/spaces/SpaceStore"; +import dispatcher from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; export interface RoomListViewState { /** * A list of rooms to be displayed in the left panel. */ rooms: Room[]; + + /** + * Create a chat room + * @param e - The click event + */ + createChatRoom: () => void; + + /** + * Whether the user can create a room in the current space + */ + canCreateRoom: boolean; + + /** + * Create a room + * @param e - The click event + */ + createRoom: () => void; + /** * A list of objects that provide the view enough information * to render primary room filters. */ primaryFilters: PrimaryFilter[]; + /** + * The currently active primary filter. + * If no primary filter is active, this will be undefined. + */ + activePrimaryFilter?: PrimaryFilter; + /** * A function to activate a given secondary filter. */ @@ -57,13 +90,30 @@ export interface RoomListViewState { * @see {@link RoomListViewState} for more information about what this view model returns. */ export function useRoomListViewModel(): RoomListViewState { - const { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter } = useFilteredRooms(); + const matrixClient = useMatrixClientContext(); + const { primaryFilters, activePrimaryFilter, rooms, activateSecondaryFilter, activeSecondaryFilter } = + useFilteredRooms(); + + const currentSpace = useEventEmitterState( + SpaceStore.instance, + UPDATE_SELECTED_SPACE, + () => SpaceStore.instance.activeSpaceRoom, + ); + const canCreateRoom = hasCreateRoomRights(matrixClient, currentSpace); + const { activeSortOption, sort } = useSorter(); const { shouldShowMessagePreview, toggleMessagePreview } = useMessagePreviewToggle(); + const createChatRoom = useCallback(() => dispatcher.fire(Action.CreateChat), []); + const createRoom = useCallback(() => createRoomFunc(currentSpace), [currentSpace]); + return { rooms, + canCreateRoom, + createRoom, + createChatRoom, primaryFilters, + activePrimaryFilter, activateSecondaryFilter, activeSecondaryFilter, activeSortOption, diff --git a/src/components/viewmodels/roomlist/useFilteredRooms.tsx b/src/components/viewmodels/roomlist/useFilteredRooms.tsx index a21918e5fa..68f8e3e380 100644 --- a/src/components/viewmodels/roomlist/useFilteredRooms.tsx +++ b/src/components/viewmodels/roomlist/useFilteredRooms.tsx @@ -27,6 +27,8 @@ export interface PrimaryFilter { active: boolean; // Text that can be used in the UI to represent this filter. name: string; + // The key of the filter + key: FilterKey; } interface FilteredRooms { @@ -34,6 +36,11 @@ interface FilteredRooms { rooms: Room[]; activateSecondaryFilter: (filter: SecondaryFilters) => void; activeSecondaryFilter: SecondaryFilters; + /** + * The currently active primary filter. + * If no primary filter is active, this will be undefined. + */ + activePrimaryFilter?: PrimaryFilter; } const filterKeyToNameMap: Map = new Map([ @@ -172,6 +179,7 @@ export function useFilteredRooms(): FilteredRooms { }, active: primaryFilter === key, name, + key, }; }; const filters: PrimaryFilter[] = []; @@ -184,5 +192,7 @@ export function useFilteredRooms(): FilteredRooms { return filters; }, [primaryFilter, updateRoomsFromStore, secondaryFilter]); - return { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter }; + const activePrimaryFilter = useMemo(() => primaryFilters.find((filter) => filter.active), [primaryFilters]); + + return { primaryFilters, activePrimaryFilter, rooms, activateSecondaryFilter, activeSecondaryFilter }; } diff --git a/src/components/viewmodels/roomlist/utils.ts b/src/components/viewmodels/roomlist/utils.ts index 3886d0e3b0..6220c3b961 100644 --- a/src/components/viewmodels/roomlist/utils.ts +++ b/src/components/viewmodels/roomlist/utils.ts @@ -5,11 +5,14 @@ * Please see LICENSE files in the repository root for full details. */ -import { type Room, KnownMembership } from "matrix-js-sdk/src/matrix"; +import { type Room, KnownMembership, EventTimeline, EventType, type MatrixClient } from "matrix-js-sdk/src/matrix"; import { isKnockDenied } from "../../../utils/membership"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../settings/UIFeature"; +import { showCreateNewRoom } from "../../../utils/space"; +import dispatcher from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; /** * Check if the user has access to the options menu. @@ -23,3 +26,33 @@ export function hasAccessToOptionsMenu(room: Room): boolean { shouldShowComponent(UIComponent.RoomOptionsMenu)) ); } + +/** + * Create a room + * @param space - The space to create the room in + */ +export async function createRoom(space?: Room | null): Promise { + if (space) { + await showCreateNewRoom(space); + } else { + dispatcher.fire(Action.CreateRoom); + } +} + +/** + * Check if the user has the rights to create a room in the given space + * If the space is not provided, it will check if the user has the rights to create a room in general + * @param matrixClient + * @param space + */ +export function hasCreateRoomRights(matrixClient: MatrixClient, space?: Room | null): boolean { + const hasUIRight = shouldShowComponent(UIComponent.CreateRooms); + if (!space || !hasUIRight) return hasUIRight; + + return Boolean( + space + ?.getLiveTimeline() + .getState(EventTimeline.FORWARDS) + ?.maySendStateEvent(EventType.RoomAvatar, matrixClient.getSafeUserId()), + ); +} diff --git a/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx new file mode 100644 index 0000000000..c4824e2b45 --- /dev/null +++ b/src/components/views/rooms/RoomListPanel/EmptyRoomList.tsx @@ -0,0 +1,149 @@ +/* + * 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, type PropsWithChildren } from "react"; +import { Button } from "@vector-im/compound-web"; +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 type { RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; +import { Flex } from "../../../utils/Flex"; +import { _t } from "../../../../languageHandler"; +import { FilterKey } from "../../../../stores/room-list-v3/skip-list/filters"; +import { type PrimaryFilter } from "../../../viewmodels/roomlist/useFilteredRooms"; + +interface EmptyRoomListProps { + /** + * The view model for the room list + */ + vm: RoomListViewState; +} + +/** + * The empty state for the room list + */ +export function EmptyRoomList({ vm }: EmptyRoomListProps): JSX.Element | undefined { + // If there is no active primary filter, show the default empty state + if (!vm.activePrimaryFilter) return ; + + switch (vm.activePrimaryFilter.key) { + case FilterKey.FavouriteFilter: + return ( + + ); + case FilterKey.PeopleFilter: + return ( + + ); + case FilterKey.RoomsFilter: + return ( + + ); + case FilterKey.UnreadFilter: + return ; + default: + return undefined; + } +} + +interface GenericPlaceholderProps { + /** + * The title of the placeholder + */ + title: string; + /** + * The description of the placeholder + */ + description?: string; +} + +/** + * A generic placeholder for the room list + */ +function GenericPlaceholder({ title, description, children }: PropsWithChildren): JSX.Element { + return ( + + {title} + {description && {description}} + {children} + + ); +} + +interface DefaultPlaceholderProps { + /** + * The view model for the room list + */ + vm: RoomListViewState; +} + +/** + * The default empty state for the room list when no primary filter is active + * The user can create chat or room (if they have the permission) + */ +function DefaultPlaceholder({ vm }: DefaultPlaceholderProps): JSX.Element { + return ( + + + + {vm.canCreateRoom && ( + + )} + + + ); +} + +interface UnreadPlaceholderProps { + filter: PrimaryFilter; +} + +/** + * The empty state for the room list when the unread filter is active + */ +function UnreadPlaceholder({ filter }: UnreadPlaceholderProps): JSX.Element { + return ( + + + + ); +} diff --git a/src/components/views/rooms/RoomListPanel/RoomListView.tsx b/src/components/views/rooms/RoomListPanel/RoomListView.tsx index 36fab95658..f4800f7009 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListView.tsx @@ -9,6 +9,7 @@ import React, { type JSX } from "react"; import { useRoomListViewModel } from "../../../viewmodels/roomlist/RoomListViewModel"; import { RoomList } from "./RoomList"; +import { EmptyRoomList } from "./EmptyRoomList"; import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters"; /** @@ -16,10 +17,12 @@ import { RoomListPrimaryFilters } from "./RoomListPrimaryFilters"; */ export function RoomListView(): JSX.Element { const vm = useRoomListViewModel(); + const isRoomListEmpty = vm.rooms.length === 0; + return ( <> - + {isRoomListEmpty ? : } ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 34e0e98be3..aa71cac8a7 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2096,6 +2096,19 @@ "add_space_label": "Add space", "breadcrumbs_empty": "No recently visited rooms", "breadcrumbs_label": "Recently visited rooms", + "empty": { + "no_chats": "No chats yet", + "no_chats_description": "Get started by messaging someone or by creating a room", + "no_chats_description_no_room_rights": "Get started by messaging someone", + "no_favourites": "You don't have favourite chat yet", + "no_favourites_description": "You can add a chat to your favourites in the chat settings", + "no_people": "You don’t have direct chats with anyone yet", + "no_people_description": "You can deselect filters in order to see your other chats", + "no_rooms": "You’re not in any room yet", + "no_rooms_description": "You can deselect filters in order to see your other chats", + "no_unread": "Congrats! You don’t have any unread messages", + "show_chats": "Show all chats" + }, "failed_add_tag": "Failed to add tag %(tagName)s to room", "failed_remove_tag": "Failed to remove tag %(tagName)s from room", "failed_set_dm_tag": "Failed to set direct message tag", diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx index 2233949d71..399943b6cf 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListHeaderViewModel-test.tsx @@ -6,13 +6,12 @@ */ import { renderHook } from "jest-matrix-react"; -import { JoinRule, type MatrixClient, type Room, type RoomState, RoomType } from "matrix-js-sdk/src/matrix"; +import { JoinRule, type MatrixClient, type Room, 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, withClientContextRenderOptions } 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"; @@ -23,9 +22,11 @@ import { showSpacePreferences, showSpaceSettings, } from "../../../../../src/utils/space"; +import { createRoom, hasCreateRoomRights } from "../../../../../src/components/viewmodels/roomlist/utils"; -jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({ - shouldShowComponent: jest.fn(), +jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ + hasCreateRoomRights: jest.fn().mockReturnValue(false), + createRoom: jest.fn(), })); jest.mock("../../../../../src/utils/space", () => ({ @@ -68,19 +69,19 @@ describe("useRoomListHeaderViewModel", () => { }); it("should be displayComposeMenu=true and canCreateRoom=true if the user can creates room", () => { - mocked(shouldShowComponent).mockReturnValue(false); + mocked(hasCreateRoomRights).mockReturnValue(false); const { result, rerender } = render(); expect(result.current.displayComposeMenu).toBe(false); expect(result.current.canCreateRoom).toBe(false); - mocked(shouldShowComponent).mockReturnValue(true); + mocked(hasCreateRoomRights).mockReturnValue(true); rerender(); expect(result.current.displayComposeMenu).toBe(true); expect(result.current.canCreateRoom).toBe(true); }); it("should be displayComposeMenu=true if the user can creates video room", () => { - mocked(shouldShowComponent).mockReturnValue(false); + mocked(hasCreateRoomRights).mockReturnValue(false); jest.spyOn(SettingsStore, "getValue").mockReturnValue(true); const { result } = render(); @@ -93,25 +94,6 @@ describe("useRoomListHeaderViewModel", () => { expect(result.current.displaySpaceMenu).toBe(true); }); - it("should be canCreateRoom=false if the user has not the right to create a room in a space", () => { - mocked(shouldShowComponent).mockReturnValue(true); - jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space); - - const { result } = render(); - expect(result.current.canCreateRoom).toBe(false); - }); - - it("should be canCreateRoom=true if the user has the right to create a room in a space", () => { - mocked(shouldShowComponent).mockReturnValue(true); - jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space); - jest.spyOn(space.getLiveTimeline(), "getState").mockReturnValue({ - maySendStateEvent: jest.fn().mockReturnValue(true), - } as unknown as RoomState); - - const { result } = render(); - expect(result.current.canCreateRoom).toBe(true); - }); - it("should be canInviteInSpace=true if the space join rule is public", () => { jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space); jest.spyOn(space, "getJoinRule").mockReturnValue(JoinRule.Public); @@ -150,20 +132,19 @@ describe("useRoomListHeaderViewModel", () => { expect(spy).toHaveBeenCalledWith(Action.CreateChat); }); - it("should fire Action.CreateRoom when createRoom is called", () => { - const spy = jest.spyOn(defaultDispatcher, "fire"); + it("should call createRoom from utils when createRoom is called", () => { const { result } = render(); result.current.createRoom(new Event("click")); - expect(spy).toHaveBeenCalledWith(Action.CreateRoom); + expect(createRoom).toHaveBeenCalled(); }); - it("should call showCreateNewRoom when createRoom is called in a space", () => { + it("should call createRoom from utils when createRoom is called in a space", () => { jest.spyOn(SpaceStore.instance, "activeSpaceRoom", "get").mockReturnValue(space); const { result } = render(); result.current.createRoom(new Event("click")); - expect(showCreateNewRoom).toHaveBeenCalledWith(space); + expect(createRoom).toHaveBeenCalledWith(space); }); it("should fire Action.CreateRoom with RoomType.UnstableCall when createVideoRoom is called and feature_element_call_video_rooms is enabled", () => { diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx index 43364e0d77..9cfb83a766 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. import { range } from "lodash"; import { act, renderHook, waitFor } from "jest-matrix-react"; +import { mocked } from "jest-mock"; import RoomListStoreV3 from "../../../../../src/stores/room-list-v3/RoomListStoreV3"; import { mkStubRoom } from "../../../../test-utils"; @@ -17,6 +18,14 @@ import { SecondaryFilters } from "../../../../../src/components/viewmodels/rooml import { SortingAlgorithm } from "../../../../../src/stores/room-list-v3/skip-list/sorters"; import { SortOption } from "../../../../../src/components/viewmodels/roomlist/useSorter"; import SettingsStore from "../../../../../src/settings/SettingsStore"; +import { hasCreateRoomRights, createRoom } from "../../../../../src/components/viewmodels/roomlist/utils"; +import dispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; + +jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ + hasCreateRoomRights: jest.fn().mockReturnValue(false), + createRoom: jest.fn(), +})); describe("RoomListViewModel", () => { function mockAndCreateRooms() { @@ -139,6 +148,19 @@ describe("RoomListViewModel", () => { ); }); + it("should return the current active primary filter", async () => { + // Let's say that the user's preferred sorting is alphabetic + mockAndCreateRooms(); + const { result: vm } = renderHook(() => useRoomListViewModel()); + // Toggle people filter + const i = vm.current.primaryFilters.findIndex((f) => f.name === "People"); + expect(vm.current.primaryFilters[i].active).toEqual(false); + act(() => vm.current.primaryFilters[i].toggle()); + + // The active primary filter should be the People filter + expect(vm.current.activePrimaryFilter).toEqual(vm.current.primaryFilters[i]); + }); + const testcases: Array<[string, { secondary: SecondaryFilters; filterKey: FilterKey }, string]> = [ [ "Mentions only", @@ -240,4 +262,31 @@ describe("RoomListViewModel", () => { expect(fn).toHaveBeenCalled(); }); }); + + describe("Create room and chat", () => { + it("should be canCreateRoom=false if hasCreateRoomRights=false", () => { + mocked(hasCreateRoomRights).mockReturnValue(false); + const { result } = renderHook(() => useRoomListViewModel()); + expect(result.current.canCreateRoom).toBe(false); + }); + + it("should be canCreateRoom=true if hasCreateRoomRights=true", () => { + mocked(hasCreateRoomRights).mockReturnValue(true); + const { result } = renderHook(() => useRoomListViewModel()); + expect(result.current.canCreateRoom).toBe(true); + }); + + it("should call createRoom", () => { + const { result } = renderHook(() => useRoomListViewModel()); + result.current.createRoom(); + expect(mocked(createRoom)).toHaveBeenCalled(); + }); + + it("should dispatch Action.CreateChat", () => { + const spy = jest.spyOn(dispatcher, "fire"); + const { result } = renderHook(() => useRoomListViewModel()); + result.current.createChatRoom(); + expect(spy).toHaveBeenCalledWith(Action.CreateChat); + }); + }); }); diff --git a/test/unit-tests/components/viewmodels/roomlist/utils-test.ts b/test/unit-tests/components/viewmodels/roomlist/utils-test.ts new file mode 100644 index 0000000000..1fd4fc1b4a --- /dev/null +++ b/test/unit-tests/components/viewmodels/roomlist/utils-test.ts @@ -0,0 +1,69 @@ +/* + * 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 { mocked } from "jest-mock"; + +import type { MatrixClient, Room, RoomState } from "matrix-js-sdk/src/matrix"; +import { createTestClient, mkStubRoom } from "../../../../test-utils"; +import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents"; +import { hasCreateRoomRights, createRoom } from "../../../../../src/components/viewmodels/roomlist/utils"; +import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; +import { showCreateNewRoom } from "../../../../../src/utils/space"; + +jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({ + shouldShowComponent: jest.fn(), +})); + +jest.mock("../../../../../src/utils/space", () => ({ + showCreateNewRoom: jest.fn(), +})); + +describe("utils", () => { + let matrixClient: MatrixClient; + let space: Room; + + beforeEach(() => { + matrixClient = createTestClient(); + space = mkStubRoom("spaceId", "spaceName", matrixClient); + }); + + describe("createRoom", () => { + it("should fire Action.CreateRoom when createRoom is called without a space", async () => { + const spy = jest.spyOn(defaultDispatcher, "fire"); + await createRoom(); + + expect(spy).toHaveBeenCalledWith(Action.CreateRoom); + }); + + it("should call showCreateNewRoom when createRoom is called in a space", async () => { + await createRoom(space); + expect(showCreateNewRoom).toHaveBeenCalledWith(space); + }); + }); + + describe("hasCreateRoomRights", () => { + it("should return false when UIComponent.CreateRooms is disabled", () => { + mocked(shouldShowComponent).mockReturnValue(false); + expect(hasCreateRoomRights(matrixClient, space)).toBe(false); + }); + + it("should return true when UIComponent.CreateRooms is enabled and no space", () => { + mocked(shouldShowComponent).mockReturnValue(true); + expect(hasCreateRoomRights(matrixClient)).toBe(true); + }); + + it("should return false in space when UIComponent.CreateRooms is enabled and the user doesn't have the rights", () => { + mocked(shouldShowComponent).mockReturnValue(true); + jest.spyOn(space.getLiveTimeline(), "getState").mockReturnValue({ + maySendStateEvent: jest.fn().mockReturnValue(true), + } as unknown as RoomState); + + expect(hasCreateRoomRights(matrixClient)).toBe(true); + }); + }); +}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx new file mode 100644 index 0000000000..5c41fb367c --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListPanel/EmptyRoomList-test.tsx @@ -0,0 +1,93 @@ +/* + * 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 { render, screen } from "jest-matrix-react"; +import userEvent from "@testing-library/user-event"; + +import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; +import { SecondaryFilters } from "../../../../../../src/components/viewmodels/roomlist/useFilteredRooms"; +import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter"; +import { EmptyRoomList } from "../../../../../../src/components/views/rooms/RoomListPanel/EmptyRoomList"; +import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/filters"; + +describe("", () => { + let vm: RoomListViewState; + + beforeEach(() => { + vm = { + rooms: [], + primaryFilters: [], + activateSecondaryFilter: jest.fn().mockReturnValue({}), + activeSecondaryFilter: SecondaryFilters.AllActivity, + sort: jest.fn(), + activeSortOption: SortOption.Activity, + createRoom: jest.fn(), + createChatRoom: jest.fn(), + canCreateRoom: true, + shouldShowMessagePreview: false, + toggleMessagePreview: jest.fn(), + }; + }); + + test("should render the default placeholder when there is no filter", async () => { + const user = userEvent.setup(); + + const { asFragment } = render(); + expect(screen.getByText("No chats yet")).toBeInTheDocument(); + expect(asFragment()).toMatchSnapshot(); + + await user.click(screen.getByRole("button", { name: "New message" })); + expect(vm.createChatRoom).toHaveBeenCalled(); + + await user.click(screen.getByRole("button", { name: "New room" })); + expect(vm.createRoom).toHaveBeenCalled(); + }); + + test("should not render the new room button if the user doesn't have the rights to create a room", async () => { + const newState = { ...vm, canCreateRoom: false }; + + const { asFragment } = render(); + expect(screen.queryByRole("button", { name: "New room" })).toBeNull(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("should display the empty state for the unread filter", async () => { + const user = userEvent.setup(); + const activePrimaryFilter = { + toggle: jest.fn(), + active: true, + name: "unread", + key: FilterKey.UnreadFilter, + }; + const newState = { + ...vm, + activePrimaryFilter, + }; + + const { asFragment } = render(); + await user.click(screen.getByRole("button", { name: "Show all chats" })); + expect(activePrimaryFilter.toggle).toHaveBeenCalled(); + expect(asFragment()).toMatchSnapshot(); + }); + + it.each([ + { key: FilterKey.FavouriteFilter, name: "favourite" }, + { key: FilterKey.PeopleFilter, name: "people" }, + { key: FilterKey.RoomsFilter, name: "rooms" }, + ])("should display empty state for filter $name", ({ name, key }) => { + const activePrimaryFilter = { + toggle: jest.fn(), + active: true, + name, + key, + }; + const newState = { ...vm, activePrimaryFilter }; + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx index 5e2d451ff8..3490a3c509 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx @@ -37,6 +37,9 @@ describe("", () => { activeSortOption: SortOption.Activity, shouldShowMessagePreview: false, toggleMessagePreview: jest.fn(), + createRoom: jest.fn(), + createChatRoom: jest.fn(), + canCreateRoom: true, }; // Needed to render a room list cell diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx index f4b97b84b6..301f293835 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx @@ -13,6 +13,7 @@ import { type RoomListViewState } from "../../../../../../src/components/viewmod import { SecondaryFilters } from "../../../../../../src/components/viewmodels/roomlist/useFilteredRooms"; import { RoomListPrimaryFilters } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListPrimaryFilters"; import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter"; +import { FilterKey } from "../../../../../../src/stores/room-list-v3/skip-list/filters"; describe("", () => { let vm: RoomListViewState; @@ -20,9 +21,12 @@ describe("", () => { beforeEach(() => { vm = { rooms: [], + canCreateRoom: true, + createRoom: jest.fn(), + createChatRoom: jest.fn(), primaryFilters: [ - { name: "People", active: false, toggle: jest.fn() }, - { name: "Rooms", active: true, toggle: jest.fn() }, + { name: "People", active: false, toggle: jest.fn(), key: FilterKey.PeopleFilter }, + { name: "Rooms", active: true, toggle: jest.fn(), key: FilterKey.RoomsFilter }, ], activateSecondaryFilter: () => {}, activeSecondaryFilter: SecondaryFilters.AllActivity, diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx new file mode 100644 index 0000000000..015fe5404d --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListView-test.tsx @@ -0,0 +1,61 @@ +/* + * 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 { mocked } from "jest-mock"; +import { render, screen } from "jest-matrix-react"; +import React from "react"; + +import { + type RoomListViewState, + useRoomListViewModel, +} from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; +import { SecondaryFilters } from "../../../../../../src/components/viewmodels/roomlist/useFilteredRooms"; +import { SortOption } from "../../../../../../src/components/viewmodels/roomlist/useSorter"; +import { RoomListView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListView"; +import { mkRoom, stubClient } from "../../../../../test-utils"; + +jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListViewModel", () => ({ + useRoomListViewModel: jest.fn(), +})); + +describe("", () => { + const defaultValue: RoomListViewState = { + rooms: [], + primaryFilters: [], + activateSecondaryFilter: jest.fn().mockReturnValue({}), + activeSecondaryFilter: SecondaryFilters.AllActivity, + sort: jest.fn(), + activeSortOption: SortOption.Activity, + createRoom: jest.fn(), + createChatRoom: jest.fn(), + canCreateRoom: true, + toggleMessagePreview: jest.fn(), + shouldShowMessagePreview: false, + }; + const matrixClient = stubClient(); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it("should render an empty room list", () => { + mocked(useRoomListViewModel).mockReturnValue(defaultValue); + + render(); + expect(screen.getByText("No chats yet")).toBeInTheDocument(); + }); + + it("should render a room list", () => { + mocked(useRoomListViewModel).mockReturnValue({ + ...defaultValue, + rooms: [mkRoom(matrixClient, "testing room")], + }); + + render(); + expect(screen.getByRole("grid", { name: "Room list" })).toBeInTheDocument(); + }); +}); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap new file mode 100644 index 0000000000..bf1733c2ed --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/EmptyRoomList-test.tsx.snap @@ -0,0 +1,204 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should display empty state for filter favourite 1`] = ` + +
+ + You don't have favourite chat yet + + + You can add a chat to your favourites in the chat settings + +
+
+`; + +exports[` should display empty state for filter people 1`] = ` + +
+ + You don’t have direct chats with anyone yet + + + You can deselect filters in order to see your other chats + +
+
+`; + +exports[` should display empty state for filter rooms 1`] = ` + +
+ + You’re not in any room yet + + + You can deselect filters in order to see your other chats + +
+
+`; + +exports[` should display the empty state for the unread filter 1`] = ` + +
+ + Congrats! You don’t have any unread messages + + +
+
+`; + +exports[` should not render the new room button if the user doesn't have the rights to create a room 1`] = ` + +
+ + No chats yet + + + Get started by messaging someone + +
+ +
+
+
+`; + +exports[` should render the default placeholder when there is no filter 1`] = ` + +
+ + No chats yet + + + Get started by messaging someone or by creating a room + +
+ + +
+
+
+`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap index 28f3befc20..df3f60dcf4 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListPanel-test.tsx.snap @@ -113,34 +113,45 @@ exports[` should not render the RoomListSearch component when U
-
-
+ + Get started by messaging someone + +
+
-
-
-
-
-
+ + New message +
@@ -322,34 +333,66 @@ exports[` should render the RoomListSearch component when UICom
-
-
+ + Get started by messaging someone or by creating a room + +
+
-
-
-
-
-
+ + New message + +