diff --git a/src/RoomNotifs.ts b/src/RoomNotifs.ts index d5254d523d..5d39b4f54b 100644 --- a/src/RoomNotifs.ts +++ b/src/RoomNotifs.ts @@ -13,16 +13,27 @@ import { PushRuleActionName, PushRuleKind, TweakName, + EventStatus, } from "matrix-js-sdk/src/matrix"; -import type { IPushRule, Room, MatrixClient } from "matrix-js-sdk/src/matrix"; +import type { IPushRule, Room, MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix"; import { NotificationLevel } from "./stores/notifications/NotificationLevel"; -import { getUnsentMessages } from "./components/structures/RoomStatusBar"; import { doesRoomHaveUnreadMessages, doesRoomOrThreadHaveUnreadMessages } from "./Unread"; import { EffectiveMembership, getEffectiveMembership, isKnockDenied } from "./utils/membership"; import SettingsStore from "./settings/SettingsStore"; import { getMarkedUnreadState } from "./utils/notifications"; +export function getUnsentMessages(room: Room, threadId?: string): MatrixEvent[] { + if (!room) { + return []; + } + return room.getPendingEvents().filter(function (ev) { + const isNotSent = ev.status === EventStatus.NOT_SENT; + const belongsToTheThread = threadId === ev.threadRootId; + return isNotSent && (!threadId || belongsToTheThread); + }); +} + export enum RoomNotifState { AllMessagesLoud = "all_messages_loud", AllMessages = "all_messages", diff --git a/src/components/structures/RoomStatusBar.tsx b/src/components/structures/RoomStatusBar.tsx index d9889342b1..5f61dbafda 100644 --- a/src/components/structures/RoomStatusBar.tsx +++ b/src/components/structures/RoomStatusBar.tsx @@ -1,4 +1,5 @@ /* +Copyright (c) 2025 Element Creations Ltd. Copyright 2024 New Vector Ltd. Copyright 2015-2021 The Matrix.org Foundation C.I.C. @@ -6,292 +7,85 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import React, { type JSX, type ReactNode } from "react"; -import { - ClientEvent, - EventStatus, - type MatrixError, - type MatrixEvent, - type Room, - RoomEvent, - type SyncState, - type SyncStateData, -} from "matrix-js-sdk/src/matrix"; +import React from "react"; +import { type Room } from "matrix-js-sdk/src/matrix"; import { RestartIcon, WarningIcon, DeleteIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t, _td } from "../../languageHandler"; -import Resend from "../../Resend"; -import dis from "../../dispatcher/dispatcher"; -import { messageForResourceLimitError } from "../../utils/ErrorUtils"; -import { Action } from "../../dispatcher/actions"; import { StaticNotificationState } from "../../stores/notifications/StaticNotificationState"; import AccessibleButton from "../views/elements/AccessibleButton"; import InlineSpinner from "../views/elements/InlineSpinner"; -import MatrixClientContext from "../../contexts/MatrixClientContext"; import { RoomStatusBarUnsentMessages } from "./RoomStatusBarUnsentMessages"; -import ExternalLink from "../views/elements/ExternalLink"; - -const STATUS_BAR_HIDDEN = 0; -const STATUS_BAR_EXPANDED = 1; -const STATUS_BAR_EXPANDED_LARGE = 2; - -export function getUnsentMessages(room: Room, threadId?: string): MatrixEvent[] { - if (!room) { - return []; - } - return room.getPendingEvents().filter(function (ev) { - const isNotSent = ev.status === EventStatus.NOT_SENT; - const belongsToTheThread = threadId === ev.threadRootId; - return isNotSent && (!threadId || belongsToTheThread); - }); -} +import { useRoomStatusBarViewModel } from "../viewmodels/rooms/RoomStatusBarViewModel"; interface IProps { // the room this statusbar is representing. room: Room; - - // true if the room is being peeked at. This affects components that shouldn't - // logically be shown when peeking, such as a prompt to invite people to a room. - isPeeking?: boolean; - // callback for when the user clicks on the 'resend all' button in the - // 'unsent messages' bar - onResendAllClick?: () => void; - - // callback for when the user clicks on the 'cancel all' button in the - // 'unsent messages' bar - onCancelAllClick?: () => void; - - // callback for when the user clicks on the 'invite others' button in the - // 'you are alone' bar - onInviteClick?: () => void; - - // callback for when we do something that changes the size of the - // status bar. This is used to trigger a re-layout in the parent - // component. - onResize?: () => void; - - // callback for when the status bar can be hidden from view, as it is - // not displaying anything - onHidden?: () => void; - - // callback for when the status bar is displaying something and should - // be visible - onVisible?: () => void; } -interface IState { - syncState: SyncState | null; - syncStateData: SyncStateData | null; - unsentMessages: MatrixEvent[]; - isResending: boolean; -} - -export default class RoomStatusBar extends React.PureComponent { - private unmounted = false; - public static contextType = MatrixClientContext; - declare public context: React.ContextType; - - public constructor(props: IProps, context: React.ContextType) { - super(props, context); - - this.state = { - syncState: this.context.getSyncState(), - syncStateData: this.context.getSyncStateData(), - unsentMessages: getUnsentMessages(this.props.room), - isResending: false, - }; +export function RoomStatusBar(props: IProps) { + const vm = useRoomStatusBarViewModel(props); + if (!vm.visible) { + return null; } - - public componentDidMount(): void { - this.unmounted = false; - - const client = this.context; - client.on(ClientEvent.Sync, this.onSyncStateChange); - client.on(RoomEvent.LocalEchoUpdated, this.onRoomLocalEchoUpdated); - - this.checkSize(); - } - - public componentDidUpdate(): void { - this.checkSize(); - } - - public componentWillUnmount(): void { - this.unmounted = true; - // we may have entirely lost our client as we're logging out before clicking login on the guest bar... - const client = this.context; - if (client) { - client.removeListener(ClientEvent.Sync, this.onSyncStateChange); - client.removeListener(RoomEvent.LocalEchoUpdated, this.onRoomLocalEchoUpdated); - } - } - - private onSyncStateChange = (state: SyncState, prevState: SyncState | null, data?: SyncStateData): void => { - if (state === "SYNCING" && prevState === "SYNCING") { - return; - } - if (this.unmounted) return; - this.setState({ - syncState: state, - syncStateData: data ?? null, - }); - }; - - private onResendAllClick = (): void => { - Resend.resendUnsentEvents(this.props.room).then(() => { - this.setState({ isResending: false }); - }); - this.setState({ isResending: true }); - dis.fire(Action.FocusSendMessageComposer); - }; - - private onCancelAllClick = (): void => { - Resend.cancelUnsentEvents(this.props.room); - dis.fire(Action.FocusSendMessageComposer); - }; - - private onRoomLocalEchoUpdated = (ev: MatrixEvent, room: Room): void => { - if (room.roomId !== this.props.room.roomId) return; - const messages = getUnsentMessages(this.props.room); - this.setState({ - unsentMessages: messages, - isResending: messages.length > 0 && this.state.isResending, - }); - }; - - // Check whether current size is greater than 0, if yes call props.onVisible - private checkSize(): void { - if (this.getSize()) { - if (this.props.onVisible) this.props.onVisible(); - } else { - if (this.props.onHidden) this.props.onHidden(); - } - } - - // We don't need the actual height - just whether it is likely to have - // changed - so we use '0' to indicate normal size, and other values to - // indicate other sizes. - private getSize(): number { - if (this.shouldShowConnectionError()) { - return STATUS_BAR_EXPANDED; - } else if (this.state.unsentMessages.length > 0 || this.state.isResending) { - return STATUS_BAR_EXPANDED_LARGE; - } - return STATUS_BAR_HIDDEN; - } - - private shouldShowConnectionError(): boolean { - // no conn bar trumps the "some not sent" msg since you can't resend without - // a connection! - // There's one situation in which we don't show this 'no connection' bar, and that's - // if it's a resource limit exceeded error: those are shown in the top bar. - const errorIsMauError = Boolean( - this.state.syncStateData && - this.state.syncStateData.error && - this.state.syncStateData.error.name === "M_RESOURCE_LIMIT_EXCEEDED", - ); - return this.state.syncState === "ERROR" && !errorIsMauError; - } - - private getUnsentMessageContent(): JSX.Element { - const unsentMessages = this.state.unsentMessages; - - let title: ReactNode; - - let consentError: MatrixError | null = null; - let resourceLimitError: MatrixError | null = null; - for (const m of unsentMessages) { - if (m.error && m.error.errcode === "M_CONSENT_NOT_GIVEN") { - consentError = m.error; - break; - } else if (m.error && m.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED") { - resourceLimitError = m.error; - break; - } - } - if (consentError) { - title = _t( - "room|status_bar|requires_consent_agreement", - {}, - { - consentLink: (sub) => ( - - {sub} - - ), - }, - ); - } else if (resourceLimitError) { - title = messageForResourceLimitError( - resourceLimitError.data.limit_type, - resourceLimitError.data.admin_contact, - { - "monthly_active_user": _td("room|status_bar|monthly_user_limit_reached"), - "hs_disabled": _td("room|status_bar|homeserver_blocked"), - "": _td("room|status_bar|exceeded_resource_limit"), - }, - ); - } else { - title = _t("room|status_bar|some_messages_not_sent"); - } - - let buttonRow = ( - <> - - - {_t("room|status_bar|delete_all")} - - - - {_t("room|status_bar|retry_all")} - - - ); - if (this.state.isResending) { - buttonRow = ( - <> - - {/* span for css */} - {_t("forward|sending")} - - ); - } - + if ("connectivityLost" in vm) { return ( - - ); - } - - public render(): React.ReactNode { - if (this.shouldShowConnectionError()) { - return ( -
-
-
- -
-
- {_t("room|status_bar|server_connectivity_lost_title")} -
-
- {_t("room|status_bar|server_connectivity_lost_description")} -
+
+
+
+ +
+
+ {_t("room|status_bar|server_connectivity_lost_title")} +
+
+ {_t("room|status_bar|server_connectivity_lost_description")}
- ); - } - - if (this.state.unsentMessages.length > 0 || this.state.isResending) { - return this.getUnsentMessageContent(); - } - - return null; +
+ ); } + + if (vm.isResending) { + return ( + + + {/* span for css */} + {_t("forward|sending")} + + } + /> + ); + } + + return ( + + {vm.onCancelAllClick && ( + + + {_t("room|status_bar|delete_all")} + + )} + {vm.onResendAllClick && ( + + + {_t("room|status_bar|retry_all")} + + )} + + } + /> + ); } diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 997d86e0dc..3a49f0749a 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -92,7 +92,7 @@ import { type IOpts } from "../../createRoom"; import EditorStateTransfer from "../../utils/EditorStateTransfer"; import ErrorDialog from "../views/dialogs/ErrorDialog"; import UploadBar from "./UploadBar"; -import RoomStatusBar from "./RoomStatusBar"; +import { RoomStatusBar } from "./RoomStatusBar"; import MessageComposer from "../views/rooms/MessageComposer"; import JumpToBottomButton from "../views/rooms/JumpToBottomButton"; import TopUnreadMessagesBar from "../views/rooms/TopUnreadMessagesBar"; @@ -1678,14 +1678,6 @@ export class RoomView extends React.Component { } } - private onInviteClick = (): void => { - // open the room inviter - defaultDispatcher.dispatch({ - action: "view_invite", - roomId: this.getRoomId(), - }); - }; - private onJoinButtonClicked = (): void => { // If the user is a ROU, allow them to transition to a PWLU if (this.context.client?.isGuest()) { @@ -2024,17 +2016,6 @@ export class RoomView extends React.Component { }; } - private onStatusBarVisible = (): void => { - if (this.unmounted || this.state.statusBarVisible) return; - this.setState({ statusBarVisible: true }); - }; - - private onStatusBarHidden = (): void => { - // This is currently not desired as it is annoying if it keeps expanding and collapsing - if (this.unmounted || !this.state.statusBarVisible) return; - this.setState({ statusBarVisible: false }); - }; - /** * called by the parent component when PageUp/Down/etc is pressed. * @@ -2385,21 +2366,12 @@ export class RoomView extends React.Component { } let statusBar: JSX.Element | undefined; - let isStatusAreaExpanded = true; + const isStatusAreaExpanded = true; if (ContentMessages.sharedInstance().getCurrentUploads().length > 0) { statusBar = ; } else if (!this.state.search) { - isStatusAreaExpanded = this.state.statusBarVisible; - statusBar = ( - - ); + statusBar = ; } const statusBarAreaClass = classNames("mx_RoomView_statusArea", { diff --git a/src/components/viewmodels/rooms/RoomStatusBarViewModel.tsx b/src/components/viewmodels/rooms/RoomStatusBarViewModel.tsx new file mode 100644 index 0000000000..25c5578d62 --- /dev/null +++ b/src/components/viewmodels/rooms/RoomStatusBarViewModel.tsx @@ -0,0 +1,177 @@ +/* +Copyright 2025 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { + ClientEvent, + EventStatus, + type MatrixError, + type Room, + RoomEvent, + SyncState, + type SyncStateData, +} from "matrix-js-sdk/src/matrix"; +import React, { type ReactNode, useCallback, useMemo, useState } from "react"; +import { _t, _td } from "@element-hq/web-shared-components"; + +import { useMatrixClientContext } from "../../../contexts/MatrixClientContext"; +import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter"; +import dis from "../../../dispatcher/dispatcher"; +import { Action } from "../../../dispatcher/actions"; +import Resend from "../../../Resend"; +import { messageForResourceLimitError } from "../../../utils/ErrorUtils"; +import ExternalLink from "../../views/elements/ExternalLink"; + +interface RoomStatusBarInvisible { + visible: false; +} + +interface RoomStatusBarWithError { + visible: true; + connectivityLost: boolean; +} + +interface RoomStatusBarWithUnsentMessages { + visible: true; + title: ReactNode; + description?: string; +} + +interface RoomStatusBarWithUnsentMessagesActions extends RoomStatusBarWithUnsentMessages { + isResending: false; + // callback for when the user clicks on the 'resend all' button in the + // 'unsent messages' bar + onResendAllClick?: () => void; + + // callback for when the user clicks on the 'cancel all' button in the + // 'unsent messages' bar + onCancelAllClick?: () => void; +} + +interface RoomStatusBarWithUnsentMessagesResending extends RoomStatusBarWithUnsentMessages { + isResending: true; +} + +type RoomStatusBarVM = + | RoomStatusBarWithError + | RoomStatusBarWithUnsentMessagesActions + | RoomStatusBarWithUnsentMessagesResending + | RoomStatusBarInvisible; + +interface IProps { + // the room this statusbar is representing. + room: Room; +} + +export function useRoomStatusBarViewModel({ room }: IProps): RoomStatusBarVM { + const client = useMatrixClientContext(); + const syncState = useTypedEventEmitterState( + client, + ClientEvent.Sync, + (state: SyncState, prevState: SyncState, data: SyncStateData) => { + return { state, data }; + }, + ); + const [isResending, setResending] = useState(false); + const unsentMessages = useTypedEventEmitterState(room, RoomEvent.LocalEchoUpdated, () => { + return room.getPendingEvents().filter(function (ev) { + const isNotSent = ev.status === EventStatus.NOT_SENT; + return isNotSent; + }); + }); + + const onResendAllClick = useCallback(() => { + setResending(true); + Resend.resendUnsentEvents(room).finally(() => { + setResending(false); + }); + dis.fire(Action.FocusSendMessageComposer); + }, [room]); + + const onCancelAllClick = useCallback(() => { + Resend.cancelUnsentEvents(room); + dis.fire(Action.FocusSendMessageComposer); + }, [room]); + + const unsentMessagesTitle = useMemo(() => { + let consentError: MatrixError | null = null; + let resourceLimitError: MatrixError | null = null; + for (const m of unsentMessages) { + if (!m.error) { + continue; + } + if (m.error.errcode === "M_CONSENT_NOT_GIVEN") { + consentError = m.error; + break; + } + if (m.error.errcode === "M_RESOURCE_LIMIT_EXCEEDED") { + resourceLimitError = m.error; + break; + } + } + if (consentError) { + return _t( + "room|status_bar|requires_consent_agreement", + {}, + { + consentLink: (sub) => ( + + {sub} + + ), + }, + ); + } else if (resourceLimitError) { + return messageForResourceLimitError( + resourceLimitError.data.limit_type, + resourceLimitError.data.admin_contact, + { + "monthly_active_user": _td("room|status_bar|monthly_user_limit_reached"), + "hs_disabled": _td("room|status_bar|homeserver_blocked"), + "": _td("room|status_bar|exceeded_resource_limit"), + }, + ); + } else { + return _t("room|status_bar|some_messages_not_sent"); + } + }, [unsentMessages]); + + const hasConnectionError = useMemo(() => { + // no conn bar trumps the "some not sent" msg since you can't resend without + // a connection! + // There's one situation in which we don't show this 'no connection' bar, and that's + // if it's a resource limit exceeded error: those are shown in the top bar. + const errorIsMauError = Boolean( + syncState.data && syncState.data.error && syncState.data.error.name === "M_RESOURCE_LIMIT_EXCEEDED", + ); + return syncState.state === SyncState.Error && !errorIsMauError; + }, [syncState]); + + if (hasConnectionError) { + return { visible: true, connectivityLost: true }; + } + + if (unsentMessages.length) { + if (isResending) { + return { + visible: true, + title: unsentMessagesTitle, + description: _t("room|status_bar|select_messages_to_retry"), + isResending: true, + }; + } + return { + visible: true, + title: unsentMessagesTitle, + description: _t("room|status_bar|select_messages_to_retry"), + isResending, + onResendAllClick, + onCancelAllClick, + }; + } + + return { visible: false }; +} diff --git a/test/unit-tests/RoomNotifs-test.ts b/test/unit-tests/RoomNotifs-test.ts index cff4ecef75..e514288a6c 100644 --- a/test/unit-tests/RoomNotifs-test.ts +++ b/test/unit-tests/RoomNotifs-test.ts @@ -26,10 +26,73 @@ import { RoomNotifState, getUnreadNotificationCount, determineUnreadState, + getUnsentMessages, } from "../../src/RoomNotifs"; import { NotificationLevel } from "../../src/stores/notifications/NotificationLevel"; import SettingsStore from "../../src/settings/SettingsStore"; import { MatrixClientPeg } from "../../src/MatrixClientPeg"; +import { mkThread } from "../test-utils/threads"; + +describe("getUnsentMessages", () => { + const ROOM_ID = "!roomId"; + let room: Room; + let event: MatrixEvent; + let client: MatrixClient; + beforeEach(() => { + client = stubClient(); + room = new Room(ROOM_ID, client, client.getUserId()!, { + pendingEventOrdering: PendingEventOrdering.Detached, + }); + event = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + event.status = EventStatus.NOT_SENT; + }); + + it("returns no unsent messages", () => { + expect(getUnsentMessages(room)).toHaveLength(0); + }); + + it("checks the event status", () => { + room.addPendingEvent(event, "123"); + + expect(getUnsentMessages(room)).toHaveLength(1); + event.status = EventStatus.SENT; + + expect(getUnsentMessages(room)).toHaveLength(0); + }); + + it("only returns events related to a thread", () => { + room.addPendingEvent(event, "123"); + + const { rootEvent, events } = mkThread({ + room, + client, + authorId: "@alice:example.org", + participantUserIds: ["@alice:example.org"], + length: 2, + }); + rootEvent.status = EventStatus.NOT_SENT; + room.addPendingEvent(rootEvent, rootEvent.getId()!); + for (const event of events) { + event.status = EventStatus.NOT_SENT; + room.addPendingEvent(event, Date.now() + Math.random() + ""); + } + + const pendingEvents = getUnsentMessages(room, rootEvent.getId()); + + expect(pendingEvents[0].threadRootId).toBe(rootEvent.getId()); + expect(pendingEvents[1].threadRootId).toBe(rootEvent.getId()); + expect(pendingEvents[2].threadRootId).toBe(rootEvent.getId()); + + // Filters out the non thread events + expect(pendingEvents.every((ev) => ev.getId() !== event.getId())).toBe(true); + }); +}); describe("RoomNotifs test", () => { let client: jest.Mocked; diff --git a/test/unit-tests/components/structures/RoomStatusBar-test.tsx b/test/unit-tests/components/structures/RoomStatusBar-test.tsx index 844ed8ab2d..493eb143e1 100644 --- a/test/unit-tests/components/structures/RoomStatusBar-test.tsx +++ b/test/unit-tests/components/structures/RoomStatusBar-test.tsx @@ -17,11 +17,10 @@ import { MatrixError, } from "matrix-js-sdk/src/matrix"; -import RoomStatusBar, { getUnsentMessages } from "../../../../src/components/structures/RoomStatusBar"; +import { RoomStatusBar } from "../../../../src/components/structures/RoomStatusBar"; import MatrixClientContext from "../../../../src/contexts/MatrixClientContext"; import { MatrixClientPeg } from "../../../../src/MatrixClientPeg"; import { mkEvent, stubClient } from "../../../test-utils/test-utils"; -import { mkThread } from "../../../test-utils/threads"; describe("RoomStatusBar", () => { const ROOM_ID = "!roomId:example.org"; @@ -55,96 +54,52 @@ describe("RoomStatusBar", () => { ), }); - describe("getUnsentMessages", () => { - it("returns no unsent messages", () => { - expect(getUnsentMessages(room)).toHaveLength(0); - }); - - it("checks the event status", () => { - room.addPendingEvent(event, "123"); - - expect(getUnsentMessages(room)).toHaveLength(1); - event.status = EventStatus.SENT; - - expect(getUnsentMessages(room)).toHaveLength(0); - }); - - it("only returns events related to a thread", () => { - room.addPendingEvent(event, "123"); - - const { rootEvent, events } = mkThread({ - room, - client, - authorId: "@alice:example.org", - participantUserIds: ["@alice:example.org"], - length: 2, - }); - rootEvent.status = EventStatus.NOT_SENT; - room.addPendingEvent(rootEvent, rootEvent.getId()!); - for (const event of events) { - event.status = EventStatus.NOT_SENT; - room.addPendingEvent(event, Date.now() + Math.random() + ""); - } - - const pendingEvents = getUnsentMessages(room, rootEvent.getId()); - - expect(pendingEvents[0].threadRootId).toBe(rootEvent.getId()); - expect(pendingEvents[1].threadRootId).toBe(rootEvent.getId()); - expect(pendingEvents[2].threadRootId).toBe(rootEvent.getId()); - - // Filters out the non thread events - expect(pendingEvents.every((ev) => ev.getId() !== event.getId())).toBe(true); - }); + it("should render nothing when room has no error or unsent messages", () => { + const { container } = getComponent(); + expect(container.firstChild).toBe(null); }); - describe("", () => { - it("should render nothing when room has no error or unsent messages", () => { + describe("unsent messages", () => { + it("should render warning when messages are unsent due to consent", () => { + const unsentMessage = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + unsentMessage.status = EventStatus.NOT_SENT; + unsentMessage.error = new MatrixError({ + errcode: "M_CONSENT_NOT_GIVEN", + data: { consent_uri: "terms.com" }, + }); + + room.addPendingEvent(unsentMessage, "123"); + const { container } = getComponent(); - expect(container.firstChild).toBe(null); + + expect(container).toMatchSnapshot(); }); - describe("unsent messages", () => { - it("should render warning when messages are unsent due to consent", () => { - const unsentMessage = mkEvent({ - event: true, - type: "m.room.message", - user: "@user1:server", - room: "!room1:server", - content: {}, - }); - unsentMessage.status = EventStatus.NOT_SENT; - unsentMessage.error = new MatrixError({ - errcode: "M_CONSENT_NOT_GIVEN", - data: { consent_uri: "terms.com" }, - }); - - room.addPendingEvent(unsentMessage, "123"); - - const { container } = getComponent(); - - expect(container).toMatchSnapshot(); + it("should render warning when messages are unsent due to resource limit", () => { + const unsentMessage = mkEvent({ + event: true, + type: "m.room.message", + user: "@user1:server", + room: "!room1:server", + content: {}, + }); + unsentMessage.status = EventStatus.NOT_SENT; + unsentMessage.error = new MatrixError({ + errcode: "M_RESOURCE_LIMIT_EXCEEDED", + data: { limit_type: "monthly_active_user" }, }); - it("should render warning when messages are unsent due to resource limit", () => { - const unsentMessage = mkEvent({ - event: true, - type: "m.room.message", - user: "@user1:server", - room: "!room1:server", - content: {}, - }); - unsentMessage.status = EventStatus.NOT_SENT; - unsentMessage.error = new MatrixError({ - errcode: "M_RESOURCE_LIMIT_EXCEEDED", - data: { limit_type: "monthly_active_user" }, - }); + room.addPendingEvent(unsentMessage, "123"); - room.addPendingEvent(unsentMessage, "123"); + const { container } = getComponent(); - const { container } = getComponent(); - - expect(container).toMatchSnapshot(); - }); + expect(container).toMatchSnapshot(); }); }); });