Merge matrix-react-sdk into element-web
Merge remote-tracking branch 'repomerge/t3chguy/repomerge' into t3chguy/repo-merge Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
commit
f0ee7f7905
3265 changed files with 484599 additions and 699 deletions
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastChunkEvents } from "../../../../src/voice-broadcast/utils/VoiceBroadcastChunkEvents";
|
||||
import { mkVoiceBroadcastChunkEvent } from "./test-utils";
|
||||
|
||||
describe("VoiceBroadcastChunkEvents", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
const txnId = "txn-id";
|
||||
let eventSeq1Time1: MatrixEvent;
|
||||
let eventSeq2Time4: MatrixEvent;
|
||||
let eventSeq3Time2: MatrixEvent;
|
||||
let eventSeq3Time2T: MatrixEvent;
|
||||
let eventSeq4Time1: MatrixEvent;
|
||||
let eventSeqUTime3: MatrixEvent;
|
||||
let eventSeq2Time4Dup: MatrixEvent;
|
||||
let chunkEvents: VoiceBroadcastChunkEvents;
|
||||
|
||||
beforeEach(() => {
|
||||
eventSeq1Time1 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 7, 1, 1);
|
||||
eventSeq2Time4 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 23, 2, 4);
|
||||
eventSeq2Time4Dup = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 3141, 2, 4);
|
||||
jest.spyOn(eventSeq2Time4Dup, "getId").mockReturnValue(eventSeq2Time4.getId());
|
||||
eventSeq3Time2 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 42, 3, 2);
|
||||
eventSeq3Time2.setTxnId(txnId);
|
||||
eventSeq3Time2T = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 42, 3, 2);
|
||||
eventSeq3Time2T.setTxnId(txnId);
|
||||
eventSeq4Time1 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 69, 4, 1);
|
||||
eventSeqUTime3 = mkVoiceBroadcastChunkEvent("info1", userId, roomId, 314, undefined, 3);
|
||||
chunkEvents = new VoiceBroadcastChunkEvents();
|
||||
});
|
||||
|
||||
describe("when adding events that all have a sequence", () => {
|
||||
beforeEach(() => {
|
||||
chunkEvents.addEvent(eventSeq2Time4);
|
||||
chunkEvents.addEvent(eventSeq1Time1);
|
||||
chunkEvents.addEvents([eventSeq4Time1, eventSeq2Time4Dup, eventSeq3Time2]);
|
||||
});
|
||||
|
||||
it("should provide the events sort by sequence", () => {
|
||||
expect(chunkEvents.getEvents()).toEqual([
|
||||
eventSeq1Time1,
|
||||
eventSeq2Time4Dup,
|
||||
eventSeq3Time2,
|
||||
eventSeq4Time1,
|
||||
]);
|
||||
});
|
||||
|
||||
it("getNumberOfEvents should return 4", () => {
|
||||
expect(chunkEvents.getNumberOfEvents()).toBe(4);
|
||||
});
|
||||
|
||||
it("getLength should return the total length of all chunks", () => {
|
||||
expect(chunkEvents.getLength()).toBe(3259);
|
||||
});
|
||||
|
||||
it("getLengthTo(first event) should return 0", () => {
|
||||
expect(chunkEvents.getLengthTo(eventSeq1Time1)).toBe(0);
|
||||
});
|
||||
|
||||
it("getLengthTo(some event) should return the time excl. that event", () => {
|
||||
expect(chunkEvents.getLengthTo(eventSeq3Time2)).toBe(7 + 3141);
|
||||
});
|
||||
|
||||
it("getLengthTo(last event) should return the time excl. that event", () => {
|
||||
expect(chunkEvents.getLengthTo(eventSeq4Time1)).toBe(7 + 3141 + 42);
|
||||
});
|
||||
|
||||
it("should return the expected next chunk", () => {
|
||||
expect(chunkEvents.getNext(eventSeq2Time4Dup)).toBe(eventSeq3Time2);
|
||||
});
|
||||
|
||||
it("should return undefined for next last chunk", () => {
|
||||
expect(chunkEvents.getNext(eventSeq4Time1)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("findByTime(0) should return the first chunk", () => {
|
||||
expect(chunkEvents.findByTime(0)).toBe(eventSeq1Time1);
|
||||
});
|
||||
|
||||
it("findByTime(some time) should return the chunk with this time", () => {
|
||||
expect(chunkEvents.findByTime(7 + 3141 + 21)).toBe(eventSeq3Time2);
|
||||
});
|
||||
|
||||
it("findByTime(entire duration) should return the last chunk", () => {
|
||||
expect(chunkEvents.findByTime(7 + 3141 + 42 + 69)).toBe(eventSeq4Time1);
|
||||
});
|
||||
|
||||
describe("and adding an event with a known transaction Id", () => {
|
||||
beforeEach(() => {
|
||||
chunkEvents.addEvent(eventSeq3Time2T);
|
||||
});
|
||||
|
||||
it("should replace the previous event", () => {
|
||||
expect(chunkEvents.getEvents()).toEqual([
|
||||
eventSeq1Time1,
|
||||
eventSeq2Time4Dup,
|
||||
eventSeq3Time2T,
|
||||
eventSeq4Time1,
|
||||
]);
|
||||
expect(chunkEvents.getNumberOfEvents()).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when adding events where at least one does not have a sequence", () => {
|
||||
beforeEach(() => {
|
||||
chunkEvents.addEvent(eventSeq2Time4);
|
||||
chunkEvents.addEvent(eventSeq1Time1);
|
||||
chunkEvents.addEvents([eventSeq4Time1, eventSeqUTime3, eventSeq2Time4Dup, eventSeq3Time2]);
|
||||
});
|
||||
|
||||
it("should provide the events sort by timestamp without duplicates", () => {
|
||||
expect(chunkEvents.getEvents()).toEqual([
|
||||
eventSeq1Time1,
|
||||
eventSeq4Time1,
|
||||
eventSeq3Time2,
|
||||
eventSeqUTime3,
|
||||
eventSeq2Time4Dup,
|
||||
]);
|
||||
expect(chunkEvents.getNumberOfEvents()).toBe(5);
|
||||
});
|
||||
|
||||
describe("getSequenceForEvent", () => {
|
||||
it("should return the sequence if provided by the event", () => {
|
||||
expect(chunkEvents.getSequenceForEvent(eventSeq3Time2)).toBe(3);
|
||||
});
|
||||
|
||||
it("should return the index if no sequence provided by event", () => {
|
||||
expect(chunkEvents.getSequenceForEvent(eventSeqUTime3)).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { ClientEvent, MatrixClient, MatrixEvent, RelationType, Room, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoEventContent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastResumer,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("VoiceBroadcastResumer", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let resumer: VoiceBroadcastResumer;
|
||||
let startedInfoEvent: MatrixEvent;
|
||||
let pausedInfoEvent: MatrixEvent;
|
||||
|
||||
const itShouldNotSendAStateEvent = (): void => {
|
||||
it("should not send a state event", () => {
|
||||
expect(client.sendStateEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldSendAStoppedStateEvent = (): void => {
|
||||
it("should send a stopped state event", () => {
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
startedInfoEvent.getRoomId(),
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
"device_id": client.getDeviceId(),
|
||||
"state": VoiceBroadcastInfoState.Stopped,
|
||||
"m.relates_to": {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: startedInfoEvent.getId(),
|
||||
},
|
||||
} as VoiceBroadcastInfoEventContent,
|
||||
client.getUserId()!,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldDeregisterFromTheClient = () => {
|
||||
it("should deregister from the client", () => {
|
||||
expect(client.off).toHaveBeenCalledWith(ClientEvent.Sync, expect.any(Function));
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
jest.spyOn(client, "off");
|
||||
room = new Room(roomId, client, client.getUserId()!);
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string | undefined) => {
|
||||
if (getRoomId === roomId) return room;
|
||||
|
||||
return null;
|
||||
});
|
||||
mocked(client.getRooms).mockReturnValue([room]);
|
||||
startedInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
pausedInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Paused,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
startedInfoEvent,
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("when the initial sync is completed", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.isInitialSyncComplete).mockReturnValue(true);
|
||||
});
|
||||
|
||||
describe("and there is no info event", () => {
|
||||
beforeEach(() => {
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
});
|
||||
|
||||
itShouldNotSendAStateEvent();
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
resumer.destroy();
|
||||
});
|
||||
|
||||
itShouldDeregisterFromTheClient();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there is a started info event", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([startedInfoEvent]);
|
||||
});
|
||||
|
||||
describe("and the client knows about the user and device", () => {
|
||||
beforeEach(() => {
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
});
|
||||
|
||||
itShouldSendAStoppedStateEvent();
|
||||
});
|
||||
|
||||
describe("and the client doesn't know about the user", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.getUserId).mockReturnValue(null);
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
});
|
||||
|
||||
itShouldNotSendAStateEvent();
|
||||
});
|
||||
|
||||
describe("and the client doesn't know about the device", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.getDeviceId).mockReturnValue(null);
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
});
|
||||
|
||||
itShouldNotSendAStateEvent();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there is a paused info event", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([pausedInfoEvent]);
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
});
|
||||
|
||||
itShouldSendAStoppedStateEvent();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the initial sync is not completed", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([pausedInfoEvent]);
|
||||
mocked(client.isInitialSyncComplete).mockReturnValue(false);
|
||||
mocked(client.getSyncState).mockReturnValue(SyncState.Prepared);
|
||||
resumer = new VoiceBroadcastResumer(client);
|
||||
});
|
||||
|
||||
itShouldNotSendAStateEvent();
|
||||
|
||||
describe("and a sync event appears", () => {
|
||||
beforeEach(() => {
|
||||
client.emit(ClientEvent.Sync, SyncState.Prepared, SyncState.Stopped);
|
||||
});
|
||||
|
||||
itShouldNotSendAStateEvent();
|
||||
|
||||
describe("and the initial sync completed and a sync event appears", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.getSyncState).mockReturnValue(SyncState.Syncing);
|
||||
client.emit(ClientEvent.Sync, SyncState.Syncing, SyncState.Prepared);
|
||||
});
|
||||
|
||||
itShouldSendAStoppedStateEvent();
|
||||
itShouldDeregisterFromTheClient();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`setUpVoiceBroadcastPreRecording when trying to start a broadcast if there is no connection should show an info dialog and not set up a pre-recording 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
Unfortunately we're unable to start a recording right now. Please try again later.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Connection error",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,116 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of another user should show an info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can't start a new voice broadcast",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there already is a live broadcast of the current user in the room should show an info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can't start a new voice broadcast",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is allowed to send voice broadcast info state events when there is already a current voice broadcast should show an info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can't start a new voice broadcast",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when the current user is not allowed to send voice broadcast info state events should show an info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can't start a new voice broadcast",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`startNewVoiceBroadcastRecording when trying to start a broadcast if there is no connection should show an info dialog and not start a recording 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
Unfortunately we're unable to start a recording right now. Please try again later.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Connection error",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -0,0 +1,42 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`textForVoiceBroadcastStoppedEvent should render other users broadcast as expected 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
@other:example.com ended a voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`textForVoiceBroadcastStoppedEvent should render own broadcast as expected 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
You ended a voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`textForVoiceBroadcastStoppedEvent should render without login as expected 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
@other:example.com ended a voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`textForVoiceBroadcastStoppedEvent when rendering an event with relation to the start event should render events with relation to the start event 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<span>
|
||||
You ended a
|
||||
<div
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_link_inline"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
voice broadcast
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import {
|
||||
cleanUpBroadcasts,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastRecording,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
import { mkVoiceBroadcastPlayback, mkVoiceBroadcastPreRecording, mkVoiceBroadcastRecording } from "./test-utils";
|
||||
|
||||
describe("cleanUpBroadcasts", () => {
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let recording: VoiceBroadcastRecording;
|
||||
let preRecording: VoiceBroadcastPreRecording;
|
||||
let stores: TestSdkContext;
|
||||
|
||||
beforeEach(() => {
|
||||
stores = new TestSdkContext();
|
||||
stores.client = stubClient();
|
||||
|
||||
playback = mkVoiceBroadcastPlayback(stores);
|
||||
jest.spyOn(playback, "stop").mockReturnValue();
|
||||
stores.voiceBroadcastPlaybacksStore.setCurrent(playback);
|
||||
|
||||
recording = mkVoiceBroadcastRecording(stores);
|
||||
jest.spyOn(recording, "stop").mockResolvedValue();
|
||||
stores.voiceBroadcastRecordingsStore.setCurrent(recording);
|
||||
|
||||
preRecording = mkVoiceBroadcastPreRecording(stores);
|
||||
jest.spyOn(preRecording, "cancel").mockReturnValue();
|
||||
stores.voiceBroadcastPreRecordingStore.setCurrent(preRecording);
|
||||
});
|
||||
|
||||
it("should stop and clear all broadcast related stuff", async () => {
|
||||
await cleanUpBroadcasts(stores);
|
||||
expect(playback.stop).toHaveBeenCalled();
|
||||
expect(stores.voiceBroadcastPlaybacksStore.getCurrent()).toBeNull();
|
||||
expect(recording.stop).toHaveBeenCalled();
|
||||
expect(stores.voiceBroadcastRecordingsStore.getCurrent()).toBeNull();
|
||||
expect(preRecording.cancel).toHaveBeenCalled();
|
||||
expect(stores.voiceBroadcastPreRecordingStore.getCurrent()).toBeNull();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { VoiceBroadcastInfoState, VoiceBroadcastLiveness } from "../../../../src/voice-broadcast";
|
||||
import { determineVoiceBroadcastLiveness } from "../../../../src/voice-broadcast/utils/determineVoiceBroadcastLiveness";
|
||||
|
||||
const testData: Array<{ state: VoiceBroadcastInfoState; expected: VoiceBroadcastLiveness }> = [
|
||||
{ state: VoiceBroadcastInfoState.Started, expected: "live" },
|
||||
{ state: VoiceBroadcastInfoState.Resumed, expected: "live" },
|
||||
{ state: VoiceBroadcastInfoState.Paused, expected: "grey" },
|
||||
{ state: VoiceBroadcastInfoState.Stopped, expected: "not-live" },
|
||||
];
|
||||
|
||||
describe("determineVoiceBroadcastLiveness", () => {
|
||||
it.each(testData)("should return the expected value for a %s broadcast", ({ state, expected }) => {
|
||||
expect(determineVoiceBroadcastLiveness(state)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should return »non-live« for an undefined state", () => {
|
||||
// @ts-ignore
|
||||
expect(determineVoiceBroadcastLiveness(undefined)).toBe("not-live");
|
||||
});
|
||||
|
||||
it("should return »non-live« for an unknown state", () => {
|
||||
// @ts-ignore
|
||||
expect(determineVoiceBroadcastLiveness("unknown test state")).toBe("not-live");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
findRoomLiveVoiceBroadcastFromUserAndDevice,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("findRoomLiveVoiceBroadcastFromUserAndDevice", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
|
||||
const itShouldReturnNull = () => {
|
||||
it("should return null", () => {
|
||||
expect(
|
||||
findRoomLiveVoiceBroadcastFromUserAndDevice(room, client.getUserId()!, client.getDeviceId()!),
|
||||
).toBeNull();
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getUserId()!);
|
||||
jest.spyOn(room.currentState, "getStateEvents");
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
||||
if (getRoomId === roomId) return room;
|
||||
return null;
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is no info event", () => {
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is an info event without content", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: client.getUserId()!,
|
||||
content: {},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is a stopped info event", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId(),
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is a started info event from another device", () => {
|
||||
beforeEach(() => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId()!,
|
||||
"JKL123",
|
||||
);
|
||||
room.currentState.setStateEvents([event]);
|
||||
});
|
||||
|
||||
itShouldReturnNull();
|
||||
});
|
||||
|
||||
describe("when there is a started info event", () => {
|
||||
let event: MatrixEvent;
|
||||
|
||||
beforeEach(() => {
|
||||
event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId(),
|
||||
);
|
||||
room.currentState.setStateEvents([event]);
|
||||
});
|
||||
|
||||
it("should return this event", () => {
|
||||
expect(room.currentState.getStateEvents).toHaveBeenCalledWith(
|
||||
VoiceBroadcastInfoEventType,
|
||||
client.getUserId()!,
|
||||
);
|
||||
|
||||
expect(findRoomLiveVoiceBroadcastFromUserAndDevice(room, client.getUserId()!, client.getDeviceId()!)).toBe(
|
||||
event,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
63
test/unit-tests/voice-broadcast/utils/getChunkLength-test.ts
Normal file
63
test/unit-tests/voice-broadcast/utils/getChunkLength-test.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import SdkConfig from "../../../../src/SdkConfig";
|
||||
import { SettingLevel } from "../../../../src/settings/SettingLevel";
|
||||
import { Features } from "../../../../src/settings/Settings";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import { getChunkLength } from "../../../../src/voice-broadcast/utils/getChunkLength";
|
||||
|
||||
describe("getChunkLength", () => {
|
||||
afterEach(() => {
|
||||
SdkConfig.reset();
|
||||
});
|
||||
|
||||
describe("when there is a value provided by Sdk config", () => {
|
||||
beforeEach(() => {
|
||||
SdkConfig.add({
|
||||
voice_broadcast: {
|
||||
chunk_length: 42,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return this value", () => {
|
||||
expect(getChunkLength()).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when Sdk config does not provide a value", () => {
|
||||
beforeEach(() => {
|
||||
SdkConfig.add({
|
||||
voice_broadcast: {
|
||||
chunk_length: 23,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return this value", () => {
|
||||
expect(getChunkLength()).toBe(23);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there are no defaults", () => {
|
||||
it("should return the fallback value", () => {
|
||||
expect(getChunkLength()).toBe(120);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the Features.VoiceBroadcastForceSmallChunks is enabled", () => {
|
||||
beforeEach(async () => {
|
||||
await SettingsStore.setValue(Features.VoiceBroadcastForceSmallChunks, null, SettingLevel.DEVICE, true);
|
||||
});
|
||||
|
||||
it("should return a chunk length of 15 seconds", () => {
|
||||
expect(getChunkLength()).toBe(15);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import SdkConfig, { DEFAULTS } from "../../../../src/SdkConfig";
|
||||
import { getMaxBroadcastLength } from "../../../../src/voice-broadcast";
|
||||
|
||||
describe("getMaxBroadcastLength", () => {
|
||||
afterEach(() => {
|
||||
SdkConfig.reset();
|
||||
});
|
||||
|
||||
describe("when there is a value provided by Sdk config", () => {
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
voice_broadcast: {
|
||||
max_length: 42,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should return this value", () => {
|
||||
expect(getMaxBroadcastLength()).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when Sdk config does not provide a value", () => {
|
||||
it("should return this value", () => {
|
||||
expect(getMaxBroadcastLength()).toBe(DEFAULTS.voice_broadcast!.max_length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("if there are no defaults", () => {
|
||||
it("should return the fallback value", () => {
|
||||
expect(DEFAULTS.voice_broadcast!.max_length).toBe(4 * 60 * 60);
|
||||
expect(getMaxBroadcastLength()).toBe(4 * 60 * 60);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
hasRoomLiveVoiceBroadcast,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
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,
|
||||
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", async () => {
|
||||
expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({
|
||||
hasBroadcast: true,
|
||||
infoEvent: expectedEvent,
|
||||
startedByUser: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldReturnTrueFalse = () => {
|
||||
it("should return true/false", async () => {
|
||||
expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({
|
||||
hasBroadcast: true,
|
||||
infoEvent: expectedEvent,
|
||||
startedByUser: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldReturnFalseFalse = () => {
|
||||
it("should return false/false", async () => {
|
||||
expect(await hasRoomLiveVoiceBroadcast(client, room, client.getSafeUserId())).toEqual({
|
||||
hasBroadcast: false,
|
||||
infoEvent: null,
|
||||
startedByUser: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
describe("when there is no voice broadcast info at all", () => {
|
||||
itShouldReturnFalseFalse();
|
||||
});
|
||||
|
||||
describe("when the »state« prop is missing", () => {
|
||||
beforeEach(() => {
|
||||
room.currentState.setStateEvents([
|
||||
mkEvent({
|
||||
event: true,
|
||||
room: room.roomId,
|
||||
user: client.getSafeUserId(),
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
skey: client.getSafeUserId(),
|
||||
content: {},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
itShouldReturnFalseFalse();
|
||||
});
|
||||
|
||||
describe("when there is a live broadcast from the current and another user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId, otherDeviceId);
|
||||
});
|
||||
|
||||
itShouldReturnTrueTrue();
|
||||
});
|
||||
|
||||
describe("when there are only stopped info events", () => {
|
||||
beforeEach(() => {
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, client.getSafeUserId(), client.getDeviceId()!);
|
||||
addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped, otherUserId, otherDeviceId);
|
||||
});
|
||||
|
||||
itShouldReturnFalseFalse();
|
||||
});
|
||||
|
||||
describe("when there is a live, started broadcast from the current user", () => {
|
||||
beforeEach(() => {
|
||||
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();
|
||||
});
|
||||
|
||||
describe("when there was a live broadcast, that has been stopped", () => {
|
||||
beforeEach(() => {
|
||||
const startedEvent = addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
addVoiceBroadcastInfoEvent(
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
startedEvent,
|
||||
);
|
||||
});
|
||||
|
||||
itShouldReturnFalseFalse();
|
||||
});
|
||||
|
||||
describe("when there is a live broadcast from another user", () => {
|
||||
beforeEach(() => {
|
||||
expectedEvent = addVoiceBroadcastInfoEvent(VoiceBroadcastInfoState.Started, otherUserId, otherDeviceId);
|
||||
});
|
||||
|
||||
itShouldReturnTrueFalse();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2023 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventType, MatrixEvent, RelationType, Room, MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import { isRelatedToVoiceBroadcast, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
const mkRelatedEvent = (
|
||||
room: Room,
|
||||
relationType: RelationType,
|
||||
relatesTo: MatrixEvent | undefined,
|
||||
client: MatrixClient,
|
||||
): MatrixEvent => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
room: room.roomId,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: relationType,
|
||||
event_id: relatesTo?.getId(),
|
||||
},
|
||||
},
|
||||
user: client.getSafeUserId(),
|
||||
});
|
||||
room.addLiveEvents([event]);
|
||||
return event;
|
||||
};
|
||||
|
||||
describe("isRelatedToVoiceBroadcast", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let broadcastEvent: MatrixEvent;
|
||||
let nonBroadcastEvent: MatrixEvent;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getSafeUserId());
|
||||
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string): Room | null => {
|
||||
if (getRoomId === roomId) return room;
|
||||
return null;
|
||||
});
|
||||
|
||||
broadcastEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
"ABC123",
|
||||
);
|
||||
nonBroadcastEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
room: roomId,
|
||||
content: {},
|
||||
user: client.getSafeUserId(),
|
||||
});
|
||||
|
||||
room.addLiveEvents([broadcastEvent, nonBroadcastEvent]);
|
||||
});
|
||||
|
||||
it("should return true if related (reference) to a broadcast event", () => {
|
||||
expect(
|
||||
isRelatedToVoiceBroadcast(mkRelatedEvent(room, RelationType.Reference, broadcastEvent, client), client),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false if related (reference) is undefeind", () => {
|
||||
expect(isRelatedToVoiceBroadcast(mkRelatedEvent(room, RelationType.Reference, undefined, client), client)).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("should return false if related (referenireplace) to a broadcast event", () => {
|
||||
expect(
|
||||
isRelatedToVoiceBroadcast(mkRelatedEvent(room, RelationType.Replace, broadcastEvent, client), client),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false if the event has no relation", () => {
|
||||
const noRelationEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
room: room.roomId,
|
||||
content: {},
|
||||
user: client.getSafeUserId(),
|
||||
});
|
||||
expect(isRelatedToVoiceBroadcast(noRelationEvent, client)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for an unknown room", () => {
|
||||
const otherRoom = new Room("!other:example.com", client, client.getSafeUserId());
|
||||
expect(
|
||||
isRelatedToVoiceBroadcast(
|
||||
mkRelatedEvent(otherRoom, RelationType.Reference, broadcastEvent, client),
|
||||
client,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { pauseNonLiveBroadcastFromOtherRoom } from "../../../../src/voice-broadcast/utils/pauseNonLiveBroadcastFromOtherRoom";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("pauseNonLiveBroadcastFromOtherRoom", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const roomId2 = "!room2@example.com";
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let playbacks: VoiceBroadcastPlaybacksStore;
|
||||
let recordings: VoiceBroadcastRecordingsStore;
|
||||
|
||||
const mkPlayback = (infoState: VoiceBroadcastInfoState, roomId: string): VoiceBroadcastPlayback => {
|
||||
const infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
infoState,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
const playback = new VoiceBroadcastPlayback(infoEvent, client, recordings);
|
||||
jest.spyOn(playback, "pause");
|
||||
playbacks.setCurrent(playback);
|
||||
return playback;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getSafeUserId());
|
||||
recordings = new VoiceBroadcastRecordingsStore();
|
||||
playbacks = new VoiceBroadcastPlaybacksStore(recordings);
|
||||
jest.spyOn(playbacks, "clearCurrent");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
playback?.destroy();
|
||||
playbacks.destroy();
|
||||
});
|
||||
|
||||
describe("when there is no current playback", () => {
|
||||
it("should not clear the current playback", () => {
|
||||
pauseNonLiveBroadcastFromOtherRoom(room, playbacks);
|
||||
expect(playbacks.clearCurrent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when listening to a live broadcast in another room", () => {
|
||||
beforeEach(() => {
|
||||
playback = mkPlayback(VoiceBroadcastInfoState.Started, roomId2);
|
||||
});
|
||||
|
||||
it("should not clear current / pause the playback", () => {
|
||||
pauseNonLiveBroadcastFromOtherRoom(room, playbacks);
|
||||
expect(playbacks.clearCurrent).not.toHaveBeenCalled();
|
||||
expect(playback.pause).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when listening to a non-live broadcast in the same room", () => {
|
||||
beforeEach(() => {
|
||||
playback = mkPlayback(VoiceBroadcastInfoState.Stopped, roomId);
|
||||
});
|
||||
|
||||
it("should not clear current / pause the playback", () => {
|
||||
pauseNonLiveBroadcastFromOtherRoom(room, playbacks);
|
||||
expect(playbacks.clearCurrent).not.toHaveBeenCalled();
|
||||
expect(playback.pause).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when listening to a non-live broadcast in another room", () => {
|
||||
beforeEach(() => {
|
||||
playback = mkPlayback(VoiceBroadcastInfoState.Stopped, roomId2);
|
||||
});
|
||||
|
||||
it("should clear current and pause the playback", () => {
|
||||
pauseNonLiveBroadcastFromOtherRoom(room, playbacks);
|
||||
expect(playbacks.getCurrent()).toBeNull();
|
||||
expect(playback.pause).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, 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());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import Modal from "../../../../src/Modal";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastPreRecordingStore,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { setUpVoiceBroadcastPreRecording } from "../../../../src/voice-broadcast/utils/setUpVoiceBroadcastPreRecording";
|
||||
import { mkRoomMemberJoinEvent, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
jest.mock("../../../../src/Modal");
|
||||
|
||||
describe("setUpVoiceBroadcastPreRecording", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let userId: string;
|
||||
let room: Room;
|
||||
let preRecordingStore: VoiceBroadcastPreRecordingStore;
|
||||
let infoEvent: MatrixEvent;
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let preRecording: VoiceBroadcastPreRecording | null;
|
||||
|
||||
const itShouldNotCreateAPreRecording = () => {
|
||||
it("should return null", () => {
|
||||
expect(preRecording).toBeNull();
|
||||
});
|
||||
|
||||
it("should not create a broadcast pre recording", () => {
|
||||
expect(preRecordingStore.getCurrent()).toBeNull();
|
||||
});
|
||||
};
|
||||
|
||||
const setUpPreRecording = async () => {
|
||||
preRecording = await setUpVoiceBroadcastPreRecording(
|
||||
room,
|
||||
client,
|
||||
playbacksStore,
|
||||
recordingsStore,
|
||||
preRecordingStore,
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
userId = client.getSafeUserId();
|
||||
room = new Room(roomId, client, userId);
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
preRecording = null;
|
||||
preRecordingStore = new VoiceBroadcastPreRecordingStore();
|
||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
playback = new VoiceBroadcastPlayback(infoEvent, client, recordingsStore);
|
||||
jest.spyOn(playback, "pause");
|
||||
playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore);
|
||||
});
|
||||
|
||||
describe("when trying to start a broadcast if there is no connection", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.getSyncState).mockReturnValue(SyncState.Error);
|
||||
await setUpPreRecording();
|
||||
});
|
||||
|
||||
it("should show an info dialog and not set up a pre-recording", () => {
|
||||
expect(preRecordingStore.getCurrent()).toBeNull();
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when setting up a pre-recording", () => {
|
||||
describe("and there is no user id", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.getUserId).mockReturnValue(null);
|
||||
await setUpPreRecording();
|
||||
});
|
||||
|
||||
itShouldNotCreateAPreRecording();
|
||||
});
|
||||
|
||||
describe("and there is no room member", () => {
|
||||
beforeEach(async () => {
|
||||
// check test precondition
|
||||
expect(room.getMember(userId)).toBeNull();
|
||||
await setUpPreRecording();
|
||||
});
|
||||
|
||||
itShouldNotCreateAPreRecording();
|
||||
});
|
||||
|
||||
describe("and there is a room member and listening to another broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
playbacksStore.setCurrent(playback);
|
||||
room.currentState.setStateEvents([mkRoomMemberJoinEvent(userId, roomId)]);
|
||||
await setUpPreRecording();
|
||||
});
|
||||
|
||||
it("should pause the current playback and create a voice broadcast pre-recording", () => {
|
||||
expect(playback.pause).toHaveBeenCalled();
|
||||
expect(playbacksStore.getCurrent()).toBeNull();
|
||||
expect(preRecording).toBeInstanceOf(VoiceBroadcastPreRecording);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { shouldDisplayAsVoiceBroadcastRecordingTile, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||
import { createTestClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
type TestTuple = [string | null, string, string, string, VoiceBroadcastInfoState, boolean];
|
||||
|
||||
const testCases: TestTuple[] = [
|
||||
[
|
||||
"@user1:example.com", // own MXID
|
||||
"@user1:example.com", // sender MXID
|
||||
"ABC123", // own device ID
|
||||
"ABC123", // sender device ID
|
||||
VoiceBroadcastInfoState.Started,
|
||||
true, // expected return value
|
||||
],
|
||||
["@user1:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Paused, true],
|
||||
["@user1:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Resumed, true],
|
||||
["@user1:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Stopped, false],
|
||||
["@user2:example.com", "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Started, false],
|
||||
[null, "@user1:example.com", "ABC123", "ABC123", VoiceBroadcastInfoState.Started, false],
|
||||
// other device
|
||||
["@user1:example.com", "@user1:example.com", "ABC123", "JKL123", VoiceBroadcastInfoState.Started, false],
|
||||
["@user1:example.com", "@user1:example.com", "ABC123", "JKL123", VoiceBroadcastInfoState.Paused, false],
|
||||
["@user1:example.com", "@user1:example.com", "ABC123", "JKL123", VoiceBroadcastInfoState.Resumed, false],
|
||||
];
|
||||
|
||||
describe("shouldDisplayAsVoiceBroadcastRecordingTile", () => {
|
||||
let event: MatrixEvent;
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeAll(() => {
|
||||
client = createTestClient();
|
||||
});
|
||||
|
||||
describe.each<TestTuple>(testCases)(
|
||||
"when called with user »%s«, sender »%s«, device »%s«, sender device »%s« state »%s«",
|
||||
(userId, senderId, deviceId, senderDeviceId, state, expected) => {
|
||||
beforeEach(() => {
|
||||
event = mkVoiceBroadcastInfoStateEvent("!room:example.com", state, senderId, senderDeviceId);
|
||||
mocked(client.getUserId).mockReturnValue(userId);
|
||||
mocked(client.getDeviceId).mockReturnValue(deviceId);
|
||||
});
|
||||
|
||||
it(`should return ${expected}`, () => {
|
||||
expect(shouldDisplayAsVoiceBroadcastRecordingTile(state, client, event)).toBe(expected);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("should return false, when all params are null", () => {
|
||||
event = mkVoiceBroadcastInfoStateEvent("!room:example.com", null, null, null);
|
||||
// @ts-ignore Simulate null state received for any reason.
|
||||
expect(shouldDisplayAsVoiceBroadcastRecordingTile(null, client, event)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false, when all params are undefined", () => {
|
||||
event = mkVoiceBroadcastInfoStateEvent("!room:example.com", undefined, undefined, undefined);
|
||||
// @ts-ignore Simulate undefined state received for any reason.
|
||||
expect(shouldDisplayAsVoiceBroadcastRecordingTile(undefined, client, event)).toBe(false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { EventType, IEvent, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
shouldDisplayAsVoiceBroadcastTile,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent } from "../../../test-utils";
|
||||
|
||||
describe("shouldDisplayAsVoiceBroadcastTile", () => {
|
||||
let event: MatrixEvent;
|
||||
const roomId = "!room:example.com";
|
||||
const senderId = "@user:example.com";
|
||||
|
||||
const itShouldReturnFalse = () => {
|
||||
it("should return false", () => {
|
||||
expect(shouldDisplayAsVoiceBroadcastTile(event)).toBe(false);
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldReturnTrue = () => {
|
||||
it("should return true", () => {
|
||||
expect(shouldDisplayAsVoiceBroadcastTile(event)).toBe(true);
|
||||
});
|
||||
};
|
||||
|
||||
describe("when a broken event occurs", () => {
|
||||
beforeEach(() => {
|
||||
event = 23 as unknown as MatrixEvent;
|
||||
});
|
||||
|
||||
itShouldReturnFalse();
|
||||
});
|
||||
|
||||
describe("when a non-voice broadcast info event occurs", () => {
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
room: roomId,
|
||||
user: senderId,
|
||||
content: {},
|
||||
});
|
||||
});
|
||||
|
||||
itShouldReturnFalse();
|
||||
});
|
||||
|
||||
describe("when a voice broadcast info event with empty content occurs", () => {
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: senderId,
|
||||
content: {},
|
||||
});
|
||||
});
|
||||
|
||||
itShouldReturnFalse();
|
||||
});
|
||||
|
||||
describe("when a voice broadcast info event with undefined content occurs", () => {
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: senderId,
|
||||
content: {},
|
||||
});
|
||||
event.getContent = () => ({}) as any;
|
||||
});
|
||||
|
||||
itShouldReturnFalse();
|
||||
});
|
||||
|
||||
describe("when a voice broadcast info event in state started occurs", () => {
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: senderId,
|
||||
content: {
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
itShouldReturnTrue();
|
||||
});
|
||||
|
||||
describe("when a redacted event occurs", () => {
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: senderId,
|
||||
content: {},
|
||||
unsigned: {
|
||||
redacted_because: {} as unknown as IEvent,
|
||||
},
|
||||
});
|
||||
event.getContent = () => ({}) as any;
|
||||
});
|
||||
|
||||
itShouldReturnTrue();
|
||||
});
|
||||
|
||||
describe.each([VoiceBroadcastInfoState.Paused, VoiceBroadcastInfoState.Resumed, VoiceBroadcastInfoState.Stopped])(
|
||||
"when a voice broadcast info event in state %s occurs",
|
||||
(state: VoiceBroadcastInfoState) => {
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
room: roomId,
|
||||
user: senderId,
|
||||
content: {
|
||||
state,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
itShouldReturnFalse();
|
||||
},
|
||||
);
|
||||
});
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { EventType, ISendEventResponse, MatrixClient, MatrixEvent, Room, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import Modal from "../../../../src/Modal";
|
||||
import {
|
||||
startNewVoiceBroadcastRecording,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPlayback,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
jest.mock("../../../../src/voice-broadcast/models/VoiceBroadcastRecording", () => ({
|
||||
VoiceBroadcastRecording: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/Modal");
|
||||
|
||||
describe("startNewVoiceBroadcastRecording", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const otherUserId = "@other:example.com";
|
||||
let client: MatrixClient;
|
||||
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let room: Room;
|
||||
let infoEvent: MatrixEvent;
|
||||
let otherEvent: MatrixEvent;
|
||||
let result: VoiceBroadcastRecording | null;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getUserId()!);
|
||||
jest.spyOn(room.currentState, "maySendStateEvent");
|
||||
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string) => {
|
||||
if (getRoomId === roomId) {
|
||||
return room;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
mocked(client.sendStateEvent).mockImplementation(
|
||||
(sendRoomId: string, eventType: string, content: any, stateKey: string): Promise<ISendEventResponse> => {
|
||||
if (sendRoomId === roomId && eventType === VoiceBroadcastInfoEventType) {
|
||||
return Promise.resolve({ event_id: infoEvent.getId()! });
|
||||
}
|
||||
|
||||
throw new Error("Unexpected sendStateEvent call");
|
||||
},
|
||||
);
|
||||
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
otherEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMember,
|
||||
content: {},
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
skey: "",
|
||||
});
|
||||
|
||||
playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore);
|
||||
recordingsStore = {
|
||||
setCurrent: jest.fn(),
|
||||
getCurrent: jest.fn(),
|
||||
} as unknown as VoiceBroadcastRecordingsStore;
|
||||
|
||||
mocked(VoiceBroadcastRecording).mockImplementation((infoEvent: MatrixEvent, client: MatrixClient): any => {
|
||||
return {
|
||||
infoEvent,
|
||||
client,
|
||||
start: jest.fn(),
|
||||
} as unknown as VoiceBroadcastRecording;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("when trying to start a broadcast if there is no connection", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.getSyncState).mockReturnValue(SyncState.Error);
|
||||
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||
});
|
||||
|
||||
it("should show an info dialog and not start a recording", () => {
|
||||
expect(result).toBeNull();
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the current user is allowed to send voice broadcast info state events", () => {
|
||||
beforeEach(() => {
|
||||
mocked(room.currentState.maySendStateEvent).mockReturnValue(true);
|
||||
});
|
||||
|
||||
describe("when currently listening to a broadcast and there is no recording", () => {
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
|
||||
beforeEach(() => {
|
||||
playback = new VoiceBroadcastPlayback(infoEvent, client, recordingsStore);
|
||||
jest.spyOn(playback, "pause");
|
||||
playbacksStore.setCurrent(playback);
|
||||
});
|
||||
|
||||
it("should stop listen to the current broadcast and create a new recording", async () => {
|
||||
mocked(client.sendStateEvent).mockImplementation(
|
||||
async (
|
||||
_roomId: string,
|
||||
_eventType: string,
|
||||
_content: any,
|
||||
_stateKey = "",
|
||||
): Promise<ISendEventResponse> => {
|
||||
window.setTimeout(() => {
|
||||
// emit state events after resolving the promise
|
||||
room.currentState.setStateEvents([otherEvent]);
|
||||
room.currentState.setStateEvents([infoEvent]);
|
||||
}, 0);
|
||||
return { event_id: infoEvent.getId()! };
|
||||
},
|
||||
);
|
||||
const recording = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||
expect(recording).not.toBeNull();
|
||||
|
||||
// expect to stop and clear the current playback
|
||||
expect(playback.pause).toHaveBeenCalled();
|
||||
expect(playbacksStore.getCurrent()).toBeNull();
|
||||
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
chunk_length: 120,
|
||||
device_id: client.getDeviceId(),
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
},
|
||||
client.getUserId()!,
|
||||
);
|
||||
expect(recording!.infoEvent).toBe(infoEvent);
|
||||
expect(recording!.start).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is already a current voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(recordingsStore.getCurrent).mockReturnValue(new VoiceBroadcastRecording(infoEvent, client));
|
||||
|
||||
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||
});
|
||||
|
||||
it("should not start a voice broadcast", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should show an info dialog", () => {
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there already is a live broadcast of the current user in the room", () => {
|
||||
beforeEach(async () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Resumed,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
),
|
||||
]);
|
||||
|
||||
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||
});
|
||||
|
||||
it("should not start a voice broadcast", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should show an info dialog", () => {
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there already is a live broadcast of another user", () => {
|
||||
beforeEach(async () => {
|
||||
room.currentState.setStateEvents([
|
||||
mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Resumed, otherUserId, "ASD123"),
|
||||
]);
|
||||
|
||||
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||
});
|
||||
|
||||
it("should not start a voice broadcast", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should show an info dialog", () => {
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the current user is not allowed to send voice broadcast info state events", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(room.currentState.maySendStateEvent).mockReturnValue(false);
|
||||
result = await startNewVoiceBroadcastRecording(room, client, playbacksStore, recordingsStore);
|
||||
});
|
||||
|
||||
it("should not start a voice broadcast", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("should show an info dialog", () => {
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
131
test/unit-tests/voice-broadcast/utils/test-utils.ts
Normal file
131
test/unit-tests/voice-broadcast/utils/test-utils.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { EventType, IContent, MatrixEvent, MsgType, RelationType, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||
import {
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastRecording,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import {
|
||||
VoiceBroadcastChunkEventType,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../../src/voice-broadcast/types";
|
||||
import { mkEvent } from "../../../test-utils";
|
||||
|
||||
// timestamp incremented on each call to prevent duplicate timestamp
|
||||
let timestamp = new Date().getTime();
|
||||
|
||||
export const mkVoiceBroadcastInfoStateEvent = (
|
||||
roomId: Optional<string>,
|
||||
state: Optional<VoiceBroadcastInfoState>,
|
||||
senderId: Optional<string>,
|
||||
senderDeviceId: Optional<string>,
|
||||
startedInfoEvent?: MatrixEvent,
|
||||
lastChunkSequence?: number,
|
||||
): MatrixEvent => {
|
||||
const relationContent: IContent = {};
|
||||
|
||||
if (startedInfoEvent) {
|
||||
relationContent["m.relates_to"] = {
|
||||
event_id: startedInfoEvent.getId(),
|
||||
rel_type: "m.reference",
|
||||
};
|
||||
}
|
||||
|
||||
const lastChunkSequenceContent = lastChunkSequence ? { last_chunk_sequence: lastChunkSequence } : {};
|
||||
|
||||
return mkEvent({
|
||||
event: true,
|
||||
// @ts-ignore allow everything here for edge test cases
|
||||
room: roomId,
|
||||
// @ts-ignore allow everything here for edge test cases
|
||||
user: senderId,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
// @ts-ignore allow everything here for edge test cases
|
||||
skey: senderId,
|
||||
content: {
|
||||
state,
|
||||
device_id: senderDeviceId,
|
||||
...relationContent,
|
||||
...lastChunkSequenceContent,
|
||||
},
|
||||
ts: timestamp++,
|
||||
});
|
||||
};
|
||||
|
||||
export const mkVoiceBroadcastChunkEvent = (
|
||||
infoEventId: string,
|
||||
userId: string,
|
||||
roomId: string,
|
||||
duration: number,
|
||||
sequence?: number,
|
||||
timestamp?: number,
|
||||
): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
user: userId,
|
||||
room: roomId,
|
||||
type: EventType.RoomMessage,
|
||||
content: {
|
||||
msgtype: MsgType.Audio,
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration,
|
||||
},
|
||||
info: {
|
||||
duration,
|
||||
},
|
||||
[VoiceBroadcastChunkEventType]: {
|
||||
...(sequence ? { sequence } : {}),
|
||||
},
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: infoEventId,
|
||||
},
|
||||
},
|
||||
ts: timestamp,
|
||||
});
|
||||
};
|
||||
|
||||
export const mkVoiceBroadcastPlayback = (stores: SdkContextClass): VoiceBroadcastPlayback => {
|
||||
const infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ASD123",
|
||||
);
|
||||
return new VoiceBroadcastPlayback(infoEvent, stores.client!, stores.voiceBroadcastRecordingsStore);
|
||||
};
|
||||
|
||||
export const mkVoiceBroadcastRecording = (stores: SdkContextClass): VoiceBroadcastRecording => {
|
||||
const infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ASD123",
|
||||
);
|
||||
return new VoiceBroadcastRecording(infoEvent, stores.client!);
|
||||
};
|
||||
|
||||
export const mkVoiceBroadcastPreRecording = (stores: SdkContextClass): VoiceBroadcastPreRecording => {
|
||||
const roomId = "!room:example.com";
|
||||
const userId = "@user:example.com";
|
||||
const room = new Room(roomId, stores.client!, userId);
|
||||
const roomMember = new RoomMember(roomId, userId);
|
||||
return new VoiceBroadcastPreRecording(
|
||||
room,
|
||||
roomMember,
|
||||
stores.client!,
|
||||
stores.voiceBroadcastPlaybacksStore,
|
||||
stores.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
|
||||
jest.mock("../../../../src/dispatcher/dispatcher");
|
||||
|
||||
describe("textForVoiceBroadcastStoppedEvent", () => {
|
||||
const otherUserId = "@other:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
|
||||
const renderText = (senderId: string, startEventId?: string) => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
senderId,
|
||||
client.deviceId!,
|
||||
);
|
||||
|
||||
if (startEventId) {
|
||||
event.getContent()["m.relates_to"] = {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: startEventId,
|
||||
};
|
||||
}
|
||||
|
||||
return render(<div>{textForVoiceBroadcastStoppedEvent(event, client)()}</div>);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
it("should render own broadcast as expected", () => {
|
||||
expect(renderText(client.getUserId()!).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render other users broadcast as expected", () => {
|
||||
expect(renderText(otherUserId).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render without login as expected", () => {
|
||||
mocked(client.getUserId).mockReturnValue(null);
|
||||
expect(renderText(otherUserId).container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when rendering an event with relation to the start event", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
result = renderText(client.getUserId()!, "$start-id");
|
||||
});
|
||||
|
||||
it("should render events with relation to the start event", () => {
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the link", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByRole("button"));
|
||||
});
|
||||
|
||||
it("should dispatch an action to highlight the event", () => {
|
||||
expect(dis.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
event_id: "$start-id",
|
||||
highlighted: true,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined, // room doesn't change
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { textForVoiceBroadcastStoppedEventWithoutLink, VoiceBroadcastInfoState } from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./test-utils";
|
||||
|
||||
describe("textForVoiceBroadcastStoppedEventWithoutLink", () => {
|
||||
const otherUserId = "@other:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
});
|
||||
|
||||
const getText = (senderId: string, startEventId?: string) => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
senderId,
|
||||
client.deviceId!,
|
||||
);
|
||||
return textForVoiceBroadcastStoppedEventWithoutLink(event);
|
||||
};
|
||||
|
||||
it("when called for an own broadcast it should return the expected text", () => {
|
||||
expect(getText(client.getUserId()!)).toBe("You ended a voice broadcast");
|
||||
});
|
||||
|
||||
it("when called for other ones broadcast it should return the expected text", () => {
|
||||
expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`);
|
||||
});
|
||||
|
||||
it("when not logged in it should return the exptected text", () => {
|
||||
mocked(client.getUserId).mockReturnValue(null);
|
||||
expect(getText(otherUserId)).toBe(`${otherUserId} ended a voice broadcast`);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue