From 628f708ab57668b951987afc6400aabc65507730 Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 30 Jan 2026 15:54:06 +0000 Subject: [PATCH] Add more menu stories and update tests --- .../default-auto.png | Bin 0 -> 3773 bytes .../RoomListItemMoreOptionsMenu.stories.tsx | 154 +++++++++ .../RoomListItemMoreOptionsMenu.test.tsx | 270 ++++++--------- ...tItemMoreOptionsMenu.stories.test.tsx.snap | 312 ++++++++++++++++++ .../RoomListItemMoreOptionsMenu.test.tsx.snap | 312 ++++++++++++++++++ 5 files changed, 882 insertions(+), 166 deletions(-) create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx/default-auto.png create mode 100644 packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx create mode 100644 packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.stories.test.tsx.snap create mode 100644 packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.test.tsx.snap diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx/default-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx/default-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..74d48285cc8a4a632d3d0353101fcad78544b17a GIT binary patch literal 3773 zcmeAS@N?(olHy`uVBq!ia0y~yU_QXWz;uCw2`F+wwC^zk17DG+i(^Q|oHw^^vqBg} z93GmVyplChqJ+IEVflid1rv0+lp=mG-+IBsBC71j64=8hD5}IJzQD}$vyt}+Ps^YC zULCr7;NhXUeUB}GW*#?|WngGX7QVp1Ai$Bqz`(&`!oa}7Bn7mTk(Ys?v4M@D!NGx< z!9igGBZGnfS<1AXzkk2~fA@F!{S{lc&akihSM~f}^ZnoTVxJGIpO>?(wPRp7@X6{V zYt_Ere-AI8tN%HF_V%>yo_`;U-~ah_>s3i<_V2$*yDO`=ZQN%DRBAM<=dpzgJA1qS z{hatc8w#I(TDmv>xAo_{H@~iaer{gn-*0zMr4)TWwv~~AVcHc>kYD_C{aa}0LFxwA zsA=RSv(a!H4L7QkSEC7OG$D;9BwDltMswC^&Kk{GqdAL`-o$7bLhmx9fQ5nK|NjXB j85@8dMmA9IlbIpyi0h$aZ=ZeyiZFP(`njxgN@xNAxv)O8 literal 0 HcmV?d00001 diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx new file mode 100644 index 0000000000..5b1ac48d4c --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.stories.tsx @@ -0,0 +1,154 @@ +/* + * 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 { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu"; +import { type RoomListItemSnapshot, type RoomListItemActions } from "./RoomListItem"; +import { useMockedViewModel } from "../../viewmodel"; +import { defaultSnapshot } from "./default-snapshot"; + +type MoreOptionsMenuProps = RoomListItemSnapshot & RoomListItemActions; + +// Wrapper component that creates a mocked ViewModel +const MoreOptionsMenuWrapper = ({ + onOpenRoom, + onMarkAsRead, + onMarkAsUnread, + onToggleFavorite, + onToggleLowPriority, + onInvite, + onCopyRoomLink, + onLeaveRoom, + onSetRoomNotifState, + ...rest +}: MoreOptionsMenuProps): JSX.Element => { + const vm = useMockedViewModel(rest, { + onOpenRoom, + onMarkAsRead, + onMarkAsUnread, + onToggleFavorite, + onToggleLowPriority, + onInvite, + onCopyRoomLink, + onLeaveRoom, + onSetRoomNotifState, + }); + return ; +}; + +const meta = { + title: "Room List/RoomListItem/MoreOptionsMenu", + component: MoreOptionsMenuWrapper, + tags: ["autodocs"], + decorators: [ + (Story) => ( +
+ +
+ ), + ], + args: { + ...defaultSnapshot, + showMoreOptionsMenu: true, + 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; + +// Closed state +export const Default: Story = { + args: { + canMarkAsRead: false, + canMarkAsUnread: true, + isFavourite: false, + isLowPriority: false, + }, +}; + +// Open state - default (can mark as unread, favourite off, low priority off) +export const Open: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "More Options" }); + await userEvent.click(trigger); + }, +}; + +// Open state - can mark as read (has unread messages) +export const OpenCanMarkAsRead: Story = { + args: { + canMarkAsRead: true, + canMarkAsUnread: false, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "More Options" }); + await userEvent.click(trigger); + }, +}; + +// Open state - favourite enabled +export const OpenFavouriteOn: Story = { + args: { + isFavourite: true, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "More Options" }); + await userEvent.click(trigger); + }, +}; + +// Open state - low priority enabled +export const OpenLowPriorityOn: Story = { + args: { + isLowPriority: true, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "More Options" }); + await userEvent.click(trigger); + }, +}; + +// Open state - without invite option (DM or no permission) +export const OpenWithoutInvite: Story = { + args: { + canInvite: false, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "More Options" }); + await userEvent.click(trigger); + }, +}; + +// Open state - without copy room link (DM room) +export const OpenWithoutCopyLink: Story = { + args: { + canCopyRoomLink: false, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const trigger = canvas.getByRole("button", { name: "More Options" }); + await userEvent.click(trigger); + }, +}; diff --git a/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx index 40b9917c5b..b077c388d4 100644 --- a/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx +++ b/packages/shared-components/src/room-list/RoomListItem/RoomListItemMoreOptionsMenu.test.tsx @@ -5,223 +5,161 @@ * 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 { RoomListItemMoreOptionsMenu } from "./RoomListItemMoreOptionsMenu"; -import { useMockedViewModel } from "../../viewmodel"; -import type { RoomListItemSnapshot } from "./RoomListItem"; -import { defaultSnapshot } from "./default-snapshot"; +import * as stories from "./RoomListItemMoreOptionsMenu.stories"; -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, Open, OpenCanMarkAsRead, OpenFavouriteOn, OpenLowPriorityOn, OpenWithoutInvite, OpenWithoutCopyLink } = + composeStories(stories); - const renderMenu = (overrides: Partial = {}): ReturnType => { - const TestComponent = (): JSX.Element => { - const vm = useMockedViewModel( - { - ...defaultSnapshot, - showMoreOptionsMenu: true, - showNotificationMenu: false, - ...overrides, - } as RoomListItemSnapshot, - mockCallbacks, - ); - return ; - }; - return render(); - }; - - it("should render the more options button", () => { - renderMenu(); - expect(screen.getByRole("button", { name: "More Options" })).toBeInTheDocument(); +describe(" stories", () => { + it("renders Default story (closed)", () => { + const { container } = render(); + expect(container).toMatchSnapshot(); }); - it("should open menu when clicked", async () => { - const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - expect(screen.getByRole("menu")).toBeInTheDocument(); + it("renders Open story", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); }); - it("should show mark as read option when canMarkAsRead is true", async () => { - const user = userEvent.setup(); - renderMenu({ canMarkAsRead: true }); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - expect(screen.getByRole("menuitem", { name: "Mark as read" })).toBeInTheDocument(); + it("renders OpenCanMarkAsRead story", async () => { + const { container } = render(); + await OpenCanMarkAsRead.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); }); - it("should not show mark as read option when canMarkAsRead is false", async () => { - const user = userEvent.setup(); - renderMenu({ canMarkAsRead: false }); + it("renders OpenFavouriteOn story", async () => { + const { container } = render(); + await OpenFavouriteOn.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); + it("renders OpenLowPriorityOn story", async () => { + const { container } = render(); + await OpenLowPriorityOn.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + it("renders OpenWithoutInvite story", async () => { + const { container } = render(); + await OpenWithoutInvite.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + + it("renders OpenWithoutCopyLink story", async () => { + const { container } = render(); + await OpenWithoutCopyLink.play?.({ canvasElement: container }); + expect(container).toMatchSnapshot(); + }); + + it("should show mark as unread by default", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); + + expect(screen.getByRole("menuitem", { name: "Mark as unread" })).toBeInTheDocument(); expect(screen.queryByRole("menuitem", { name: "Mark as read" })).not.toBeInTheDocument(); }); - it("should call onMarkAsRead when mark as read clicked", async () => { - const user = userEvent.setup(); - renderMenu({ canMarkAsRead: true }); + it("should show mark as read when canMarkAsRead is true", async () => { + const { container } = render(); + await OpenCanMarkAsRead.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - const markAsReadOption = screen.getByRole("menuitem", { name: "Mark as read" }); - await user.click(markAsReadOption); - - expect(mockCallbacks.onMarkAsRead).toHaveBeenCalled(); + expect(screen.getByRole("menuitem", { name: "Mark as read" })).toBeInTheDocument(); + expect(screen.queryByRole("menuitem", { name: "Mark as unread" })).not.toBeInTheDocument(); }); - it("should show mark as unread option when canMarkAsUnread is true", async () => { - const user = userEvent.setup(); - renderMenu({ canMarkAsUnread: true }); + it("should show favourite as checked when isFavourite is true", async () => { + const { container } = render(); + await OpenFavouriteOn.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - expect(screen.getByRole("menuitem", { name: "Mark as unread" })).toBeInTheDocument(); + const favouriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); + expect(favouriteOption).toHaveAttribute("aria-checked", "true"); }); - it("should call onMarkAsUnread when mark as unread clicked", async () => { - const user = userEvent.setup(); - renderMenu({ canMarkAsUnread: true }); + it("should show favourite as unchecked by default", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - const markAsUnreadOption = screen.getByRole("menuitem", { name: "Mark as unread" }); - await user.click(markAsUnreadOption); - - expect(mockCallbacks.onMarkAsUnread).toHaveBeenCalled(); + const favouriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); + expect(favouriteOption).toHaveAttribute("aria-checked", "false"); }); - it("should show favorite option and call onToggleFavorite", async () => { - const user = userEvent.setup(); - renderMenu({ isFavourite: false }); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - const favoriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); - expect(favoriteOption).toBeInTheDocument(); - expect(favoriteOption).toHaveAttribute("aria-checked", "false"); - - await user.click(favoriteOption); - expect(mockCallbacks.onToggleFavorite).toHaveBeenCalled(); - }); - - it("should show favorite as checked when isFavourite is true", async () => { - const user = userEvent.setup(); - renderMenu({ isFavourite: true }); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - const favoriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); - expect(favoriteOption).toHaveAttribute("aria-checked", "true"); - }); - - it("should show low priority option and call onToggleLowPriority", async () => { - const user = userEvent.setup(); - renderMenu({ isLowPriority: false }); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); + it("should show low priority as checked when isLowPriority is true", async () => { + const { container } = render(); + await OpenLowPriorityOn.play?.({ canvasElement: container }); const lowPriorityOption = screen.getByRole("menuitemcheckbox", { name: "Low priority" }); - expect(lowPriorityOption).toBeInTheDocument(); - expect(lowPriorityOption).toHaveAttribute("aria-checked", "false"); - - await user.click(lowPriorityOption); - expect(mockCallbacks.onToggleLowPriority).toHaveBeenCalled(); + expect(lowPriorityOption).toHaveAttribute("aria-checked", "true"); }); - it("should show invite option when canInvite is true", async () => { - const user = userEvent.setup(); - renderMenu({ canInvite: true }); + it("should show low priority as unchecked by default", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); + const lowPriorityOption = screen.getByRole("menuitemcheckbox", { name: "Low priority" }); + expect(lowPriorityOption).toHaveAttribute("aria-checked", "false"); + }); + + it("should show invite option by default", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); expect(screen.getByRole("menuitem", { name: "Invite" })).toBeInTheDocument(); }); - it("should call onInvite when invite clicked", async () => { - const user = userEvent.setup(); - renderMenu({ canInvite: true }); + it("should hide invite option when canInvite is false", async () => { + const { container } = render(); + await OpenWithoutInvite.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - const inviteOption = screen.getByRole("menuitem", { name: "Invite" }); - await user.click(inviteOption); - - expect(mockCallbacks.onInvite).toHaveBeenCalled(); + expect(screen.queryByRole("menuitem", { name: "Invite" })).not.toBeInTheDocument(); }); - it("should show copy link option when canCopyRoomLink is true", async () => { - const user = userEvent.setup(); - renderMenu({ canCopyRoomLink: true }); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); + it("should show copy room link by default", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); expect(screen.getByRole("menuitem", { name: "Copy room link" })).toBeInTheDocument(); }); - it("should call onCopyRoomLink when copy link clicked", async () => { - const user = userEvent.setup(); - renderMenu({ canCopyRoomLink: true }); + it("should hide copy room link when canCopyRoomLink is false", async () => { + const { container } = render(); + await OpenWithoutCopyLink.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); - - const copyLinkOption = screen.getByRole("menuitem", { name: "Copy room link" }); - await user.click(copyLinkOption); - - expect(mockCallbacks.onCopyRoomLink).toHaveBeenCalled(); + expect(screen.queryByRole("menuitem", { name: "Copy room link" })).not.toBeInTheDocument(); }); - it("should show leave room option", async () => { - const user = userEvent.setup(); - renderMenu(); - - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); + it("should always show leave room option", async () => { + const { container } = render(); + await Open.play?.({ canvasElement: container }); expect(screen.getByRole("menuitem", { name: "Leave room" })).toBeInTheDocument(); }); - it("should call onLeaveRoom when leave room clicked", async () => { + it("should call onToggleFavorite when favourite is clicked", async () => { const user = userEvent.setup(); - renderMenu(); + const { container } = render(); + await Open.play?.({ canvasElement: container }); - const button = screen.getByRole("button", { name: "More Options" }); - await user.click(button); + const favouriteOption = screen.getByRole("menuitemcheckbox", { name: "Favourited" }); + await user.click(favouriteOption); - const leaveRoomOption = screen.getByRole("menuitem", { name: "Leave room" }); - await user.click(leaveRoomOption); + expect(Open.args.onToggleFavorite).toHaveBeenCalled(); + }); - expect(mockCallbacks.onLeaveRoom).toHaveBeenCalled(); + it("should call onToggleLowPriority when low priority is clicked", async () => { + const user = userEvent.setup(); + const { container } = render(); + await Open.play?.({ canvasElement: container }); + + const lowPriorityOption = screen.getByRole("menuitemcheckbox", { name: "Low priority" }); + await user.click(lowPriorityOption); + + expect(Open.args.onToggleLowPriority).toHaveBeenCalled(); }); }); diff --git a/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.stories.test.tsx.snap b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.stories.test.tsx.snap new file mode 100644 index 0000000000..424ea36322 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.stories.test.tsx.snap @@ -0,0 +1,312 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` stories > renders Default story (closed) 1`] = ` +
+
+ +
+
+`; + +exports[` stories > renders Open story 1`] = ` + +`; + +exports[` stories > renders OpenCanMarkAsRead story 1`] = ` + +`; + +exports[` stories > renders OpenFavouriteOn story 1`] = ` + +`; + +exports[` stories > renders OpenLowPriorityOn story 1`] = ` + +`; + +exports[` stories > renders OpenWithoutCopyLink story 1`] = ` + +`; + +exports[` stories > renders OpenWithoutInvite story 1`] = ` + +`; diff --git a/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.test.tsx.snap b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.test.tsx.snap new file mode 100644 index 0000000000..424ea36322 --- /dev/null +++ b/packages/shared-components/src/room-list/RoomListItem/__snapshots__/RoomListItemMoreOptionsMenu.test.tsx.snap @@ -0,0 +1,312 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[` stories > renders Default story (closed) 1`] = ` +
+
+ +
+
+`; + +exports[` stories > renders Open story 1`] = ` + +`; + +exports[` stories > renders OpenCanMarkAsRead story 1`] = ` + +`; + +exports[` stories > renders OpenFavouriteOn story 1`] = ` + +`; + +exports[` stories > renders OpenLowPriorityOn story 1`] = ` + +`; + +exports[` stories > renders OpenWithoutCopyLink story 1`] = ` + +`; + +exports[` stories > renders OpenWithoutInvite story 1`] = ` + +`;