diff --git a/src/components/structures/RightPanel.tsx b/src/components/structures/RightPanel.tsx index a040786a69..94a5d34d60 100644 --- a/src/components/structures/RightPanel.tsx +++ b/src/components/structures/RightPanel.tsx @@ -15,7 +15,7 @@ import dis from "../../dispatcher/dispatcher"; import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases"; import RightPanelStore from "../../stores/right-panel/RightPanelStore"; import MatrixClientContext from "../../contexts/MatrixClientContext"; -import RoomSummaryCard from "../views/right_panel/RoomSummaryCard"; +import RoomSummaryCardView from "../views/right_panel/RoomSummaryCardView"; import WidgetCard from "../views/right_panel/WidgetCard"; import UserInfo from "../views/right_panel/UserInfo"; import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo"; @@ -255,7 +255,7 @@ export default class RightPanel extends React.Component { case RightPanelPhases.RoomSummary: if (!!this.props.room) { card = ( - ; + /** + * The callback when new value is entered in the search input + */ + onUpdateSearchInput: (e: React.KeyboardEvent) => void; + /** + * Callbacks to all the actions button in the right panel + */ + onRoomMembersClick: () => void; + onRoomThreadsClick: () => void; + onRoomFilesClick: () => void; + onRoomExtensionsClick: () => void; + onRoomPinsClick: () => void; + onRoomSettingsClick: (ev: Event) => void; + onLeaveRoomClick: () => void; + onShareRoomClick: () => void; + onRoomExportClick: () => Promise; + onRoomPollHistoryClick: () => void; + onReportRoomClick: () => Promise; + onFavoriteToggleClick: () => void; + onInviteToRoomClick: () => void; +} + +/** + * Hook to check if the room is a direct message or not + * @param room - The room to check + * @returns Whether the room is a direct message + */ +const useIsDirectMessage = (room: Room): boolean => { + const directRoomsList = useAccountData>(room.client, EventType.Direct); + const [isDirectMessage, setDirectMessage] = useState(false); + + useEffect(() => { + for (const [, dmRoomList] of Object.entries(directRoomsList)) { + if (dmRoomList.includes(room?.roomId ?? "")) { + setDirectMessage(true); + break; + } + } + }, [room, directRoomsList]); + + return isDirectMessage; +}; + +/** + * Hook to handle the search input in the right panel + * @param onSearchCancel - The callback when the search input is cancelled + * @returns The search input ref and the callback when the search input is updated + */ +const useSearchInput = ( + onSearchCancel?: () => void, +): { + searchInputRef: React.RefObject; + onUpdateSearchInput: (e: React.KeyboardEvent) => void; +} => { + const searchInputRef = useRef(null); + + const onUpdateSearchInput = (e: React.KeyboardEvent): void => { + if (searchInputRef.current && e.key === Key.ESCAPE) { + searchInputRef.current.value = ""; + onSearchCancel?.(); + } + }; + + // Focus the search field when the user clicks on the search button component + useDispatcher(defaultDispatcher, (payload) => { + if (payload.action === Action.FocusMessageSearch) { + searchInputRef.current?.focus(); + } + }); + + return { + searchInputRef, + onUpdateSearchInput, + }; +}; + +export function useRoomSummaryCardViewModel( + room: Room, + permalinkCreator: RoomPermalinkCreator, + onSearchCancel?: () => void, +): RoomSummaryCardState { + const cli = useMatrixClientContext(); + + const isRoomEncrypted = useIsEncrypted(cli, room) ?? false; + const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType"); + const e2eStatus = roomContext.e2eStatus; + const isVideoRoom = calcIsVideoRoom(room); + + const roomState = useRoomState(room); + // used to check if the room is public or not + const roomJoinRule = roomState.getJoinRule(); + const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; + const pinCount = usePinnedEvents(room).length; + // value to check if the user can invite to the room + const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room)); + + const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => + RoomListStore.instance.getTagsForRoom(room), + ); + const isFavorite = roomTags.includes(DefaultTagID.Favourite); + + const isDirectMessage = useIsDirectMessage(room); + + const onRoomMembersClick = (): void => { + RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true); + }; + + const onRoomThreadsClick = (): void => { + RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true); + }; + + const onRoomFilesClick = (): void => { + RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true); + }; + + const onRoomExtensionsClick = (): void => { + RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true); + }; + + const onRoomPinsClick = (): void => { + PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton"); + RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true); + }; + + const onRoomSettingsClick = (ev: Event): void => { + defaultDispatcher.dispatch({ action: "open_room_settings" }); + PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev); + }; + + const onShareRoomClick = (): void => { + Modal.createDialog(ShareDialog, { + target: room, + }); + }; + + const onRoomExportClick = async (): Promise => { + Modal.createDialog(ExportDialog, { + room, + }); + }; + + const onRoomPollHistoryClick = (): void => { + Modal.createDialog(PollHistoryDialog, { + room, + matrixClient: cli, + permalinkCreator, + }); + }; + + const onLeaveRoomClick = (): void => { + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: room.roomId, + }); + }; + + const onReportRoomClick = async (): Promise => { + const [leave] = await Modal.createDialog(ReportRoomDialog, { + roomId: room.roomId, + }).finished; + if (leave) { + defaultDispatcher.dispatch({ + action: "leave_room", + room_id: room.roomId, + }); + } + }; + + const onFavoriteToggleClick = (): void => { + tagRoom(room, DefaultTagID.Favourite); + }; + + const onInviteToRoomClick = (): void => { + inviteToRoom(room); + }; + + // Room Search element ref + const { searchInputRef, onUpdateSearchInput } = useSearchInput(onSearchCancel); + + return { + isDirectMessage, + isRoomEncrypted, + roomJoinRule, + e2eStatus, + isVideoRoom, + alias, + isFavorite, + canInviteToState, + searchInputRef, + pinCount, + onRoomMembersClick, + onRoomThreadsClick, + onRoomFilesClick, + onRoomExtensionsClick, + onRoomPinsClick, + onRoomSettingsClick, + onLeaveRoomClick, + onShareRoomClick, + onRoomExportClick, + onRoomPollHistoryClick, + onReportRoomClick, + onUpdateSearchInput, + onFavoriteToggleClick, + onInviteToRoomClick, + }; +} diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCardView.tsx similarity index 59% rename from src/components/views/right_panel/RoomSummaryCard.tsx rename to src/components/views/right_panel/RoomSummaryCardView.tsx index 19b04b6a27..d994e697c6 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCardView.tsx @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, useContext, useEffect, useRef, useState } from "react"; +import React, { useEffect, useState, type JSX } from "react"; import classNames from "classnames"; import { MenuItem, @@ -38,43 +38,19 @@ import PublicIcon from "@vector-im/compound-design-tokens/assets/web/icons/publi import ErrorIcon from "@vector-im/compound-design-tokens/assets/web/icons/error"; import ErrorSolidIcon from "@vector-im/compound-design-tokens/assets/web/icons/error-solid"; import ChevronDownIcon from "@vector-im/compound-design-tokens/assets/web/icons/chevron-down"; -import { EventType, JoinRule, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix"; +import { JoinRule, type Room } from "matrix-js-sdk/src/matrix"; -import MatrixClientContext from "../../../contexts/MatrixClientContext"; -import { useIsEncrypted } from "../../../hooks/useIsEncrypted"; -import BaseCard from "./BaseCard"; -import { _t } from "../../../languageHandler"; -import RoomAvatar from "../avatars/RoomAvatar"; -import defaultDispatcher from "../../../dispatcher/dispatcher"; -import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases"; -import Modal from "../../../Modal"; -import { ShareDialog } from "../dialogs/ShareDialog"; -import { useEventEmitterState } from "../../../hooks/useEventEmitter"; -import { E2EStatus } from "../../../utils/ShieldUtils"; -import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks"; -import RoomName from "../elements/RoomName"; -import ExportDialog from "../dialogs/ExportDialog"; -import RightPanelStore from "../../../stores/right-panel/RightPanelStore"; -import PosthogTrackers from "../../../PosthogTrackers"; -import { PollHistoryDialog } from "../dialogs/PollHistoryDialog"; -import { Flex } from "../../utils/Flex"; -import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore"; -import { DefaultTagID } from "../../../stores/room-list/models"; -import { tagRoom } from "../../../utils/room/tagRoom"; -import { canInviteTo } from "../../../utils/room/canInviteTo"; -import { inviteToRoom } from "../../../utils/room/inviteToRoom"; -import { useAccountData } from "../../../hooks/useAccountData"; -import { useRoomState } from "../../../hooks/useRoomState"; -import { Linkify, topicToHtml } from "../../../HtmlUtils"; -import { Box } from "../../utils/Box"; -import { useDispatcher } from "../../../hooks/useDispatcher"; -import { Action } from "../../../dispatcher/actions"; -import { Key } from "../../../Keyboard"; -import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms"; -import { usePinnedEvents } from "../../../hooks/usePinnedEvents"; +import BaseCard from "./BaseCard.tsx"; +import { _t } from "../../../languageHandler.tsx"; +import RoomAvatar from "../avatars/RoomAvatar.tsx"; +import { E2EStatus } from "../../../utils/ShieldUtils.ts"; +import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts"; +import RoomName from "../elements/RoomName.tsx"; +import { Flex } from "../../utils/Flex.tsx"; +import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx"; +import { Box } from "../../utils/Box.tsx"; import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx"; -import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx"; -import { ReportRoomDialog } from "../dialogs/ReportRoomDialog.tsx"; +import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx"; import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx"; interface IProps { @@ -86,32 +62,6 @@ interface IProps { searchTerm?: string; } -const onRoomMembersClick = (): void => { - RightPanelStore.instance.pushCard({ phase: RightPanelPhases.MemberList }, true); -}; - -const onRoomThreadsClick = (): void => { - RightPanelStore.instance.pushCard({ phase: RightPanelPhases.ThreadPanel }, true); -}; - -const onRoomFilesClick = (): void => { - RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true); -}; - -const onRoomExtensionsClick = (): void => { - RightPanelStore.instance.pushCard({ phase: RightPanelPhases.Extensions }, true); -}; - -const onRoomPinsClick = (): void => { - PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton"); - RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true); -}; - -const onRoomSettingsClick = (ev: Event): void => { - defaultDispatcher.dispatch({ action: "open_room_settings" }); - PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev); -}; - const RoomTopic: React.FC> = ({ room }): JSX.Element | null => { const vm = useRoomTopicViewModel(room); @@ -142,6 +92,7 @@ const RoomTopic: React.FC> = ({ room }): JSX.Element | null } const content = vm.expanded ? {body} : body; + return ( > = ({ room }): JSX.Element | null ); }; -const RoomSummaryCard: React.FC = ({ +const RoomSummaryCardView: React.FC = ({ room, permalinkCreator, onSearchChange, @@ -181,69 +132,7 @@ const RoomSummaryCard: React.FC = ({ focusRoomSearch, searchTerm = "", }) => { - const cli = useContext(MatrixClientContext); - - const onShareRoomClick = (): void => { - Modal.createDialog(ShareDialog, { - target: room, - }); - }; - - const onRoomExportClick = async (): Promise => { - Modal.createDialog(ExportDialog, { - room, - }); - }; - - const onRoomPollHistoryClick = (): void => { - Modal.createDialog(PollHistoryDialog, { - room, - matrixClient: cli, - permalinkCreator, - }); - }; - - const onLeaveRoomClick = (): void => { - defaultDispatcher.dispatch({ - action: "leave_room", - room_id: room.roomId, - }); - }; - const onReportRoomClick = async (): Promise => { - const [leave] = await Modal.createDialog(ReportRoomDialog, { - roomId: room.roomId, - }).finished; - if (leave) { - defaultDispatcher.dispatch({ - action: "leave_room", - room_id: room.roomId, - }); - } - }; - - const isRoomEncrypted = useIsEncrypted(cli, room); - const roomContext = useScopedRoomContext("e2eStatus", "timelineRenderingType"); - const e2eStatus = roomContext.e2eStatus; - const isVideoRoom = calcIsVideoRoom(room); - - const roomState = useRoomState(room); - const directRoomsList = useAccountData>(room.client, EventType.Direct); - const [isDirectMessage, setDirectMessage] = useState(false); - useEffect(() => { - for (const [, dmRoomList] of Object.entries(directRoomsList)) { - if (dmRoomList.includes(room?.roomId ?? "")) { - setDirectMessage(true); - break; - } - } - }, [room, directRoomsList]); - - const searchInputRef = useRef(null); - useDispatcher(defaultDispatcher, (payload) => { - if (payload.action === Action.FocusMessageSearch) { - searchInputRef.current?.focus(); - } - }); + const vm = useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel); // The search field is controlled and onSearchChange is debounced in RoomView, // so we need to set the value of the input right away @@ -252,7 +141,6 @@ const RoomSummaryCard: React.FC = ({ setSearchValue(searchTerm); }, [searchTerm]); - const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || ""; const roomInfo = (
@@ -274,34 +162,34 @@ const RoomSummaryCard: React.FC = ({ size="sm" weight="semibold" className="mx_RoomSummaryCard_alias text-secondary" - title={alias} + title={vm.alias} > - {alias} + {vm.alias} - {!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && ( + {!vm.isDirectMessage && vm.roomJoinRule === JoinRule.Public && ( {_t("common|public_room")} )} - {isRoomEncrypted && e2eStatus !== E2EStatus.Warning && ( + {vm.isRoomEncrypted && vm.e2eStatus !== E2EStatus.Warning && ( {_t("common|encrypted")} )} - {!e2eStatus && ( + {!vm.isRoomEncrypted && ( {_t("common|unencrypted")} )} - {e2eStatus === E2EStatus.Warning && ( + {vm.e2eStatus === E2EStatus.Warning && ( {_t("common|not_trusted")} @@ -313,14 +201,6 @@ const RoomSummaryCard: React.FC = ({
); - const pinCount = usePinnedEvents(room).length; - - const roomTags = useEventEmitterState(RoomListStore.instance, LISTS_UPDATE_EVENT, () => - RoomListStore.instance.getTagsForRoom(room), - ); - const canInviteToState = useEventEmitterState(room, RoomStateEvent.Update, () => canInviteTo(room)); - const isFavorite = roomTags.includes(DefaultTagID.Favourite); - const header = onSearchChange && ( e.preventDefault()}> = ({ }} value={searchValue} className="mx_no_textinput" - ref={searchInputRef} + ref={vm.searchInputRef} autoFocus={focusRoomSearch} - onKeyDown={(e) => { - if (searchInputRef.current && e.key === Key.ESCAPE) { - searchInputRef.current.value = ""; - onSearchCancel?.(); - } - }} + onKeyDown={vm.onUpdateSearchInput} /> ); @@ -360,21 +235,21 @@ const RoomSummaryCard: React.FC = ({ tagRoom(room, DefaultTagID.Favourite)} + checked={vm.isFavorite} + onSelect={vm.onFavoriteToggleClick} /> inviteToRoom(room)} + disabled={!vm.canInviteToState} + onSelect={vm.onInviteToRoomClick} /> - - - {!isVideoRoom && ( + + + {!vm.isVideoRoom && ( <> = ({ - {pinCount} + {vm.pinCount} - + )} - + - {!isVideoRoom && ( + {!vm.isVideoRoom && ( <> )} - +
@@ -431,14 +310,14 @@ const RoomSummaryCard: React.FC = ({ Icon={ErrorIcon} kind="critical" label={_t("action|report_room")} - onSelect={onReportRoomClick} + onSelect={vm.onReportRoomClick} />
@@ -446,4 +325,4 @@ const RoomSummaryCard: React.FC = ({ ); }; -export default RoomSummaryCard; +export default RoomSummaryCardView; diff --git a/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardViewModel-test.tsx b/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardViewModel-test.tsx new file mode 100644 index 0000000000..0c5745a086 --- /dev/null +++ b/test/unit-tests/components/viewmodels/right_panel/RoomSummaryCardViewModel-test.tsx @@ -0,0 +1,265 @@ +/* + * Copyright 2025 New Vector 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 { act, renderHook, waitFor } from "jest-matrix-react"; +import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix"; + +import { useRoomSummaryCardViewModel } from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel"; +import { mkStubRoom, stubClient, withClientContextRenderOptions } from "../../../../test-utils"; +import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; +import RoomListStore from "../../../../../src/stores/room-list/RoomListStore"; +import { DefaultTagID } from "../../../../../src/stores/room-list/models"; +import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore"; +import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases"; +import Modal from "../../../../../src/Modal"; +import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog"; +import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog"; +import { PollHistoryDialog } from "../../../../../src/components/views/dialogs/PollHistoryDialog"; +import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/ReportRoomDialog"; +import { inviteToRoom } from "../../../../../src/utils/room/inviteToRoom"; +import DMRoomMap from "../../../../../src/utils/DMRoomMap"; +import * as hooks from "../../../../../src/hooks/useAccountData"; + +jest.mock("../../../../../src/utils/room/inviteToRoom", () => ({ + inviteToRoom: jest.fn(), +})); + +describe("useRoomSummaryCardViewModel", () => { + let matrixClient: MatrixClient; + let room: Room; + let permalinkCreator: any; + const onSearchCancel = jest.fn(); + + beforeEach(() => { + matrixClient = stubClient(); + room = mkStubRoom("roomId", "roomName", matrixClient); + permalinkCreator = {}; + + DMRoomMap.setShared({ + getUserIdForRoomId: jest.fn(), + } as unknown as DMRoomMap); + + jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([]); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + function render() { + return renderHook( + () => useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel), + withClientContextRenderOptions(matrixClient), + ); + } + + it("should return correct initial state", () => { + const { result } = render(); + + expect(result.current.isDirectMessage).toBe(false); + expect(result.current.isRoomEncrypted).toBe(false); + expect(result.current.isVideoRoom).toBe(false); + expect(result.current.isFavorite).toBe(false); + expect(result.current.pinCount).toBe(0); + expect(result.current.searchInputRef.current).toBe(null); + }); + + it("should handle room members click", () => { + const spy = jest.spyOn(RightPanelStore.instance, "pushCard"); + const { result } = render(); + + result.current.onRoomMembersClick(); + expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList }, true); + }); + + it("should handle room settings click", () => { + const spy = jest.spyOn(defaultDispatcher, "dispatch"); + const { result } = render(); + + result.current.onRoomSettingsClick(new Event("click")); + expect(spy).toHaveBeenCalledWith({ action: "open_room_settings" }); + }); + + it("should handle leave room click", () => { + const spy = jest.spyOn(defaultDispatcher, "dispatch"); + const { result } = render(); + + result.current.onLeaveRoomClick(); + expect(spy).toHaveBeenCalledWith({ + action: "leave_room", + room_id: room.roomId, + }); + }); + + it("should handle room threads click", () => { + const spy = jest.spyOn(RightPanelStore.instance, "pushCard"); + const { result } = render(); + + result.current.onRoomThreadsClick(); + expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel }, true); + }); + + it("should handle room files click", () => { + const spy = jest.spyOn(RightPanelStore.instance, "pushCard"); + const { result } = render(); + + result.current.onRoomFilesClick(); + expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true); + }); + + it("should handle room extensions click", () => { + const spy = jest.spyOn(RightPanelStore.instance, "pushCard"); + const { result } = render(); + + result.current.onRoomExtensionsClick(); + expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.Extensions }, true); + }); + + it("should handle room pins click", () => { + const spy = jest.spyOn(RightPanelStore.instance, "pushCard"); + const { result } = render(); + + result.current.onRoomPinsClick(); + expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.PinnedMessages }, true); + }); + + it("should handle room invite click", () => { + const { result } = render(); + + result.current.onInviteToRoomClick(); + expect(inviteToRoom).toHaveBeenCalledWith(room); + }); + + describe("action that trigger a dialog", () => { + let createDialogSpy: jest.SpyInstance; + + beforeEach(() => { + createDialogSpy = jest.spyOn(Modal, "createDialog").mockImplementation(() => ({ + finished: Promise.resolve([false]), + close: jest.fn(), + })); + }); + + afterEach(() => { + createDialogSpy.mockRestore(); + }); + + it("should handle room export click", async () => { + const { result } = render(); + + await act(async () => { + await result.current.onRoomExportClick(); + }); + expect(createDialogSpy).toHaveBeenCalledWith(ExportDialog, { room }); + }); + + it("should handle room poll history click", async () => { + const { result } = render(); + + await act(async () => { + await result.current.onRoomPollHistoryClick(); + }); + expect(createDialogSpy).toHaveBeenCalledWith(PollHistoryDialog, { + room, + matrixClient, + permalinkCreator, + }); + }); + + it("should handle room report click", async () => { + const { result } = render(); + + await act(async () => { + await result.current.onReportRoomClick(); + }); + + expect(createDialogSpy).toHaveBeenCalledWith(ReportRoomDialog, { roomId: room.roomId }); + }); + + it("should handle share room click", async () => { + const { result } = render(); + + await act(async () => { + await result.current.onShareRoomClick(); + }); + + expect(createDialogSpy).toHaveBeenCalledWith(ShareDialog, { + target: room, + }); + }); + }); + + describe("favorite room state", () => { + it("should identify favorite rooms", () => { + jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([DefaultTagID.Favourite]); + const { result } = render(); + + expect(result.current.isFavorite).toBe(true); + }); + + it("should identify non-favorite rooms", () => { + jest.spyOn(RoomListStore.instance, "getTagsForRoom").mockReturnValue([]); + const { result } = render(); + + expect(result.current.isFavorite).toBe(false); + }); + }); + + describe("direct message state", () => { + it("should identify direct message rooms", async () => { + // Mock the direct rooms account data + const directRoomsList = { + "@user:domain.com": [room.roomId], + }; + // Mock the useAccountData hook result + jest.spyOn(hooks, "useAccountData").mockReturnValue(directRoomsList); + + const { result } = render(); + + await waitFor(() => { + expect(result.current.isDirectMessage).toBe(true); + }); + }); + + it("should identify non-direct message rooms", async () => { + // Mock the direct rooms account data + const directRoomsList = {}; + // Mock the useAccountData hook result + jest.spyOn(hooks, "useAccountData").mockReturnValue(directRoomsList); + + const { result } = render(); + + await waitFor(() => { + expect(result.current.isDirectMessage).toBe(false); + }); + }); + }); + + describe("search input", () => { + it("should handle search input escape key", () => { + const directRoomsList = {}; + jest.spyOn(hooks, "useAccountData").mockReturnValue(directRoomsList); + const { result } = render(); + // Create a mock input element and set it as the current ref value + const mockInputElement = document.createElement("input"); + mockInputElement.value = "some search text"; + + result.current.searchInputRef.current = mockInputElement; + + const event = { + key: "Escape", + preventDefault: jest.fn(), + stopPropagation: jest.fn(), + }; + + result.current.onUpdateSearchInput(event as any); + + expect(onSearchCancel).toHaveBeenCalled(); + expect(mockInputElement?.value).toBe(""); + }); + }); +}); diff --git a/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx b/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx deleted file mode 100644 index 082b2f554e..0000000000 --- a/test/unit-tests/components/views/right_panel/RoomSummaryCard-test.tsx +++ /dev/null @@ -1,394 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2023 The Matrix.org Foundation C.I.C. - -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 from "react"; -import { render, fireEvent, screen, waitFor } from "jest-matrix-react"; -import { EventType, MatrixEvent, Room, type MatrixClient, JoinRule } from "matrix-js-sdk/src/matrix"; -import { KnownMembership } from "matrix-js-sdk/src/types"; -import { mocked, type MockedObject } from "jest-mock"; -import userEvent from "@testing-library/user-event"; - -import DMRoomMap from "../../../../../src/utils/DMRoomMap"; -import RoomSummaryCard from "../../../../../src/components/views/right_panel/RoomSummaryCard"; -import { ShareDialog } from "../../../../../src/components/views/dialogs/ShareDialog"; -import ExportDialog from "../../../../../src/components/views/dialogs/ExportDialog"; -import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; -import defaultDispatcher from "../../../../../src/dispatcher/dispatcher"; -import * as settingsHooks from "../../../../../src/hooks/useSettings"; -import Modal from "../../../../../src/Modal"; -import RightPanelStore from "../../../../../src/stores/right-panel/RightPanelStore"; -import { RightPanelPhases } from "../../../../../src/stores/right-panel/RightPanelStorePhases"; -import { flushPromises, stubClient, untilDispatch } from "../../../../test-utils"; -import { PollHistoryDialog } from "../../../../../src/components/views/dialogs/PollHistoryDialog"; -import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks"; -import { _t } from "../../../../../src/languageHandler"; -import { tagRoom } from "../../../../../src/utils/room/tagRoom"; -import { DefaultTagID } from "../../../../../src/stores/room-list/models"; -import { Action } from "../../../../../src/dispatcher/actions"; -import { ReportRoomDialog } from "../../../../../src/components/views/dialogs/ReportRoomDialog.tsx"; -import SettingsStore from "../../../../../src/settings/SettingsStore.ts"; -import { SettingLevel } from "../../../../../src/settings/SettingLevel.ts"; - -jest.mock("../../../../../src/utils/room/tagRoom"); - -describe("", () => { - const userId = "@alice:domain.org"; - - const roomId = "!room:domain.org"; - let mockClient!: MockedObject; - let room!: Room; - - const getComponent = (props = {}) => { - const defaultProps = { - room, - onClose: jest.fn(), - permalinkCreator: new RoomPermalinkCreator(room), - }; - - return render(, { - wrapper: ({ children }) => ( - {children} - ), - }); - }; - - beforeEach(() => { - mockClient = mocked(stubClient()); - room = new Room(roomId, mockClient, userId); - const roomCreateEvent = new MatrixEvent({ - type: "m.room.create", - room_id: roomId, - sender: userId, - content: { - creator: userId, - room_version: "5", - }, - state_key: "", - }); - room.currentState.setStateEvents([roomCreateEvent]); - room.updateMyMembership(KnownMembership.Join); - - jest.spyOn(Modal, "createDialog"); - jest.spyOn(RightPanelStore.instance, "pushCard"); - jest.spyOn(settingsHooks, "useFeatureEnabled").mockReturnValue(false); - jest.spyOn(defaultDispatcher, "dispatch"); - jest.clearAllMocks(); - DMRoomMap.makeShared(mockClient); - - SettingsStore.setValue("releaseAnnouncementData", null, SettingLevel.DEVICE, { pinningMessageList: true }); - - mockClient.getRoom.mockReturnValue(room); - jest.spyOn(room, "isElementVideoRoom").mockRestore(); - jest.spyOn(room, "isCallRoom").mockRestore(); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it("renders the room summary", () => { - const { container } = getComponent(); - expect(container).toMatchSnapshot(); - }); - - it("renders the room topic in the summary", () => { - room.currentState.setStateEvents([ - new MatrixEvent({ - type: "m.room.topic", - room_id: roomId, - sender: userId, - content: { - topic: "This is the room's topic.", - }, - state_key: "", - }), - ]); - const { container } = getComponent(); - expect(container).toMatchSnapshot(); - }); - - it("has button to edit topic", () => { - room.currentState.setStateEvents([ - new MatrixEvent({ - type: "m.room.topic", - room_id: roomId, - sender: userId, - content: { - topic: "This is the room's topic.", - }, - state_key: "", - }), - ]); - const { container, getByText } = getComponent(); - expect(getByText("Edit")).toBeInTheDocument(); - expect(container).toMatchSnapshot(); - }); - - describe("search", () => { - it("has the search field", async () => { - const onSearchChange = jest.fn(); - const { getByPlaceholderText } = getComponent({ - onSearchChange, - }); - expect(getByPlaceholderText("Search messages…")).toBeVisible(); - }); - - it("should focus the search field if Action.FocusMessageSearch is fired", async () => { - const onSearchChange = jest.fn(); - const { getByPlaceholderText } = getComponent({ - onSearchChange, - }); - expect(getByPlaceholderText("Search messages…")).not.toHaveFocus(); - defaultDispatcher.fire(Action.FocusMessageSearch); - await waitFor(() => { - expect(getByPlaceholderText("Search messages…")).toHaveFocus(); - }); - }); - - it("should focus the search field if focusRoomSearch=true", () => { - const onSearchChange = jest.fn(); - const { getByPlaceholderText } = getComponent({ - onSearchChange, - focusRoomSearch: true, - }); - expect(getByPlaceholderText("Search messages…")).toHaveFocus(); - }); - - it("should cancel search on escape", () => { - const onSearchChange = jest.fn(); - const onSearchCancel = jest.fn(); - const { getByPlaceholderText } = getComponent({ - onSearchChange, - onSearchCancel, - focusRoomSearch: true, - }); - expect(getByPlaceholderText("Search messages…")).toHaveFocus(); - fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" }); - expect(onSearchCancel).toHaveBeenCalled(); - }); - it("should update the search field value correctly", async () => { - const user = userEvent.setup(); - - const onSearchChange = jest.fn(); - const { getByPlaceholderText } = getComponent({ - onSearchChange, - }); - - const searchInput = getByPlaceholderText("Search messages…"); - await user.type(searchInput, "test query"); - - expect(onSearchChange).toHaveBeenCalledWith("test query"); - expect(searchInput).toHaveValue("test query"); - }); - }); - - it("opens room file panel on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText("Files")); - - expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.FilePanel }, true); - }); - - it("opens room export dialog on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("export_chat|title"))); - - expect(Modal.createDialog).toHaveBeenCalledWith(ExportDialog, { room }); - }); - - it("opens share room dialog on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("action|copy_link"))); - - expect(Modal.createDialog).toHaveBeenCalledWith(ShareDialog, { target: room }); - }); - - it("opens invite dialog on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("action|invite"))); - - expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "view_invite", roomId: room.roomId }); - }); - - it("fires favourite dispatch on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("room|context_menu|favourite"))); - - expect(tagRoom).toHaveBeenCalledWith(room, DefaultTagID.Favourite); - }); - - it("opens room settings on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("common|settings"))); - - expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: "open_room_settings" }); - }); - - it("opens room member list on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText("People")); - - expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList }, true); - }); - - it("opens room threads list on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText("Threads")); - - expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith({ phase: RightPanelPhases.ThreadPanel }, true); - }); - - it("opens room pinned messages on button click", () => { - const { getByText } = getComponent(); - - fireEvent.click(getByText("Pinned messages")); - - expect(RightPanelStore.instance.pushCard).toHaveBeenCalledWith( - { phase: RightPanelPhases.PinnedMessages }, - true, - ); - }); - - it("dispatches leave room on button click", async () => { - jest.spyOn(Modal, "createDialog").mockReturnValueOnce({ - finished: Promise.resolve([true]), - close: () => {}, - }); - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("room_list|more_options|leave_room"))); - await untilDispatch("leave_room", defaultDispatcher); - expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ - action: "leave_room", - room_id: room.roomId, - }); - }); - - it("opens report dialog on button click", async () => { - jest.spyOn(Modal, "createDialog").mockReturnValueOnce({ - finished: Promise.resolve([true]), - close: () => {}, - }); - const { getByText } = getComponent(); - - fireEvent.click(getByText(_t("action|report_room"))); - expect(Modal.createDialog).toHaveBeenCalledWith(ReportRoomDialog, { roomId: room.roomId }); - await untilDispatch("leave_room", defaultDispatcher); - expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ - action: "leave_room", - room_id: room.roomId, - }); - }); - - describe("pinning", () => { - it("renders pins options", () => { - const { getByText } = getComponent(); - - expect(getByText("Pinned messages")).toBeInTheDocument(); - }); - }); - - describe("poll history", () => { - it("renders poll history option", () => { - const { getByText } = getComponent(); - - expect(getByText("Polls")).toBeInTheDocument(); - }); - - it("opens poll history dialog on button click", () => { - const permalinkCreator = new RoomPermalinkCreator(room); - const { getByText } = getComponent({ permalinkCreator }); - - fireEvent.click(getByText("Polls")); - - expect(Modal.createDialog).toHaveBeenCalledWith(PollHistoryDialog, { - room, - matrixClient: mockClient, - permalinkCreator: permalinkCreator, - }); - }); - }); - - describe("video rooms", () => { - it("does not render irrelevant options for element video room", () => { - jest.spyOn(room, "isElementVideoRoom").mockReturnValue(true); - mocked(settingsHooks.useFeatureEnabled).mockImplementation((feature) => feature === "feature_video_rooms"); - const { queryByText } = getComponent(); - - // options not rendered - expect(queryByText("Files")).not.toBeInTheDocument(); - expect(queryByText("Pinned")).not.toBeInTheDocument(); - expect(queryByText("Export chat")).not.toBeInTheDocument(); - }); - - it("does not render irrelevant options for element call room", () => { - jest.spyOn(room, "isCallRoom").mockReturnValue(true); - mocked(settingsHooks.useFeatureEnabled).mockImplementation( - (feature) => feature === "feature_element_call_video_rooms" || feature === "feature_video_rooms", - ); - const { queryByText } = getComponent(); - - // options not rendered - expect(queryByText("Files")).not.toBeInTheDocument(); - expect(queryByText("Pinned")).not.toBeInTheDocument(); - expect(queryByText("Export chat")).not.toBeInTheDocument(); - }); - }); - - describe("public room label", () => { - beforeEach(() => { - jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Public); - }); - - it("does not show public room label for a DM", async () => { - mockClient.getAccountData.mockImplementation((eventType) => { - if (eventType === EventType.Direct) { - return new MatrixEvent({ - type: EventType.Direct, - content: { - "@bob:sesame.st": ["some-room-id"], - // this room is a DM with ernie - "@ernie:sesame.st": ["some-other-room-id", room.roomId], - }, - }); - } - }); - getComponent(); - - await flushPromises(); - - expect(screen.queryByText("Public room")).not.toBeInTheDocument(); - }); - - it("does not show public room label for non public room", async () => { - jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Invite); - getComponent(); - - await flushPromises(); - - expect(screen.queryByText("Public room")).not.toBeInTheDocument(); - }); - - it("shows a public room label for a public room", async () => { - jest.spyOn(room.currentState, "getJoinRule").mockReturnValue(JoinRule.Public); - getComponent(); - - await flushPromises(); - - expect(screen.queryByText("Public room")).toBeInTheDocument(); - }); - }); -}); diff --git a/test/unit-tests/components/views/right_panel/RoomSummaryCardView-test.tsx b/test/unit-tests/components/views/right_panel/RoomSummaryCardView-test.tsx new file mode 100644 index 0000000000..0e5c7efffe --- /dev/null +++ b/test/unit-tests/components/views/right_panel/RoomSummaryCardView-test.tsx @@ -0,0 +1,324 @@ +/* +Copyright 2024 New Vector Ltd. +Copyright 2023 The Matrix.org Foundation C.I.C. + +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 from "react"; +import { render, fireEvent, screen } from "jest-matrix-react"; +import { Room, type MatrixClient, JoinRule, MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { mocked, type MockedObject } from "jest-mock"; +import userEvent from "@testing-library/user-event"; + +import RoomSummaryCardView from "../../../../../src/components/views/right_panel/RoomSummaryCardView"; +import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext"; +import { flushPromises, stubClient } from "../../../../test-utils"; +import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks"; +import { _t } from "../../../../../src/languageHandler"; +import { + type RoomSummaryCardState, + useRoomSummaryCardViewModel, +} from "../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel"; +import DMRoomMap from "../../../../../src/utils/DMRoomMap"; + +// Mock the viewmodel hooks +jest.mock("../../../../../src/components/viewmodels/right_panel/RoomSummaryCardViewModel", () => ({ + useRoomSummaryCardViewModel: jest.fn(), +})); + +describe("", () => { + const userId = "@alice:domain.org"; + + const roomId = "!room:domain.org"; + let mockClient!: MockedObject; + let room!: Room; + + const getComponent = (props = {}) => { + const defaultProps = { + room, + onClose: jest.fn(), + permalinkCreator: new RoomPermalinkCreator(room), + }; + + return render(, { + wrapper: ({ children }) => ( + {children} + ), + }); + }; + + // Setup mock view models + const vmDefaultValues: RoomSummaryCardState = { + isDirectMessage: false, + isRoomEncrypted: false, + e2eStatus: undefined, + isVideoRoom: false, + roomJoinRule: JoinRule.Public, + alias: "", + isFavorite: false, + canInviteToState: true, + pinCount: 0, + searchInputRef: { current: null }, + onUpdateSearchInput: jest.fn(), + onRoomMembersClick: jest.fn(), + onRoomThreadsClick: jest.fn(), + onRoomFilesClick: jest.fn(), + onRoomExtensionsClick: jest.fn(), + onRoomPinsClick: jest.fn(), + onRoomSettingsClick: jest.fn(), + onLeaveRoomClick: jest.fn(), + onShareRoomClick: jest.fn(), + onRoomExportClick: jest.fn(), + onRoomPollHistoryClick: jest.fn(), + onReportRoomClick: jest.fn(), + onFavoriteToggleClick: jest.fn(), + onInviteToRoomClick: jest.fn(), + }; + + beforeEach(() => { + mockClient = mocked(stubClient()); + room = new Room(roomId, mockClient, userId); + mocked(useRoomSummaryCardViewModel).mockReturnValue(vmDefaultValues); + DMRoomMap.makeShared(mockClient); + + mockClient.getRoom.mockReturnValue(room); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("renders the room summary", () => { + const { container } = getComponent(); + expect(container).toMatchSnapshot(); + }); + + it("renders the room topic in the summary", () => { + room.currentState.setStateEvents([ + new MatrixEvent({ + type: "m.room.topic", + room_id: roomId, + sender: userId, + content: { + topic: "This is the room's topic.", + }, + state_key: "", + }), + ]); + const { container } = getComponent(); + expect(container).toMatchSnapshot(); + }); + + it("has button to edit topic", () => { + room.currentState.setStateEvents([ + new MatrixEvent({ + type: "m.room.topic", + room_id: roomId, + sender: userId, + content: { + topic: "This is the room's topic.", + }, + state_key: "", + }), + ]); + const { container, getByText } = getComponent(); + expect(getByText("Edit")).toBeInTheDocument(); + expect(container).toMatchSnapshot(); + }); + + describe("search", () => { + it("has the search field", async () => { + const onSearchChange = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + }); + expect(getByPlaceholderText("Search messages…")).toBeVisible(); + }); + + it("should focus the search field if focusRoomSearch=true", () => { + const onSearchChange = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + focusRoomSearch: true, + }); + expect(getByPlaceholderText("Search messages…")).toHaveFocus(); + }); + + it("should cancel search on escape", () => { + const onSearchChange = jest.fn(); + const onSearchCancel = jest.fn(); + + const { getByPlaceholderText } = getComponent({ + onSearchChange, + onSearchCancel, + focusRoomSearch: true, + }); + expect(getByPlaceholderText("Search messages…")).toHaveFocus(); + fireEvent.keyDown(getByPlaceholderText("Search messages…"), { key: "Escape" }); + expect(vmDefaultValues.onUpdateSearchInput).toHaveBeenCalled(); + }); + + it("should update the search field value correctly", async () => { + const user = userEvent.setup(); + + const onSearchChange = jest.fn(); + const { getByPlaceholderText } = getComponent({ + onSearchChange, + }); + + const searchInput = getByPlaceholderText("Search messages…"); + await user.type(searchInput, "test query"); + + expect(onSearchChange).toHaveBeenCalledWith("test query"); + expect(searchInput).toHaveValue("test query"); + }); + }); + + it("opens room file panel on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText("Files")); + + expect(vmDefaultValues.onRoomFilesClick).toHaveBeenCalled(); + }); + + it("opens room export dialog on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText(_t("export_chat|title"))); + + expect(vmDefaultValues.onRoomExportClick).toHaveBeenCalled(); + }); + + it("opens share room dialog on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText(_t("action|copy_link"))); + + expect(vmDefaultValues.onShareRoomClick).toHaveBeenCalled(); + }); + + it("opens invite dialog on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText(_t("action|invite"))); + + expect(vmDefaultValues.onInviteToRoomClick).toHaveBeenCalled(); + }); + + it("fires favourite dispatch on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText(_t("room|context_menu|favourite"))); + + expect(vmDefaultValues.onFavoriteToggleClick).toHaveBeenCalled(); + }); + + it("opens room settings on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText(_t("common|settings"))); + + expect(vmDefaultValues.onRoomSettingsClick).toHaveBeenCalled(); + }); + + it("opens room member list on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText("People")); + + expect(vmDefaultValues.onRoomMembersClick).toHaveBeenCalled(); + }); + + it("opens room threads list on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText("Threads")); + + expect(vmDefaultValues.onRoomThreadsClick).toHaveBeenCalled(); + }); + + it("opens room pinned messages on button click", () => { + const { getByText } = getComponent(); + + fireEvent.click(getByText("Pinned messages")); + + expect(vmDefaultValues.onRoomPinsClick).toHaveBeenCalled(); + }); + + it("does not render irrelevant options if video room", () => { + mocked(useRoomSummaryCardViewModel).mockReturnValue({ + ...vmDefaultValues, + isVideoRoom: true, + }); + const { queryByText } = getComponent(); + + // options not rendered + expect(queryByText("Files")).not.toBeInTheDocument(); + expect(queryByText("Pinned")).not.toBeInTheDocument(); + expect(queryByText("Export chat")).not.toBeInTheDocument(); + }); + + describe("pinning", () => { + it("renders pins options", () => { + const { getByText } = getComponent(); + + expect(getByText("Pinned messages")).toBeInTheDocument(); + }); + }); + + describe("poll history", () => { + it("renders poll history option", () => { + const { getByText } = getComponent(); + + expect(getByText("Polls")).toBeInTheDocument(); + }); + + it("opens poll history dialog on button click", () => { + const permalinkCreator = new RoomPermalinkCreator(room); + const { getByText } = getComponent({ permalinkCreator }); + + fireEvent.click(getByText("Polls")); + + expect(vmDefaultValues.onRoomPollHistoryClick).toHaveBeenCalled(); + }); + }); + + describe("public room label", () => { + it("does not show public room label for a DM", async () => { + mocked(useRoomSummaryCardViewModel).mockReturnValue({ + ...vmDefaultValues, + isDirectMessage: true, + }); + + getComponent(); + + await flushPromises(); + + expect(screen.queryByText("Public room")).not.toBeInTheDocument(); + }); + + it("does not show public room label for non public room", async () => { + mocked(useRoomSummaryCardViewModel).mockReturnValue({ + ...vmDefaultValues, + isDirectMessage: false, + roomJoinRule: JoinRule.Invite, + }); + getComponent(); + + await flushPromises(); + + expect(screen.queryByText("Public room")).not.toBeInTheDocument(); + }); + + it("shows a public room label for a public room", async () => { + getComponent(); + + await flushPromises(); + + expect(screen.queryByText("Public room")).toBeInTheDocument(); + }); + }); +}); diff --git a/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap b/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCardView-test.tsx.snap similarity index 90% rename from test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap rename to test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCardView-test.tsx.snap index 6606ce9aad..10e9ae776d 100644 --- a/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCard-test.tsx.snap +++ b/test/unit-tests/components/views/right_panel/__snapshots__/RoomSummaryCardView-test.tsx.snap @@ -10,12 +10,13 @@ exports[` has button to edit topic 1`] = ` >