Allow reactions for broadcasts (#9856)

This commit is contained in:
Michael Weimann 2023-01-05 08:40:09 +01:00 committed by GitHub
parent 720bf0573a
commit 488a08a25e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 57 additions and 7 deletions

View File

@ -59,6 +59,7 @@ import { Action } from "../../../dispatcher/actions";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import useFavouriteMessages from "../../../hooks/useFavouriteMessages"; import useFavouriteMessages from "../../../hooks/useFavouriteMessages";
import { GetRelationsForEvent } from "../rooms/EventTile"; import { GetRelationsForEvent } from "../rooms/EventTile";
import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types";
interface IOptionsButtonProps { interface IOptionsButtonProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -394,7 +395,8 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
* until cross-platform support * until cross-platform support
* (PSF-1041) * (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; return inNotThreadTimeline && isAllowedMessageType;
} }

View File

@ -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 { MatrixEvent, MatrixClient } from "matrix-js-sdk/src/matrix";
import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation"; import { getShareableLocationEventForBeacon } from "../../utils/beacon/getShareableLocation";
import { VoiceBroadcastInfoEventType } from "../../voice-broadcast/types";
/** /**
* Get forwardable event for a given event * Get forwardable event for a given event
@ -29,6 +30,8 @@ export const getForwardableEvent = (event: MatrixEvent, cli: MatrixClient): Matr
return null; return null;
} }
if (event.getType() === VoiceBroadcastInfoEventType) return null;
// Live location beacons should forward their latest location as a static pin location // 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 the beacon is not live, or doesn't have a location forwarding is not allowed
if (M_BEACON_INFO.matches(event.getType())) { if (M_BEACON_INFO.matches(event.getType())) {

View File

@ -32,6 +32,7 @@ import { TimelineRenderingType } from "../contexts/RoomContext";
import { launchPollEditor } from "../components/views/messages/MPollBody"; import { launchPollEditor } from "../components/views/messages/MPollBody";
import { Action } from "../dispatcher/actions"; import { Action } from "../dispatcher/actions";
import { ViewRoomPayload } from "../dispatcher/payloads/ViewRoomPayload"; 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. * Returns whether an event should allow actions like reply, reactions, edit, etc.
@ -56,7 +57,9 @@ export function isContentActionable(mxEvent: MatrixEvent): boolean {
} else if ( } else if (
mxEvent.getType() === "m.sticker" || mxEvent.getType() === "m.sticker" ||
M_POLL_START.matches(mxEvent.getType()) || 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; return true;
} }

View File

@ -42,14 +42,15 @@ import dispatcher from "../../../../src/dispatcher/dispatcher";
import SettingsStore from "../../../../src/settings/SettingsStore"; import SettingsStore from "../../../../src/settings/SettingsStore";
import { ReadPinsEventId } from "../../../../src/components/views/right_panel/types"; import { ReadPinsEventId } from "../../../../src/components/views/right_panel/types";
import { Action } from "../../../../src/dispatcher/actions"; 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", () => ({ jest.mock("../../../../src/utils/strings", () => ({
copyPlaintext: jest.fn(), copyPlaintext: jest.fn(),
getSelectedText: jest.fn(), getSelectedText: jest.fn(),
})); }));
jest.mock("../../../../src/utils/EventUtils", () => ({ jest.mock("../../../../src/utils/EventUtils", () => ({
// @ts-ignore don't mock everything ...(jest.requireActual("../../../../src/utils/EventUtils") as object),
...jest.requireActual("../../../../src/utils/EventUtils"),
canEditContent: jest.fn(), canEditContent: jest.fn(),
})); }));
jest.mock("../../../../src/dispatcher/dispatcher"); jest.mock("../../../../src/dispatcher/dispatcher");
@ -241,6 +242,17 @@ describe("MessageContextMenu", () => {
expect(menu.find('div[aria-label="Forward"]')).toHaveLength(0); 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", () => { describe("forwarding beacons", () => {
const aliceId = "@alice:server.org"; const aliceId = "@alice:server.org";

View File

@ -15,8 +15,7 @@ limitations under the License.
*/ */
import React from "react"; import React from "react";
import { render, fireEvent } from "@testing-library/react"; import { act, render, fireEvent } from "@testing-library/react";
import { act } from "react-test-renderer";
import { EventType, EventStatus, MatrixEvent, MatrixEventEvent, MsgType, Room } from "matrix-js-sdk/src/matrix"; import { EventType, EventStatus, MatrixEvent, MatrixEventEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
import { FeatureSupport, Thread } from "matrix-js-sdk/src/models/thread"; 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 SettingsStore from "../../../../src/settings/SettingsStore";
import { Action } from "../../../../src/dispatcher/actions"; import { Action } from "../../../../src/dispatcher/actions";
import { UserTab } from "../../../../src/components/views/dialogs/UserTab"; 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"); jest.mock("../../../../src/dispatcher/dispatcher");
@ -405,6 +406,17 @@ describe("<MessageActionBar />", () => {
expect(queryByLabelText("Reply in thread")).toBeTruthy(); 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", () => { it("opens user settings on click", () => {
jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); jest.spyOn(SettingsStore, "getValue").mockReturnValue(false);
const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent }); const { getByLabelText } = getComponent({ mxEvent: alicesMessageEvent });

View File

@ -43,6 +43,8 @@ import {
import { getMockClientWithEventEmitter, makeBeaconInfoEvent, makePollStartEvent, stubClient } from "../test-utils"; import { getMockClientWithEventEmitter, makeBeaconInfoEvent, makePollStartEvent, stubClient } from "../test-utils";
import dis from "../../src/dispatcher/dispatcher"; import dis from "../../src/dispatcher/dispatcher";
import { Action } from "../../src/dispatcher/actions"; 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"); 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()", () => { describe("isContentActionable()", () => {
type TestCase = [string, MatrixEvent]; type TestCase = [string, MatrixEvent];
it.each<TestCase>([ it.each<TestCase>([
@ -161,6 +177,7 @@ describe("EventUtils", () => {
["room member event", roomMemberEvent], ["room member event", roomMemberEvent],
["event without msgtype", noMsgType], ["event without msgtype", noMsgType],
["event without content body property", noContentBody], ["event without content body property", noContentBody],
["broadcast stop event", voiceBroadcastStop],
])("returns false for %s", (_description, event) => { ])("returns false for %s", (_description, event) => {
expect(isContentActionable(event)).toBe(false); expect(isContentActionable(event)).toBe(false);
}); });
@ -171,6 +188,7 @@ describe("EventUtils", () => {
["event with empty content body", emptyContentBody], ["event with empty content body", emptyContentBody],
["event with a content body", niceTextMessage], ["event with a content body", niceTextMessage],
["beacon_info event", beaconInfoEvent], ["beacon_info event", beaconInfoEvent],
["broadcast start event", voiceBroadcastStart],
])("returns true for %s", (_description, event) => { ])("returns true for %s", (_description, event) => {
expect(isContentActionable(event)).toBe(true); expect(isContentActionable(event)).toBe(true);
}); });

View File

@ -21,7 +21,7 @@ import {
VoiceBroadcastChunkEventType, VoiceBroadcastChunkEventType,
VoiceBroadcastInfoEventType, VoiceBroadcastInfoEventType,
VoiceBroadcastInfoState, VoiceBroadcastInfoState,
} from "../../../src/voice-broadcast"; } from "../../../src/voice-broadcast/types";
import { mkEvent } from "../../test-utils"; import { mkEvent } from "../../test-utils";
// timestamp incremented on each call to prevent duplicate timestamp // timestamp incremented on each call to prevent duplicate timestamp