diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/default-auto.png new file mode 100644 index 0000000000..039b8d9253 Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/default-auto.png differ diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/muted-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/muted-auto.png new file mode 100644 index 0000000000..8c7d5de46b Binary files /dev/null and b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/muted-auto.png differ diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx new file mode 100644 index 0000000000..e0b5dbc09b --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx @@ -0,0 +1,130 @@ +/* + * Copyright 2026 Element Creations 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 } from "react"; +import { fn } from "storybook/test"; +import { userEvent, within } from "storybook/test"; + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu"; +import { type RoomListItemSnapshot, type RoomListItemActions } from "./RoomListItem"; +import { useMockedViewModel } from "../../viewmodel"; +import { defaultSnapshot } from "./default-snapshot"; +import { RoomNotifState } from "./RoomNotifs"; + +type NotificationMenuProps = RoomListItemSnapshot & RoomListItemActions; + +// Wrapper component that creates a mocked ViewModel +const NotificationMenuWrapper = ({ + onOpenRoom, + onMarkAsRead, + onMarkAsUnread, + onToggleFavorite, + onToggleLowPriority, + onInvite, + onCopyRoomLink, + onLeaveRoom, + onSetRoomNotifState, + ...rest +}: NotificationMenuProps): JSX.Element => { + const vm = useMockedViewModel(rest, { + onOpenRoom, + onMarkAsRead, + onMarkAsUnread, + onToggleFavorite, + onToggleLowPriority, + onInvite, + onCopyRoomLink, + onLeaveRoom, + onSetRoomNotifState, + }); + return ; +}; + +const meta = { + title: "Room List/RoomListItem/NotificationMenu", + component: NotificationMenuWrapper, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + args: { + ...defaultSnapshot, + onOpenRoom: fn(), + onMarkAsRead: fn(), + onMarkAsUnread: fn(), + onToggleFavorite: fn(), + onToggleLowPriority: fn(), + onInvite: fn(), + onCopyRoomLink: fn(), + onLeaveRoom: fn(), + onSetRoomNotifState: fn(), + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + roomNotifState: RoomNotifState.AllMessages, + }, +}; + +export const Muted: Story = { + args: { + roomNotifState: RoomNotifState.Mute, + }, +}; + +export const Open: Story = { + args: { + roomNotifState: RoomNotifState.AllMessages, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "Notification options" }); + await userEvent.click(trigger); + }, +}; + +export const OpenMuted: Story = { + args: { + roomNotifState: RoomNotifState.Mute, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "Notification options" }); + await userEvent.click(trigger); + }, +}; + +export const AllMessagesLoud: Story = { + args: { + roomNotifState: RoomNotifState.AllMessagesLoud, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "Notification options" }); + await userEvent.click(trigger); + }, +}; + +export const MentionsOnly: Story = { + args: { + roomNotifState: RoomNotifState.MentionsOnly, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "Notification options" }); + await userEvent.click(trigger); + }, +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx index 3f88e2f8a1..af1d32e346 100644 --- a/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.test.tsx @@ -5,160 +5,80 @@ * Please see LICENSE files in the repository root for full details. */ -import React, { type JSX } from "react"; +import React from "react"; import { render, screen } from "@test-utils"; import userEvent from "@testing-library/user-event"; -import { describe, it, expect, vi } from "vitest"; +import { composeStories } from "@storybook/react-vite"; +import { describe, it, expect } from "vitest"; -import { RoomListItemNotificationMenu } from "./RoomListItemNotificationMenu"; +import * as stories from "./RoomListItemNotificationMenu.stories"; import { RoomNotifState } from "./RoomNotifs"; -import { useMockedViewModel } from "../../viewmodel"; -import type { RoomListItemSnapshot } from "./RoomListItem"; -import { defaultSnapshot } from "./default-snapshot"; -describe("", () => { - const mockCallbacks = { - onOpenRoom: vi.fn(), - onMarkAsRead: vi.fn(), - onMarkAsUnread: vi.fn(), - onToggleFavorite: vi.fn(), - onToggleLowPriority: vi.fn(), - onInvite: vi.fn(), - onCopyRoomLink: vi.fn(), - onLeaveRoom: vi.fn(), - onSetRoomNotifState: vi.fn(), - }; +const { Default, Muted, Open, OpenMuted, AllMessagesLoud, MentionsOnly } = composeStories(stories); - const renderMenu = (roomNotifState: RoomNotifState = RoomNotifState.AllMessages): ReturnType => { - const TestComponent = (): JSX.Element => { - const vm = useMockedViewModel( - { - ...defaultSnapshot, - showMoreOptionsMenu: false, - showNotificationMenu: true, - roomNotifState, - } as RoomListItemSnapshot, - mockCallbacks, - ); - return ; - }; - return render(); - }; - - it("should render the notification menu button", () => { - renderMenu(); - expect(screen.getByRole("button", { name: "Notification options" })).toBeInTheDocument(); +describe(" stories", () => { + it("renders Default story (closed, unmuted)", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); }); - it("should show muted icon when notifications are muted", () => { - renderMenu(RoomNotifState.Mute); + it("renders Muted story (closed, muted icon)", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders Open story", async () => { + const { container } = render(); + // Wait for play function to open the menu + await Open.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + + it("renders OpenMuted story", async () => { + const { container } = render(); + // Wait for play function to open the menu + await OpenMuted.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + + it("renders AllMessagesLoud story", async () => { + const { container } = render(); + // Wait for play function to open the menu + await AllMessagesLoud.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + + it("renders MentionsOnly story", async () => { + const { container } = render(); + // Wait for play function to open the menu + await MentionsOnly.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + + it("should show unmuted icon by default", () => { + render(); const button = screen.getByRole("button", { name: "Notification options" }); - expect(button.querySelector("svg")).toBeInTheDocument(); + expect(button).toBeInTheDocument(); }); - it("should open menu when clicked", async () => { + it("should show muted icon when muted", () => { + render(); + const button = screen.getByRole("button", { name: "Notification options" }); + expect(button).toBeInTheDocument(); + }); + + it("should call onSetRoomNotifState when menu item is clicked", async () => { const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); + const { container } = render(); + await Open.play?.({ canvasElement: container }); + // Menu should be open expect(screen.getByRole("menu")).toBeInTheDocument(); - }); - - it("should call onSetRoomNotifState with AllMessages when default settings selected", async () => { - const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const defaultOption = screen.getByRole("menuitem", { name: "Match default settings" }); - await user.click(defaultOption); - - expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessages); - }); - - it("should call onSetRoomNotifState with AllMessagesLoud when all messages selected", async () => { - const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const allMessagesOption = screen.getByRole("menuitem", { name: "All messages" }); - await user.click(allMessagesOption); - - expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.AllMessagesLoud); - }); - - it("should call onSetRoomNotifState with MentionsOnly when mentions and keywords selected", async () => { - const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const mentionsOption = screen.getByRole("menuitem", { name: "Mentions and keywords" }); - await user.click(mentionsOption); - - expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.MentionsOnly); - }); - - it("should call onSetRoomNotifState with Mute when mute selected", async () => { - const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); + // Click on "Mute room" option const muteOption = screen.getByRole("menuitem", { name: "Mute room" }); await user.click(muteOption); - expect(mockCallbacks.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute); - }); - - it("should show check mark next to selected option - AllMessage", async () => { - const user = userEvent.setup(); - renderMenu(RoomNotifState.AllMessages); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const defaultOption = screen.getByRole("menuitem", { name: "Match default settings" }); - expect(defaultOption).toHaveAttribute("aria-selected", "true"); - }); - - it("should show check mark next to selected option - AllMessagesLoud", async () => { - const user = userEvent.setup(); - renderMenu(RoomNotifState.AllMessagesLoud); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const allMessagesOption = screen.getByRole("menuitem", { name: "All messages" }); - expect(allMessagesOption).toHaveAttribute("aria-selected", "true"); - }); - - it("should show check mark next to selected option - MentionsOnly", async () => { - const user = userEvent.setup(); - renderMenu(RoomNotifState.MentionsOnly); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const mentionsOption = screen.getByRole("menuitem", { name: "Mentions and keywords" }); - expect(mentionsOption).toHaveAttribute("aria-selected", "true"); - }); - - it("should show check mark next to selected option - Mute", async () => { - const user = userEvent.setup(); - renderMenu(RoomNotifState.Mute); - - const button = screen.getByRole("button", { name: "Notification options" }); - await user.click(button); - - const muteOption = screen.getByRole("menuitem", { name: "Mute room" }); - expect(muteOption).toHaveAttribute("aria-selected", "true"); + expect(Open.args.onSetRoomNotifState).toHaveBeenCalledWith(RoomNotifState.Mute); }); }); diff --git a/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.stories.test.tsx.snap b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.stories.test.tsx.snap new file mode 100644 index 0000000000..48870d3c21 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.stories.test.tsx.snap @@ -0,0 +1,269 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` stories > renders AllMessagesLoud story 1`] = ` + +`; + +exports[` stories > renders Default story (closed, unmuted) 1`] = ` +
+
+ +
+
+`; + +exports[` stories > renders MentionsOnly story 1`] = ` + +`; + +exports[` stories > renders Muted story (closed, muted icon) 1`] = ` +
+
+ +
+
+`; + +exports[` stories > renders Open story 1`] = ` + +`; + +exports[` stories > renders OpenMuted story 1`] = ` + +`; diff --git a/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.test.tsx.snap b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.test.tsx.snap new file mode 100644 index 0000000000..48870d3c21 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.test.tsx.snap @@ -0,0 +1,269 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` stories > renders AllMessagesLoud story 1`] = ` + +`; + +exports[` stories > renders Default story (closed, unmuted) 1`] = ` +
+
+ +
+
+`; + +exports[` stories > renders MentionsOnly story 1`] = ` + +`; + +exports[` stories > renders Muted story (closed, muted icon) 1`] = ` +
+
+ +
+
+`; + +exports[` stories > renders Open story 1`] = ` + +`; + +exports[` stories > renders OpenMuted story 1`] = ` + +`;