From b511bf064db5e7d9f85bc69429f7989881b4cdc2 Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Wed, 16 Apr 2025 11:23:23 +0200 Subject: [PATCH] New room list: new visual for invitation (#29773) * feat: rework invitation styling in room list item * test: update notification decoration test * test: add test for vm * test(e2e): update to new invitation styling --- .../room-list-item-invited-linux.png | Bin 2945 -> 2841 bytes .../RoomListPanel/_RoomListItemView.pcss | 4 --- .../roomlist/RoomListItemViewModel.tsx | 10 ++++++-- .../views/rooms/NotificationDecoration.tsx | 3 ++- .../roomlist/RoomListItemViewModel-test.tsx | 23 +++++++++++++++++- .../NotificationDecoration-test.tsx.snap | 14 ++++++++--- 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-invited-linux.png index aa35ea711815c11e556bf749fff65826a18cee5c..4d9fd3ccd2dbc44b101ab0af211c20edae160e36 100644 GIT binary patch delta 2821 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 room.tags); const isArchived = Boolean(roomTags[DefaultTagID.Archived]); - const showHoverMenu = - hasAccessToOptionsMenu(room) || hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived); const notificationState = useMemo(() => RoomNotificationStateStore.instance.getRoomState(room), [room]); + const invited = notificationState.invited; const a11yLabel = getA11yLabel(room, notificationState); const isBold = notificationState.hasAnyNotificationOrActivity; + // We don't want to show the hover menu if + // - there is an invitation for this room + // - the user doesn't have access to both notification and more options menus + const showHoverMenu = + !invited && + (hasAccessToOptionsMenu(room) || hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived)); + // Video room const isVideoRoom = room.isElementVideoRoom() || room.isCallRoom(); // EC video call or video room diff --git a/src/components/views/rooms/NotificationDecoration.tsx b/src/components/views/rooms/NotificationDecoration.tsx index cfb82c461c..9cc1bee738 100644 --- a/src/components/views/rooms/NotificationDecoration.tsx +++ b/src/components/views/rooms/NotificationDecoration.tsx @@ -10,6 +10,7 @@ import MentionIcon from "@vector-im/compound-design-tokens/assets/web/icons/ment import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid"; import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid"; import VideoCallIcon from "@vector-im/compound-design-tokens/assets/web/icons/video-call-solid"; +import EmailIcon from "@vector-im/compound-design-tokens/assets/web/icons/email-solid"; import { UnreadCounter, Unread } from "@vector-im/compound-web"; import { Flex } from "../../utils/Flex"; @@ -56,7 +57,7 @@ export function NotificationDecoration({ > {isUnsetMessage && } {hasVideoCall && } - {invited && } + {invited && } {isMention && } {(isMention || isNotification) && } {isActivityNotification && } diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx index 867a909f2e..be309b36ed 100644 --- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx +++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx @@ -33,6 +33,10 @@ describe("RoomListItemViewModel", () => { room = mkStubRoom("roomId", "roomName", matrixClient); }); + afterEach(() => { + jest.resetAllMocks(); + }); + it("should dispatch view room action on openRoom", async () => { const { result: vm } = renderHook( () => useRoomListItemViewModel(room), @@ -68,6 +72,20 @@ describe("RoomListItemViewModel", () => { expect(vm.current.showHoverMenu).toBe(true); }); + it("should not show hover menu if user has an invitation notification", async () => { + mocked(hasAccessToOptionsMenu).mockReturnValue(true); + + const notificationState = new RoomNotificationState(room, false); + jest.spyOn(RoomNotificationStateStore.instance, "getRoomState").mockReturnValue(notificationState); + jest.spyOn(notificationState, "invited", "get").mockReturnValue(false); + + const { result: vm } = renderHook( + () => useRoomListItemViewModel(room), + withClientContextRenderOptions(room.client), + ); + expect(vm.current.showHoverMenu).toBe(true); + }); + describe("a11yLabel", () => { let notificationState: RoomNotificationState; beforeEach(() => { @@ -108,7 +126,10 @@ describe("RoomListItemViewModel", () => { }, ])("should return the $label label", ({ mock, expected }) => { mock?.(); - const { result: vm } = renderHook(() => useRoomListItemViewModel(room)); + const { result: vm } = renderHook( + () => useRoomListItemViewModel(room), + withClientContextRenderOptions(room.client), + ); expect(vm.current.a11yLabel).toBe(expected); }); }); diff --git a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap index be81664eb8..33ee60102b 100644 --- a/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap +++ b/test/unit-tests/components/views/rooms/__snapshots__/NotificationDecoration-test.tsx.snap @@ -23,11 +23,17 @@ exports[` should render the invitation decoration 1`] data-testid="notification-decoration" style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: center; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-1-5x); --mx-flex-wrap: nowrap;" > - - 1 - + + `;