diff --git a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
index ebca899a13..ccf9489e8c 100644
--- a/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
+++ b/playwright/e2e/left-panel/room-list-panel/room-list.spec.ts
@@ -85,6 +85,43 @@ test.describe("Room list", () => {
await expect(roomItem).not.toBeVisible();
});
+ test("should open the notification options menu", { tag: "@screenshot" }, async ({ page, app, user }) => {
+ const roomListView = getRoomList(page);
+
+ const roomItem = roomListView.getByRole("gridcell", { name: "Open room room29" });
+ await roomItem.hover();
+
+ await expect(roomItem).toMatchScreenshot("room-list-item-hover.png");
+ let roomItemMenu = roomItem.getByRole("button", { name: "Notification options" });
+ await roomItemMenu.click();
+
+ // Default settings should be selected
+ await expect(page.getByRole("menuitem", { name: "Match default settings" })).toHaveAttribute(
+ "aria-selected",
+ "true",
+ );
+ await expect(page).toMatchScreenshot("room-list-item-open-notification-options.png");
+
+ // It should make the room muted
+ await page.getByRole("menuitem", { name: "Mute room" }).click();
+
+ // Remove hover on the room list item
+ await roomListView.hover();
+
+ // The room decoration should have the muted icon
+ await expect(roomItem.getByTestId("notification-decoration")).toBeVisible();
+
+ await roomItem.hover();
+ // On hover, the room should show the muted icon
+ await expect(roomItem).toMatchScreenshot("room-list-item-hover-silent.png");
+
+ roomItemMenu = roomItem.getByRole("button", { name: "Notification options" });
+ await roomItemMenu.click();
+ // The Mute room option should be selected
+ await expect(page.getByRole("menuitem", { name: "Mute room" })).toHaveAttribute("aria-selected", "true");
+ await expect(page).toMatchScreenshot("room-list-item-open-notification-options-selection.png");
+ });
+
test("should scroll to the current room", async ({ page, app, user }) => {
const roomListView = getRoomList(page);
await roomListView.hover();
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png
index eccf48a528..b0cab67c66 100644
Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png
new file mode 100644
index 0000000000..a69315feff
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-hover-silent-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png
index 40206cc22b..a1106b4dc3 100644
Binary files a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-more-options-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png
new file mode 100644
index 0000000000..d360a8e31d
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-linux.png differ
diff --git a/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png
new file mode 100644
index 0000000000..2ae4b2e417
Binary files /dev/null and b/playwright/snapshots/left-panel/room-list-panel/room-list.spec.ts/room-list-item-open-notification-options-selection-linux.png differ
diff --git a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx
index 6b089495a0..997b515f27 100644
--- a/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx
+++ b/src/components/viewmodels/roomlist/RoomListItemMenuViewModel.tsx
@@ -11,7 +11,7 @@ import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import { useUnreadNotifications } from "../../../hooks/useUnreadNotifications";
-import { hasAccessToOptionsMenu } from "./utils";
+import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils";
import DMRoomMap from "../../../utils/DMRoomMap";
import { DefaultTagID } from "../../../stores/room-list/models";
import { NotificationLevel } from "../../../stores/notifications/NotificationLevel";
@@ -21,12 +21,18 @@ import dispatcher from "../../../dispatcher/dispatcher";
import { clearRoomNotification, setMarkedUnreadState } from "../../../utils/notifications";
import PosthogTrackers from "../../../PosthogTrackers";
import { tagRoom } from "../../../utils/room/tagRoom";
+import { RoomNotifState } from "../../../RoomNotifs";
+import { useNotificationState } from "../../../hooks/useRoomNotificationState";
export interface RoomListItemMenuViewState {
/**
* Whether the more options menu should be shown.
*/
showMoreOptionsMenu: boolean;
+ /**
+ * Whether the notification menu should be shown.
+ */
+ showNotificationMenu: boolean;
/**
* Whether the room is a favourite room.
*/
@@ -47,6 +53,22 @@ export interface RoomListItemMenuViewState {
* Can mark the room as unread.
*/
canMarkAsUnread: boolean;
+ /**
+ * Whether the notification is set to all messages.
+ */
+ isNotificationAllMessage: boolean;
+ /**
+ * Whether the notification is set to all messages loud.
+ */
+ isNotificationAllMessageLoud: boolean;
+ /**
+ * Whether the notification is set to mentions and keywords only.
+ */
+ isNotificationMentionOnly: boolean;
+ /**
+ * Whether the notification is muted.
+ */
+ isNotificationMute: boolean;
/**
* Mark the room as read.
* @param evt
@@ -81,6 +103,11 @@ export interface RoomListItemMenuViewState {
* @param evt
*/
leaveRoom: (evt: Event) => void;
+ /**
+ * Set the room notification state.
+ * @param state
+ */
+ setRoomNotifState: (state: RoomNotifState) => void;
}
export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewState {
@@ -88,12 +115,13 @@ export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewSt
const roomTags = useEventEmitterState(room, RoomEvent.Tags, () => room.tags);
const { level: notificationLevel } = useUnreadNotifications(room);
- const showMoreOptionsMenu = hasAccessToOptionsMenu(room);
-
const isDm = Boolean(DMRoomMap.shared().getUserIdForRoomId(room.roomId));
const isFavourite = Boolean(roomTags[DefaultTagID.Favourite]);
const isArchived = Boolean(roomTags[DefaultTagID.Archived]);
+ const showMoreOptionsMenu = hasAccessToOptionsMenu(room);
+ const showNotificationMenu = hasAccessToNotificationMenu(room, matrixClient.isGuest(), isArchived);
+
const canMarkAsRead = notificationLevel > NotificationLevel.None;
const canMarkAsUnread = !canMarkAsRead && !isArchived;
@@ -101,6 +129,12 @@ export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewSt
room.canInvite(matrixClient.getUserId()!) && !isDm && shouldShowComponent(UIComponent.InviteUsers);
const canCopyRoomLink = !isDm;
+ const [roomNotifState, setRoomNotifState] = useNotificationState(room);
+ const isNotificationAllMessage = roomNotifState === RoomNotifState.AllMessages;
+ const isNotificationAllMessageLoud = roomNotifState === RoomNotifState.AllMessagesLoud;
+ const isNotificationMentionOnly = roomNotifState === RoomNotifState.MentionsOnly;
+ const isNotificationMute = roomNotifState === RoomNotifState.Mute;
+
// Actions
const markAsRead = useCallback(
@@ -164,11 +198,16 @@ export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewSt
return {
showMoreOptionsMenu,
+ showNotificationMenu,
isFavourite,
canInvite,
canCopyRoomLink,
canMarkAsRead,
canMarkAsUnread,
+ isNotificationAllMessage,
+ isNotificationAllMessageLoud,
+ isNotificationMentionOnly,
+ isNotificationMute,
markAsRead,
markAsUnread,
toggleFavorite,
@@ -176,5 +215,6 @@ export function useRoomListItemMenuViewModel(room: Room): RoomListItemMenuViewSt
invite,
copyRoomLink,
leaveRoom,
+ setRoomNotifState,
};
}
diff --git a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx
index 1d5d9aba11..d1c9fef4ab 100644
--- a/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx
+++ b/src/components/viewmodels/roomlist/RoomListItemViewModel.tsx
@@ -6,15 +6,18 @@
*/
import { useCallback, useMemo } from "react";
-import { type Room } from "matrix-js-sdk/src/matrix";
+import { type Room, RoomEvent } from "matrix-js-sdk/src/matrix";
import dispatcher from "../../../dispatcher/dispatcher";
import type { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions";
-import { hasAccessToOptionsMenu } from "./utils";
+import { hasAccessToNotificationMenu, hasAccessToOptionsMenu } from "./utils";
import { _t } from "../../../languageHandler";
import { type RoomNotificationState } from "../../../stores/notifications/RoomNotificationState";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
+import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
+import { useEventEmitterState } from "../../../hooks/useEventEmitter";
+import { DefaultTagID } from "../../../stores/room-list/models";
export interface RoomListItemViewState {
/**
@@ -40,8 +43,12 @@ export interface RoomListItemViewState {
* @see {@link RoomListItemViewState} for more information about what this view model returns.
*/
export function useRoomListItemViewModel(room: Room): RoomListItemViewState {
- // incoming: Check notification menu rights
- const showHoverMenu = hasAccessToOptionsMenu(room);
+ const matrixClient = useMatrixClientContext();
+ const roomTags = useEventEmitterState(room, RoomEvent.Tags, () => 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 a11yLabel = getA11yLabel(room, notificationState);
diff --git a/src/components/viewmodels/roomlist/utils.ts b/src/components/viewmodels/roomlist/utils.ts
index 6220c3b961..dfa20e0d1c 100644
--- a/src/components/viewmodels/roomlist/utils.ts
+++ b/src/components/viewmodels/roomlist/utils.ts
@@ -27,6 +27,16 @@ export function hasAccessToOptionsMenu(room: Room): boolean {
);
}
+/**
+ * Check if the user has access to the notification menu.
+ * @param room
+ * @param isGuest
+ * @param isArchived
+ */
+export function hasAccessToNotificationMenu(room: Room, isGuest: boolean, isArchived: boolean): boolean {
+ return !isGuest && !isArchived && hasAccessToOptionsMenu(room);
+}
+
/**
* Create a room
* @param space - The space to create the room in
diff --git a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx
index 00d23cc2d2..71e84984c5 100644
--- a/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx
+++ b/src/components/views/rooms/RoomListPanel/RoomListItemMenuView.tsx
@@ -15,6 +15,9 @@ import UserAddIcon from "@vector-im/compound-design-tokens/assets/web/icons/user
import LinkIcon from "@vector-im/compound-design-tokens/assets/web/icons/link";
import LeaveIcon from "@vector-im/compound-design-tokens/assets/web/icons/leave";
import OverflowIcon from "@vector-im/compound-design-tokens/assets/web/icons/overflow-horizontal";
+import NotificationIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-solid";
+import NotificationOffIcon from "@vector-im/compound-design-tokens/assets/web/icons/notifications-off-solid";
+import CheckIcon from "@vector-im/compound-design-tokens/assets/web/icons/check";
import { type Room } from "matrix-js-sdk/src/matrix";
import { _t } from "../../../../languageHandler";
@@ -23,6 +26,7 @@ import {
type RoomListItemMenuViewState,
useRoomListItemMenuViewModel,
} from "../../../viewmodels/roomlist/RoomListItemMenuViewModel";
+import { RoomNotifState } from "../../../../RoomNotifs";
interface RoomListItemMenuViewProps {
/**
@@ -45,6 +49,7 @@ export function RoomListItemMenuView({ room, setMenuOpen }: RoomListItemMenuView
return (
{vm.showMoreOptionsMenu && }
+ {vm.showNotificationMenu && }
);
}
@@ -152,3 +157,93 @@ export const MoreOptionsButton = forwardRef void;
+}
+
+function NotificationMenu({ vm, setMenuOpen }: NotificationMenuProps): JSX.Element {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+ );
+}
+
+interface NotificationButtonProps extends ComponentProps {
+ /**
+ * Whether the room is muted.
+ */
+ isRoomMuted: boolean;
+}
+
+/**
+ * A button to trigger the notification menu.
+ */
+export const NotificationButton = forwardRef(function MoreOptionsButton(
+ { isRoomMuted, ...props },
+ ref,
+) {
+ return (
+
+
+ {isRoomMuted ? : }
+
+
+ );
+});
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index ce1c2e503b..98b0b7af96 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1677,6 +1677,7 @@
"class_global": "Global",
"class_other": "Other",
"default": "Default",
+ "default_settings": "Match default settings",
"email_pusher_app_display_name": "Email Notifications",
"enable_prompt_toast_description": "Enable desktop notifications",
"enable_prompt_toast_title": "Notifications",
@@ -1693,9 +1694,10 @@
"mark_all_read": "Mark all as read",
"mentions_and_keywords": "@mentions & keywords",
"mentions_and_keywords_description": "Get notified only with mentions and keywords as set up in your settings",
- "mentions_keywords": "Mentions & keywords",
+ "mentions_keywords": "Mentions and keywords",
"message_didnt_send": "Message didn't send. Click for info.",
- "mute_description": "You won't get any notifications"
+ "mute_description": "You won't get any notifications",
+ "mute_room": "Mute room"
},
"notifier": {
"m.key.verification.request": "%(name)s is requesting verification"
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx
index 89dd644208..ddb3cba609 100644
--- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemMenuViewModel-test.tsx
@@ -11,7 +11,10 @@ import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils";
import { useRoomListItemMenuViewModel } from "../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel";
-import { hasAccessToOptionsMenu } from "../../../../../src/components/viewmodels/roomlist/utils";
+import {
+ hasAccessToNotificationMenu,
+ hasAccessToOptionsMenu,
+} from "../../../../../src/components/viewmodels/roomlist/utils";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { DefaultTagID } from "../../../../../src/stores/room-list/models";
import { useUnreadNotifications } from "../../../../../src/hooks/useUnreadNotifications";
@@ -19,15 +22,22 @@ import { NotificationLevel } from "../../../../../src/stores/notifications/Notif
import { clearRoomNotification, setMarkedUnreadState } from "../../../../../src/utils/notifications";
import { tagRoom } from "../../../../../src/utils/room/tagRoom";
import dispatcher from "../../../../../src/dispatcher/dispatcher";
+import { useNotificationState } from "../../../../../src/hooks/useRoomNotificationState";
+import { RoomNotifState } from "../../../../../src/RoomNotifs";
jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
hasAccessToOptionsMenu: jest.fn().mockReturnValue(false),
+ hasAccessToNotificationMenu: jest.fn().mockReturnValue(false),
}));
jest.mock("../../../../../src/hooks/useUnreadNotifications", () => ({
useUnreadNotifications: jest.fn(),
}));
+jest.mock("../../../../../src/hooks/useRoomNotificationState", () => ({
+ useNotificationState: jest.fn(),
+}));
+
jest.mock("../../../../../src/utils/notifications", () => ({
clearRoomNotification: jest.fn(),
setMarkedUnreadState: jest.fn(),
@@ -49,6 +59,7 @@ describe("RoomListItemMenuViewModel", () => {
jest.spyOn(DMRoomMap.shared(), "getUserIdForRoomId").mockReturnValue(null);
mocked(useUnreadNotifications).mockReturnValue({ symbol: null, count: 0, level: NotificationLevel.None });
+ mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessages, jest.fn()]);
jest.spyOn(dispatcher, "dispatch");
});
@@ -76,6 +87,12 @@ describe("RoomListItemMenuViewModel", () => {
expect(result.current.showMoreOptionsMenu).toBe(true);
});
+ it("should has showNotificationMenu to be true", () => {
+ mocked(hasAccessToNotificationMenu).mockReturnValue(true);
+ const { result } = render();
+ expect(result.current.showNotificationMenu).toBe(true);
+ });
+
it("should be able to invite", () => {
jest.spyOn(room, "canInvite").mockReturnValue(true);
const { result } = render();
@@ -106,6 +123,29 @@ describe("RoomListItemMenuViewModel", () => {
expect(result.current.canMarkAsUnread).toBe(false);
});
+ it("should has isNotificationAllMessage to be true", () => {
+ const { result } = render();
+ expect(result.current.isNotificationAllMessage).toBe(true);
+ });
+
+ it("should has isNotificationAllMessageLoud to be true", () => {
+ mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessagesLoud, jest.fn()]);
+ const { result } = render();
+ expect(result.current.isNotificationAllMessageLoud).toBe(true);
+ });
+
+ it("should has isNotificationMentionOnly to be true", () => {
+ mocked(useNotificationState).mockReturnValue([RoomNotifState.MentionsOnly, jest.fn()]);
+ const { result } = render();
+ expect(result.current.isNotificationMentionOnly).toBe(true);
+ });
+
+ it("should has isNotificationMute to be true", () => {
+ mocked(useNotificationState).mockReturnValue([RoomNotifState.Mute, jest.fn()]);
+ const { result } = render();
+ expect(result.current.isNotificationMute).toBe(true);
+ });
+
// Actions
it("should mark as read", () => {
@@ -170,4 +210,12 @@ describe("RoomListItemMenuViewModel", () => {
room_id: room.roomId,
});
});
+
+ it("should call setRoomNotifState", () => {
+ const setRoomNotifState = jest.fn();
+ mocked(useNotificationState).mockReturnValue([RoomNotifState.AllMessages, setRoomNotifState]);
+ const { result } = render();
+ result.current.setRoomNotifState(RoomNotifState.Mute);
+ expect(setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute);
+ });
});
diff --git a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
index caba9abd1e..9558ced09b 100644
--- a/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
+++ b/test/unit-tests/components/viewmodels/roomlist/RoomListItemViewModel-test.tsx
@@ -12,13 +12,17 @@ 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";
+import { createTestClient, mkStubRoom, withClientContextRenderOptions } from "../../../../test-utils";
+import {
+ hasAccessToNotificationMenu,
+ hasAccessToOptionsMenu,
+} from "../../../../../src/components/viewmodels/roomlist/utils";
import { RoomNotificationState } from "../../../../../src/stores/notifications/RoomNotificationState";
import { RoomNotificationStateStore } from "../../../../../src/stores/notifications/RoomNotificationStateStore";
jest.mock("../../../../../src/components/viewmodels/roomlist/utils", () => ({
hasAccessToOptionsMenu: jest.fn().mockReturnValue(false),
+ hasAccessToNotificationMenu: jest.fn().mockReturnValue(false),
}));
describe("RoomListItemViewModel", () => {
@@ -30,7 +34,10 @@ describe("RoomListItemViewModel", () => {
});
it("should dispatch view room action on openRoom", async () => {
- const { result: vm } = renderHook(() => useRoomListItemViewModel(room));
+ const { result: vm } = renderHook(
+ () => useRoomListItemViewModel(room),
+ withClientContextRenderOptions(room.client),
+ );
const fn = jest.spyOn(dispatcher, "dispatch");
vm.current.openRoom();
@@ -45,7 +52,19 @@ 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));
+ const { result: vm } = renderHook(
+ () => useRoomListItemViewModel(room),
+ withClientContextRenderOptions(room.client),
+ );
+ expect(vm.current.showHoverMenu).toBe(true);
+ });
+
+ it("should show hover menu if user has access to notification menu", async () => {
+ mocked(hasAccessToNotificationMenu).mockReturnValue(true);
+ const { result: vm } = renderHook(
+ () => useRoomListItemViewModel(room),
+ withClientContextRenderOptions(room.client),
+ );
expect(vm.current.showHoverMenu).toBe(true);
});
diff --git a/test/unit-tests/components/viewmodels/roomlist/utils-test.ts b/test/unit-tests/components/viewmodels/roomlist/utils-test.ts
index 1fd4fc1b4a..322d2a5cc6 100644
--- a/test/unit-tests/components/viewmodels/roomlist/utils-test.ts
+++ b/test/unit-tests/components/viewmodels/roomlist/utils-test.ts
@@ -10,7 +10,11 @@ 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 {
+ hasCreateRoomRights,
+ createRoom,
+ hasAccessToNotificationMenu,
+} from "../../../../../src/components/viewmodels/roomlist/utils";
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../../src/dispatcher/actions";
import { showCreateNewRoom } from "../../../../../src/utils/space";
@@ -66,4 +70,13 @@ describe("utils", () => {
expect(hasCreateRoomRights(matrixClient)).toBe(true);
});
});
+
+ it("hasAccessToNotificationMenu", () => {
+ mocked(shouldShowComponent).mockReturnValue(true);
+ const room = mkStubRoom("roomId", "roomName", matrixClient);
+ const isGuest = false;
+ const isArchived = false;
+
+ expect(hasAccessToNotificationMenu(room, isGuest, isArchived)).toBe(true);
+ });
});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx
index de1d37ed08..2994d7629c 100644
--- a/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/RoomListItemMenuView-test.tsx
@@ -17,6 +17,7 @@ import {
import type { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
import { mkRoom, stubClient } from "../../../../../test-utils";
import { RoomListItemMenuView } from "../../../../../../src/components/views/rooms/RoomListPanel/RoomListItemMenuView";
+import { RoomNotifState } from "../../../../../../src/RoomNotifs";
jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemMenuViewModel", () => ({
useRoomListItemMenuViewModel: jest.fn(),
@@ -25,11 +26,16 @@ jest.mock("../../../../../../src/components/viewmodels/roomlist/RoomListItemMenu
describe("", () => {
const defaultValue: RoomListItemMenuViewState = {
showMoreOptionsMenu: true,
+ showNotificationMenu: true,
isFavourite: true,
canInvite: true,
canMarkAsUnread: true,
canMarkAsRead: true,
canCopyRoomLink: true,
+ isNotificationAllMessage: true,
+ isNotificationMentionOnly: true,
+ isNotificationAllMessageLoud: true,
+ isNotificationMute: true,
copyRoomLink: jest.fn(),
markAsUnread: jest.fn(),
markAsRead: jest.fn(),
@@ -37,6 +43,7 @@ describe("", () => {
toggleLowPriority: jest.fn(),
toggleFavorite: jest.fn(),
invite: jest.fn(),
+ setRoomNotifState: jest.fn(),
};
let matrixClient: MatrixClient;
@@ -58,22 +65,37 @@ describe("", () => {
expect(asFragment()).toMatchSnapshot();
});
+ it("should render the notification options menu", () => {
+ const { asFragment } = renderMenu();
+ expect(screen.getByRole("button", { name: "Notification options" })).toBeInTheDocument();
+ expect(asFragment()).toMatchSnapshot();
+ });
+
it("should not render the more options menu when showMoreOptionsMenu is false", () => {
mocked(useRoomListItemMenuViewModel).mockReturnValue({ ...defaultValue, showMoreOptionsMenu: false });
renderMenu();
expect(screen.queryByRole("button", { name: "More Options" })).toBeNull();
});
- it("should call setMenuOpen when the menu is opened", async () => {
- const user = userEvent.setup();
- const setMenuOpen = jest.fn();
- renderMenu(setMenuOpen);
-
- await user.click(screen.getByRole("button", { name: "More Options" }));
- expect(setMenuOpen).toHaveBeenCalledWith(true);
+ it("should not render the notification options menu when showNotificationMenu is false", () => {
+ mocked(useRoomListItemMenuViewModel).mockReturnValue({ ...defaultValue, showNotificationMenu: false });
+ renderMenu();
+ expect(screen.queryByRole("button", { name: "Notification options" })).toBeNull();
});
- it("should display all the buttons and have the actions linked", async () => {
+ it.each([["More Options"], ["Notification options"]])(
+ "should call setMenuOpen when the menu is opened for %s menu",
+ async (label) => {
+ const user = userEvent.setup();
+ const setMenuOpen = jest.fn();
+ renderMenu(setMenuOpen);
+
+ await user.click(screen.getByRole("button", { name: label }));
+ expect(setMenuOpen).toHaveBeenCalledWith(true);
+ },
+ );
+
+ it("should display all the buttons and have the actions linked for the more options menu", async () => {
const user = userEvent.setup();
renderMenu();
@@ -107,4 +129,27 @@ describe("", () => {
await user.click(screen.getByRole("menuitem", { name: "Leave room" }));
expect(defaultValue.leaveRoom).toHaveBeenCalled();
});
+
+ it("should display all the buttons and have the actions linked for the notification options menu", async () => {
+ const user = userEvent.setup();
+ renderMenu();
+
+ const openMenu = screen.getByRole("button", { name: "Notification options" });
+ await user.click(openMenu);
+
+ await user.click(screen.getByRole("menuitem", { name: "Match default settings" }));
+ expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessages);
+
+ await user.click(openMenu);
+ await user.click(screen.getByRole("menuitem", { name: "All messages" }));
+ expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessagesLoud);
+
+ await user.click(openMenu);
+ await user.click(screen.getByRole("menuitem", { name: "Mentions and keywords" }));
+ expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.MentionsOnly);
+
+ await user.click(openMenu);
+ await user.click(screen.getByRole("menuitem", { name: "Mute room" }));
+ expect(defaultValue.setRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute);
+ });
});
diff --git a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap
index 0910706d9b..6489d61c35 100644
--- a/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap
+++ b/test/unit-tests/components/views/rooms/RoomListPanel/__snapshots__/RoomListItemMenuView-test.tsx.snap
@@ -37,6 +37,115 @@ exports[` should render the more options menu 1`] = `
+
+
+
+`;
+
+exports[` should render the notification options menu 1`] = `
+
+
`;