From 706f17d2443aa3decee1b4b94f30cda2ce05f02b Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 10 Mar 2025 15:49:18 +0100 Subject: [PATCH 01/16] refactor(room list item): rename `RoomListCell` into `RoomListItemView` --- res/css/_components.pcss | 2 +- ...omListCell.pcss => _RoomListItemView.pcss} | 8 +- .../views/rooms/RoomListPanel/RoomList.tsx | 9 +- ...{RoomListCell.tsx => RoomListItemView.tsx} | 12 +-- ...ell-test.tsx => RoomListItemView-test.tsx} | 10 +- .../__snapshots__/RoomListCell-test.tsx.snap | 50 ---------- .../RoomListItemView-test.tsx.snap | 99 +++++++++++++++++++ 7 files changed, 122 insertions(+), 68 deletions(-) rename res/css/views/rooms/RoomListPanel/{_RoomListCell.pcss => _RoomListItemView.pcss} (88%) rename src/components/views/rooms/RoomListPanel/{RoomListCell.tsx => RoomListItemView.tsx} (73%) rename test/unit-tests/components/views/rooms/RoomListPanel/{RoomListCell-test.tsx => RoomListItemView-test.tsx} (77%) delete mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListCell-test.tsx.snap create mode 100644 test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 89948acd20f..116b0b3c0e6 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -271,8 +271,8 @@ @import "./views/right_panel/_WidgetCard.pcss"; @import "./views/room_settings/_AliasSettings.pcss"; @import "./views/rooms/RoomListPanel/_RoomList.pcss"; -@import "./views/rooms/RoomListPanel/_RoomListCell.pcss"; @import "./views/rooms/RoomListPanel/_RoomListHeaderView.pcss"; +@import "./views/rooms/RoomListPanel/_RoomListItemView.pcss"; @import "./views/rooms/RoomListPanel/_RoomListPanel.pcss"; @import "./views/rooms/RoomListPanel/_RoomListPrimaryFilters.pcss"; @import "./views/rooms/RoomListPanel/_RoomListSearch.pcss"; diff --git a/res/css/views/rooms/RoomListPanel/_RoomListCell.pcss b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss similarity index 88% rename from res/css/views/rooms/RoomListPanel/_RoomListCell.pcss rename to res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss index 812145a73e3..e86769b8c49 100644 --- a/res/css/views/rooms/RoomListPanel/_RoomListCell.pcss +++ b/res/css/views/rooms/RoomListPanel/_RoomListItemView.pcss @@ -6,7 +6,7 @@ */ /** - * The RoomCell has the following structure: + * The RoomListItemView has the following structure: * button----------------------------------------| * | <-12px-> container--------------------------| * | | room avatar <-12px-> content-----| @@ -14,19 +14,19 @@ * | | | ----------| <-- border * |---------------------------------------------| */ -.mx_RoomListCell { +.mx_RoomListItemView { all: unset; &:hover { background-color: var(--cpd-color-bg-action-secondary-hovered); } - .mx_RoomListCell_container { + .mx_RoomListItemView_container { padding-left: var(--cpd-space-3x); font: var(--cpd-font-body-md-regular); height: 100%; - .mx_RoomListCell_content { + .mx_RoomListItemView_content { height: 100%; flex: 1; /* The border is only under the room name and the future hover menu */ diff --git a/src/components/views/rooms/RoomListPanel/RoomList.tsx b/src/components/views/rooms/RoomListPanel/RoomList.tsx index 3645a72bb91..f13d63ba77b 100644 --- a/src/components/views/rooms/RoomListPanel/RoomList.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomList.tsx @@ -10,7 +10,7 @@ import { AutoSizer, List, type ListRowProps } from "react-virtualized"; import { type RoomListViewState } from "../../../viewmodels/roomlist/RoomListViewModel"; import { _t } from "../../../../languageHandler"; -import { RoomListCell } from "./RoomListCell"; +import { RoomListItemView } from "./RoomListItemView"; interface RoomListProps { /** @@ -25,7 +25,12 @@ interface RoomListProps { export function RoomList({ vm: { rooms, openRoom } }: RoomListProps): JSX.Element { const roomRendererMemoized = useCallback( ({ key, index, style }: ListRowProps) => ( - openRoom(rooms[index].roomId)} /> + openRoom(rooms[index].roomId)} + /> ), [rooms, openRoom], ); diff --git a/src/components/views/rooms/RoomListPanel/RoomListCell.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx similarity index 73% rename from src/components/views/rooms/RoomListPanel/RoomListCell.tsx rename to src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index a5e9cc5df23..705cb1d43a3 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListCell.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -12,7 +12,7 @@ import { _t } from "../../../../languageHandler"; import { Flex } from "../../../utils/Flex"; import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; -interface RoomListCellProps extends React.HTMLAttributes { +interface RoomListItemViewPropsProps extends React.HTMLAttributes { /** * The room to display */ @@ -20,20 +20,20 @@ interface RoomListCellProps extends React.HTMLAttributes { } /** - * A cell in the room list + * An item in the room list */ -export function RoomListCell({ room, ...props }: RoomListCellProps): JSX.Element { +export function RoomListItemView({ room, ...props }: RoomListItemViewPropsProps): JSX.Element { return ( - -`; diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap new file mode 100644 index 00000000000..cc0779b38bd --- /dev/null +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap @@ -0,0 +1,99 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render a room item 1`] = ` + + + +`; + +exports[` should render a room item 1`] = ` + + + +`; From 8bd28d2392cf9a10a1e0e279118cde32de9f5b46 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 10 Mar 2025 15:46:25 +0100 Subject: [PATCH 02/16] refactor(room list item): move open room action to new room list item view model --- .../roomlist/RoomListItemViewModel.tsx | 35 ++++++++++++++++++ .../viewmodels/roomlist/RoomListViewModel.tsx | 20 ---------- .../views/rooms/RoomListPanel/RoomList.tsx | 13 ++----- .../rooms/RoomListPanel/RoomListItemView.tsx | 4 ++ .../roomlist/RoomListItemViewModel-test.tsx | 37 +++++++++++++++++++ .../roomlist/RoomListViewModel-test.tsx | 17 --------- .../rooms/RoomListPanel/RoomList-test.tsx | 15 +------- .../RoomListPanel/RoomListItemView-test.tsx | 21 ++++++++--- .../RoomListPrimaryFilters-test.tsx | 1 - .../RoomListItemView-test.tsx.snap | 4 +- 10 files changed, 98 insertions(+), 69 deletions(-) create mode 100644 src/components/viewmodels/roomlist/RoomListItemViewModel.tsx create mode 100644 test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx new file mode 100644 index 00000000000..2e751f3ba4a --- /dev/null +++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx @@ -0,0 +1,35 @@ +/* + * 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 { useCallback } from "react"; + +import dispatcher from "../../../dispatcher/dispatcher"; +import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { Action } from "../../../dispatcher/actions"; + +export interface RoomListItemViewState { + /** + * Open the room having given roomId. + */ + openRoom: (roomId: string) => void; +} + +/** + * View model for the room list item + * @see {@link RoomListItemViewState} for more information about what this view model returns. + */ +export function useRoomListItemViewModel(): RoomListItemViewState { + const openRoom = useCallback((roomId: string): void => { + dispatcher.dispatch({ + action: Action.ViewRoom, + room_id: roomId, + metricsTrigger: "RoomList", + }); + }, []); + + return { openRoom }; +} diff --git a/src/components/viewmodels/roomlist/RoomListViewModel.tsx b/src/components/viewmodels/roomlist/RoomListViewModel.tsx index fb827c48891..09fe68e5a21 100644 --- a/src/components/viewmodels/roomlist/RoomListViewModel.tsx +++ b/src/components/viewmodels/roomlist/RoomListViewModel.tsx @@ -5,12 +5,7 @@ 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 { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; -import dispatcher from "../../../dispatcher/dispatcher"; -import { Action } from "../../../dispatcher/actions"; import { type PrimaryFilter, type SecondaryFilters, useFilteredRooms } from "./useFilteredRooms"; export interface RoomListViewState { @@ -18,12 +13,6 @@ export interface RoomListViewState { * A list of rooms to be displayed in the left panel. */ rooms: Room[]; - - /** - * Open the room having given roomId. - */ - openRoom: (roomId: string) => void; - /** * A list of objects that provide the view enough information * to render primary room filters. @@ -48,17 +37,8 @@ export interface RoomListViewState { export function useRoomListViewModel(): RoomListViewState { const { primaryFilters, rooms, activateSecondaryFilter, activeSecondaryFilter } = useFilteredRooms(); - const openRoom = useCallback((roomId: string): void => { - dispatcher.dispatch({ - action: Action.ViewRoom, - room_id: roomId, - metricsTrigger: "RoomList", - }); - }, []); - return { rooms, - openRoom, primaryFilters, activateSecondaryFilter, activeSecondaryFilter, diff --git a/src/components/views/rooms/RoomListPanel/RoomList.tsx b/src/components/views/rooms/RoomListPanel/RoomList.tsx index f13d63ba77b..006d1b97320 100644 --- a/src/components/views/rooms/RoomListPanel/RoomList.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomList.tsx @@ -22,17 +22,10 @@ interface RoomListProps { /** * A virtualized list of rooms. */ -export function RoomList({ vm: { rooms, openRoom } }: RoomListProps): JSX.Element { +export function RoomList({ vm: { rooms } }: RoomListProps): JSX.Element { const roomRendererMemoized = useCallback( - ({ key, index, style }: ListRowProps) => ( - openRoom(rooms[index].roomId)} - /> - ), - [rooms, openRoom], + ({ key, index, style }: ListRowProps) => , + [rooms], ); // The first div is needed to make the virtualized list take all the remaining space and scroll correctly diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx index 705cb1d43a3..e1cb26dcb7f 100644 --- a/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx +++ b/src/components/views/rooms/RoomListPanel/RoomListItemView.tsx @@ -11,6 +11,7 @@ import { type Room } from "matrix-js-sdk/src/matrix"; import { _t } from "../../../../languageHandler"; import { Flex } from "../../../utils/Flex"; import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar"; +import { useRoomListItemViewModel } from "../../../viewmodels/roomlist/RoomListItemViewModel"; interface RoomListItemViewPropsProps extends React.HTMLAttributes { /** @@ -23,11 +24,14 @@ interface RoomListItemViewPropsProps extends React.HTMLAttributes vm.openRoom(room.roomId)} {...props} > {/* We need this extra div between the button and the content in order to add a padding which is not messing with the virtualized list */} diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx new file mode 100644 index 00000000000..0f9d6ebad77 --- /dev/null +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx @@ -0,0 +1,37 @@ +/* + * 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 Room } from "matrix-js-sdk/src/matrix"; + +import dispatcher from "../../../../../src/dispatcher/dispatcher"; +import { Action } from "../../../../../src/dispatcher/actions"; +import { useRoomListItemViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel"; +import { createTestClient, mkStubRoom } from "../../../../test-utils"; + +describe("RoomListItemViewModel", () => { + let room: Room; + + beforeEach(() => { + const matrixClient = createTestClient(); + room = mkStubRoom("roomId", "roomName", matrixClient); + }); + + it("should dispatch view room action on openRoom", async () => { + const { result: vm } = renderHook(() => useRoomListItemViewModel()); + + const fn = jest.spyOn(dispatcher, "dispatch"); + vm.current.openRoom(room.roomId); + expect(fn).toHaveBeenCalledWith( + expect.objectContaining({ + action: Action.ViewRoom, + room_id: room.roomId, + metricsTrigger: "RoomList", + }), + ); + }); +}); diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx index 055feb84e62..985a5d7f92e 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListViewModel-test.tsx @@ -12,8 +12,6 @@ import RoomListStoreV3 from "../../../../../src/stores/room-list-v3/RoomListStor import { mkStubRoom } from "../../../../test-utils"; import { LISTS_UPDATE_EVENT } from "../../../../../src/stores/room-list/SlidingRoomListStore"; import { useRoomListViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; -import dispatcher from "../../../../../src/dispatcher/dispatcher"; -import { Action } from "../../../../../src/dispatcher/actions"; import { FilterKey } from "../../../../../src/stores/room-list-v3/skip-list/filters"; import { SecondaryFilters } from "../../../../../src/components/viewmodels/roomlist/useFilteredRooms"; @@ -49,21 +47,6 @@ describe("RoomListViewModel", () => { }); }); - it("should dispatch view room action on openRoom", async () => { - const { rooms } = mockAndCreateRooms(); - const { result: vm } = renderHook(() => useRoomListViewModel()); - - const fn = jest.spyOn(dispatcher, "dispatch"); - act(() => vm.current.openRoom(rooms[7].roomId)); - expect(fn).toHaveBeenCalledWith( - expect.objectContaining({ - action: Action.ViewRoom, - room_id: rooms[7].roomId, - metricsTrigger: "RoomList", - }), - ); - }); - describe("Filters", () => { it("should provide list of available filters", () => { mockAndCreateRooms(); 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 e720798f040..11725c7a760 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomList-test.tsx @@ -7,8 +7,7 @@ import React from "react"; import { type MatrixClient } from "matrix-js-sdk/src/matrix"; -import { render, screen, waitFor } from "jest-matrix-react"; -import userEvent from "@testing-library/user-event"; +import { render } from "jest-matrix-react"; import { mkRoom, stubClient } from "../../../../../test-utils"; import { type RoomListViewState } from "../../../../../../src/components/viewmodels/roomlist/RoomListViewModel"; @@ -30,7 +29,6 @@ describe("", () => { const rooms = Array.from({ length: 10 }, (_, i) => mkRoom(matrixClient, `room${i}`)); vm = { rooms, - openRoom: jest.fn(), primaryFilters: [], activateSecondaryFilter: () => {}, activeSecondaryFilter: SecondaryFilters.AllActivity, @@ -45,15 +43,4 @@ describe("", () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); - - it("should open the room", async () => { - const user = userEvent.setup(); - - render(); - await waitFor(async () => { - expect(screen.getByRole("gridcell", { name: "Open room room9" })).toBeVisible(); - await user.click(screen.getByRole("gridcell", { name: "Open room room9" })); - }); - expect(vm.openRoom).toHaveBeenCalledWith(vm.rooms[9].roomId); - }); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx index 7d8a5dcc18a..146deed1747 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx @@ -9,16 +9,29 @@ import React from "react"; import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; import { render, screen } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; +import { mocked } from "jest-mock"; import { mkRoom, stubClient } from "../../../../../test-utils"; import { RoomListItemView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemView"; import DMRoomMap from "../../../../../../src/utils/DMRoomMap"; +import { + type RoomListItemViewState, + useRoomListItemViewModel, +} from "../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel"; + +jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel", () => ({ + useRoomListItemViewModel: jest.fn(), +})); describe("", () => { + const defaultValue: RoomListItemViewState = { + openRoom: jest.fn(), + }; let matrixClient: MatrixClient; let room: Room; beforeEach(() => { + mocked(useRoomListItemViewModel).mockReturnValue(defaultValue); matrixClient = stubClient(); room = mkRoom(matrixClient, "room1"); @@ -32,13 +45,11 @@ describe("", () => { expect(asFragment()).toMatchSnapshot(); }); - test("should call onClick when clicked", async () => { + test("should call openRoom when clicked", async () => { const user = userEvent.setup(); - - const onClick = jest.fn(); - render(); + render(); await user.click(screen.getByRole("button", { name: `Open room ${room.name}` })); - expect(onClick).toHaveBeenCalled(); + expect(defaultValue.openRoom).toHaveBeenCalled(); }); }); 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 3b1b29a5ff9..3500e039bf3 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListPrimaryFilters-test.tsx @@ -19,7 +19,6 @@ describe("", () => { beforeEach(() => { vm = { rooms: [], - openRoom: jest.fn(), primaryFilters: [ { name: "People", active: false, toggle: jest.fn() }, { name: "Rooms", active: true, toggle: jest.fn() }, diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap index cc0779b38bd..cad3e06bf1b 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` should render a room item 1`] = ` +exports[` should render a room item 1`] = ` From d9fee69f7afe94651ef3dceb8fd7893f7ff53095 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 10 Mar 2025 15:44:29 +0100 Subject: [PATCH 08/16] feat(hover menu): update i18n --- src/i18n/strings/en_EN.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 55702791159..920e19ac826 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2112,6 +2112,14 @@ "other": "Currently joining %(count)s rooms" }, "list_title": "Room list", + "more_options": { + "copy_link": "Copy room link", + "favourited": "Favourited", + "leave_room": "Leave room", + "low_priority": "Low priority", + "mark_read": "Mark as read", + "mark_unread": "Mark as unread" + }, "notification_options": "Notification options", "open_space_menu": "Open space menu", "primary_filters": "Room list filters", @@ -2120,6 +2128,7 @@ "other": "Currently removing messages in %(count)s rooms" }, "room": { + "more_options": "More Options", "open_room": "Open room %(roomName)s" }, "show_less": "Show less", From 9984f78548674eb744a643c31cc026ddcb32cda5 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Mon, 10 Mar 2025 16:20:39 +0100 Subject: [PATCH 09/16] test(view model list item): update test and add test to `showHoverMenu` --- .../roomlist/RoomListItemViewModel-test.tsx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx index 0f9d6ebad77..2854c433e7b 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx @@ -7,11 +7,17 @@ import { renderHook } from "jest-matrix-react"; import { type Room } from "matrix-js-sdk/src/matrix"; +import { mocked } from "jest-mock"; import dispatcher from "../../../../../src/dispatcher/dispatcher"; import { Action } from "../../../../../src/dispatcher/actions"; import { useRoomListItemViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemViewModel"; import { createTestClient, mkStubRoom } from "../../../../test-utils"; +import { hasAccessToOptionsMenu } from "../../../../../src/components/viewmodels/roomlist/utils"; + +jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({ + hasAccessToOptionsMenu: jest.fn().mockReturnValue(false), +})); describe("RoomListItemViewModel", () => { let room: Room; @@ -22,10 +28,10 @@ describe("RoomListItemViewModel", () => { }); it("should dispatch view room action on openRoom", async () => { - const { result: vm } = renderHook(() => useRoomListItemViewModel()); + const { result: vm } = renderHook(() => useRoomListItemViewModel(room)); const fn = jest.spyOn(dispatcher, "dispatch"); - vm.current.openRoom(room.roomId); + vm.current.openRoom(); expect(fn).toHaveBeenCalledWith( expect.objectContaining({ action: Action.ViewRoom, @@ -34,4 +40,10 @@ describe("RoomListItemViewModel", () => { }), ); }); + + it("should show hover menu if user has access to options menu", async () => { + mocked(hasAccessToOptionsMenu).mockReturnValue(true); + const { result: vm } = renderHook(() => useRoomListItemViewModel(room)); + expect(vm.current.showHoverMenu).toBe(true); + }); }); From 41e2883a3c2cbcb73d281286de40bb548d0be7a1 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 11 Mar 2025 09:32:49 +0100 Subject: [PATCH 10/16] test(room list): update snapshot --- .../__snapshots__/RoomList-test.tsx.snap | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap index 8d3559031ab..37f8a0364a6 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomList-test.tsx.snap @@ -24,13 +24,13 @@ exports[` should render a room list 1`] = ` > + + +`; From f35b21dabc331806302151509ff9a91cca65e2e8 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Tue, 11 Mar 2025 16:43:06 +0100 Subject: [PATCH 13/16] test(room list item): add tests --- .../RoomListPanel/RoomListItemView-test.tsx | 17 +++++- .../RoomListItemView-test.tsx.snap | 53 +------------------ 2 files changed, 17 insertions(+), 53 deletions(-) diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx index 146deed1747..3023f9a9a79 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx +++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemView-test.tsx @@ -7,11 +7,11 @@ import React from "react"; import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; -import { render, screen } from "jest-matrix-react"; +import { render, screen, waitFor } from "jest-matrix-react"; import userEvent from "@testing-library/user-event"; import { mocked } from "jest-mock"; -import { mkRoom, stubClient } from "../../../../../test-utils"; +import { mkRoom, stubClient, withClientContextRenderOptions } from "../../../../../test-utils"; import { RoomListItemView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemView"; import DMRoomMap from "../../../../../../src/utils/DMRoomMap"; import { @@ -26,6 +26,7 @@ jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemView describe("", () => { const defaultValue: RoomListItemViewState = { openRoom: jest.fn(), + showHoverMenu: false, }; let matrixClient: MatrixClient; let room: Room; @@ -52,4 +53,16 @@ describe("", () => { await user.click(screen.getByRole("button", { name: `Open room ${room.name}` })); expect(defaultValue.openRoom).toHaveBeenCalled(); }); + + test("should hover decoration if hovered", async () => { + mocked(useRoomListItemViewModel).mockReturnValue({ ...defaultValue, showHoverMenu: true }); + + const user = userEvent.setup(); + render(, withClientContextRenderOptions(matrixClient)); + const listItem = screen.getByRole("button", { name: `Open room ${room.name}` }); + expect(screen.queryByRole("button", { name: "More Options" })).toBeNull(); + + await user.hover(listItem); + await waitFor(() => expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument()); + }); }); diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap index cad3e06bf1b..e63d9f91a63 100644 --- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemView-test.tsx.snap @@ -9,7 +9,7 @@ exports[` should render a room item 1`] = ` >
should render a room item 1`] = `
- - room1 - -
-
- - -`; - -exports[` should render a room item 1`] = ` - -