Improve live voice broadcast detection by testing if the started event has been redacted (#9780)
This commit is contained in:
parent
fbc3228143
commit
b81582d045
15 changed files with 365 additions and 83 deletions
|
@ -313,3 +313,13 @@ export const concat = (...arrays: Uint8Array[]): Uint8Array => {
|
|||
return concatenated;
|
||||
}, new Uint8Array(0));
|
||||
};
|
||||
|
||||
/**
|
||||
* Async version of Array.every.
|
||||
*/
|
||||
export async function asyncEvery<T>(values: T[], predicate: (value: T) => Promise<boolean>): Promise<boolean> {
|
||||
for (const value of values) {
|
||||
if (!(await predicate(value))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -14,18 +14,34 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { useContext, useEffect, useMemo, useState } from "react";
|
||||
import { Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { hasRoomLiveVoiceBroadcast } from "../utils/hasRoomLiveVoiceBroadcast";
|
||||
import { useTypedEventEmitter } from "../../hooks/useEventEmitter";
|
||||
import { SDKContext } from "../../contexts/SDKContext";
|
||||
|
||||
export const useHasRoomLiveVoiceBroadcast = (room: Room) => {
|
||||
const [hasLiveVoiceBroadcast, setHasLiveVoiceBroadcast] = useState(hasRoomLiveVoiceBroadcast(room).hasBroadcast);
|
||||
const sdkContext = useContext(SDKContext);
|
||||
const [hasLiveVoiceBroadcast, setHasLiveVoiceBroadcast] = useState(false);
|
||||
|
||||
useTypedEventEmitter(room.currentState, RoomStateEvent.Update, () => {
|
||||
setHasLiveVoiceBroadcast(hasRoomLiveVoiceBroadcast(room).hasBroadcast);
|
||||
});
|
||||
const update = useMemo(() => {
|
||||
return sdkContext.client
|
||||
? () => {
|
||||
hasRoomLiveVoiceBroadcast(sdkContext.client!, room).then(
|
||||
({ hasBroadcast }) => {
|
||||
setHasLiveVoiceBroadcast(hasBroadcast);
|
||||
},
|
||||
() => {}, // no update on error
|
||||
);
|
||||
}
|
||||
: () => {}; // noop without client
|
||||
}, [room, sdkContext, setHasLiveVoiceBroadcast]);
|
||||
|
||||
useEffect(() => {
|
||||
update();
|
||||
}, [update]);
|
||||
|
||||
useTypedEventEmitter(room.currentState, RoomStateEvent.Update, () => update());
|
||||
return hasLiveVoiceBroadcast;
|
||||
};
|
||||
|
|
|
@ -49,6 +49,7 @@ export * from "./utils/getChunkLength";
|
|||
export * from "./utils/getMaxBroadcastLength";
|
||||
export * from "./utils/hasRoomLiveVoiceBroadcast";
|
||||
export * from "./utils/findRoomLiveVoiceBroadcastFromUserAndDevice";
|
||||
export * from "./utils/retrieveStartedInfoEvent";
|
||||
export * from "./utils/shouldDisplayAsVoiceBroadcastRecordingTile";
|
||||
export * from "./utils/shouldDisplayAsVoiceBroadcastTile";
|
||||
export * from "./utils/shouldDisplayAsVoiceBroadcastStoppedText";
|
||||
|
|
|
@ -67,11 +67,11 @@ const showOthersAlreadyRecordingDialog = () => {
|
|||
});
|
||||
};
|
||||
|
||||
export const checkVoiceBroadcastPreConditions = (
|
||||
export const checkVoiceBroadcastPreConditions = async (
|
||||
room: Room,
|
||||
client: MatrixClient,
|
||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||
): boolean => {
|
||||
): Promise<boolean> => {
|
||||
if (recordingsStore.getCurrent()) {
|
||||
showAlreadyRecordingDialog();
|
||||
return false;
|
||||
|
@ -86,7 +86,7 @@ export const checkVoiceBroadcastPreConditions = (
|
|||
return false;
|
||||
}
|
||||
|
||||
const { hasBroadcast, startedByUser } = hasRoomLiveVoiceBroadcast(room, currentUserId);
|
||||
const { hasBroadcast, startedByUser } = await hasRoomLiveVoiceBroadcast(client, room, currentUserId);
|
||||
|
||||
if (hasBroadcast && startedByUser) {
|
||||
showAlreadyRecordingDialog();
|
||||
|
|
|
@ -34,12 +34,12 @@ import {
|
|||
* @param {VoiceBroadcastPlaybacksStore} voiceBroadcastPlaybacksStore
|
||||
* @param {VoiceBroadcastRecordingsStore} voiceBroadcastRecordingsStore
|
||||
*/
|
||||
export const doMaybeSetCurrentVoiceBroadcastPlayback = (
|
||||
export const doMaybeSetCurrentVoiceBroadcastPlayback = async (
|
||||
room: Room,
|
||||
client: MatrixClient,
|
||||
voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore,
|
||||
voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore,
|
||||
): void => {
|
||||
): Promise<void> => {
|
||||
// do not disturb the current recording
|
||||
if (voiceBroadcastRecordingsStore.hasCurrent()) return;
|
||||
|
||||
|
@ -50,7 +50,7 @@ export const doMaybeSetCurrentVoiceBroadcastPlayback = (
|
|||
return;
|
||||
}
|
||||
|
||||
const { infoEvent } = hasRoomLiveVoiceBroadcast(room);
|
||||
const { infoEvent } = await hasRoomLiveVoiceBroadcast(client, room);
|
||||
|
||||
if (infoEvent) {
|
||||
// live broadcast in the room + no recording + not listening yet: set the current broadcast
|
||||
|
|
|
@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
|
||||
import { retrieveStartedInfoEvent, VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "..";
|
||||
import { asyncEvery } from "../../utils/arrays";
|
||||
|
||||
interface Result {
|
||||
// whether there is a live broadcast in the room
|
||||
|
@ -27,22 +28,26 @@ interface Result {
|
|||
startedByUser: boolean;
|
||||
}
|
||||
|
||||
export const hasRoomLiveVoiceBroadcast = (room: Room, userId?: string): Result => {
|
||||
export const hasRoomLiveVoiceBroadcast = async (client: MatrixClient, room: Room, userId?: string): Promise<Result> => {
|
||||
let hasBroadcast = false;
|
||||
let startedByUser = false;
|
||||
let infoEvent: MatrixEvent | null = null;
|
||||
|
||||
const stateEvents = room.currentState.getStateEvents(VoiceBroadcastInfoEventType);
|
||||
stateEvents.every((event: MatrixEvent) => {
|
||||
await asyncEvery(stateEvents, async (event: MatrixEvent) => {
|
||||
const state = event.getContent()?.state;
|
||||
|
||||
if (state && state !== VoiceBroadcastInfoState.Stopped) {
|
||||
const startEvent = await retrieveStartedInfoEvent(event, client);
|
||||
|
||||
// skip if started voice broadcast event is redacted
|
||||
if (startEvent?.isRedacted()) return true;
|
||||
|
||||
hasBroadcast = true;
|
||||
infoEvent = event;
|
||||
infoEvent = startEvent;
|
||||
|
||||
// state key = sender's MXID
|
||||
if (event.getStateKey() === userId) {
|
||||
infoEvent = event;
|
||||
startedByUser = true;
|
||||
// break here, because more than true / true is not possible
|
||||
return false;
|
||||
|
|
45
src/voice-broadcast/utils/retrieveStartedInfoEvent.ts
Normal file
45
src/voice-broadcast/utils/retrieveStartedInfoEvent.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastInfoState } from "..";
|
||||
|
||||
export const retrieveStartedInfoEvent = async (
|
||||
event: MatrixEvent,
|
||||
client: MatrixClient,
|
||||
): Promise<MatrixEvent | null> => {
|
||||
// started event passed as argument
|
||||
if (event.getContent()?.state === VoiceBroadcastInfoState.Started) return event;
|
||||
|
||||
const relatedEventId = event.getRelation()?.event_id;
|
||||
|
||||
// no related event
|
||||
if (!relatedEventId) return null;
|
||||
|
||||
const roomId = event.getRoomId() || "";
|
||||
const relatedEventFromRoom = client.getRoom(roomId)?.findEventById(relatedEventId);
|
||||
|
||||
// event found
|
||||
if (relatedEventFromRoom) return relatedEventFromRoom;
|
||||
|
||||
try {
|
||||
const relatedEventData = await client.fetchRoomEvent(roomId, relatedEventId);
|
||||
return new MatrixEvent(relatedEventData);
|
||||
} catch (e) {}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -38,7 +38,7 @@ const startBroadcast = async (
|
|||
const userId = client.getUserId();
|
||||
|
||||
if (!userId) {
|
||||
reject("unable to start voice broadcast if current user is unkonwn");
|
||||
reject("unable to start voice broadcast if current user is unknown");
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ export const startNewVoiceBroadcastRecording = async (
|
|||
playbacksStore: VoiceBroadcastPlaybacksStore,
|
||||
recordingsStore: VoiceBroadcastRecordingsStore,
|
||||
): Promise<VoiceBroadcastRecording | null> => {
|
||||
if (!checkVoiceBroadcastPreConditions(room, client, recordingsStore)) {
|
||||
if (!(await checkVoiceBroadcastPreConditions(room, client, recordingsStore))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,17 +32,19 @@ import {
|
|||
useMockedCalls,
|
||||
setupAsyncStoreWithClient,
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
} from "../../../test-utils";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import RoomTile from "../../../../src/components/views/rooms/RoomTile";
|
||||
import { DefaultTagID } from "../../../../src/stores/room-list/models";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import PlatformPeg from "../../../../src/PlatformPeg";
|
||||
import BasePlatform from "../../../../src/BasePlatform";
|
||||
import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { TestSdkContext } from "../../../TestSdkContext";
|
||||
import { SDKContext } from "../../../../src/contexts/SDKContext";
|
||||
|
||||
describe("RoomTile", () => {
|
||||
jest.spyOn(PlatformPeg, "get").mockReturnValue({
|
||||
|
@ -50,22 +52,25 @@ describe("RoomTile", () => {
|
|||
} as unknown as BasePlatform);
|
||||
useMockedCalls();
|
||||
|
||||
const setUpVoiceBroadcast = (state: VoiceBroadcastInfoState): void => {
|
||||
const setUpVoiceBroadcast = async (state: VoiceBroadcastInfoState): Promise<void> => {
|
||||
voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
state,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([voiceBroadcastInfoEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
};
|
||||
|
||||
const renderRoomTile = (): void => {
|
||||
renderResult = render(
|
||||
<RoomTile room={room} showMessagePreview={false} isMinimized={false} tag={DefaultTagID.Untagged} />,
|
||||
<SDKContext.Provider value={sdkContext}>
|
||||
<RoomTile room={room} showMessagePreview={false} isMinimized={false} tag={DefaultTagID.Untagged} />
|
||||
</SDKContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -74,15 +79,18 @@ describe("RoomTile", () => {
|
|||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let room: Room;
|
||||
let renderResult: RenderResult;
|
||||
let sdkContext: TestSdkContext;
|
||||
|
||||
beforeEach(() => {
|
||||
sdkContext = new TestSdkContext();
|
||||
|
||||
restoreConsole = filterConsole(
|
||||
// irrelevant for this test
|
||||
"Room !1:example.org does not have an m.room.create event",
|
||||
);
|
||||
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.get());
|
||||
client = mocked(stubClient());
|
||||
sdkContext.client = client;
|
||||
DMRoomMap.makeShared();
|
||||
|
||||
room = new Room("!1:example.org", client, "@alice:example.org", {
|
||||
|
@ -136,7 +144,7 @@ describe("RoomTile", () => {
|
|||
|
||||
// Insert an await point in the connection method so we can inspect
|
||||
// the intermediate connecting state
|
||||
let completeConnection: () => void;
|
||||
let completeConnection: () => void = () => {};
|
||||
const connectionCompleted = new Promise<void>((resolve) => (completeConnection = resolve));
|
||||
jest.spyOn(call, "performConnection").mockReturnValue(connectionCompleted);
|
||||
|
||||
|
@ -180,8 +188,8 @@ describe("RoomTile", () => {
|
|||
});
|
||||
|
||||
describe("and a live broadcast starts", () => {
|
||||
beforeEach(() => {
|
||||
setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
beforeEach(async () => {
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should still render the call subtitle", () => {
|
||||
|
@ -192,8 +200,8 @@ describe("RoomTile", () => {
|
|||
});
|
||||
|
||||
describe("when a live voice broadcast starts", () => {
|
||||
beforeEach(() => {
|
||||
setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
beforeEach(async () => {
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should render the »Live« subtitle", () => {
|
||||
|
@ -201,16 +209,17 @@ describe("RoomTile", () => {
|
|||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId(),
|
||||
client.getDeviceId(),
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
voiceBroadcastInfoEvent,
|
||||
);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import {
|
|||
wrapInMatrixClientContext,
|
||||
wrapInSdkContext,
|
||||
mkRoomCreateEvent,
|
||||
flushPromises,
|
||||
} from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
|
@ -70,6 +71,12 @@ describe("PipView", () => {
|
|||
let voiceBroadcastPreRecordingStore: VoiceBroadcastPreRecordingStore;
|
||||
let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore;
|
||||
|
||||
const actFlushPromises = async () => {
|
||||
await act(async () => {
|
||||
await flushPromises();
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
stubClient();
|
||||
client = mocked(MatrixClientPeg.get());
|
||||
|
@ -264,10 +271,11 @@ describe("PipView", () => {
|
|||
});
|
||||
|
||||
describe("when there is a voice broadcast recording and pre-recording", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
setUpVoiceBroadcastRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast recording PiP", () => {
|
||||
|
@ -277,10 +285,11 @@ describe("PipView", () => {
|
|||
});
|
||||
|
||||
describe("when there is a voice broadcast playback and pre-recording", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
mkVoiceBroadcast(room);
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast pre-recording PiP", () => {
|
||||
|
@ -290,9 +299,10 @@ describe("PipView", () => {
|
|||
});
|
||||
|
||||
describe("when there is a voice broadcast pre-recording", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast pre-recording PiP", () => {
|
||||
|
@ -306,6 +316,10 @@ describe("PipView", () => {
|
|||
setUpRoomViewStore();
|
||||
viewRoom(room.roomId);
|
||||
mkVoiceBroadcast(room);
|
||||
await actFlushPromises();
|
||||
|
||||
expect(voiceBroadcastPlaybacksStore.getCurrent()).toBeTruthy();
|
||||
|
||||
await voiceBroadcastPlaybacksStore.getCurrent()?.start();
|
||||
viewRoom(room2.roomId);
|
||||
renderPip();
|
||||
|
@ -322,11 +336,12 @@ describe("PipView", () => {
|
|||
describe("when viewing a room with a live voice broadcast", () => {
|
||||
let startEvent!: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
setUpRoomViewStore();
|
||||
viewRoom(room.roomId);
|
||||
startEvent = mkVoiceBroadcast(room);
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast playback pip", () => {
|
||||
|
@ -335,8 +350,7 @@ describe("PipView", () => {
|
|||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
|
@ -344,6 +358,8 @@ describe("PipView", () => {
|
|||
client.getDeviceId() || "",
|
||||
startEvent,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>(
|
||||
{
|
||||
|
@ -354,6 +370,7 @@ describe("PipView", () => {
|
|||
},
|
||||
true,
|
||||
);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -364,9 +381,10 @@ describe("PipView", () => {
|
|||
});
|
||||
|
||||
describe("and leaving the room", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
viewRoom(room2.roomId);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ export function createTestClient(): MatrixClient {
|
|||
hasLazyLoadMembersEnabled: jest.fn().mockReturnValue(false),
|
||||
isInitialSyncComplete: jest.fn().mockReturnValue(true),
|
||||
downloadKeys: jest.fn(),
|
||||
fetchRoomEvent: jest.fn(),
|
||||
fetchRoomEvent: jest.fn().mockRejectedValue({}),
|
||||
makeTxnId: jest.fn().mockImplementation(() => `t${txnId++}`),
|
||||
sendToDevice: jest.fn().mockResolvedValue(undefined),
|
||||
queueToDevice: jest.fn().mockResolvedValue(undefined),
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
ArrayUtil,
|
||||
GroupedArray,
|
||||
concat,
|
||||
asyncEvery,
|
||||
} from "../../src/utils/arrays";
|
||||
|
||||
type TestParams = { input: number[]; output: number[] };
|
||||
|
@ -413,4 +414,34 @@ describe("arrays", () => {
|
|||
expect(concat(array1(), array2(), array3())).toEqual(new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9]));
|
||||
});
|
||||
});
|
||||
|
||||
describe("asyncEvery", () => {
|
||||
it("when called with an empty array, it should return true", async () => {
|
||||
expect(await asyncEvery([], jest.fn().mockResolvedValue(true))).toBe(true);
|
||||
});
|
||||
|
||||
it("when called with some items and the predicate resolves to true for all of them, it should return true", async () => {
|
||||
const predicate = jest.fn().mockResolvedValue(true);
|
||||
expect(await asyncEvery([1, 2, 3], predicate)).toBe(true);
|
||||
expect(predicate).toHaveBeenCalledTimes(3);
|
||||
expect(predicate).toHaveBeenCalledWith(1);
|
||||
expect(predicate).toHaveBeenCalledWith(2);
|
||||
expect(predicate).toHaveBeenCalledWith(3);
|
||||
});
|
||||
|
||||
it("when called with some items and the predicate resolves to false for all of them, it should return false", async () => {
|
||||
const predicate = jest.fn().mockResolvedValue(false);
|
||||
expect(await asyncEvery([1, 2, 3], predicate)).toBe(false);
|
||||
expect(predicate).toHaveBeenCalledTimes(1);
|
||||
expect(predicate).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("when called with some items and the predicate resolves to false for one of them, it should return false", async () => {
|
||||
const predicate = jest.fn().mockResolvedValueOnce(true).mockResolvedValueOnce(false);
|
||||
expect(await asyncEvery([1, 2, 3], predicate)).toBe(false);
|
||||
expect(predicate).toHaveBeenCalledTimes(2);
|
||||
expect(predicate).toHaveBeenCalledWith(1);
|
||||
expect(predicate).toHaveBeenCalledWith(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
|
@ -26,20 +27,28 @@ import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
|||
|
||||
describe("hasRoomLiveVoiceBroadcast", () => {
|
||||
const otherUserId = "@other:example.com";
|
||||
const otherDeviceId = "ASD123";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let expectedEvent: MatrixEvent | null = null;
|
||||
|
||||
const addVoiceBroadcastInfoEvent = (state: VoiceBroadcastInfoState, sender: string): MatrixEvent => {
|
||||
const infoEvent = mkVoiceBroadcastInfoStateEvent(room.roomId, state, sender, "ASD123");
|
||||
const addVoiceBroadcastInfoEvent = (
|
||||
state: VoiceBroadcastInfoState,
|
||||
userId: string,
|
||||
deviceId: string,
|
||||
startedEvent?: MatrixEvent,
|
||||
): MatrixEvent => {
|
||||
const infoEvent = mkVoiceBroadcastInfoStateEvent(room.roomId, state, userId, deviceId, startedEvent);
|
||||
room.addLiveEvents([infoEvent]);
|
||||
room.currentState.setStateEvents([infoEvent]);
|
||||
room.relations.aggregateChildEvent(infoEvent);
|
||||
return infoEvent;
|
||||
};
|
||||
|
||||
const itShouldReturnTrueTrue = () => {
|
||||
it("should return true/true", () => {
|
||||
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
|
||||
it("should return true/true", async () => {
|
||||
expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({
|
||||
hasBroadcast: true,
|
||||
infoEvent: expectedEvent,
|
||||
startedByUser: true,
|
||||
|
@ -48,8 +57,8 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
};
|
||||
|
||||
const itShouldReturnTrueFalse = () => {
|
||||
it("should return true/false", () => {
|
||||
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
|
||||
it("should return true/false", async () => {
|
||||
expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({
|
||||
hasBroadcast: true,
|
||||
infoEvent: expectedEvent,
|
||||
startedByUser: false,
|
||||
|
@ -58,8 +67,8 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
};
|
||||
|
||||
const itShouldReturnFalseFalse = () => {
|
||||
it("should return false/false", () => {
|
||||
expect(hasRoomLiveVoiceBroadcast(room, client.getUserId())).toEqual({
|
||||
it("should return false/false", async () => {
|
||||
expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({
|
||||
hasBroadcast: false,
|
||||
infoEvent: null,
|
||||
startedByUser: false,
|
||||
|
@ -67,13 +76,13 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
});
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getSafeUserId());
|
||||
mocked(client.getRoom).mockImplementation((roomId: string): Room | null => {
|
||||
return roomId === room.roomId ? room : null;
|
||||
});
|
||||
expectedEvent = null;
|
||||
room = new Room(roomId, client, client.getUserId());
|
||||
});
|
||||
|
||||
describe("when there is no voice broadcast info at all", () => {
|
||||
|
@ -86,9 +95,9 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
mkEvent({
|
||||
event: true,
|
||||
room: room.roomId,
|
||||
user: client.getUserId(),
|
||||
user: client.getSafeUserId(),
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
skey: client.getUserId(),
|
||||
skey: client.getSafeUserId(),
|
||||
content: {},
|
||||
}),
|
||||
]);
|
||||
|
@ -98,8 +107,12 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
|
||||
describe("when there is a live broadcast from the current and another user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, client.getUserId());
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId);
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId, otherDeviceId);
|
||||
});
|
||||
|
||||
itShouldReturnTrueTrue();
|
||||
|
@ -107,21 +120,56 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
|
||||
describe("when there are only stopped info events", () => {
|
||||
beforeEach(() => {
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getUserId());
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, otherUserId);
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getSafeUserId(), client.getDeviceId()!);
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, otherUserId, otherDeviceId);
|
||||
});
|
||||
|
||||
itShouldReturnFalseFalse();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
// all there are kind of live states
|
||||
VoiceBroadcastInfoState.Started,
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
])("when there is a live broadcast (%s) from the current user", (state: VoiceBroadcastInfoState) => {
|
||||
describe("when there is a live, started broadcast from the current user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(state, client.getUserId());
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
});
|
||||
|
||||
itShouldReturnTrueTrue();
|
||||
});
|
||||
|
||||
describe("when there is a live, paused broadcast from the current user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
expectedEvent,
|
||||
);
|
||||
});
|
||||
|
||||
itShouldReturnTrueTrue();
|
||||
});
|
||||
|
||||
describe("when there is a live, resumed broadcast from the current user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
expectedEvent,
|
||||
);
|
||||
});
|
||||
|
||||
itShouldReturnTrueTrue();
|
||||
|
@ -129,8 +177,17 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
|
||||
describe("when there was a live broadcast, that has been stopped", () => {
|
||||
beforeEach(() => {
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, client.getUserId());
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getUserId());
|
||||
const startedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
startedEvent,
|
||||
);
|
||||
});
|
||||
|
||||
itShouldReturnFalseFalse();
|
||||
|
@ -138,7 +195,7 @@ describe("hasRoomLiveVoiceBroadcast", () => {
|
|||
|
||||
describe("when there is a live broadcast from another user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Resumed, otherUserId);
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId, otherDeviceId);
|
||||
});
|
||||
|
||||
itShouldReturnTrueFalse();
|
||||
|
|
90
test/voice-broadcast/utils/retrieveStartedInfoEvent-test.ts
Normal file
90
test/voice-broadcast/utils/retrieveStartedInfoEvent-test.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||
import { MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
retrieveStartedInfoEvent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("retrieveStartedInfoEvent", () => {
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const mkStartEvent = () => {
|
||||
return mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
};
|
||||
|
||||
const mkStopEvent = (startEvent: MatrixEvent) => {
|
||||
return mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
startEvent,
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room("!room:example.com", client, client.getUserId()!);
|
||||
mocked(client.getRoom).mockImplementation((roomId: string): Room | null => {
|
||||
if (roomId === room.roomId) return room;
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
it("when passing a started event, it should return the event", async () => {
|
||||
const event = mkStartEvent();
|
||||
expect(await retrieveStartedInfoEvent(event, client)).toBe(event);
|
||||
});
|
||||
|
||||
it("when passing an event without relation, it should return null", async () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getUserId()!,
|
||||
content: {},
|
||||
});
|
||||
expect(await retrieveStartedInfoEvent(event, client)).toBeNull();
|
||||
});
|
||||
|
||||
it("when the room contains the event, it should return it", async () => {
|
||||
const startEvent = mkStartEvent();
|
||||
const stopEvent = mkStopEvent(startEvent);
|
||||
room.addLiveEvents([startEvent]);
|
||||
expect(await retrieveStartedInfoEvent(stopEvent, client)).toBe(startEvent);
|
||||
});
|
||||
|
||||
it("when the room not contains the event, it should fetch it", async () => {
|
||||
const startEvent = mkStartEvent();
|
||||
const stopEvent = mkStopEvent(startEvent);
|
||||
mocked(client.fetchRoomEvent).mockResolvedValue(startEvent.event);
|
||||
expect((await retrieveStartedInfoEvent(stopEvent, client))?.getId()).toBe(startEvent.getId());
|
||||
expect(client.fetchRoomEvent).toHaveBeenCalledWith(room.roomId, startEvent.getId());
|
||||
});
|
||||
});
|
|
@ -75,7 +75,7 @@ describe("setUpVoiceBroadcastPreRecording", () => {
|
|||
|
||||
describe("when the preconditions fail", () => {
|
||||
beforeEach(() => {
|
||||
mocked(checkVoiceBroadcastPreConditions).mockReturnValue(false);
|
||||
mocked(checkVoiceBroadcastPreConditions).mockResolvedValue(false);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
|
@ -83,7 +83,7 @@ describe("setUpVoiceBroadcastPreRecording", () => {
|
|||
|
||||
describe("when the preconditions pass", () => {
|
||||
beforeEach(() => {
|
||||
mocked(checkVoiceBroadcastPreConditions).mockReturnValue(true);
|
||||
mocked(checkVoiceBroadcastPreConditions).mockResolvedValue(true);
|
||||
});
|
||||
|
||||
describe("and there is no user id", () => {
|
||||
|
|
Loading…
Reference in a new issue