mirror of
https://github.com/vector-im/element-web.git
synced 2025-11-29 22:41:44 +01:00
Mvvm RoomSummaryCard (#29674)
* feat: create roomsummarycard viewmodel * feat: use roomsummurycard vm in component * test: jest unit RoomSummaryCard and RoomSummaryCardViewModel * chore: rename to roomsummarycardview * feat: reput room topic without vm * test: roomSummaryCard and roomSummaryCardVM tests * chore: add comments on roomsummarycardVM * fix: merge conflict with roomsummarytopic, and move to vm right_panel * fix(roomsummarycard): remove usetransition for search update * fix: merged file that should be deleted * fix: roomsummurycard not well merge with roomtopic * test: update snapshots
This commit is contained in:
parent
9642af9930
commit
b07225eb60
@ -15,7 +15,7 @@ import dis from "../../dispatcher/dispatcher";
|
|||||||
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
import { RightPanelPhases } from "../../stores/right-panel/RightPanelStorePhases";
|
||||||
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
import RightPanelStore from "../../stores/right-panel/RightPanelStore";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
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 WidgetCard from "../views/right_panel/WidgetCard";
|
||||||
import UserInfo from "../views/right_panel/UserInfo";
|
import UserInfo from "../views/right_panel/UserInfo";
|
||||||
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
|
import ThirdPartyMemberInfo from "../views/rooms/ThirdPartyMemberInfo";
|
||||||
@ -255,7 +255,7 @@ export default class RightPanel extends React.Component<Props, IState> {
|
|||||||
case RightPanelPhases.RoomSummary:
|
case RightPanelPhases.RoomSummary:
|
||||||
if (!!this.props.room) {
|
if (!!this.props.room) {
|
||||||
card = (
|
card = (
|
||||||
<RoomSummaryCard
|
<RoomSummaryCardView
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
// whenever RightPanel is passed a room it is passed a permalinkcreator
|
||||||
permalinkCreator={this.props.permalinkCreator!}
|
permalinkCreator={this.props.permalinkCreator!}
|
||||||
|
|||||||
@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2025 New Vector Ltd.
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||||
|
Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { EventType, type JoinRule, type Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
|
||||||
|
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
||||||
|
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext";
|
||||||
|
import { type E2EStatus } from "../../../utils/ShieldUtils";
|
||||||
|
import { isVideoRoom as calcIsVideoRoom } from "../../../utils/video-rooms";
|
||||||
|
import { useRoomState } from "../../../hooks/useRoomState";
|
||||||
|
import { useAccountData } from "../../../hooks/useAccountData";
|
||||||
|
import { useDispatcher } from "../../../hooks/useDispatcher";
|
||||||
|
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
||||||
|
import { Action } from "../../../dispatcher/actions";
|
||||||
|
import RoomListStore, { LISTS_UPDATE_EVENT } from "../../../stores/room-list/RoomListStore";
|
||||||
|
import { canInviteTo } from "../../../utils/room/canInviteTo";
|
||||||
|
import { DefaultTagID } from "../../../stores/room-list/models";
|
||||||
|
import { useEventEmitterState } from "../../../hooks/useEventEmitter";
|
||||||
|
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
|
||||||
|
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
||||||
|
import PosthogTrackers from "../../../PosthogTrackers";
|
||||||
|
import { PollHistoryDialog } from "../../views/dialogs/PollHistoryDialog";
|
||||||
|
import Modal from "../../../Modal";
|
||||||
|
import ExportDialog from "../../views/dialogs/ExportDialog";
|
||||||
|
import { ShareDialog } from "../../views/dialogs/ShareDialog";
|
||||||
|
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks";
|
||||||
|
import { ReportRoomDialog } from "../../views/dialogs/ReportRoomDialog";
|
||||||
|
import { Key } from "../../../Keyboard";
|
||||||
|
import { usePinnedEvents } from "../../../hooks/usePinnedEvents";
|
||||||
|
import { tagRoom } from "../../../utils/room/tagRoom";
|
||||||
|
import { inviteToRoom } from "../../../utils/room/inviteToRoom";
|
||||||
|
|
||||||
|
export interface RoomSummaryCardState {
|
||||||
|
isDirectMessage: boolean;
|
||||||
|
/**
|
||||||
|
* Whether the room is encrypted, used to display the correct badge and icon
|
||||||
|
*/
|
||||||
|
isRoomEncrypted: boolean;
|
||||||
|
/**
|
||||||
|
* The e2e status of the room, used to display the correct badge and icon
|
||||||
|
*/
|
||||||
|
e2eStatus: E2EStatus | undefined;
|
||||||
|
/**
|
||||||
|
* The join rule of the room, used to display the correct badge and icon
|
||||||
|
*/
|
||||||
|
roomJoinRule: JoinRule;
|
||||||
|
/**
|
||||||
|
* if it is a video room, it should not display export chat, polls, files, extensions
|
||||||
|
*/
|
||||||
|
isVideoRoom: boolean;
|
||||||
|
/**
|
||||||
|
* display the alias of the room, if it exists
|
||||||
|
*/
|
||||||
|
alias: string;
|
||||||
|
/**
|
||||||
|
* value to check if the room is a favorite or not
|
||||||
|
*/
|
||||||
|
isFavorite: boolean;
|
||||||
|
/**
|
||||||
|
* value to check if we disable invite button or not
|
||||||
|
*/
|
||||||
|
canInviteToState: boolean;
|
||||||
|
/**
|
||||||
|
* Getting the number of pinned messages in the room, next to the pin button
|
||||||
|
*/
|
||||||
|
pinCount: number;
|
||||||
|
searchInputRef: React.RefObject<HTMLInputElement | null>;
|
||||||
|
/**
|
||||||
|
* The callback when new value is entered in the search input
|
||||||
|
*/
|
||||||
|
onUpdateSearchInput: (e: React.KeyboardEvent<HTMLInputElement>) => 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<void>;
|
||||||
|
onRoomPollHistoryClick: () => void;
|
||||||
|
onReportRoomClick: () => Promise<void>;
|
||||||
|
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<Record<string, string[]>>(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<HTMLInputElement | null>;
|
||||||
|
onUpdateSearchInput: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
||||||
|
} => {
|
||||||
|
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const onUpdateSearchInput = (e: React.KeyboardEvent<HTMLInputElement>): 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<void> => {
|
||||||
|
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<void> => {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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.
|
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 classNames from "classnames";
|
||||||
import {
|
import {
|
||||||
MenuItem,
|
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 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 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 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 BaseCard from "./BaseCard.tsx";
|
||||||
import { useIsEncrypted } from "../../../hooks/useIsEncrypted";
|
import { _t } from "../../../languageHandler.tsx";
|
||||||
import BaseCard from "./BaseCard";
|
import RoomAvatar from "../avatars/RoomAvatar.tsx";
|
||||||
import { _t } from "../../../languageHandler";
|
import { E2EStatus } from "../../../utils/ShieldUtils.ts";
|
||||||
import RoomAvatar from "../avatars/RoomAvatar";
|
import { type RoomPermalinkCreator } from "../../../utils/permalinks/Permalinks.ts";
|
||||||
import defaultDispatcher from "../../../dispatcher/dispatcher";
|
import RoomName from "../elements/RoomName.tsx";
|
||||||
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
|
import { Flex } from "../../utils/Flex.tsx";
|
||||||
import Modal from "../../../Modal";
|
import { Linkify, topicToHtml } from "../../../HtmlUtils.tsx";
|
||||||
import { ShareDialog } from "../dialogs/ShareDialog";
|
import { Box } from "../../utils/Box.tsx";
|
||||||
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 { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
|
import { ReleaseAnnouncement } from "../../structures/ReleaseAnnouncement.tsx";
|
||||||
import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext.tsx";
|
import { useRoomSummaryCardViewModel } from "../../viewmodels/right_panel/RoomSummaryCardViewModel.tsx";
|
||||||
import { ReportRoomDialog } from "../dialogs/ReportRoomDialog.tsx";
|
|
||||||
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
|
import { useRoomTopicViewModel } from "../../viewmodels/right_panel/RoomSummaryCardTopicViewModel.tsx";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -86,32 +62,6 @@ interface IProps {
|
|||||||
searchTerm?: string;
|
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<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
|
const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null => {
|
||||||
const vm = useRoomTopicViewModel(room);
|
const vm = useRoomTopicViewModel(room);
|
||||||
|
|
||||||
@ -142,6 +92,7 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
|
|||||||
}
|
}
|
||||||
|
|
||||||
const content = vm.expanded ? <Linkify>{body}</Linkify> : body;
|
const content = vm.expanded ? <Linkify>{body}</Linkify> : body;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
as="section"
|
as="section"
|
||||||
@ -173,7 +124,7 @@ const RoomTopic: React.FC<Pick<IProps, "room">> = ({ room }): JSX.Element | null
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const RoomSummaryCard: React.FC<IProps> = ({
|
const RoomSummaryCardView: React.FC<IProps> = ({
|
||||||
room,
|
room,
|
||||||
permalinkCreator,
|
permalinkCreator,
|
||||||
onSearchChange,
|
onSearchChange,
|
||||||
@ -181,69 +132,7 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
focusRoomSearch,
|
focusRoomSearch,
|
||||||
searchTerm = "",
|
searchTerm = "",
|
||||||
}) => {
|
}) => {
|
||||||
const cli = useContext(MatrixClientContext);
|
const vm = useRoomSummaryCardViewModel(room, permalinkCreator, onSearchCancel);
|
||||||
|
|
||||||
const onShareRoomClick = (): void => {
|
|
||||||
Modal.createDialog(ShareDialog, {
|
|
||||||
target: room,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRoomExportClick = async (): Promise<void> => {
|
|
||||||
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<void> => {
|
|
||||||
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<Record<string, string[]>>(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<HTMLInputElement>(null);
|
|
||||||
useDispatcher(defaultDispatcher, (payload) => {
|
|
||||||
if (payload.action === Action.FocusMessageSearch) {
|
|
||||||
searchInputRef.current?.focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The search field is controlled and onSearchChange is debounced in RoomView,
|
// The search field is controlled and onSearchChange is debounced in RoomView,
|
||||||
// so we need to set the value of the input right away
|
// so we need to set the value of the input right away
|
||||||
@ -252,7 +141,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
setSearchValue(searchTerm);
|
setSearchValue(searchTerm);
|
||||||
}, [searchTerm]);
|
}, [searchTerm]);
|
||||||
|
|
||||||
const alias = room.getCanonicalAlias() || room.getAltAliases()[0] || "";
|
|
||||||
const roomInfo = (
|
const roomInfo = (
|
||||||
<header className="mx_RoomSummaryCard_container">
|
<header className="mx_RoomSummaryCard_container">
|
||||||
<RoomAvatar room={room} size="80px" viewAvatarOnClick />
|
<RoomAvatar room={room} size="80px" viewAvatarOnClick />
|
||||||
@ -274,34 +162,34 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
size="sm"
|
size="sm"
|
||||||
weight="semibold"
|
weight="semibold"
|
||||||
className="mx_RoomSummaryCard_alias text-secondary"
|
className="mx_RoomSummaryCard_alias text-secondary"
|
||||||
title={alias}
|
title={vm.alias}
|
||||||
>
|
>
|
||||||
{alias}
|
{vm.alias}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
|
<Flex as="section" justify="center" gap="var(--cpd-space-2x)" className="mx_RoomSummaryCard_badges">
|
||||||
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
{!vm.isDirectMessage && vm.roomJoinRule === JoinRule.Public && (
|
||||||
<Badge kind="grey">
|
<Badge kind="grey">
|
||||||
<PublicIcon width="1em" />
|
<PublicIcon width="1em" />
|
||||||
{_t("common|public_room")}
|
{_t("common|public_room")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isRoomEncrypted && e2eStatus !== E2EStatus.Warning && (
|
{vm.isRoomEncrypted && vm.e2eStatus !== E2EStatus.Warning && (
|
||||||
<Badge kind="green">
|
<Badge kind="green">
|
||||||
<LockIcon width="1em" />
|
<LockIcon width="1em" />
|
||||||
{_t("common|encrypted")}
|
{_t("common|encrypted")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!e2eStatus && (
|
{!vm.isRoomEncrypted && (
|
||||||
<Badge kind="grey">
|
<Badge kind="grey">
|
||||||
<LockOffIcon width="1em" />
|
<LockOffIcon width="1em" />
|
||||||
{_t("common|unencrypted")}
|
{_t("common|unencrypted")}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{e2eStatus === E2EStatus.Warning && (
|
{vm.e2eStatus === E2EStatus.Warning && (
|
||||||
<Badge kind="red">
|
<Badge kind="red">
|
||||||
<ErrorSolidIcon width="1em" />
|
<ErrorSolidIcon width="1em" />
|
||||||
{_t("common|not_trusted")}
|
{_t("common|not_trusted")}
|
||||||
@ -313,14 +201,6 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
|
|
||||||
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 && (
|
const header = onSearchChange && (
|
||||||
<Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
|
<Form.Root className="mx_RoomSummaryCard_search" onSubmit={(e) => e.preventDefault()}>
|
||||||
<Search
|
<Search
|
||||||
@ -332,14 +212,9 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
}}
|
}}
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
className="mx_no_textinput"
|
className="mx_no_textinput"
|
||||||
ref={searchInputRef}
|
ref={vm.searchInputRef}
|
||||||
autoFocus={focusRoomSearch}
|
autoFocus={focusRoomSearch}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={vm.onUpdateSearchInput}
|
||||||
if (searchInputRef.current && e.key === Key.ESCAPE) {
|
|
||||||
searchInputRef.current.value = "";
|
|
||||||
onSearchCancel?.();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Form.Root>
|
</Form.Root>
|
||||||
);
|
);
|
||||||
@ -360,21 +235,21 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
<ToggleMenuItem
|
<ToggleMenuItem
|
||||||
Icon={FavouriteIcon}
|
Icon={FavouriteIcon}
|
||||||
label={_t("room|context_menu|favourite")}
|
label={_t("room|context_menu|favourite")}
|
||||||
checked={isFavorite}
|
checked={vm.isFavorite}
|
||||||
onSelect={() => tagRoom(room, DefaultTagID.Favourite)}
|
onSelect={vm.onFavoriteToggleClick}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={UserAddIcon}
|
Icon={UserAddIcon}
|
||||||
label={_t("action|invite")}
|
label={_t("action|invite")}
|
||||||
disabled={!canInviteToState}
|
disabled={!vm.canInviteToState}
|
||||||
onSelect={() => inviteToRoom(room)}
|
onSelect={vm.onInviteToRoomClick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={onRoomMembersClick} />
|
<MenuItem Icon={UserProfileIcon} label={_t("common|people")} onSelect={vm.onRoomMembersClick} />
|
||||||
<MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={onRoomThreadsClick} />
|
<MenuItem Icon={ThreadsIcon} label={_t("common|threads")} onSelect={vm.onRoomThreadsClick} />
|
||||||
{!isVideoRoom && (
|
{!vm.isVideoRoom && (
|
||||||
<>
|
<>
|
||||||
<ReleaseAnnouncement
|
<ReleaseAnnouncement
|
||||||
feature="pinningMessageList"
|
feature="pinningMessageList"
|
||||||
@ -387,43 +262,47 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={PinIcon}
|
Icon={PinIcon}
|
||||||
label={_t("right_panel|pinned_messages_button")}
|
label={_t("right_panel|pinned_messages_button")}
|
||||||
onSelect={onRoomPinsClick}
|
onSelect={vm.onRoomPinsClick}
|
||||||
>
|
>
|
||||||
<Text as="span" size="sm">
|
<Text as="span" size="sm">
|
||||||
{pinCount}
|
{vm.pinCount}
|
||||||
</Text>
|
</Text>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
</ReleaseAnnouncement>
|
</ReleaseAnnouncement>
|
||||||
<MenuItem Icon={FilesIcon} label={_t("right_panel|files_button")} onSelect={onRoomFilesClick} />
|
<MenuItem
|
||||||
|
Icon={FilesIcon}
|
||||||
|
label={_t("right_panel|files_button")}
|
||||||
|
onSelect={vm.onRoomFilesClick}
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={ExtensionsIcon}
|
Icon={ExtensionsIcon}
|
||||||
label={_t("right_panel|extensions_button")}
|
label={_t("right_panel|extensions_button")}
|
||||||
onSelect={onRoomExtensionsClick}
|
onSelect={vm.onRoomExtensionsClick}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={onShareRoomClick} />
|
<MenuItem Icon={LinkIcon} label={_t("action|copy_link")} onSelect={vm.onShareRoomClick} />
|
||||||
|
|
||||||
{!isVideoRoom && (
|
{!vm.isVideoRoom && (
|
||||||
<>
|
<>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={PollsIcon}
|
Icon={PollsIcon}
|
||||||
label={_t("right_panel|polls_button")}
|
label={_t("right_panel|polls_button")}
|
||||||
onSelect={onRoomPollHistoryClick}
|
onSelect={vm.onRoomPollHistoryClick}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
Icon={ExportArchiveIcon}
|
Icon={ExportArchiveIcon}
|
||||||
label={_t("export_chat|title")}
|
label={_t("export_chat|title")}
|
||||||
onSelect={onRoomExportClick}
|
onSelect={vm.onRoomExportClick}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={onRoomSettingsClick} />
|
<MenuItem Icon={SettingsIcon} label={_t("common|settings")} onSelect={vm.onRoomSettingsClick} />
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="mx_RoomSummaryCard_bottomOptions">
|
<div className="mx_RoomSummaryCard_bottomOptions">
|
||||||
@ -431,14 +310,14 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
Icon={ErrorIcon}
|
Icon={ErrorIcon}
|
||||||
kind="critical"
|
kind="critical"
|
||||||
label={_t("action|report_room")}
|
label={_t("action|report_room")}
|
||||||
onSelect={onReportRoomClick}
|
onSelect={vm.onReportRoomClick}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
className="mx_RoomSummaryCard_leave"
|
className="mx_RoomSummaryCard_leave"
|
||||||
Icon={LeaveIcon}
|
Icon={LeaveIcon}
|
||||||
kind="critical"
|
kind="critical"
|
||||||
label={_t("action|leave_room")}
|
label={_t("action|leave_room")}
|
||||||
onSelect={onLeaveRoomClick}
|
onSelect={vm.onLeaveRoomClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -446,4 +325,4 @@ const RoomSummaryCard: React.FC<IProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RoomSummaryCard;
|
export default RoomSummaryCardView;
|
||||||
@ -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("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -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("<RoomSummaryCard />", () => {
|
|
||||||
const userId = "@alice:domain.org";
|
|
||||||
|
|
||||||
const roomId = "!room:domain.org";
|
|
||||||
let mockClient!: MockedObject<MatrixClient>;
|
|
||||||
let room!: Room;
|
|
||||||
|
|
||||||
const getComponent = (props = {}) => {
|
|
||||||
const defaultProps = {
|
|
||||||
room,
|
|
||||||
onClose: jest.fn(),
|
|
||||||
permalinkCreator: new RoomPermalinkCreator(room),
|
|
||||||
};
|
|
||||||
|
|
||||||
return render(<RoomSummaryCard {...defaultProps} {...props} />, {
|
|
||||||
wrapper: ({ children }) => (
|
|
||||||
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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("<RoomSummaryCard />", () => {
|
||||||
|
const userId = "@alice:domain.org";
|
||||||
|
|
||||||
|
const roomId = "!room:domain.org";
|
||||||
|
let mockClient!: MockedObject<MatrixClient>;
|
||||||
|
let room!: Room;
|
||||||
|
|
||||||
|
const getComponent = (props = {}) => {
|
||||||
|
const defaultProps = {
|
||||||
|
room,
|
||||||
|
onClose: jest.fn(),
|
||||||
|
permalinkCreator: new RoomPermalinkCreator(room),
|
||||||
|
};
|
||||||
|
|
||||||
|
return render(<RoomSummaryCardView {...defaultProps} {...props} />, {
|
||||||
|
wrapper: ({ children }) => (
|
||||||
|
<MatrixClientContext.Provider value={mockClient}>{children}</MatrixClientContext.Provider>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -10,12 +10,13 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_BaseCard_header"
|
class="mx_BaseCard_header"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_BaseCard_header_spacer"
|
class="mx_BaseCard_header_spacer"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-labelledby="«rm»"
|
aria-labelledby="«rq»"
|
||||||
class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
|
class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
|
||||||
data-testid="base-card-close-button"
|
data-testid="base-card-close-button"
|
||||||
role="button"
|
role="button"
|
||||||
@ -46,6 +47,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
class="mx_RoomSummaryCard_container"
|
class="mx_RoomSummaryCard_container"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
@ -71,6 +73,23 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
class="mx_Flex mx_RoomSummaryCard_badges"
|
class="mx_Flex mx_RoomSummaryCard_badges"
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
||||||
|
data-kind="grey"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Public room
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
||||||
data-kind="grey"
|
data-kind="grey"
|
||||||
@ -151,6 +170,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</header>
|
</header>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
@ -162,6 +182,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
<div
|
<div
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitemcheckbox"
|
role="menuitemcheckbox"
|
||||||
>
|
>
|
||||||
@ -189,7 +210,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="_input_19o42_24"
|
class="_input_19o42_24"
|
||||||
id="«rr»"
|
id="«rv»"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@ -199,6 +220,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -236,12 +258,14 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -285,6 +309,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -324,8 +349,11 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-expanded="false"
|
aria-controls="«r12»"
|
||||||
|
aria-describedby="«r12»"
|
||||||
|
aria-expanded="true"
|
||||||
aria-haspopup="dialog"
|
aria-haspopup="dialog"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
@ -372,8 +400,34 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
data-floating-ui-inert=""
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-floating-ui-focus-guard=""
|
||||||
|
data-type="outside"
|
||||||
|
role="button"
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-owns="«r14»"
|
||||||
|
data-floating-ui-inert=""
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-floating-ui-focus-guard=""
|
||||||
|
data-type="outside"
|
||||||
|
role="button"
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -411,6 +465,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -448,12 +503,14 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -491,6 +548,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -528,6 +586,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -565,6 +624,7 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -605,12 +665,14 @@ exports[`<RoomSummaryCard /> has button to edit topic 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="mx_RoomSummaryCard_bottomOptions"
|
class="mx_RoomSummaryCard_bottomOptions"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
@ -706,6 +768,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_BaseCard_header"
|
class="mx_BaseCard_header"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_BaseCard_header_spacer"
|
class="mx_BaseCard_header_spacer"
|
||||||
@ -742,6 +805,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
class="mx_RoomSummaryCard_container"
|
class="mx_RoomSummaryCard_container"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
@ -767,6 +831,23 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
class="mx_Flex mx_RoomSummaryCard_badges"
|
class="mx_Flex mx_RoomSummaryCard_badges"
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
||||||
|
data-kind="grey"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Public room
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
||||||
data-kind="grey"
|
data-kind="grey"
|
||||||
@ -810,6 +891,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</header>
|
</header>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
@ -821,6 +903,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
<div
|
<div
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitemcheckbox"
|
role="menuitemcheckbox"
|
||||||
>
|
>
|
||||||
@ -858,6 +941,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -895,12 +979,14 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -944,6 +1030,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -983,8 +1070,11 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-expanded="false"
|
aria-controls="«r8»"
|
||||||
|
aria-describedby="«r8»"
|
||||||
|
aria-expanded="true"
|
||||||
aria-haspopup="dialog"
|
aria-haspopup="dialog"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
@ -1031,8 +1121,34 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
data-floating-ui-inert=""
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-floating-ui-focus-guard=""
|
||||||
|
data-type="outside"
|
||||||
|
role="button"
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-owns="«ra»"
|
||||||
|
data-floating-ui-inert=""
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-floating-ui-focus-guard=""
|
||||||
|
data-type="outside"
|
||||||
|
role="button"
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1070,6 +1186,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1107,12 +1224,14 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1150,6 +1269,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1187,6 +1307,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1224,6 +1345,7 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1264,12 +1386,14 @@ exports[`<RoomSummaryCard /> renders the room summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="mx_RoomSummaryCard_bottomOptions"
|
class="mx_RoomSummaryCard_bottomOptions"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
@ -1365,12 +1489,13 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_BaseCard_header"
|
class="mx_BaseCard_header"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mx_BaseCard_header_spacer"
|
class="mx_BaseCard_header_spacer"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
aria-labelledby="«rb»"
|
aria-labelledby="«rd»"
|
||||||
class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
|
class="_icon-button_m2erp_8 _subtle-bg_m2erp_29"
|
||||||
data-testid="base-card-close-button"
|
data-testid="base-card-close-button"
|
||||||
role="button"
|
role="button"
|
||||||
@ -1401,6 +1526,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
class="mx_RoomSummaryCard_container"
|
class="mx_RoomSummaryCard_container"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||||
@ -1426,6 +1552,23 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
class="mx_Flex mx_RoomSummaryCard_badges"
|
class="mx_Flex mx_RoomSummaryCard_badges"
|
||||||
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
style="--mx-flex-display: flex; --mx-flex-direction: row; --mx-flex-align: start; --mx-flex-justify: center; --mx-flex-gap: var(--cpd-space-2x); --mx-flex-wrap: nowrap;"
|
||||||
>
|
>
|
||||||
|
<span
|
||||||
|
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
||||||
|
data-kind="grey"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
height="1em"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width="1em"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 22a9.7 9.7 0 0 1-3.9-.788 10.1 10.1 0 0 1-3.175-2.137q-1.35-1.35-2.137-3.175A9.7 9.7 0 0 1 2 12q0-2.075.788-3.9a10.1 10.1 0 0 1 2.137-3.175q1.35-1.35 3.175-2.137A9.7 9.7 0 0 1 12 2q2.075 0 3.9.788a10.1 10.1 0 0 1 3.175 2.137q1.35 1.35 2.137 3.175A9.7 9.7 0 0 1 22 12a9.7 9.7 0 0 1-.788 3.9 10.1 10.1 0 0 1-2.137 3.175q-1.35 1.35-3.175 2.137A9.7 9.7 0 0 1 12 22m-1-2.05V18q-.825 0-1.412-.587A1.93 1.93 0 0 1 9 16v-1l-4.8-4.8q-.075.45-.138.9Q4 11.55 4 12q0 3.025 1.987 5.3T11 19.95m6.9-2.55q.5-.55.9-1.187.4-.638.662-1.326.263-.687.4-1.412Q20 12.75 20 12a7.85 7.85 0 0 0-1.363-4.475A7.7 7.7 0 0 0 15 4.6V5q0 .824-.588 1.412A1.93 1.93 0 0 1 13 7h-2v2q0 .424-.287.713A.97.97 0 0 1 10 10H8v2h6q.424 0 .713.287.287.288.287.713v3h1q.65 0 1.175.387.525.388.725 1.013"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
Public room
|
||||||
|
</span>
|
||||||
<span
|
<span
|
||||||
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
class="_typography_6v6n8_153 _font-body-sm-medium_6v6n8_41 _badge_1t12g_8"
|
||||||
data-kind="grey"
|
data-kind="grey"
|
||||||
@ -1506,6 +1649,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</header>
|
</header>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
@ -1517,6 +1661,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
<div
|
<div
|
||||||
aria-checked="false"
|
aria-checked="false"
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitemcheckbox"
|
role="menuitemcheckbox"
|
||||||
>
|
>
|
||||||
@ -1544,7 +1689,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
<input
|
<input
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
class="_input_19o42_24"
|
class="_input_19o42_24"
|
||||||
id="«rg»"
|
id="«ri»"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@ -1554,6 +1699,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1591,12 +1737,14 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1640,6 +1788,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1679,8 +1828,11 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
aria-expanded="false"
|
aria-controls="«rl»"
|
||||||
|
aria-describedby="«rl»"
|
||||||
|
aria-expanded="true"
|
||||||
aria-haspopup="dialog"
|
aria-haspopup="dialog"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
@ -1727,8 +1879,34 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
data-floating-ui-inert=""
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="-1"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-floating-ui-focus-guard=""
|
||||||
|
data-type="outside"
|
||||||
|
role="button"
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-owns="«rn»"
|
||||||
|
data-floating-ui-inert=""
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
data-floating-ui-focus-guard=""
|
||||||
|
data-type="outside"
|
||||||
|
role="button"
|
||||||
|
style="border: 0px; height: 1px; margin: -1px; overflow: hidden; padding: 0px; position: fixed; white-space: nowrap; width: 1px; top: 0px; left: 0px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1766,6 +1944,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1803,12 +1982,14 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1846,6 +2027,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1883,6 +2065,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1920,6 +2103,7 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
@ -1960,12 +2144,14 @@ exports[`<RoomSummaryCard /> renders the room topic in the summary 1`] = `
|
|||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
class="_separator_7ckbw_8"
|
class="_separator_7ckbw_8"
|
||||||
|
data-floating-ui-inert=""
|
||||||
data-kind="primary"
|
data-kind="primary"
|
||||||
data-orientation="horizontal"
|
data-orientation="horizontal"
|
||||||
role="separator"
|
role="separator"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="mx_RoomSummaryCard_bottomOptions"
|
class="mx_RoomSummaryCard_bottomOptions"
|
||||||
|
data-floating-ui-inert=""
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
class="_item_dyt4i_8 _interactive_dyt4i_26"
|
||||||
Loading…
x
Reference in New Issue
Block a user