From 488a08a25e6cd9092cf7a78e7c886c2504dce33c Mon Sep 17 00:00:00 2001 From: Michael Weimann <michaelw@matrix.org> Date: Thu, 5 Jan 2023 08:40:09 +0100 Subject: [PATCH] Allow reactions for broadcasts (#9856) --- .../views/messages/MessageActionBar.tsx | 4 +++- src/events/forward/getForwardableEvent.ts | 3 +++ src/utils/EventUtils.ts | 5 ++++- .../context_menus/MessageContextMenu-test.tsx | 16 ++++++++++++++-- .../views/messages/MessageActionBar-test.tsx | 16 ++++++++++++++-- test/utils/EventUtils-test.ts | 18 ++++++++++++++++++ test/voice-broadcast/utils/test-utils.ts | 2 +- 7 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 3cac66a79c..1ec7fae751 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -59,6 +59,7 @@ import { Action } from "../../../dispatcher/actions"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import useFavouriteMessages from "../../../hooks/useFavouriteMessages"; import { GetRelationsForEvent } from "../rooms/EventTile"; +import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types"; interface IOptionsButtonProps { mxEvent: MatrixEvent; @@ -394,7 +395,8 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction * until cross-platform support * (PSF-1041) */ - !M_BEACON_INFO.matches(this.props.mxEvent.getType()); + !M_BEACON_INFO.matches(this.props.mxEvent.getType()) && + !(this.props.mxEvent.getType() === VoiceBroadcastInfoEventType); return inNotThreadTimeline && isAllowedMessageType; } diff --git a/src/events/forward/getForwardableEvent.ts b/src/events/forward/getForwardableEvent.ts index ac6132de11..7d1782d7ae 100644 --- a/src/events/forward/getForwardableEvent.ts +++ b/src/events/forward/getForwardableEvent.ts @@ -19,6 +19,7 @@ import { M_BEACON_INFO } from "matrix-js-sdk/src/@types/beacon"; import { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix"; import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation"; +import { VoiceBroadcastInfoEventType } from "../../voice-broadcast/types"; /** * Get forwardable event for a given event @@ -29,6 +30,8 @@ export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): Matr return null; } + if (event.getType() === VoiceBroadcastInfoEventType) return null; + // Live location beacons should forward their latest location as a static pin location // If the beacon is not live, or doesn't have a location forwarding is not allowed if (M_BEACON_INFO.matches(event.getType())) { diff --git a/src/utils/EventUtils.ts b/src/utils/EventUtils.ts index eeebabd5c1..2459a9f745 100644 --- a/src/utils/EventUtils.ts +++ b/src/utils/EventUtils.ts @@ -32,6 +32,7 @@ import { TimelineRenderingType } from "../contexts/RoomContext"; import { launchPollEditor } from "../components/views/messages/MPollBody"; import { Action } from "../dispatcher/actions"; import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; +import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../voice-broadcast/types"; /** * Returns whether an event should allow actions like reply, reactions, edit, etc. @@ -56,7 +57,9 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean { } else if ( mxEvent.getType() === "m.sticker" || M_POLL_START.matches(mxEvent.getType()) || - M_BEACON_INFO.matches(mxEvent.getType()) + M_BEACON_INFO.matches(mxEvent.getType()) || + (mxEvent.getType() === VoiceBroadcastInfoEventType && + mxEvent.getContent()?.state === VoiceBroadcastInfoState.Started) ) { return true; } diff --git a/test/components/views/context_menus/MessageContextMenu-test.tsx b/test/components/views/context_menus/MessageContextMenu-test.tsx index 46a17ec786..3fdf26832d 100644 --- a/test/components/views/context_menus/MessageContextMenu-test.tsx +++ b/test/components/views/context_menus/MessageContextMenu-test.tsx @@ -42,14 +42,15 @@ import dispatcher from "../../../../src/dispatcher/dispatcher"; import SettingsStore from "../../../../src/settings/SettingsStore"; import { ReadPinsEventId } from "../../../../src/components/views/right_panel/types"; import { Action } from "../../../../src/dispatcher/actions"; +import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; +import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; jest.mock("../../../../src/utils/strings", () => ({ copyPlaintext: jest.fn(), getSelectedText: jest.fn(), })); jest.mock("../../../../src/utils/EventUtils", () => ({ - // @ts-ignore don't mock everything - ...jest.requireActual("../../../../src/utils/EventUtils"), + ...(jest.requireActual("../../../../src/utils/EventUtils") as object), canEditContent: jest.fn(), })); jest.mock("../../../../src/dispatcher/dispatcher"); @@ -241,6 +242,17 @@ describe("MessageContextMenu", () => { expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); }); + it("should not allow forwarding a voice broadcast", () => { + const broadcastStartEvent = mkVoiceBroadcastInfoStateEvent( + roomId, + VoiceBroadcastInfoState.Started, + "@user:example.com", + "ABC123", + ); + const menu = createMenu(broadcastStartEvent); + expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); + }); + describe("forwarding beacons", () => { const aliceId = "@alice:server.org"; diff --git a/test/components/views/messages/MessageActionBar-test.tsx b/test/components/views/messages/MessageActionBar-test.tsx index a3a3ffe141..8b64f205f0 100644 --- a/test/components/views/messages/MessageActionBar-test.tsx +++ b/test/components/views/messages/MessageActionBar-test.tsx @@ -15,8 +15,7 @@ limitations under the License. */ import React from "react"; -import { render, fireEvent } from "@testing-library/react"; -import { act } from "react-test-renderer"; +import { act, render, fireEvent } from "@testing-library/react"; import { EventType, EventStatus, MatrixEvent, MatrixEventEvent, MsgType, Room } from "matrix-js-sdk/src/matrix"; import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread"; @@ -34,6 +33,8 @@ import dispatcher from "../../../../src/dispatcher/dispatcher"; import SettingsStore from "../../../../src/settings/SettingsStore"; import { Action } from "../../../../src/dispatcher/actions"; import { UserTab } from "../../../../src/components/views/dialogs/UserTab"; +import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils"; +import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast"; jest.mock("../../../../src/dispatcher/dispatcher"); @@ -405,6 +406,17 @@ describe("<MessageActionBar />", () => { expect(queryByLabelText("Reply in thread")).toBeTruthy(); }); + it("does not render thread button for a voice broadcast", () => { + const broadcastEvent = mkVoiceBroadcastInfoStateEvent( + roomId, + VoiceBroadcastInfoState.Started, + userId, + "ABC123", + ); + const { queryByLabelText } = getComponent({ mxEvent: broadcastEvent }); + expect(queryByLabelText("Reply in thread")).not.toBeInTheDocument(); + }); + it("opens user settings on click", () => { jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); diff --git a/test/utils/EventUtils-test.ts b/test/utils/EventUtils-test.ts index ca77e64662..decf42931a 100644 --- a/test/utils/EventUtils-test.ts +++ b/test/utils/EventUtils-test.ts @@ -43,6 +43,8 @@ import { import { getMockClientWithEventEmitter, makeBeaconInfoEvent, makePollStartEvent, stubClient } from "../test-utils"; import dis from "../../src/dispatcher/dispatcher"; import { Action } from "../../src/dispatcher/actions"; +import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils"; +import { VoiceBroadcastInfoState } from "../../src/voice-broadcast/types"; jest.mock("../../src/dispatcher/dispatcher"); @@ -151,6 +153,20 @@ describe("EventUtils", () => { }, }); + const voiceBroadcastStart = mkVoiceBroadcastInfoStateEvent( + "!room:example.com", + VoiceBroadcastInfoState.Started, + "@user:example.com", + "ABC123", + ); + + const voiceBroadcastStop = mkVoiceBroadcastInfoStateEvent( + "!room:example.com", + VoiceBroadcastInfoState.Stopped, + "@user:example.com", + "ABC123", + ); + describe("isContentActionable()", () => { type TestCase = [string, MatrixEvent]; it.each<TestCase>([ @@ -161,6 +177,7 @@ describe("EventUtils", () => { ["room member event", roomMemberEvent], ["event without msgtype", noMsgType], ["event without content body property", noContentBody], + ["broadcast stop event", voiceBroadcastStop], ])("returns false for %s", (_description, event) => { expect(isContentActionable(event)).toBe(false); }); @@ -171,6 +188,7 @@ describe("EventUtils", () => { ["event with empty content body", emptyContentBody], ["event with a content body", niceTextMessage], ["beacon_info event", beaconInfoEvent], + ["broadcast start event", voiceBroadcastStart], ])("returns true for %s", (_description, event) => { expect(isContentActionable(event)).toBe(true); }); diff --git a/test/voice-broadcast/utils/test-utils.ts b/test/voice-broadcast/utils/test-utils.ts index cbf0a5989a..fc1ffd4b15 100644 --- a/test/voice-broadcast/utils/test-utils.ts +++ b/test/voice-broadcast/utils/test-utils.ts @@ -21,7 +21,7 @@ import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState, -} from "../../../src/voice-broadcast"; +} from "../../../src/voice-broadcast/types"; import { mkEvent } from "../../test-utils"; // timestamp incremented on each call to prevent duplicate timestamp