From 91e078d96b9e0c279d5ed8e48b0e8eb5b5689d40 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Mon, 2 Jan 2023 12:05:51 +0100 Subject: [PATCH] Show room broadcast when ending a recording (#9841) --- src/stores/RoomViewStore.tsx | 18 ++++++- .../stores/VoiceBroadcastRecordingsStore.ts | 20 ++++--- test/stores/RoomViewStore-test.ts | 53 +++++++++++++++++-- .../VoiceBroadcastRecordingsStore-test.ts | 7 +++ 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/stores/RoomViewStore.tsx b/src/stores/RoomViewStore.tsx index de8cc70efd..79b6f2f689 100644 --- a/src/stores/RoomViewStore.tsx +++ b/src/stores/RoomViewStore.tsx @@ -54,6 +54,8 @@ import { ThreadPayload } from "../dispatcher/payloads/ThreadPayload"; import { doClearCurrentVoiceBroadcastPlaybackIfStopped, doMaybeSetCurrentVoiceBroadcastPlayback, + VoiceBroadcastRecording, + VoiceBroadcastRecordingsStoreEvent, } from "../voice-broadcast"; import { IRoomStateEventsActionPayload } from "../actions/MatrixActionCreators"; import { showCantStartACallDialog } from "../voice-broadcast/utils/showCantStartACallDialog"; @@ -153,6 +155,10 @@ export class RoomViewStore extends EventEmitter { public constructor(dis: MatrixDispatcher, private readonly stores: SdkContextClass) { super(); this.resetDispatcher(dis); + this.stores.voiceBroadcastRecordingsStore.addListener( + VoiceBroadcastRecordingsStoreEvent.CurrentChanged, + this.onCurrentBroadcastRecordingChanged, + ); } public addRoomListener(roomId: string, fn: Listener): void { @@ -167,13 +173,23 @@ export class RoomViewStore extends EventEmitter { this.emit(roomId, isActive); } + private onCurrentBroadcastRecordingChanged = (recording: VoiceBroadcastRecording | null) => { + if (recording === null) { + const room = this.stores.client?.getRoom(this.state.roomId || undefined); + + if (room) { + this.doMaybeSetCurrentVoiceBroadcastPlayback(room); + } + } + }; + private setState(newState: Partial): void { // If values haven't changed, there's nothing to do. // This only tries a shallow comparison, so unchanged objects will slip // through, but that's probably okay for now. let stateChanged = false; for (const key of Object.keys(newState)) { - if (this.state[key] !== newState[key]) { + if (this.state[key as keyof State] !== newState[key as keyof State]) { stateChanged = true; break; } diff --git a/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts b/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts index b869014f31..b7eb7c36c9 100644 --- a/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts +++ b/src/voice-broadcast/stores/VoiceBroadcastRecordingsStore.ts @@ -24,7 +24,7 @@ export enum VoiceBroadcastRecordingsStoreEvent { } interface EventMap { - [VoiceBroadcastRecordingsStoreEvent.CurrentChanged]: (recording: VoiceBroadcastRecording) => void; + [VoiceBroadcastRecordingsStoreEvent.CurrentChanged]: (recording: VoiceBroadcastRecording | null) => void; } /** @@ -41,17 +41,23 @@ export class VoiceBroadcastRecordingsStore extends TypedEventEmitter { diff --git a/test/stores/RoomViewStore-test.ts b/test/stores/RoomViewStore-test.ts index 04f595a914..c5075beb77 100644 --- a/test/stores/RoomViewStore-test.ts +++ b/test/stores/RoomViewStore-test.ts @@ -79,6 +79,7 @@ jest.mock("../../src/utils/DMRoomMap", () => { describe("RoomViewStore", function () { const userId = "@alice:server"; const roomId = "!randomcharacters:aser.ver"; + const roomId2 = "!room2:example.com"; // we need to change the alias to ensure cache misses as the cache exists // through all tests. let alias = "#somealias2:aser.ver"; @@ -87,9 +88,14 @@ describe("RoomViewStore", function () { getRoom: jest.fn(), getRoomIdForAlias: jest.fn(), isGuest: jest.fn(), - getSafeUserId: jest.fn(), + getUserId: jest.fn().mockReturnValue(userId), + getSafeUserId: jest.fn().mockReturnValue(userId), + getDeviceId: jest.fn().mockReturnValue("ABC123"), + sendStateEvent: jest.fn().mockResolvedValue({}), + supportsExperimentalThreads: jest.fn(), }); const room = new Room(roomId, mockClient, userId); + const room2 = new Room(roomId2, mockClient, userId); const viewCall = async (): Promise => { dis.dispatch({ @@ -110,7 +116,11 @@ describe("RoomViewStore", function () { jest.clearAllMocks(); mockClient.credentials = { userId: userId }; mockClient.joinRoom.mockResolvedValue(room); - mockClient.getRoom.mockReturnValue(room); + mockClient.getRoom.mockImplementation((roomId: string): Room | null => { + if (roomId === room.roomId) return room; + if (roomId === room2.roomId) return room2; + return null; + }); mockClient.isGuest.mockReturnValue(false); mockClient.getSafeUserId.mockReturnValue(userId); @@ -118,6 +128,7 @@ describe("RoomViewStore", function () { dis = new MatrixDispatcher(); slidingSyncManager = new MockSlidingSyncManager(); stores = new TestSdkContext(); + stores.client = mockClient; stores._SlidingSyncManager = slidingSyncManager; stores._PosthogAnalytics = new MockPosthogAnalytics(); stores._SpaceStore = new MockSpaceStore(); @@ -142,7 +153,6 @@ describe("RoomViewStore", function () { }); it("emits ActiveRoomChanged when the viewed room changes", async () => { - const roomId2 = "!roomid:2"; dis.dispatch({ action: Action.ViewRoom, room_id: roomId }); let payload = (await untilDispatch(Action.ActiveRoomChanged, dis)) as ActiveRoomChangedPayload; expect(payload.newRoomId).toEqual(roomId); @@ -155,7 +165,6 @@ describe("RoomViewStore", function () { }); it("invokes room activity listeners when the viewed room changes", async () => { - const roomId2 = "!roomid:2"; const callback = jest.fn(); roomViewStore.addRoomListener(roomId, callback); dis.dispatch({ action: Action.ViewRoom, room_id: roomId }); @@ -225,7 +234,6 @@ describe("RoomViewStore", function () { }); it("swaps to the replied event room if it is not the current room", async () => { - const roomId2 = "!room2:bar"; dis.dispatch({ action: Action.ViewRoom, room_id: roomId }); await untilDispatch(Action.ActiveRoomChanged, dis); const replyToEvent = { @@ -295,6 +303,41 @@ describe("RoomViewStore", function () { expect(Modal.createDialog).toMatchSnapshot(); expect(roomViewStore.isViewingCall()).toBe(false); }); + + describe("and viewing a room with a broadcast", () => { + beforeEach(async () => { + const broadcastEvent = mkVoiceBroadcastInfoStateEvent( + roomId2, + VoiceBroadcastInfoState.Started, + mockClient.getSafeUserId(), + "ABC123", + ); + room2.addLiveEvents([broadcastEvent]); + + stores.voiceBroadcastPlaybacksStore.getByInfoEvent(broadcastEvent, mockClient); + dis.dispatch({ action: Action.ViewRoom, room_id: roomId2 }); + await untilDispatch(Action.ActiveRoomChanged, dis); + }); + + it("should continue recording", () => { + expect(stores.voiceBroadcastPlaybacksStore.getCurrent()).toBeNull(); + expect(stores.voiceBroadcastRecordingsStore.getCurrent()?.getState()).toBe( + VoiceBroadcastInfoState.Started, + ); + }); + + describe("and stopping the recording", () => { + beforeEach(async () => { + await stores.voiceBroadcastRecordingsStore.getCurrent()?.stop(); + // check test precondition + expect(stores.voiceBroadcastRecordingsStore.getCurrent()).toBeNull(); + }); + + it("should view the broadcast", () => { + expect(stores.voiceBroadcastPlaybacksStore.getCurrent()?.infoEvent.getRoomId()).toBe(roomId2); + }); + }); + }); }); describe("Sliding Sync", function () { diff --git a/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts b/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts index 3cea40df0a..fbfbf14ab0 100644 --- a/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts +++ b/test/voice-broadcast/stores/VoiceBroadcastRecordingsStore-test.ts @@ -69,6 +69,13 @@ describe("VoiceBroadcastRecordingsStore", () => { recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged); }); + it("when setting a recording without info event Id, it should raise an error", () => { + infoEvent.event.event_id = undefined; + expect(() => { + recordings.setCurrent(recording); + }).toThrowError("Got broadcast info event without Id"); + }); + describe("when setting a current Voice Broadcast recording", () => { beforeEach(() => { recordings.setCurrent(recording);