From 30d177f956d831e3369bbbc6feafb7713dfafafa Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 30 Jan 2026 15:54:37 +0000 Subject: [PATCH] Add notification menu stories and tests --- .../default-auto.png | Bin 0 -> 3840 bytes .../muted-auto.png | Bin 0 -> 4004 bytes .../RoomListItemNotificationMenu.stories.tsx | 130 +++++++++ .../RoomListItemNotificationMenu.test.tsx | 194 ++++--------- ...ItemNotificationMenu.stories.test.tsx.snap | 269 ++++++++++++++++++ ...RoomListItemNotificationMenu.test.tsx.snap | 269 ++++++++++++++++++ 6 files changed, 725 insertions(+), 137 deletions(-) create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/default-auto.png create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx/muted-auto.png create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemNotificationMenu.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.stories.test.tsx.snap create mode 100644 packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemNotificationMenu.test.tsx.snap 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 0000000000000000000000000000000000000000..039b8d9253c723ed560c8c2cb795ded30bfde8a7 GIT binary patch literal 3840 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk1K(Cp7srr_IdAUn?(++j zXn1J5NbyTkn2L&vjI+zaz#Xh}kL9k|qUX9$QA9+D)u@o`*uo+iFQdsnd{vc{XO{b? zp8q`M{;o!I?bjPB&1Um5FdVp*BgeqdU@ClpfkA*HgMopA#e{)@g-Hr%F(XjEv4M@D z!NGx!XVaXw z2c4h3J|_0hueawPE@b|#AG^l@=#r9LDYu=Kzu(1w-Sg<-pUJJ}@7Wj_W~`gY`1jAz zL#sZAufNa7z+e!oeBMD{S9h&de9Rv+kgndnE*9q7U;TK#Lij2J!-xGJdSrnfP-d>G zth7AK$dDj)EJkTh+23Qo`R9kl?kfL(@XzCPW(EeID>GzY#9Einu`mA?zxwv}TxkY| zghg(3OFrE`mG|XztNC(&IqSM7K>soXZ@=f=;?6IBXIJUzeS52cuDZc+Z8tM8bfmk| z-!KppDntg}s50^r-DtRth8tB%uhE1wnvg~l5=j;QXqFkxGNV~$G|N!ZX&5a-hI1M6 q2iSl8|3B06Whk&S#RlrYGBXIJ#HeprVXze_!re!b`Z{=JbS_Wv)doLKi>Gr#4R85tOM7)rA7}U}2I1I)sszfuXU1jiJH8ftkTUVF4q9g1`j^1_2Iy$~GjvxL5bSIoSRm(3}Im zj8F4@d3JWYj73RG=KFVR%kwHu(p; z-TQ0ze0lTk=+UF$an+wcojPT4XW!d@w*K~}K;!<*ddhM9#j9JlrfRSL%_lq0N6q{E zTeBd3L>r@$Xgh`+e`PUS0kBzJ<}>++AC~&-9(0C#SByUl{0t zGn*xut8#OTY7O5x_w(B7#qGFu;q?9(2?mCQy0a1&>bGtxO8vj^FgG`Mva`OP9v=gP z!Tx{k4RY&DzwB#nH`mFj_|$nE7+^EvjoH4W82!Bc1nA6v&F<&^@38?pQgy-o+W&`* z)6f4ewln*(dj0;=UvGAToOVU$f8)O|Kb|i?|EF3tepgB5pI?tYSHIhr{TdkK#p%;# zbCl=QC*1gV{MX#Y$NlE~d3e!XPHO(^$-2?+BI5ReVqk{1@jaIx%P*&&ul@P8vt9m9 z+3(!#AQ!&>mko@i1G|h)3j(*M$3?qQHJbiW?=aL|Mo}b%y6e@uGAKZJ0KATPgg&e IbxsLQ07b6IJpcdz literal 0 HcmV?d00001 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`] = ` + +`;