Remove abandoned Voice Broadcasts labs flag (#28548)
* Remove abandoned Voice Broadcasts labs flag Any existing voice broadcasts will be shown as a series of voice messages which will sequence play as normal Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Remove dead code Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
5d72735b1f
commit
d8ebc68aa8
174 changed files with 29 additions and 13632 deletions
|
@ -39,10 +39,6 @@ import { Action } from "../../src/dispatcher/actions";
|
|||
import { getFunctionalMembers } from "../../src/utils/room/getFunctionalMembers";
|
||||
import SettingsStore from "../../src/settings/SettingsStore";
|
||||
import { UIFeature } from "../../src/settings/UIFeature";
|
||||
import { VoiceBroadcastInfoState, VoiceBroadcastPlayback, VoiceBroadcastRecording } from "../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils";
|
||||
import { SdkContextClass } from "../../src/contexts/SDKContext";
|
||||
import Modal from "../../src/Modal";
|
||||
import { createAudioContext } from "../../src/audio/compat";
|
||||
import * as ManagedHybrid from "../../src/widgets/ManagedHybrid";
|
||||
|
||||
|
@ -403,53 +399,6 @@ describe("LegacyCallHandler", () => {
|
|||
await callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||
expect(spy).toHaveBeenCalledWith(MatrixClientPeg.safeGet().getRoom(NATIVE_ROOM_ALICE));
|
||||
});
|
||||
|
||||
describe("when listening to a voice broadcast", () => {
|
||||
let voiceBroadcastPlayback: VoiceBroadcastPlayback;
|
||||
|
||||
beforeEach(() => {
|
||||
voiceBroadcastPlayback = new VoiceBroadcastPlayback(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
MatrixClientPeg.safeGet().getSafeUserId(),
|
||||
"d42",
|
||||
),
|
||||
MatrixClientPeg.safeGet(),
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
|
||||
jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation();
|
||||
});
|
||||
|
||||
it("and placing a call should pause the broadcast", async () => {
|
||||
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||
await untilCallHandlerEvent(callHandler, LegacyCallHandlerEvent.CallState);
|
||||
|
||||
expect(voiceBroadcastPlayback.pause).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when recording a voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(
|
||||
new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
MatrixClientPeg.safeGet().getSafeUserId(),
|
||||
"d42",
|
||||
),
|
||||
MatrixClientPeg.safeGet(),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("and placing a call should show the info dialog", async () => {
|
||||
callHandler.placeCall(NATIVE_ROOM_ALICE, CallType.Voice);
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("LegacyCallHandler without third party protocols", () => {
|
||||
|
@ -528,9 +477,6 @@ describe("LegacyCallHandler without third party protocols", () => {
|
|||
audioElement.id = "remoteAudio";
|
||||
document.body.appendChild(audioElement);
|
||||
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.clearCurrent();
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||
|
||||
fetchMock.get(
|
||||
"/media/ring.mp3",
|
||||
{ body: new Blob(["1", "2", "3", "4"], { type: "audio/mpeg" }) },
|
||||
|
|
|
@ -43,8 +43,6 @@ import { mkThread } from "../test-utils/threads";
|
|||
import dis from "../../src/dispatcher/dispatcher";
|
||||
import { ThreadPayload } from "../../src/dispatcher/payloads/ThreadPayload";
|
||||
import { Action } from "../../src/dispatcher/actions";
|
||||
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoState } from "../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "./voice-broadcast/utils/test-utils";
|
||||
import { addReplyToMessageContent } from "../../src/utils/Reply";
|
||||
|
||||
jest.mock("../../src/utils/notifications", () => ({
|
||||
|
@ -85,16 +83,13 @@ describe("Notifier", () => {
|
|||
});
|
||||
};
|
||||
|
||||
const mkAudioEvent = (broadcastChunkContent?: object): MatrixEvent => {
|
||||
const chunkContent = broadcastChunkContent ? { [VoiceBroadcastChunkEventType]: broadcastChunkContent } : {};
|
||||
|
||||
const mkAudioEvent = (): MatrixEvent => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: "@user:example.com",
|
||||
room: "!room:example.com",
|
||||
content: {
|
||||
...chunkContent,
|
||||
msgtype: MsgType.Audio,
|
||||
body: "test audio message",
|
||||
},
|
||||
|
@ -320,24 +315,6 @@ describe("Notifier", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("should display the expected notification for a broadcast chunk with sequence = 1", () => {
|
||||
const audioEvent = mkAudioEvent({ sequence: 1 });
|
||||
Notifier.displayPopupNotification(audioEvent, testRoom);
|
||||
expect(MockPlatform.displayNotification).toHaveBeenCalledWith(
|
||||
"@user:example.com (!room1:server)",
|
||||
"@user:example.com started a voice broadcast",
|
||||
"data:image/png;base64,00",
|
||||
testRoom,
|
||||
audioEvent,
|
||||
);
|
||||
});
|
||||
|
||||
it("should display the expected notification for a broadcast chunk with sequence = 2", () => {
|
||||
const audioEvent = mkAudioEvent({ sequence: 2 });
|
||||
Notifier.displayPopupNotification(audioEvent, testRoom);
|
||||
expect(MockPlatform.displayNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should strip reply fallback", () => {
|
||||
const event = mkMessage({
|
||||
msg: "Test",
|
||||
|
@ -581,24 +558,6 @@ describe("Notifier", () => {
|
|||
Notifier.evaluateEvent(mkAudioEvent());
|
||||
expect(Notifier.displayPopupNotification).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not show a notification for broadcast info events in any case", () => {
|
||||
// Let client decide to show a notification
|
||||
mockClient.getPushActionsForEvent.mockReturnValue({
|
||||
notify: true,
|
||||
tweaks: {},
|
||||
});
|
||||
|
||||
const broadcastStartedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
"!other:example.org",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
|
||||
Notifier.evaluateEvent(broadcastStartedEvent);
|
||||
expect(Notifier.displayPopupNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("setPromptHidden", () => {
|
||||
|
|
|
@ -18,10 +18,6 @@ describe("SdkConfig", () => {
|
|||
describe("with custom values", () => {
|
||||
beforeEach(() => {
|
||||
SdkConfig.put({
|
||||
voice_broadcast: {
|
||||
chunk_length: 42,
|
||||
max_length: 1337,
|
||||
},
|
||||
feedback: {
|
||||
existing_issues_url: "https://existing",
|
||||
} as any,
|
||||
|
@ -30,8 +26,6 @@ describe("SdkConfig", () => {
|
|||
|
||||
it("should return the custom config", () => {
|
||||
const customConfig = JSON.parse(JSON.stringify(DEFAULTS));
|
||||
customConfig.voice_broadcast.chunk_length = 42;
|
||||
customConfig.voice_broadcast.max_length = 1337;
|
||||
customConfig.feedback.existing_issues_url = "https://existing";
|
||||
expect(SdkConfig.get()).toEqual(customConfig);
|
||||
});
|
||||
|
|
|
@ -16,11 +16,6 @@ import { SpaceStoreClass } from "../../src/stores/spaces/SpaceStore";
|
|||
import { WidgetLayoutStore } from "../../src/stores/widgets/WidgetLayoutStore";
|
||||
import { WidgetPermissionStore } from "../../src/stores/widgets/WidgetPermissionStore";
|
||||
import WidgetStore from "../../src/stores/WidgetStore";
|
||||
import {
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecordingStore,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../src/voice-broadcast";
|
||||
|
||||
/**
|
||||
* A class which provides the same API as SdkContextClass but adds additional unsafe setters which can
|
||||
|
@ -36,9 +31,6 @@ export class TestSdkContext extends SdkContextClass {
|
|||
declare public _PosthogAnalytics?: PosthogAnalytics;
|
||||
declare public _SlidingSyncManager?: SlidingSyncManager;
|
||||
declare public _SpaceStore?: SpaceStoreClass;
|
||||
declare public _VoiceBroadcastRecordingsStore?: VoiceBroadcastRecordingsStore;
|
||||
declare public _VoiceBroadcastPreRecordingStore?: VoiceBroadcastPreRecordingStore;
|
||||
declare public _VoiceBroadcastPlaybacksStore?: VoiceBroadcastPlaybacksStore;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LegacyCallHandler when recording a voice broadcast and placing a call should show the info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can’t start a call",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -44,7 +44,6 @@ import {
|
|||
} from "../../../test-utils";
|
||||
import * as leaveRoomUtils from "../../../../src/utils/leave-behaviour";
|
||||
import { OidcClientError } from "../../../../src/utils/oidc/error";
|
||||
import * as voiceBroadcastUtils from "../../../../src/voice-broadcast/utils/cleanUpBroadcasts";
|
||||
import LegacyCallHandler from "../../../../src/LegacyCallHandler";
|
||||
import { CallStore } from "../../../../src/stores/CallStore";
|
||||
import { Call } from "../../../../src/models/Call";
|
||||
|
@ -811,7 +810,6 @@ describe("<MatrixChat />", () => {
|
|||
jest.spyOn(LegacyCallHandler.instance, "hangupAllCalls")
|
||||
.mockClear()
|
||||
.mockImplementation(() => {});
|
||||
jest.spyOn(voiceBroadcastUtils, "cleanUpBroadcasts").mockImplementation(async () => {});
|
||||
jest.spyOn(PosthogAnalytics.instance, "logout").mockImplementation(() => {});
|
||||
jest.spyOn(EventIndexPeg, "deleteEventIndex").mockImplementation(async () => {});
|
||||
|
||||
|
@ -831,22 +829,12 @@ describe("<MatrixChat />", () => {
|
|||
jest.spyOn(logger, "warn").mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.spyOn(voiceBroadcastUtils, "cleanUpBroadcasts").mockRestore();
|
||||
});
|
||||
|
||||
it("should hangup all legacy calls", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
expect(LegacyCallHandler.instance.hangupAllCalls).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should cleanup broadcasts", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
expect(voiceBroadcastUtils.cleanUpBroadcasts).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should disconnect all calls", async () => {
|
||||
await getComponentAndWaitForReady();
|
||||
await dispatchLogoutAndWait();
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from "react";
|
|||
import { mocked, Mocked } from "jest-mock";
|
||||
import { screen, render, act, cleanup } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MatrixClient, PendingEventOrdering, Room, MatrixEvent, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { Widget, ClientWidgetApi } from "matrix-widget-api";
|
||||
import { UserEvent } from "@testing-library/user-event/dist/types/setup/setup";
|
||||
|
||||
|
@ -26,7 +26,6 @@ import {
|
|||
wrapInSdkContext,
|
||||
mkRoomCreateEvent,
|
||||
mockPlatformPeg,
|
||||
flushPromises,
|
||||
useMockMediaDevices,
|
||||
} from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
|
@ -39,17 +38,7 @@ import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
|||
import { Action } from "../../../../src/dispatcher/actions";
|
||||
import { ViewRoomPayload } from "../../../../src/dispatcher/payloads/ViewRoomPayload";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastPreRecordingStore,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../voice-broadcast/utils/test-utils";
|
||||
import { RoomViewStore } from "../../../../src/stores/RoomViewStore";
|
||||
import { IRoomStateEventsActionPayload } from "../../../../src/actions/MatrixActionCreators";
|
||||
import { Container, WidgetLayoutStore } from "../../../../src/stores/widgets/WidgetLayoutStore";
|
||||
import WidgetStore from "../../../../src/stores/WidgetStore";
|
||||
import { WidgetType } from "../../../../src/widgets/WidgetType";
|
||||
|
@ -76,13 +65,6 @@ describe("PipContainer", () => {
|
|||
let room: Room;
|
||||
let room2: Room;
|
||||
let alice: RoomMember;
|
||||
let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let voiceBroadcastPreRecordingStore: VoiceBroadcastPreRecordingStore;
|
||||
let voiceBroadcastPlaybacksStore: VoiceBroadcastPlaybacksStore;
|
||||
|
||||
const actFlushPromises = async () => {
|
||||
await flushPromises();
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
useMockMediaDevices();
|
||||
|
@ -125,13 +107,7 @@ describe("PipContainer", () => {
|
|||
sdkContext = new TestSdkContext();
|
||||
// @ts-ignore PipContainer uses SDKContext in the constructor
|
||||
SdkContextClass.instance = sdkContext;
|
||||
voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
voiceBroadcastPreRecordingStore = new VoiceBroadcastPreRecordingStore();
|
||||
voiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore(voiceBroadcastRecordingsStore);
|
||||
sdkContext.client = client;
|
||||
sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore;
|
||||
sdkContext._VoiceBroadcastPreRecordingStore = voiceBroadcastPreRecordingStore;
|
||||
sdkContext._VoiceBroadcastPlaybacksStore = voiceBroadcastPlaybacksStore;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
@ -190,51 +166,10 @@ describe("PipContainer", () => {
|
|||
ActiveWidgetStore.instance.destroyPersistentWidget("1", room.roomId);
|
||||
};
|
||||
|
||||
const makeVoiceBroadcastInfoStateEvent = (): MatrixEvent => {
|
||||
return mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
alice.userId,
|
||||
client.getDeviceId() || "",
|
||||
);
|
||||
};
|
||||
|
||||
const setUpVoiceBroadcastRecording = () => {
|
||||
const infoEvent = makeVoiceBroadcastInfoStateEvent();
|
||||
const voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording);
|
||||
};
|
||||
|
||||
const setUpVoiceBroadcastPreRecording = () => {
|
||||
const voiceBroadcastPreRecording = new VoiceBroadcastPreRecording(
|
||||
room,
|
||||
alice,
|
||||
client,
|
||||
voiceBroadcastPlaybacksStore,
|
||||
voiceBroadcastRecordingsStore,
|
||||
);
|
||||
voiceBroadcastPreRecordingStore.setCurrent(voiceBroadcastPreRecording);
|
||||
};
|
||||
|
||||
const setUpRoomViewStore = () => {
|
||||
sdkContext._RoomViewStore = new RoomViewStore(defaultDispatcher, sdkContext);
|
||||
};
|
||||
|
||||
const mkVoiceBroadcast = (room: Room): MatrixEvent => {
|
||||
const infoEvent = makeVoiceBroadcastInfoStateEvent();
|
||||
room.currentState.setStateEvents([infoEvent]);
|
||||
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>(
|
||||
{
|
||||
action: "MatrixActions.RoomState.events",
|
||||
event: infoEvent,
|
||||
state: room.currentState,
|
||||
lastStateEvent: null,
|
||||
},
|
||||
true,
|
||||
);
|
||||
return infoEvent;
|
||||
};
|
||||
|
||||
it("hides if there's no content", () => {
|
||||
renderPip();
|
||||
expect(screen.queryByRole("complementary")).toBeNull();
|
||||
|
@ -339,138 +274,4 @@ describe("PipContainer", () => {
|
|||
|
||||
WidgetStore.instance.removeVirtualWidget("1", room.roomId);
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast recording and pre-recording", () => {
|
||||
beforeEach(async () => {
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
setUpVoiceBroadcastRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast recording PiP", () => {
|
||||
// check for the „Live“ badge to be present
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("and a call it should show both, the call and the recording", async () => {
|
||||
await withCall(async () => {
|
||||
// Broadcast: Check for the „Live“ badge to be present
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
// Call: Check for the „Leave“ button to be present
|
||||
screen.getByRole("button", { name: "Leave" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast playback and pre-recording", () => {
|
||||
beforeEach(async () => {
|
||||
mkVoiceBroadcast(room);
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast pre-recording PiP", () => {
|
||||
// check for the „Go live“ button
|
||||
expect(screen.queryByText("Go live")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast pre-recording", () => {
|
||||
beforeEach(async () => {
|
||||
setUpVoiceBroadcastPreRecording();
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast pre-recording PiP", () => {
|
||||
// check for the „Go live“ button
|
||||
expect(screen.queryByText("Go live")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when listening to a voice broadcast in a room and then switching to another room", () => {
|
||||
beforeEach(async () => {
|
||||
setUpRoomViewStore();
|
||||
viewRoom(room.roomId);
|
||||
mkVoiceBroadcast(room);
|
||||
await actFlushPromises();
|
||||
|
||||
expect(voiceBroadcastPlaybacksStore.getCurrent()).toBeTruthy();
|
||||
|
||||
await voiceBroadcastPlaybacksStore.getCurrent()?.start();
|
||||
viewRoom(room2.roomId);
|
||||
renderPip();
|
||||
});
|
||||
|
||||
it("should render the small voice broadcast playback PiP", () => {
|
||||
// check for the „pause voice broadcast“ button
|
||||
expect(screen.getByLabelText("pause voice broadcast")).toBeInTheDocument();
|
||||
// check for the absence of the „30s forward“ button
|
||||
expect(screen.queryByLabelText("30s forward")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when viewing a room with a live voice broadcast", () => {
|
||||
let startEvent!: MatrixEvent;
|
||||
|
||||
beforeEach(async () => {
|
||||
setUpRoomViewStore();
|
||||
viewRoom(room.roomId);
|
||||
startEvent = mkVoiceBroadcast(room);
|
||||
renderPip();
|
||||
await actFlushPromises();
|
||||
});
|
||||
|
||||
it("should render the voice broadcast playback pip", () => {
|
||||
// check for the „resume voice broadcast“ button
|
||||
expect(screen.queryByLabelText("play voice broadcast")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
alice.userId,
|
||||
client.getDeviceId() || "",
|
||||
startEvent,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
defaultDispatcher.dispatch<IRoomStateEventsActionPayload>(
|
||||
{
|
||||
action: "MatrixActions.RoomState.events",
|
||||
event: stopEvent,
|
||||
state: room.currentState,
|
||||
lastStateEvent: stopEvent,
|
||||
},
|
||||
true,
|
||||
);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the voice broadcast playback pip", () => {
|
||||
// check for the „resume voice broadcast“ button
|
||||
expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and leaving the room", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
viewRoom(room2.roomId);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the voice broadcast playback pip", () => {
|
||||
// check for the „resume voice broadcast“ button
|
||||
expect(screen.queryByLabelText("play voice broadcast")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,20 +7,14 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import React from "react";
|
||||
import { act, render, RenderResult, screen, waitFor } from "jest-matrix-react";
|
||||
import { DEVICE_CODE_SCOPE, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import { DEVICE_CODE_SCOPE, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { CryptoApi } from "matrix-js-sdk/src/crypto-api";
|
||||
import { mocked } from "jest-mock";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import UnwrappedUserMenu from "../../../../src/components/structures/UserMenu";
|
||||
import { stubClient, wrapInSdkContext } from "../../../test-utils";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../voice-broadcast/utils/test-utils";
|
||||
import { TestSdkContext } from "../../TestSdkContext";
|
||||
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
|
||||
import LogoutDialog from "../../../../src/components/views/dialogs/LogoutDialog";
|
||||
|
@ -34,71 +28,12 @@ import { UserTab } from "../../../../src/components/views/dialogs/UserTab";
|
|||
|
||||
describe("<UserMenu>", () => {
|
||||
let client: MatrixClient;
|
||||
let renderResult: RenderResult;
|
||||
let sdkContext: TestSdkContext;
|
||||
|
||||
beforeEach(() => {
|
||||
sdkContext = new TestSdkContext();
|
||||
});
|
||||
|
||||
describe("<UserMenu> when video broadcast", () => {
|
||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let voiceBroadcastRecording: VoiceBroadcastRecording;
|
||||
let voiceBroadcastRecordingsStore: VoiceBroadcastRecordingsStore;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId() || "",
|
||||
client.getDeviceId() || "",
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
sdkContext._VoiceBroadcastRecordingsStore = voiceBroadcastRecordingsStore;
|
||||
|
||||
voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client);
|
||||
});
|
||||
|
||||
describe("when rendered", () => {
|
||||
beforeEach(() => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and a live voice broadcast starts", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
voiceBroadcastRecordingsStore.setCurrent(voiceBroadcastRecording);
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the live voice broadcast avatar addon", () => {
|
||||
expect(renderResult.queryByTestId("user-menu-live-vb")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast ends", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
voiceBroadcastRecordingsStore.clearCurrent();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the live voice broadcast avatar addon", () => {
|
||||
expect(renderResult.queryByTestId("user-menu-live-vb")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("<UserMenu> logout", () => {
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
|
@ -106,7 +41,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
it("should logout directly if no crypto", async () => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
render(<UserMenu isPanelCollapsed={true} />);
|
||||
|
||||
mocked(client.getRooms).mockReturnValue([
|
||||
{
|
||||
|
@ -128,7 +63,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
it("should logout directly if no encrypted rooms", async () => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
render(<UserMenu isPanelCollapsed={true} />);
|
||||
|
||||
mocked(client.getRooms).mockReturnValue([
|
||||
{
|
||||
|
@ -152,7 +87,7 @@ describe("<UserMenu>", () => {
|
|||
|
||||
it("should show dialog if some encrypted rooms", async () => {
|
||||
const UserMenu = wrapInSdkContext(UnwrappedUserMenu, sdkContext);
|
||||
renderResult = render(<UserMenu isPanelCollapsed={true} />);
|
||||
render(<UserMenu isPanelCollapsed={true} />);
|
||||
|
||||
mocked(client.getRooms).mockReturnValue([
|
||||
{
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<UserMenu> <UserMenu> when video broadcast when rendered should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_UserMenu"
|
||||
>
|
||||
<div
|
||||
aria-expanded="false"
|
||||
aria-haspopup="true"
|
||||
aria-label="User menu"
|
||||
class="mx_AccessibleButton mx_UserMenu_contextMenuButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_UserMenu_userAvatar"
|
||||
>
|
||||
<span
|
||||
class="_avatar_mcap2_17 mx_BaseAvatar mx_UserMenu_userAvatar_BaseAvatar _avatar-imageless_mcap2_61"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
style="--cpd-avatar-size: 32px;"
|
||||
>
|
||||
u
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -37,8 +37,6 @@ import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
|||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { ReadPinsEventId } from "../../../../../src/components/views/right_panel/types";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createMessageEventContent } from "../../../../test-utils/events";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
|
@ -234,17 +232,6 @@ describe("MessageContextMenu", () => {
|
|||
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should not allow forwarding a voice broadcast", () => {
|
||||
const broadcastStartEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
createMenu(broadcastStartEvent);
|
||||
expect(document.querySelector('li[aria-label="Forward"]')).toBeFalsy();
|
||||
});
|
||||
|
||||
describe("forwarding beacons", () => {
|
||||
const aliceId = "@alice:server.org";
|
||||
|
||||
|
|
|
@ -6,14 +6,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
|||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Feature, ServerSupport } from "matrix-js-sdk/src/feature";
|
||||
import { MatrixClient, MatrixEvent, RelationType } from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { screen, act } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { flushPromises, mkEvent, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { createRedactEventDialog } from "../../../../../src/components/views/dialogs/ConfirmRedactDialog";
|
||||
|
||||
describe("ConfirmRedactDialog", () => {
|
||||
|
@ -21,15 +18,6 @@ describe("ConfirmRedactDialog", () => {
|
|||
let client: MatrixClient;
|
||||
let mxEvent: MatrixEvent;
|
||||
|
||||
const setUpVoiceBroadcastStartedEvent = () => {
|
||||
mxEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
};
|
||||
|
||||
const confirmDeleteVoiceBroadcastStartedEvent = async () => {
|
||||
act(() => createRedactEventDialog({ mxEvent }));
|
||||
// double-flush promises required for the dialog to show up
|
||||
|
@ -68,44 +56,4 @@ describe("ConfirmRedactDialog", () => {
|
|||
`cannot redact event ${mxEvent.getId()} without room ID`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when redacting a voice broadcast started event", () => {
|
||||
beforeEach(() => {
|
||||
setUpVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
describe("and the server does not support relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unsupported);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact without `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the server supports relation based redactions", () => {
|
||||
beforeEach(() => {
|
||||
client.canSupport.set(Feature.RelationBasedRedactions, ServerSupport.Unstable);
|
||||
});
|
||||
|
||||
describe("and displaying and confirm the dialog for a voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
await confirmDeleteVoiceBroadcastStartedEvent();
|
||||
});
|
||||
|
||||
it("should call redact with `with_rel_types`", () => {
|
||||
expect(client.redactEvent).toHaveBeenCalledWith(roomId, mxEvent.getId(), undefined, {
|
||||
with_rel_types: [RelationType.Reference],
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -185,33 +185,6 @@ exports[`DevtoolsDialog renders the devtools dialog 1`] = `
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_SettingsFlag"
|
||||
>
|
||||
<label
|
||||
class="mx_SettingsFlag_label"
|
||||
for="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
>
|
||||
<span
|
||||
class="mx_SettingsFlag_labelText"
|
||||
>
|
||||
Force 15s voice broadcast chunk length
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
aria-checked="false"
|
||||
aria-disabled="false"
|
||||
aria-label="Force 15s voice broadcast chunk length"
|
||||
class="mx_AccessibleButton mx_ToggleSwitch mx_ToggleSwitch_enabled"
|
||||
id="mx_SettingsFlag_4yVCeEefiPqp"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_ToggleSwitch_ball"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
@ -14,7 +14,6 @@ import fs from "fs";
|
|||
import path from "path";
|
||||
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { mkEvent, mkRoom, stubClient } from "../../../../test-utils";
|
||||
import MessageEvent from "../../../../../src/components/views/messages/MessageEvent";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
|
@ -24,10 +23,6 @@ jest.mock("../../../../../src/components/views/messages/UnknownBody", () => ({
|
|||
default: () => <div data-testid="unknown-body" />,
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/voice-broadcast/components/VoiceBroadcastBody", () => ({
|
||||
VoiceBroadcastBody: () => <div data-testid="voice-broadcast-body" />,
|
||||
}));
|
||||
|
||||
jest.mock("../../../../../src/components/views/messages/MImageBody", () => ({
|
||||
__esModule: true,
|
||||
default: () => <div data-testid="image-body" />,
|
||||
|
@ -81,27 +76,6 @@ describe("MessageEvent", () => {
|
|||
jest.spyOn(SettingsStore, "unwatchSetting").mockImplementation(jest.fn());
|
||||
});
|
||||
|
||||
describe("when a voice broadcast start event occurs", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
event = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getUserId()!,
|
||||
room: room.roomId,
|
||||
content: {
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
},
|
||||
});
|
||||
result = renderMessageEvent();
|
||||
});
|
||||
|
||||
it("should render a VoiceBroadcast component", () => {
|
||||
result.getByTestId("voice-broadcast-body");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when an image with a caption is sent", () => {
|
||||
let result: RenderResult;
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { EventType, MatrixEvent, Room, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixEvent, RoomMember, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
|
||||
import { act, fireEvent, render, screen, waitFor } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
|
@ -19,7 +19,6 @@ import {
|
|||
mkStubRoom,
|
||||
mockPlatformPeg,
|
||||
stubClient,
|
||||
waitEnoughCyclesForModal,
|
||||
} from "../../../../test-utils";
|
||||
import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
|
@ -28,7 +27,6 @@ import { IRoomState } from "../../../../../src/components/structures/RoomView";
|
|||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||
import { Features } from "../../../../../src/settings/Settings";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { SettingLevel } from "../../../../../src/settings/SettingLevel";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
|
@ -36,9 +34,6 @@ import { E2EStatus } from "../../../../../src/utils/ShieldUtils";
|
|||
import { addTextToComposerRTL } from "../../../../test-utils/composer";
|
||||
import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { VoiceBroadcastInfoState, VoiceBroadcastRecording } from "../../../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
const openStickerPicker = async (): Promise<void> => {
|
||||
|
@ -51,15 +46,6 @@ const startVoiceMessage = async (): Promise<void> => {
|
|||
await userEvent.click(screen.getByLabelText("Voice Message"));
|
||||
};
|
||||
|
||||
const setCurrentBroadcastRecording = (room: Room, state: VoiceBroadcastInfoState): void => {
|
||||
const recording = new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(room.roomId, state, "@user:example.com", "ABC123"),
|
||||
MatrixClientPeg.safeGet(),
|
||||
state,
|
||||
);
|
||||
act(() => SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording));
|
||||
};
|
||||
|
||||
const expectVoiceMessageRecordingTriggered = (): void => {
|
||||
// Checking for the voice message dialog text, if no mic can be found.
|
||||
// By this we know at least that starting a voice message was triggered.
|
||||
|
@ -78,14 +64,11 @@ describe("MessageComposer", () => {
|
|||
await clearAllModals();
|
||||
jest.useRealTimers();
|
||||
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||
|
||||
// restore settings
|
||||
act(() => {
|
||||
[
|
||||
"MessageComposerInput.showStickersButton",
|
||||
"MessageComposerInput.showPollsButton",
|
||||
Features.VoiceBroadcast,
|
||||
"feature_wysiwyg_composer",
|
||||
].forEach((setting: string): void => {
|
||||
SettingsStore.setValue(setting, null, SettingLevel.DEVICE, SettingsStore.getDefaultValue(setting));
|
||||
|
@ -212,10 +195,6 @@ describe("MessageComposer", () => {
|
|||
setting: "MessageComposerInput.showPollsButton",
|
||||
buttonLabel: "Poll",
|
||||
},
|
||||
{
|
||||
setting: Features.VoiceBroadcast,
|
||||
buttonLabel: "Voice broadcast",
|
||||
},
|
||||
].forEach(({ setting, buttonLabel }) => {
|
||||
[true, false].forEach((value: boolean) => {
|
||||
describe(`when ${setting} = ${value}`, () => {
|
||||
|
@ -437,34 +416,6 @@ describe("MessageComposer", () => {
|
|||
expectVoiceMessageRecordingTriggered();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when recording a voice broadcast and trying to start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Started);
|
||||
wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should not start a voice message and display the info dialog", async () => {
|
||||
expect(screen.queryByLabelText("Stop recording")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Can't start voice message")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a stopped voice broadcast recording and trying to start a voice message", () => {
|
||||
beforeEach(async () => {
|
||||
setCurrentBroadcastRecording(room, VoiceBroadcastInfoState.Stopped);
|
||||
wrapAndRender({ room });
|
||||
await startVoiceMessage();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should try to start a voice message and should not display the info dialog", async () => {
|
||||
expect(screen.queryByText("Can't start voice message")).not.toBeInTheDocument();
|
||||
expectVoiceMessageRecordingTriggered();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("for a LocalRoom", () => {
|
||||
|
|
|
@ -168,27 +168,4 @@ describe("MessageComposerButtons", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with showVoiceBroadcastButton = true", () => {
|
||||
it("should render the »Voice broadcast« button", () => {
|
||||
wrapAndRender(
|
||||
<MessageComposerButtons
|
||||
{...mockProps}
|
||||
isMenuOpen={true}
|
||||
showLocationButton={true}
|
||||
showPollsButton={true}
|
||||
showStickersButton={true}
|
||||
showVoiceBroadcastButton={true}
|
||||
/>,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(getButtonLabels()).toEqual([
|
||||
"Emoji",
|
||||
"Attachment",
|
||||
"More options",
|
||||
["Sticker", "Voice Message", "Voice broadcast", "Poll", "Location"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,14 +9,7 @@ Please see LICENSE files in the repository root for full details.
|
|||
import React from "react";
|
||||
import { render, screen, act, RenderResult } from "jest-matrix-react";
|
||||
import { mocked, Mocked } from "jest-mock";
|
||||
import {
|
||||
MatrixClient,
|
||||
PendingEventOrdering,
|
||||
Room,
|
||||
MatrixEvent,
|
||||
RoomStateEvent,
|
||||
Thread,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { MatrixClient, PendingEventOrdering, Room, RoomStateEvent, Thread } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
import { Widget } from "matrix-widget-api";
|
||||
|
||||
|
@ -40,8 +33,6 @@ import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
|||
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";
|
||||
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
|
||||
|
@ -61,20 +52,6 @@ describe("RoomTile", () => {
|
|||
} as unknown as BasePlatform);
|
||||
useMockedCalls();
|
||||
|
||||
const setUpVoiceBroadcast = async (state: VoiceBroadcastInfoState): Promise<void> => {
|
||||
voiceBroadcastInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
state,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([voiceBroadcastInfoEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
};
|
||||
|
||||
const renderRoomTile = (): RenderResult => {
|
||||
return render(
|
||||
<SDKContext.Provider value={sdkContext}>
|
||||
|
@ -89,7 +66,6 @@ describe("RoomTile", () => {
|
|||
};
|
||||
|
||||
let client: Mocked<MatrixClient>;
|
||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let room: Room;
|
||||
let sdkContext: TestSdkContext;
|
||||
let showMessagePreview = false;
|
||||
|
@ -303,49 +279,6 @@ describe("RoomTile", () => {
|
|||
});
|
||||
expect(screen.queryByLabelText(/participant/)).toBe(null);
|
||||
});
|
||||
|
||||
describe("and a live broadcast starts", () => {
|
||||
beforeEach(async () => {
|
||||
renderRoomTile();
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should still render the call subtitle", () => {
|
||||
expect(screen.queryByText("Video")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Live")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when a live voice broadcast starts", () => {
|
||||
beforeEach(async () => {
|
||||
renderRoomTile();
|
||||
await setUpVoiceBroadcast(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should render the »Live« subtitle", () => {
|
||||
expect(screen.queryByText("Live")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the broadcast stops", () => {
|
||||
beforeEach(async () => {
|
||||
const stopEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
room.roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.getDeviceId()!,
|
||||
voiceBroadcastInfoEvent,
|
||||
);
|
||||
await act(async () => {
|
||||
room.currentState.setStateEvents([stopEvent]);
|
||||
await flushPromises();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render the »Live« subtitle", () => {
|
||||
expect(screen.queryByText("Live")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import userEvent from "@testing-library/user-event";
|
|||
import RolesRoomSettingsTab from "../../../../../../../src/components/views/settings/tabs/room/RolesRoomSettingsTab";
|
||||
import { mkStubRoom, withClientContextRenderOptions, stubClient } from "../../../../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg";
|
||||
import { VoiceBroadcastInfoEventType } from "../../../../../../../src/voice-broadcast";
|
||||
import SettingsStore from "../../../../../../../src/settings/SettingsStore";
|
||||
import { ElementCall } from "../../../../../../../src/models/Call";
|
||||
|
||||
|
@ -34,14 +33,6 @@ describe("RolesRoomSettingsTab", () => {
|
|||
return renderResult;
|
||||
};
|
||||
|
||||
const getVoiceBroadcastsSelect = async (): Promise<Element> => {
|
||||
return (await renderTab()).container.querySelector("select[label='Voice broadcasts']")!;
|
||||
};
|
||||
|
||||
const getVoiceBroadcastsSelectedOption = async (): Promise<Element> => {
|
||||
return (await renderTab()).container.querySelector("select[label='Voice broadcasts'] option:checked")!;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
cli = MatrixClientPeg.safeGet();
|
||||
|
@ -76,26 +67,6 @@ describe("RolesRoomSettingsTab", () => {
|
|||
expect(container.querySelector(`[placeholder="@admin:server"]`)).toBeDisabled();
|
||||
});
|
||||
|
||||
it("should initially show »Moderator« permission for »Voice broadcasts«", async () => {
|
||||
expect((await getVoiceBroadcastsSelectedOption()).textContent).toBe("Moderator");
|
||||
});
|
||||
|
||||
describe("when setting »Default« permission for »Voice broadcasts«", () => {
|
||||
beforeEach(async () => {
|
||||
fireEvent.change(await getVoiceBroadcastsSelect(), {
|
||||
target: { value: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("should update the power levels", () => {
|
||||
expect(cli.sendStateEvent).toHaveBeenCalledWith(roomId, EventType.RoomPowerLevels, {
|
||||
events: {
|
||||
[VoiceBroadcastInfoEventType]: 0,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Element Call", () => {
|
||||
const setGroupCallsEnabled = (val: boolean): void => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((name: string) => {
|
||||
|
|
|
@ -11,11 +11,8 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
|||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||
import { OidcClientStore } from "../../../src/stores/oidc/OidcClientStore";
|
||||
import { UserProfilesStore } from "../../../src/stores/UserProfilesStore";
|
||||
import { VoiceBroadcastPreRecordingStore } from "../../../src/voice-broadcast";
|
||||
import { createTestClient } from "../../test-utils";
|
||||
|
||||
jest.mock("../../../src/voice-broadcast/stores/VoiceBroadcastPreRecordingStore");
|
||||
|
||||
describe("SdkContextClass", () => {
|
||||
let sdkContext = SdkContextClass.instance;
|
||||
let client: MatrixClient;
|
||||
|
@ -33,12 +30,6 @@ describe("SdkContextClass", () => {
|
|||
expect(SdkContextClass.instance).toBe(globalInstance);
|
||||
});
|
||||
|
||||
it("voiceBroadcastPreRecordingStore should always return the same VoiceBroadcastPreRecordingStore", () => {
|
||||
const first = sdkContext.voiceBroadcastPreRecordingStore;
|
||||
expect(first).toBeInstanceOf(VoiceBroadcastPreRecordingStore);
|
||||
expect(sdkContext.voiceBroadcastPreRecordingStore).toBe(first);
|
||||
});
|
||||
|
||||
it("userProfilesStore should raise an error without a client", () => {
|
||||
expect(() => sdkContext.userProfilesStore).toThrow("Unable to create UserProfilesStore without a client");
|
||||
});
|
||||
|
|
|
@ -7,19 +7,16 @@ Please see LICENSE files in the repository root for full details.
|
|||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { EventType, MatrixClient, MatrixEvent, MsgType, RelationType, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { EventType, MatrixClient, MatrixEvent, MsgType, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
JSONEventFactory,
|
||||
MessageEventFactory,
|
||||
pickFactory,
|
||||
RoomCreateEventFactory,
|
||||
TextualEventFactory,
|
||||
} from "../../../src/events/EventTileFactory";
|
||||
import SettingsStore from "../../../src/settings/SettingsStore";
|
||||
import { VoiceBroadcastChunkEventType, VoiceBroadcastInfoState } from "../../../src/voice-broadcast";
|
||||
import { createTestClient, mkEvent } from "../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils";
|
||||
|
||||
const roomId = "!room:example.com";
|
||||
|
||||
|
@ -31,11 +28,7 @@ describe("pickFactory", () => {
|
|||
let createEventWithoutPredecessor: MatrixEvent;
|
||||
let dynamicPredecessorEvent: MatrixEvent;
|
||||
|
||||
let voiceBroadcastStartedEvent: MatrixEvent;
|
||||
let voiceBroadcastStoppedEvent: MatrixEvent;
|
||||
let voiceBroadcastChunkEvent: MatrixEvent;
|
||||
let utdEvent: MatrixEvent;
|
||||
let utdBroadcastChunkEvent: MatrixEvent;
|
||||
let audioMessageEvent: MatrixEvent;
|
||||
|
||||
beforeAll(() => {
|
||||
|
@ -82,29 +75,6 @@ describe("pickFactory", () => {
|
|||
last_known_event_id: null,
|
||||
},
|
||||
});
|
||||
voiceBroadcastStartedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
room.addLiveEvents([voiceBroadcastStartedEvent], { addToState: true });
|
||||
voiceBroadcastStoppedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getUserId()!,
|
||||
client.deviceId!,
|
||||
);
|
||||
voiceBroadcastChunkEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
msgtype: MsgType.Audio,
|
||||
[VoiceBroadcastChunkEventType]: {},
|
||||
},
|
||||
});
|
||||
audioMessageEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
|
@ -123,20 +93,6 @@ describe("pickFactory", () => {
|
|||
msgtype: "m.bad.encrypted",
|
||||
},
|
||||
});
|
||||
utdBroadcastChunkEvent = mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomMessage,
|
||||
user: client.getUserId()!,
|
||||
room: roomId,
|
||||
content: {
|
||||
"msgtype": "m.bad.encrypted",
|
||||
"m.relates_to": {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: voiceBroadcastStartedEvent.getId(),
|
||||
},
|
||||
},
|
||||
});
|
||||
jest.spyOn(utdBroadcastChunkEvent, "isDecryptionFailure").mockReturnValue(true);
|
||||
});
|
||||
|
||||
it("should return JSONEventFactory for a no-op m.room.power_levels event", () => {
|
||||
|
@ -151,10 +107,6 @@ describe("pickFactory", () => {
|
|||
});
|
||||
|
||||
describe("when showing hidden events", () => {
|
||||
it("should return a JSONEventFactory for a voice broadcast event", () => {
|
||||
expect(pickFactory(voiceBroadcastChunkEvent, client, true)).toBe(JSONEventFactory);
|
||||
});
|
||||
|
||||
it("should return a JSONEventFactory for a room create event without predecessor", () => {
|
||||
room.currentState.events.set(
|
||||
EventType.RoomCreate,
|
||||
|
@ -164,17 +116,9 @@ describe("pickFactory", () => {
|
|||
expect(pickFactory(createEventWithoutPredecessor, client, true)).toBe(JSONEventFactory);
|
||||
});
|
||||
|
||||
it("should return a TextualEventFactory for a voice broadcast stopped event", () => {
|
||||
expect(pickFactory(voiceBroadcastStoppedEvent, client, true)).toBe(TextualEventFactory);
|
||||
});
|
||||
|
||||
it("should return a MessageEventFactory for an audio message event", () => {
|
||||
expect(pickFactory(audioMessageEvent, client, true)).toBe(MessageEventFactory);
|
||||
});
|
||||
|
||||
it("should return a MessageEventFactory for a UTD broadcast chunk event", () => {
|
||||
expect(pickFactory(utdBroadcastChunkEvent, client, true)).toBe(MessageEventFactory);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when not showing hidden events", () => {
|
||||
|
@ -252,14 +196,6 @@ describe("pickFactory", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("should return undefined for a voice broadcast event", () => {
|
||||
expect(pickFactory(voiceBroadcastChunkEvent, client, false)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return a TextualEventFactory for a voice broadcast stopped event", () => {
|
||||
expect(pickFactory(voiceBroadcastStoppedEvent, client, false)).toBe(TextualEventFactory);
|
||||
});
|
||||
|
||||
it("should return a MessageEventFactory for an audio message event", () => {
|
||||
expect(pickFactory(audioMessageEvent, client, false)).toBe(MessageEventFactory);
|
||||
});
|
||||
|
@ -267,9 +203,5 @@ describe("pickFactory", () => {
|
|||
it("should return a MessageEventFactory for a UTD event", () => {
|
||||
expect(pickFactory(utdEvent, client, false)).toBe(MessageEventFactory);
|
||||
});
|
||||
|
||||
it("should return undefined for a UTD broadcast chunk event", () => {
|
||||
expect(pickFactory(utdBroadcastChunkEvent, client, false)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,13 +24,6 @@ import { ActiveRoomChangedPayload } from "../../../src/dispatcher/payloads/Activ
|
|||
import { SpaceStoreClass } from "../../../src/stores/spaces/SpaceStore";
|
||||
import { TestSdkContext } from "../TestSdkContext";
|
||||
import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastRecording,
|
||||
} from "../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils";
|
||||
import Modal from "../../../src/Modal";
|
||||
import ErrorDialog from "../../../src/components/views/dialogs/ErrorDialog";
|
||||
import { CancelAskToJoinPayload } from "../../../src/dispatcher/payloads/CancelAskToJoinPayload";
|
||||
|
@ -160,7 +153,6 @@ describe("RoomViewStore", function () {
|
|||
stores._SlidingSyncManager = slidingSyncManager;
|
||||
stores._PosthogAnalytics = new MockPosthogAnalytics();
|
||||
stores._SpaceStore = new MockSpaceStore();
|
||||
stores._VoiceBroadcastPlaybacksStore = new VoiceBroadcastPlaybacksStore(stores.voiceBroadcastRecordingsStore);
|
||||
roomViewStore = new RoomViewStore(dis, stores);
|
||||
stores._RoomViewStore = roomViewStore;
|
||||
});
|
||||
|
@ -343,88 +335,6 @@ describe("RoomViewStore", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("when listening to a voice broadcast", () => {
|
||||
let voiceBroadcastPlayback: VoiceBroadcastPlayback;
|
||||
|
||||
beforeEach(() => {
|
||||
voiceBroadcastPlayback = new VoiceBroadcastPlayback(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
mockClient.getSafeUserId(),
|
||||
"d42",
|
||||
),
|
||||
mockClient,
|
||||
stores.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
stores.voiceBroadcastPlaybacksStore.setCurrent(voiceBroadcastPlayback);
|
||||
jest.spyOn(voiceBroadcastPlayback, "pause").mockImplementation();
|
||||
});
|
||||
|
||||
it("and viewing a call it should pause the current broadcast", async () => {
|
||||
await viewCall();
|
||||
expect(voiceBroadcastPlayback.pause).toHaveBeenCalled();
|
||||
expect(roomViewStore.isViewingCall()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when recording a voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
stores.voiceBroadcastRecordingsStore.setCurrent(
|
||||
new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
mockClient.getSafeUserId(),
|
||||
"d42",
|
||||
),
|
||||
mockClient,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("and trying to view a call, it should not actually view it and show the info dialog", async () => {
|
||||
await viewCall();
|
||||
expect(Modal.createDialog).toMatchSnapshot();
|
||||
expect(roomViewStore.isViewingCall()).toBe(false);
|
||||
});
|
||||
|
||||
describe("and viewing a room with a broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
const broadcastEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId2,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
mockClient.getSafeUserId(),
|
||||
"ABC123",
|
||||
);
|
||||
room2.addLiveEvents([broadcastEvent], { addToState: true });
|
||||
|
||||
stores.voiceBroadcastPlaybacksStore.getByInfoEvent(broadcastEvent, mockClient);
|
||||
dis.dispatch({ action: Action.ViewRoom, room_id: roomId2 });
|
||||
await untilDispatch(Action.ActiveRoomChanged, dis);
|
||||
});
|
||||
|
||||
it("should continue recording", () => {
|
||||
expect(stores.voiceBroadcastPlaybacksStore.getCurrent()).toBeNull();
|
||||
expect(stores.voiceBroadcastRecordingsStore.getCurrent()?.getState()).toBe(
|
||||
VoiceBroadcastInfoState.Started,
|
||||
);
|
||||
});
|
||||
|
||||
describe("and stopping the recording", () => {
|
||||
beforeEach(async () => {
|
||||
await stores.voiceBroadcastRecordingsStore.getCurrent()?.stop();
|
||||
// check test precondition
|
||||
expect(stores.voiceBroadcastRecordingsStore.getCurrent()).toBeNull();
|
||||
});
|
||||
|
||||
it("should view the broadcast", () => {
|
||||
expect(stores.voiceBroadcastPlaybacksStore.getCurrent()?.infoEvent.getRoomId()).toBe(roomId2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sliding Sync", function () {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, value) => {
|
||||
|
|
|
@ -18,26 +18,3 @@ exports[`RoomViewStore should display the generic error message when the roomId
|
|||
"title": "Failed to join",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`RoomViewStore when recording a voice broadcast and trying to view a call, it should not actually view it and show the info dialog 1`] = `
|
||||
[MockFunction] {
|
||||
"calls": [
|
||||
[
|
||||
[Function],
|
||||
{
|
||||
"description": <p>
|
||||
You can’t start a call as you are currently recording a live broadcast. Please end your live broadcast in order to start a call.
|
||||
</p>,
|
||||
"hasCloseButton": true,
|
||||
"title": "Can’t start a call",
|
||||
},
|
||||
],
|
||||
],
|
||||
"results": [
|
||||
{
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -71,18 +71,5 @@ describe("MessageEventPreview", () => {
|
|||
});
|
||||
expect(preview.getTextFor(event)).toBe(`${userId}: test new content body`);
|
||||
});
|
||||
|
||||
it("when called with a broadcast chunk event it should return null", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {
|
||||
body: "test body",
|
||||
["io.element.voice_broadcast_chunk"]: {},
|
||||
},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
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 { Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { VoiceBroadcastPreview } from "../../../../../src/stores/room-list/previews/VoiceBroadcastPreview";
|
||||
import { VoiceBroadcastInfoState } from "../../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/test-utils";
|
||||
|
||||
describe("VoiceBroadcastPreview.getTextFor", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const userId = "@user:example.com";
|
||||
const deviceId = "d42";
|
||||
let preview: VoiceBroadcastPreview;
|
||||
|
||||
beforeAll(() => {
|
||||
preview = new VoiceBroadcastPreview();
|
||||
});
|
||||
|
||||
it("when passing an event with empty content, it should return null", () => {
|
||||
const event = mkEvent({
|
||||
event: true,
|
||||
content: {},
|
||||
user: userId,
|
||||
type: "m.room.message",
|
||||
});
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("when passing a broadcast started event, it should return null", () => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId);
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
|
||||
it("when passing a broadcast stopped event, it should return the expected text", () => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId);
|
||||
expect(preview.getTextFor(event)).toBe("@user:example.com ended a voice broadcast");
|
||||
});
|
||||
|
||||
it("when passing a redacted broadcast stopped event, it should return null", () => {
|
||||
const event = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Stopped, userId, deviceId);
|
||||
event.makeRedacted(
|
||||
mkEvent({ event: true, content: {}, user: userId, type: "m.room.redaction" }),
|
||||
new Room(roomId, stubClient(), userId),
|
||||
);
|
||||
expect(preview.getTextFor(event)).toBeNull();
|
||||
});
|
||||
});
|
|
@ -22,9 +22,6 @@ import { waitFor } from "jest-matrix-react";
|
|||
import { stubClient, mkRoom, mkEvent } from "../../../test-utils";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { StopGapWidget } from "../../../../src/stores/widgets/StopGapWidget";
|
||||
import { ElementWidgetActions } from "../../../../src/stores/widgets/ElementWidgetActions";
|
||||
import { VoiceBroadcastInfoEventType, VoiceBroadcastRecording } from "../../../../src/voice-broadcast";
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||
import ActiveWidgetStore from "../../../../src/stores/ActiveWidgetStore";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
|
||||
|
@ -225,41 +222,6 @@ describe("StopGapWidget", () => {
|
|||
expect(messaging.feedEvent).toHaveBeenLastCalledWith(event.getEffectiveEvent(), "!1:example.org");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a voice broadcast recording", () => {
|
||||
let voiceBroadcastInfoEvent: MatrixEvent;
|
||||
let voiceBroadcastRecording: VoiceBroadcastRecording;
|
||||
|
||||
beforeEach(() => {
|
||||
voiceBroadcastInfoEvent = mkEvent({
|
||||
event: true,
|
||||
room: client.getRoom("x")?.roomId,
|
||||
user: client.getUserId()!,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
content: {},
|
||||
});
|
||||
voiceBroadcastRecording = new VoiceBroadcastRecording(voiceBroadcastInfoEvent, client);
|
||||
jest.spyOn(voiceBroadcastRecording, "pause");
|
||||
jest.spyOn(SdkContextClass.instance.voiceBroadcastRecordingsStore, "getCurrent").mockReturnValue(
|
||||
voiceBroadcastRecording,
|
||||
);
|
||||
});
|
||||
|
||||
describe(`and receiving a action:${ElementWidgetActions.JoinCall} message`, () => {
|
||||
beforeEach(async () => {
|
||||
messaging.on.mock.calls.find(([event, listener]) => {
|
||||
if (event === `action:${ElementWidgetActions.JoinCall}`) {
|
||||
listener();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should pause the current voice broadcast recording", () => {
|
||||
expect(voiceBroadcastRecording.pause).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe("StopGapWidget with stickyPromise", () => {
|
||||
let client: MockedObject<MatrixClient>;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
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 { getEventDisplayInfo } from "../../../src/utils/EventRenderingUtils";
|
||||
import { VoiceBroadcastInfoState } from "../../../src/voice-broadcast";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils";
|
||||
import { createTestClient } from "../../test-utils";
|
||||
|
||||
describe("getEventDisplayInfo", () => {
|
||||
const mkBroadcastInfoEvent = (state: VoiceBroadcastInfoState) => {
|
||||
return mkVoiceBroadcastInfoStateEvent("!room:example.com", state, "@user:example.com", "ASD123");
|
||||
};
|
||||
|
||||
it("should return the expected value for a broadcast started event", () => {
|
||||
expect(getEventDisplayInfo(createTestClient(), mkBroadcastInfoEvent(VoiceBroadcastInfoState.Started), false))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"hasRenderer": true,
|
||||
"isBubbleMessage": false,
|
||||
"isInfoMessage": false,
|
||||
"isLeftAlignedBubbleMessage": false,
|
||||
"isSeeingThroughMessageHiddenForModeration": false,
|
||||
"noBubbleEvent": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should return the expected value for a broadcast stopped event", () => {
|
||||
expect(getEventDisplayInfo(createTestClient(), mkBroadcastInfoEvent(VoiceBroadcastInfoState.Stopped), false))
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"hasRenderer": true,
|
||||
"isBubbleMessage": false,
|
||||
"isInfoMessage": true,
|
||||
"isLeftAlignedBubbleMessage": false,
|
||||
"isSeeingThroughMessageHiddenForModeration": false,
|
||||
"noBubbleEvent": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -35,8 +35,6 @@ import {
|
|||
import { getMockClientWithEventEmitter, makeBeaconInfoEvent, makePollStartEvent, stubClient } from "../../test-utils";
|
||||
import dis from "../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../voice-broadcast/utils/test-utils";
|
||||
import { VoiceBroadcastInfoState } from "../../../src/voice-broadcast/types";
|
||||
|
||||
jest.mock("../../../src/dispatcher/dispatcher");
|
||||
|
||||
|
@ -148,20 +146,6 @@ describe("EventUtils", () => {
|
|||
},
|
||||
});
|
||||
|
||||
const voiceBroadcastStart = mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Started,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
|
||||
const voiceBroadcastStop = mkVoiceBroadcastInfoStateEvent(
|
||||
"!room:example.com",
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
"@user:example.com",
|
||||
"ABC123",
|
||||
);
|
||||
|
||||
describe("isContentActionable()", () => {
|
||||
type TestCase = [string, MatrixEvent];
|
||||
it.each<TestCase>([
|
||||
|
@ -172,7 +156,6 @@ describe("EventUtils", () => {
|
|||
["room member event", roomMemberEvent],
|
||||
["event without msgtype", noMsgType],
|
||||
["event without content body property", noContentBody],
|
||||
["broadcast stop event", voiceBroadcastStop],
|
||||
])("returns false for %s", (_description, event) => {
|
||||
expect(isContentActionable(event)).toBe(false);
|
||||
});
|
||||
|
@ -183,7 +166,6 @@ describe("EventUtils", () => {
|
|||
["event with empty content body", emptyContentBody],
|
||||
["event with a content body", niceTextMessage],
|
||||
["beacon_info event", beaconInfoEvent],
|
||||
["broadcast start event", voiceBroadcastStart],
|
||||
])("returns true for %s", (_description, event) => {
|
||||
expect(isContentActionable(event)).toBe(true);
|
||||
});
|
||||
|
|
|
@ -1,251 +0,0 @@
|
|||
/*
|
||||
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 { Optional } from "matrix-events-sdk";
|
||||
|
||||
import { VoiceRecording } from "../../../../src/audio/VoiceRecording";
|
||||
import SdkConfig from "../../../../src/SdkConfig";
|
||||
import { concat } from "../../../../src/utils/arrays";
|
||||
import {
|
||||
ChunkRecordedPayload,
|
||||
createVoiceBroadcastRecorder,
|
||||
VoiceBroadcastRecorder,
|
||||
VoiceBroadcastRecorderEvent,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
|
||||
// mock VoiceRecording because it contains all the audio APIs
|
||||
jest.mock("../../../../src/audio/VoiceRecording", () => ({
|
||||
VoiceRecording: jest.fn().mockReturnValue({
|
||||
disableMaxLength: jest.fn(),
|
||||
emit: jest.fn(),
|
||||
liveData: {
|
||||
onUpdate: jest.fn(),
|
||||
},
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/settings/SettingsStore");
|
||||
|
||||
describe("VoiceBroadcastRecorder", () => {
|
||||
describe("createVoiceBroadcastRecorder", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(SdkConfig, "get").mockImplementation((key: string) => {
|
||||
if (key === "voice_broadcast") {
|
||||
return {
|
||||
chunk_length: 1337,
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mocked(SdkConfig.get).mockRestore();
|
||||
});
|
||||
|
||||
it("should return a VoiceBroadcastRecorder instance with targetChunkLength from config", () => {
|
||||
const voiceBroadcastRecorder = createVoiceBroadcastRecorder();
|
||||
expect(voiceBroadcastRecorder).toBeInstanceOf(VoiceBroadcastRecorder);
|
||||
expect(voiceBroadcastRecorder.targetChunkLength).toBe(1337);
|
||||
});
|
||||
});
|
||||
|
||||
describe("instance", () => {
|
||||
const chunkLength = 30;
|
||||
// 0... OpusHead
|
||||
const headers1 = new Uint8Array([...Array(28).fill(0), 79, 112, 117, 115, 72, 101, 97, 100]);
|
||||
// 0... OpusTags
|
||||
const headers2 = new Uint8Array([...Array(28).fill(0), 79, 112, 117, 115, 84, 97, 103, 115]);
|
||||
const chunk1 = new Uint8Array([5, 6]);
|
||||
const chunk2a = new Uint8Array([7, 8]);
|
||||
const chunk2b = new Uint8Array([9, 10]);
|
||||
const contentType = "test content type";
|
||||
|
||||
let voiceRecording: VoiceRecording;
|
||||
let voiceBroadcastRecorder: VoiceBroadcastRecorder;
|
||||
let onChunkRecorded: (chunk: ChunkRecordedPayload) => void;
|
||||
|
||||
const simulateFirstChunk = (): void => {
|
||||
// send headers in wrong order and multiple times to test robustness for that
|
||||
voiceRecording.onDataAvailable!(headers2);
|
||||
voiceRecording.onDataAvailable!(headers1);
|
||||
voiceRecording.onDataAvailable!(headers1);
|
||||
voiceRecording.onDataAvailable!(headers2);
|
||||
// set recorder seconds to something greater than the test chunk length of 30
|
||||
// @ts-ignore
|
||||
voiceRecording.recorderSeconds = 42;
|
||||
voiceRecording.onDataAvailable!(chunk1);
|
||||
voiceRecording.onDataAvailable!(headers1);
|
||||
};
|
||||
|
||||
const expectOnFirstChunkRecorded = (): void => {
|
||||
expect(onChunkRecorded).toHaveBeenNthCalledWith(1, {
|
||||
buffer: concat(headers1, headers2, chunk1),
|
||||
length: 42,
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldNotEmitAChunkRecordedEvent = (): void => {
|
||||
it("should not emit a ChunkRecorded event", (): void => {
|
||||
expect(voiceRecording.emit).not.toHaveBeenCalledWith(
|
||||
VoiceBroadcastRecorderEvent.ChunkRecorded,
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
voiceRecording = new VoiceRecording();
|
||||
// @ts-ignore
|
||||
voiceRecording.recorderSeconds = 23;
|
||||
// @ts-ignore
|
||||
voiceRecording.contentType = contentType;
|
||||
|
||||
voiceBroadcastRecorder = new VoiceBroadcastRecorder(voiceRecording, chunkLength);
|
||||
jest.spyOn(voiceBroadcastRecorder, "removeAllListeners");
|
||||
onChunkRecorded = jest.fn();
|
||||
voiceBroadcastRecorder.on(VoiceBroadcastRecorderEvent.ChunkRecorded, onChunkRecorded);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
voiceBroadcastRecorder.destroy();
|
||||
});
|
||||
|
||||
it("start should forward the call to VoiceRecording.start", async () => {
|
||||
await voiceBroadcastRecorder.start();
|
||||
expect(voiceRecording.start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("stop", () => {
|
||||
beforeEach(async () => {
|
||||
await voiceBroadcastRecorder.stop();
|
||||
});
|
||||
|
||||
it("should forward the call to VoiceRecording.stop", async () => {
|
||||
expect(voiceRecording.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
itShouldNotEmitAChunkRecordedEvent();
|
||||
});
|
||||
|
||||
describe("when calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecorder.destroy();
|
||||
});
|
||||
|
||||
it("should call VoiceRecording.destroy", () => {
|
||||
expect(voiceRecording.destroy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should remove all listeners", () => {
|
||||
expect(voiceBroadcastRecorder.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("contentType should return the value from VoiceRecording", () => {
|
||||
expect(voiceBroadcastRecorder.contentType).toBe(contentType);
|
||||
});
|
||||
|
||||
describe("when the first header from recorder has been received", () => {
|
||||
beforeEach(() => {
|
||||
voiceRecording.onDataAvailable!(headers1);
|
||||
});
|
||||
|
||||
itShouldNotEmitAChunkRecordedEvent();
|
||||
});
|
||||
|
||||
describe("when the second header from recorder has been received", () => {
|
||||
beforeEach(() => {
|
||||
voiceRecording.onDataAvailable!(headers1);
|
||||
voiceRecording.onDataAvailable!(headers2);
|
||||
});
|
||||
|
||||
itShouldNotEmitAChunkRecordedEvent();
|
||||
});
|
||||
|
||||
describe("when a third page from recorder has been received", () => {
|
||||
beforeEach(() => {
|
||||
voiceRecording.onDataAvailable!(headers1);
|
||||
voiceRecording.onDataAvailable!(headers2);
|
||||
voiceRecording.onDataAvailable!(chunk1);
|
||||
});
|
||||
|
||||
itShouldNotEmitAChunkRecordedEvent();
|
||||
|
||||
describe("and calling stop", () => {
|
||||
let stopPayload: Optional<ChunkRecordedPayload>;
|
||||
|
||||
beforeEach(async () => {
|
||||
stopPayload = await voiceBroadcastRecorder.stop();
|
||||
});
|
||||
|
||||
it("should return the remaining chunk", () => {
|
||||
expect(stopPayload).toEqual({
|
||||
buffer: concat(headers1, headers2, chunk1),
|
||||
length: 23,
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling start again and receiving some data", () => {
|
||||
beforeEach(() => {
|
||||
simulateFirstChunk();
|
||||
});
|
||||
|
||||
it("should emit the ChunkRecorded event for the first chunk", () => {
|
||||
expectOnFirstChunkRecorded();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling stop() with recording.stop error)", () => {
|
||||
let stopPayload: Optional<ChunkRecordedPayload>;
|
||||
|
||||
beforeEach(async () => {
|
||||
mocked(voiceRecording.stop).mockRejectedValue("Error");
|
||||
stopPayload = await voiceBroadcastRecorder.stop();
|
||||
});
|
||||
|
||||
it("should return the remaining chunk", () => {
|
||||
expect(stopPayload).toEqual({
|
||||
buffer: concat(headers1, headers2, chunk1),
|
||||
length: 23,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when some chunks have been received", () => {
|
||||
beforeEach(() => {
|
||||
simulateFirstChunk();
|
||||
|
||||
// simulate a second chunk
|
||||
voiceRecording.onDataAvailable!(chunk2a);
|
||||
|
||||
// send headers again to test robustness for that
|
||||
voiceRecording.onDataAvailable!(headers2);
|
||||
|
||||
// add another 30 seconds for the next chunk
|
||||
// @ts-ignore
|
||||
voiceRecording.recorderSeconds = 72;
|
||||
voiceRecording.onDataAvailable!(chunk2b);
|
||||
});
|
||||
|
||||
it("should emit ChunkRecorded events", () => {
|
||||
expectOnFirstChunkRecorded();
|
||||
|
||||
expect(onChunkRecorded).toHaveBeenNthCalledWith(2, {
|
||||
buffer: concat(headers1, headers2, chunk2a, chunk2b),
|
||||
length: 72 - 42, // 72 (position at second chunk) - 42 (position of first chunk)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,171 +0,0 @@
|
|||
/*
|
||||
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, { ReactElement } from "react";
|
||||
import { act, render, screen } from "jest-matrix-react";
|
||||
import { mocked } from "jest-mock";
|
||||
import { MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastBody as UnwrappedVoiceBroadcastBody,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecordingBody,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastPlaybackBody,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { withClientContextRenderOptions, stubClient, wrapInSdkContext } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
||||
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks";
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||
|
||||
jest.mock("../../../../src/voice-broadcast/components/molecules/VoiceBroadcastRecordingBody", () => ({
|
||||
VoiceBroadcastRecordingBody: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/voice-broadcast/components/molecules/VoiceBroadcastPlaybackBody", () => ({
|
||||
VoiceBroadcastPlaybackBody: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/utils/permalinks/Permalinks");
|
||||
jest.mock("../../../../src/utils/MediaEventHelper");
|
||||
jest.mock("../../../../src/stores/WidgetStore");
|
||||
jest.mock("../../../../src/stores/widgets/WidgetLayoutStore");
|
||||
|
||||
describe("VoiceBroadcastBody", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let userId: string;
|
||||
let deviceId: string;
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let infoEvent: MatrixEvent;
|
||||
let stoppedEvent: MatrixEvent;
|
||||
let testRecording: VoiceBroadcastRecording;
|
||||
let testPlayback: VoiceBroadcastPlayback;
|
||||
|
||||
const renderVoiceBroadcast = () => {
|
||||
const VoiceBroadcastBody = wrapInSdkContext(UnwrappedVoiceBroadcastBody, SdkContextClass.instance);
|
||||
render(
|
||||
<VoiceBroadcastBody
|
||||
mxEvent={infoEvent}
|
||||
mediaEventHelper={new MediaEventHelper(infoEvent)}
|
||||
onHeightChanged={() => {}}
|
||||
onMessageAllowed={() => {}}
|
||||
permalinkCreator={new RoomPermalinkCreator(room)}
|
||||
/>,
|
||||
withClientContextRenderOptions(client),
|
||||
);
|
||||
testRecording = SdkContextClass.instance.voiceBroadcastRecordingsStore.getByInfoEvent(infoEvent, client);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
userId = client.getUserId() || "";
|
||||
deviceId = client.getDeviceId() || "";
|
||||
mocked(client.relations).mockClear();
|
||||
mocked(client.relations).mockResolvedValue({ events: [] });
|
||||
room = new Room(roomId, client, userId);
|
||||
mocked(client.getRoom).mockImplementation((getRoomId?: string) => {
|
||||
if (getRoomId === roomId) return room;
|
||||
return null;
|
||||
});
|
||||
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId);
|
||||
stoppedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
userId,
|
||||
deviceId,
|
||||
infoEvent,
|
||||
);
|
||||
room.addEventsToTimeline([infoEvent], true, true, room.getLiveTimeline());
|
||||
testRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
testPlayback = new VoiceBroadcastPlayback(infoEvent, client, new VoiceBroadcastRecordingsStore());
|
||||
mocked(VoiceBroadcastRecordingBody).mockImplementation(({ recording }): ReactElement | null => {
|
||||
if (testRecording === recording) {
|
||||
return <div data-testid="voice-broadcast-recording-body" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
mocked(VoiceBroadcastPlaybackBody).mockImplementation(({ playback }): ReactElement | null => {
|
||||
if (testPlayback === playback) {
|
||||
return <div data-testid="voice-broadcast-playback-body" />;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
jest.spyOn(SdkContextClass.instance.voiceBroadcastRecordingsStore, "getByInfoEvent").mockImplementation(
|
||||
(getEvent: MatrixEvent, getClient: MatrixClient): VoiceBroadcastRecording => {
|
||||
if (getEvent === infoEvent && getClient === client) {
|
||||
return testRecording;
|
||||
}
|
||||
|
||||
throw new Error("unexpected event");
|
||||
},
|
||||
);
|
||||
|
||||
jest.spyOn(SdkContextClass.instance.voiceBroadcastPlaybacksStore, "getByInfoEvent").mockImplementation(
|
||||
(getEvent: MatrixEvent): VoiceBroadcastPlayback => {
|
||||
if (getEvent === infoEvent) {
|
||||
return testPlayback;
|
||||
}
|
||||
|
||||
throw new Error("unexpected event");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("when there is a stopped voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
room.addEventsToTimeline([stoppedEvent], true, true, room.getLiveTimeline());
|
||||
renderVoiceBroadcast();
|
||||
});
|
||||
|
||||
it("should render a voice broadcast playback body", () => {
|
||||
screen.getByTestId("voice-broadcast-playback-body");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a started voice broadcast from the current user", () => {
|
||||
beforeEach(() => {
|
||||
renderVoiceBroadcast();
|
||||
});
|
||||
|
||||
it("should render a voice broadcast recording body", () => {
|
||||
screen.getByTestId("voice-broadcast-recording-body");
|
||||
});
|
||||
|
||||
describe("and the recordings ends", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
room.addEventsToTimeline([stoppedEvent], true, true, room.getLiveTimeline());
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a voice broadcast playback body", () => {
|
||||
screen.getByTestId("voice-broadcast-playback-body");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when displaying a voice broadcast playback", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client).getUserId.mockReturnValue("@other:example.com");
|
||||
renderVoiceBroadcast();
|
||||
});
|
||||
|
||||
it("should render a voice broadcast playback body", () => {
|
||||
screen.getByTestId("voice-broadcast-playback-body");
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
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 } from "jest-matrix-react";
|
||||
|
||||
import { LiveBadge } from "../../../../../src/voice-broadcast";
|
||||
|
||||
describe("LiveBadge", () => {
|
||||
it("should render as expected with default props", () => {
|
||||
const { container } = render(<LiveBadge />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should render in grey as expected", () => {
|
||||
const { container } = render(<LiveBadge grey={true} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -1,44 +0,0 @@
|
|||
/*
|
||||
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 { VoiceBroadcastControl } from "../../../../../src/voice-broadcast";
|
||||
import { Icon as StopIcon } from "../../../../res/img/compound/stop-16.svg";
|
||||
|
||||
describe("VoiceBroadcastControl", () => {
|
||||
let result: RenderResult;
|
||||
let onClick: () => void;
|
||||
|
||||
beforeEach(() => {
|
||||
onClick = jest.fn();
|
||||
});
|
||||
|
||||
describe("when rendering it", () => {
|
||||
beforeEach(() => {
|
||||
const stopIcon = <StopIcon className="mx_Icon mx_Icon_16" />;
|
||||
result = render(<VoiceBroadcastControl onClick={onClick} label="test label" icon={stopIcon} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("when clicking it", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("test label"));
|
||||
});
|
||||
|
||||
it("should call onClick", () => {
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
/*
|
||||
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 { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
|
||||
import { VoiceBroadcastHeader, VoiceBroadcastLiveness } from "../../../../../src/voice-broadcast";
|
||||
import { mkRoom, stubClient } from "../../../../test-utils";
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(({ room }) => {
|
||||
return <div data-testid="room-avatar">room avatar: {room.name}</div>;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastHeader", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
const sender = new RoomMember(roomId, userId);
|
||||
let container: RenderResult["container"];
|
||||
|
||||
const renderHeader = (live: VoiceBroadcastLiveness, showBroadcast?: boolean, buffering?: boolean): RenderResult => {
|
||||
return render(
|
||||
<VoiceBroadcastHeader
|
||||
live={live}
|
||||
microphoneLabel={sender.name}
|
||||
room={room}
|
||||
showBroadcast={showBroadcast}
|
||||
showBuffering={buffering}
|
||||
/>,
|
||||
);
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
room = mkRoom(client, roomId);
|
||||
sender.name = "test user";
|
||||
});
|
||||
|
||||
describe("when rendering a live broadcast header with broadcast info", () => {
|
||||
beforeEach(() => {
|
||||
container = renderHeader("live", true, true).container;
|
||||
});
|
||||
|
||||
it("should render the header with a red live badge", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a buffering live broadcast header with broadcast info", () => {
|
||||
beforeEach(() => {
|
||||
container = renderHeader("live", true).container;
|
||||
});
|
||||
|
||||
it("should render the header with a red live badge", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a live (grey) broadcast header with broadcast info", () => {
|
||||
beforeEach(() => {
|
||||
container = renderHeader("grey", true).container;
|
||||
});
|
||||
|
||||
it("should render the header with a grey live badge", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a non-live broadcast header", () => {
|
||||
beforeEach(() => {
|
||||
container = renderHeader("not-live").container;
|
||||
});
|
||||
|
||||
it("should render the header without a live badge", () => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 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 React from "react";
|
||||
import { render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { VoiceBroadcastPlaybackControl, VoiceBroadcastPlaybackState } from "../../../../../src/voice-broadcast";
|
||||
|
||||
describe("<VoiceBroadcastPlaybackControl />", () => {
|
||||
const renderControl = (state: VoiceBroadcastPlaybackState): { result: RenderResult; onClick: () => void } => {
|
||||
const onClick = jest.fn();
|
||||
return {
|
||||
onClick,
|
||||
result: render(<VoiceBroadcastPlaybackControl state={state} onClick={onClick} />),
|
||||
};
|
||||
};
|
||||
|
||||
it.each([
|
||||
VoiceBroadcastPlaybackState.Stopped,
|
||||
VoiceBroadcastPlaybackState.Paused,
|
||||
VoiceBroadcastPlaybackState.Buffering,
|
||||
VoiceBroadcastPlaybackState.Playing,
|
||||
])("should render state %s as expected", (state: VoiceBroadcastPlaybackState) => {
|
||||
expect(renderControl(state).result.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("should not render for error state", () => {
|
||||
expect(renderControl(VoiceBroadcastPlaybackState.Error).result.asFragment()).toMatchInlineSnapshot(
|
||||
`<DocumentFragment />`,
|
||||
);
|
||||
});
|
||||
|
||||
describe("when clicking the control", () => {
|
||||
let onClick: () => void;
|
||||
|
||||
beforeEach(async () => {
|
||||
onClick = renderControl(VoiceBroadcastPlaybackState.Playing).onClick;
|
||||
await userEvent.click(screen.getByLabelText("pause voice broadcast"));
|
||||
});
|
||||
|
||||
it("should invoke the onClick callback", () => {
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,27 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`LiveBadge should render as expected with default props 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`LiveBadge should render in grey as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_LiveBadge mx_LiveBadge--grey"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,16 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastControl when rendering it should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="test label"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,277 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastHeader when rendering a buffering live broadcast header with broadcast info should render the header with a red live badge 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
!room:example.com
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
!room:example.com
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
test user
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastHeader when rendering a live (grey) broadcast header with broadcast info should render the header with a grey live badge 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
!room:example.com
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
!room:example.com
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
test user
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge mx_LiveBadge--grey"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastHeader when rendering a live broadcast header with broadcast info should render the header with a red live badge 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
!room:example.com
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
!room:example.com
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
test user
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 14px; height: 14px;"
|
||||
/>
|
||||
</div>
|
||||
Buffering…
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastHeader when rendering a non-live broadcast header should render the header without a live badge 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
!room:example.com
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
!room:example.com
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
test user
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,97 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state buffering as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state pause as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state playing as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastPlaybackControl /> should render state stopped as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
aria-label="play voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,249 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 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 React from "react";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { act, render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastLiveness,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybackBody,
|
||||
VoiceBroadcastPlaybackEvent,
|
||||
VoiceBroadcastPlaybackState,
|
||||
} from "../../../../../src/voice-broadcast";
|
||||
import { filterConsole, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(({ room }) => {
|
||||
return <div data-testid="room-avatar">room avatar: {room.name}</div>;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastPlaybackBody", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
const duration = 23 * 60 + 42; // 23:42
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let renderResult: RenderResult;
|
||||
|
||||
filterConsole(
|
||||
// expected for some tests
|
||||
"voice broadcast chunk event to skip to not found",
|
||||
);
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
mocked(client.relations).mockClear();
|
||||
mocked(client.relations).mockResolvedValue({ events: [] });
|
||||
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
userId,
|
||||
client.getDeviceId(),
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
playback = new VoiceBroadcastPlayback(
|
||||
infoEvent,
|
||||
client,
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve());
|
||||
jest.spyOn(playback, "getLiveness");
|
||||
jest.spyOn(playback, "getState");
|
||||
jest.spyOn(playback, "skipTo");
|
||||
jest.spyOn(playback, "durationSeconds", "get").mockReturnValue(duration);
|
||||
});
|
||||
|
||||
describe("when rendering a buffering voice broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering);
|
||||
mocked(playback.getLiveness).mockReturnValue("live");
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a playing broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing);
|
||||
mocked(playback.getLiveness).mockReturnValue("not-live");
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and being in the middle of the playback", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
playback.emit(VoiceBroadcastPlaybackEvent.TimesChanged, {
|
||||
duration,
|
||||
position: 10 * 60,
|
||||
timeLeft: duration - 10 * 60,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking 30s backward", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("30s backward"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should seek 30s backward", () => {
|
||||
expect(playback.skipTo).toHaveBeenCalledWith(9 * 60 + 30);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking 30s forward", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByLabelText("30s forward"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should seek 30s forward", () => {
|
||||
expect(playback.skipTo).toHaveBeenCalledWith(10 * 60 + 30);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking the room name", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("My room"));
|
||||
});
|
||||
|
||||
it("should not view the room", () => {
|
||||
expect(dis.dispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a playing broadcast in pip mode", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing);
|
||||
mocked(playback.getLiveness).mockReturnValue("not-live");
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody pip={true} playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the room name", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("My room"));
|
||||
});
|
||||
|
||||
it("should view the room", () => {
|
||||
expect(dis.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when rendering a stopped broadcast`, () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
|
||||
mocked(playback.getLiveness).mockReturnValue("not-live");
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the play button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(renderResult.getByLabelText("play voice broadcast"));
|
||||
});
|
||||
|
||||
it("should toggle the recording", () => {
|
||||
expect(playback.toggle).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the times update", () => {
|
||||
beforeEach(() => {
|
||||
act(() => {
|
||||
playback.emit(VoiceBroadcastPlaybackEvent.TimesChanged, {
|
||||
duration,
|
||||
position: 5 * 60 + 13,
|
||||
timeLeft: 7 * 60 + 5,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the times", async () => {
|
||||
expect(await screen.findByText("05:13")).toBeInTheDocument();
|
||||
expect(await screen.findByText("-07:05")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering an error broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Error);
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
[VoiceBroadcastPlaybackState.Paused, "not-live"],
|
||||
[VoiceBroadcastPlaybackState.Playing, "live"],
|
||||
] satisfies [VoiceBroadcastPlaybackState, VoiceBroadcastLiveness][])(
|
||||
"when rendering a %s/%s broadcast",
|
||||
(state: VoiceBroadcastPlaybackState, liveness: VoiceBroadcastLiveness) => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(state);
|
||||
mocked(playback.getLiveness).mockReturnValue(liveness);
|
||||
renderResult = render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("when there is a broadcast without sender, it should raise an error", () => {
|
||||
infoEvent.sender = null;
|
||||
expect(() => {
|
||||
render(<VoiceBroadcastPlaybackBody playback={playback} />);
|
||||
}).toThrow(`Voice Broadcast sender not found (event ${playback.infoEvent.getId()})`);
|
||||
});
|
||||
});
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
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 { mocked } from "jest-mock";
|
||||
import { MatrixClient, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { act, render, RenderResult, screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastPreRecordingPip,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../../src/voice-broadcast";
|
||||
import { flushPromises, stubClient } from "../../../../test-utils";
|
||||
import { requestMediaPermissions } from "../../../../../src/utils/media/requestMediaPermissions";
|
||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../../src/MediaDeviceHandler";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
jest.mock("../../../../../src/utils/media/requestMediaPermissions");
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(({ room }) => {
|
||||
return <div data-testid="room-avatar">room avatar: {room.name}</div>;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastPreRecordingPip", () => {
|
||||
let renderResult: RenderResult;
|
||||
let preRecording: VoiceBroadcastPreRecording;
|
||||
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let sender: RoomMember;
|
||||
|
||||
const itShouldShowTheBroadcastRoom = () => {
|
||||
it("should show the broadcast room", () => {
|
||||
expect(dis.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: room.roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = new Room("!room@example.com", client, client.getUserId() || "");
|
||||
sender = new RoomMember(room.roomId, client.getUserId() || "");
|
||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore);
|
||||
mocked(requestMediaPermissions).mockResolvedValue({
|
||||
getTracks: (): Array<MediaStreamTrack> => [],
|
||||
} as unknown as MediaStream);
|
||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||
[MediaDeviceKindEnum.AudioInput]: [
|
||||
{
|
||||
deviceId: "d1",
|
||||
label: "Device 1",
|
||||
} as MediaDeviceInfo,
|
||||
{
|
||||
deviceId: "d2",
|
||||
label: "Device 2",
|
||||
} as MediaDeviceInfo,
|
||||
],
|
||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||
[MediaDeviceKindEnum.VideoInput]: [],
|
||||
});
|
||||
jest.spyOn(MediaDeviceHandler.instance, "setDevice").mockImplementation();
|
||||
preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||
jest.spyOn(preRecording, "start").mockResolvedValue();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("when rendered", () => {
|
||||
beforeEach(async () => {
|
||||
renderResult = render(<VoiceBroadcastPreRecordingPip voiceBroadcastPreRecording={preRecording} />);
|
||||
|
||||
await flushPromises();
|
||||
});
|
||||
|
||||
it("should match the snapshot", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and double clicking »Go live«", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("Go live"));
|
||||
await userEvent.click(screen.getByText("Go live"));
|
||||
});
|
||||
|
||||
it("should call start once", () => {
|
||||
expect(preRecording.start).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking the room name", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText(room.name));
|
||||
});
|
||||
|
||||
itShouldShowTheBroadcastRoom();
|
||||
});
|
||||
|
||||
describe("and clicking the room avatar", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText(`room avatar: ${room.name}`));
|
||||
});
|
||||
|
||||
itShouldShowTheBroadcastRoom();
|
||||
});
|
||||
|
||||
describe("and clicking the device label", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText("Default Device"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should display the device selection", () => {
|
||||
expect(screen.queryAllByText("Default Device").length).toBe(2);
|
||||
expect(screen.queryByText("Device 1")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Device 2")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and selecting a device", () => {
|
||||
beforeEach(async () => {
|
||||
await act(async () => {
|
||||
await userEvent.click(screen.getByText("Device 1"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should set it as current device", () => {
|
||||
expect(MediaDeviceHandler.instance.setDevice).toHaveBeenCalledWith(
|
||||
"d1",
|
||||
MediaDeviceKindEnum.AudioInput,
|
||||
);
|
||||
});
|
||||
|
||||
it("should not show the device selection", () => {
|
||||
expect(screen.queryByText("Default Device")).not.toBeInTheDocument();
|
||||
// expected to be one in the document, displayed in the pip directly
|
||||
expect(screen.queryByText("Device 1")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Device 2")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022, 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 React from "react";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
import { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingBody,
|
||||
} from "../../../../../src/voice-broadcast";
|
||||
import { mkEvent, stubClient } from "../../../../test-utils";
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(({ room }) => {
|
||||
return <div data-testid="room-avatar">room avatar: {room.name}</div>;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastRecordingBody", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let recording: VoiceBroadcastRecording;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
infoEvent = mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
content: {},
|
||||
room: roomId,
|
||||
user: userId,
|
||||
});
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client, VoiceBroadcastInfoState.Resumed);
|
||||
});
|
||||
|
||||
describe("when rendering a live broadcast", () => {
|
||||
let renderResult: RenderResult;
|
||||
|
||||
beforeEach(() => {
|
||||
renderResult = render(<VoiceBroadcastRecordingBody recording={recording} />);
|
||||
});
|
||||
|
||||
it("should render with a red live badge", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a paused broadcast", () => {
|
||||
let renderResult: RenderResult;
|
||||
|
||||
beforeEach(async () => {
|
||||
await recording.pause();
|
||||
renderResult = render(<VoiceBroadcastRecordingBody recording={recording} />);
|
||||
});
|
||||
|
||||
it("should render with a grey live badge", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it("when there is a broadcast without sender, it should raise an error", () => {
|
||||
infoEvent.sender = null;
|
||||
expect(() => {
|
||||
render(<VoiceBroadcastRecordingBody recording={recording} />);
|
||||
}).toThrow(`Voice Broadcast sender not found (event ${recording.infoEvent.getId()})`);
|
||||
});
|
||||
});
|
|
@ -1,217 +0,0 @@
|
|||
/*
|
||||
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 { ClientEvent, MatrixClient, MatrixEvent, SyncState } from "matrix-js-sdk/src/matrix";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingPip,
|
||||
} from "../../../../../src/voice-broadcast";
|
||||
import { flushPromises, stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
|
||||
import { requestMediaPermissions } from "../../../../../src/utils/media/requestMediaPermissions";
|
||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../../../src/MediaDeviceHandler";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
|
||||
jest.mock("../../../../../src/dispatcher/dispatcher");
|
||||
jest.mock("../../../../../src/utils/media/requestMediaPermissions");
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(({ room }) => {
|
||||
return <div data-testid="room-avatar">room avatar: {room.name}</div>;
|
||||
}),
|
||||
}));
|
||||
|
||||
// mock VoiceRecording because it contains all the audio APIs
|
||||
jest.mock("../../../../../src/audio/VoiceRecording", () => ({
|
||||
VoiceRecording: jest.fn().mockReturnValue({
|
||||
disableMaxLength: jest.fn(),
|
||||
liveData: {
|
||||
onUpdate: jest.fn(),
|
||||
},
|
||||
start: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastRecordingPip", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let recording: VoiceBroadcastRecording;
|
||||
let renderResult: RenderResult;
|
||||
|
||||
const renderPip = async (state: VoiceBroadcastInfoState) => {
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(roomId, state, client.getUserId() || "", client.getDeviceId() || "");
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client, state);
|
||||
jest.spyOn(recording, "pause");
|
||||
jest.spyOn(recording, "resume");
|
||||
renderResult = render(<VoiceBroadcastRecordingPip recording={recording} />);
|
||||
await flushPromises();
|
||||
};
|
||||
|
||||
const itShouldShowTheBroadcastRoom = () => {
|
||||
it("should show the broadcast room", () => {
|
||||
expect(dis.dispatch).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
mocked(requestMediaPermissions).mockResolvedValue({
|
||||
getTracks: (): Array<MediaStreamTrack> => [],
|
||||
} as unknown as MediaStream);
|
||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||
[MediaDeviceKindEnum.AudioInput]: [
|
||||
{
|
||||
deviceId: "d1",
|
||||
label: "Device 1",
|
||||
} as MediaDeviceInfo,
|
||||
{
|
||||
deviceId: "d2",
|
||||
label: "Device 2",
|
||||
} as MediaDeviceInfo,
|
||||
],
|
||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||
[MediaDeviceKindEnum.VideoInput]: [],
|
||||
});
|
||||
jest.spyOn(MediaDeviceHandler.instance, "setDevice").mockImplementation();
|
||||
});
|
||||
|
||||
describe("when rendering a started recording", () => {
|
||||
beforeEach(async () => {
|
||||
await renderPip(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and selecting another input device", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("Change input device"));
|
||||
await userEvent.click(screen.getByText("Device 1"));
|
||||
});
|
||||
|
||||
it("should select the device and pause and resume the broadcast", () => {
|
||||
expect(MediaDeviceHandler.instance.setDevice).toHaveBeenCalledWith(
|
||||
"d1",
|
||||
MediaDeviceKindEnum.AudioInput,
|
||||
);
|
||||
expect(recording.pause).toHaveBeenCalled();
|
||||
expect(recording.resume).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking the room name", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("My room"));
|
||||
});
|
||||
|
||||
itShouldShowTheBroadcastRoom();
|
||||
});
|
||||
|
||||
describe("and clicking the room avatar", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("room avatar: My room"));
|
||||
});
|
||||
|
||||
itShouldShowTheBroadcastRoom();
|
||||
});
|
||||
|
||||
describe("and clicking the pause button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("pause voice broadcast"));
|
||||
});
|
||||
|
||||
it("should pause the recording", () => {
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and clicking the stop button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("Stop Recording"));
|
||||
await screen.findByText("Stop live broadcasting?");
|
||||
// modal rendering has some weird sleeps
|
||||
await sleep(200);
|
||||
});
|
||||
|
||||
it("should display the confirm end dialog", () => {
|
||||
screen.getByText("Stop live broadcasting?");
|
||||
});
|
||||
|
||||
describe("and confirming the dialog", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("Yes, stop broadcast"));
|
||||
});
|
||||
|
||||
it("should end the recording", () => {
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Stopped);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there is no connection and clicking the pause button", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.sendStateEvent).mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
await userEvent.click(screen.getByLabelText("pause voice broadcast"));
|
||||
});
|
||||
|
||||
it("should show a connection error info", () => {
|
||||
expect(screen.getByText("Connection error - Recording paused")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("and the connection is back", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendStateEvent).mockResolvedValue({ event_id: "e1" });
|
||||
client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error);
|
||||
});
|
||||
|
||||
it("should render a paused recording", async () => {
|
||||
await expect(screen.findByLabelText("resume voice broadcast")).resolves.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a paused recording", () => {
|
||||
beforeEach(async () => {
|
||||
await renderPip(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the resume button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByLabelText("resume voice broadcast"));
|
||||
});
|
||||
|
||||
it("should resume the recording", () => {
|
||||
expect(recording.getState()).toBe(VoiceBroadcastInfoState.Resumed);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
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 { MatrixClient, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
import { render, RenderResult } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { mocked } from "jest-mock";
|
||||
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastLiveness,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastSmallPlaybackBody,
|
||||
VoiceBroadcastPlaybackState,
|
||||
} from "../../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../../utils/test-utils";
|
||||
import { SdkContextClass } from "../../../../../src/contexts/SDKContext";
|
||||
|
||||
// mock RoomAvatar, because it is doing too much fancy stuff
|
||||
jest.mock("../../../../../src/components/views/avatars/RoomAvatar", () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation(({ room }) => {
|
||||
return <div data-testid="room-avatar">room avatar: {room.name}</div>;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("<VoiceBroadcastSmallPlaybackBody />", () => {
|
||||
const userId = "@user:example.com";
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let renderResult: RenderResult;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
mocked(client.relations).mockClear();
|
||||
mocked(client.relations).mockResolvedValue({ events: [] });
|
||||
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
userId,
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
playback = new VoiceBroadcastPlayback(
|
||||
infoEvent,
|
||||
client,
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
jest.spyOn(playback, "toggle").mockImplementation(() => Promise.resolve());
|
||||
jest.spyOn(playback, "getLiveness");
|
||||
jest.spyOn(playback, "getState");
|
||||
});
|
||||
|
||||
describe("when rendering a buffering broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Buffering);
|
||||
mocked(playback.getLiveness).mockReturnValue("live");
|
||||
renderResult = render(<VoiceBroadcastSmallPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe("when rendering a playing broadcast", () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Playing);
|
||||
mocked(playback.getLiveness).mockReturnValue("not-live");
|
||||
renderResult = render(<VoiceBroadcastSmallPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when rendering a stopped broadcast`, () => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(VoiceBroadcastPlaybackState.Stopped);
|
||||
mocked(playback.getLiveness).mockReturnValue("not-live");
|
||||
renderResult = render(<VoiceBroadcastSmallPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("and clicking the play button", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(renderResult.getByLabelText("play voice broadcast"));
|
||||
});
|
||||
|
||||
it("should toggle the playback", () => {
|
||||
expect(playback.toggle).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
{ state: VoiceBroadcastPlaybackState.Paused, liveness: "not-live" },
|
||||
{ state: VoiceBroadcastPlaybackState.Playing, liveness: "live" },
|
||||
] as Array<{ state: VoiceBroadcastPlaybackState; liveness: VoiceBroadcastLiveness }>)(
|
||||
"when rendering a %s/%s broadcast",
|
||||
({ state, liveness }) => {
|
||||
beforeEach(() => {
|
||||
mocked(playback.getState).mockReturnValue(state);
|
||||
mocked(playback.getLiveness).mockReturnValue(liveness);
|
||||
renderResult = render(<VoiceBroadcastSmallPlaybackBody playback={playback} />);
|
||||
});
|
||||
|
||||
it("should render as expected", () => {
|
||||
expect(renderResult.container).toMatchSnapshot();
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
|
@ -1,914 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a buffering voice broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 14px; height: 14px;"
|
||||
/>
|
||||
</div>
|
||||
Buffering…
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Audio seek bar"
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT0S"
|
||||
>
|
||||
00:00
|
||||
</time>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="-PT23M42S"
|
||||
>
|
||||
-23:42
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a pause/not-live broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Audio seek bar"
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT0S"
|
||||
>
|
||||
00:00
|
||||
</time>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="-PT23M42S"
|
||||
>
|
||||
-23:42
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast in pip mode should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Audio seek bar"
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT0S"
|
||||
>
|
||||
00:00
|
||||
</time>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="-PT23M42S"
|
||||
>
|
||||
-23:42
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a playing broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Audio seek bar"
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT0S"
|
||||
>
|
||||
00:00
|
||||
</time>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="-PT23M42S"
|
||||
>
|
||||
-23:42
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a playing/live broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="30s backward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="30s forward"
|
||||
class="mx_AccessibleButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_secondary_content"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Audio seek bar"
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT0S"
|
||||
>
|
||||
00:00
|
||||
</time>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="-PT23M42S"
|
||||
>
|
||||
-23:42
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering a stopped broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="play voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
aria-label="Audio seek bar"
|
||||
class="mx_SeekBar"
|
||||
max="1"
|
||||
min="0"
|
||||
step="0.001"
|
||||
style="--fillTo: 0;"
|
||||
tabindex="0"
|
||||
type="range"
|
||||
value="0"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_timerow"
|
||||
>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT0S"
|
||||
>
|
||||
00:00
|
||||
</time>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="-PT23M42S"
|
||||
>
|
||||
-23:42
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastPlaybackBody when rendering an error broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastRecordingConnectionError"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M12.713 17.713A.968.968 0 0 1 12 18a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 17a.97.97 0 0 1 .287-.712A.968.968 0 0 1 12 16a.97.97 0 0 1 .713.288A.968.968 0 0 1 13 17a.97.97 0 0 1-.287.713Zm0-4A.968.968 0 0 1 12 14a.968.968 0 0 1-.713-.287A.967.967 0 0 1 11 13V9a.97.97 0 0 1 .287-.712A.968.968 0 0 1 12 8a.97.97 0 0 1 .713.288A.968.968 0 0 1 13 9v4a.97.97 0 0 1-.287.713Z"
|
||||
/>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M10.264 3.039c.767-1.344 2.705-1.344 3.472 0l8.554 14.969c.762 1.333-.2 2.992-1.736 2.992H3.446c-1.535 0-2.498-1.659-1.736-2.992l8.553-14.969ZM3.446 19 12 4.031l8.554 14.97H3.446Z"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Unable to play this voice broadcast
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,98 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastPreRecordingPip when rendered should match the snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
!room@example.com
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
!room@example.com
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line mx_VoiceBroadcastHeader_mic--clickable"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
Default Device
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastBody_blockButton mx_AccessibleButton_hasKind mx_AccessibleButton_kind_danger"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Go live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,131 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastRecordingBody when rendering a live broadcast should render with a red live badge 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastRecordingBody when rendering a paused broadcast should render with a grey live badge 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge mx_LiveBadge--grey"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,238 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`VoiceBroadcastRecordingPip when rendering a paused recording should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT4H"
|
||||
>
|
||||
4h 0m 0s left
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge mx_LiveBadge--grey"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
class="mx_VoiceBroadcastBody_divider"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-recording"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_12"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_alert"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Stop Recording"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`VoiceBroadcastRecordingPip when rendering a started recording should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_line"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
<time
|
||||
class="mx_Clock"
|
||||
datetime="PT4H"
|
||||
>
|
||||
4h 0m 0s left
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
<hr
|
||||
class="mx_VoiceBroadcastBody_divider"
|
||||
/>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody_controls"
|
||||
>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16 mx_Icon_alert"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Stop Recording"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,558 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 'pause', liveness: 'not-live' }/%s broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="resume voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_8 mx_VoiceBroadcastBody__small-close"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a { state: 'playing', liveness: 'live' }/%s broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_8 mx_VoiceBroadcastBody__small-close"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a buffering broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
<div
|
||||
class="mx_Spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading…"
|
||||
class="mx_Spinner_icon"
|
||||
data-testid="spinner"
|
||||
role="progressbar"
|
||||
style="width: 12px; height: 12px;"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="mx_LiveBadge"
|
||||
>
|
||||
<div
|
||||
class="mx_Icon mx_Icon_16"
|
||||
/>
|
||||
Live
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_8 mx_VoiceBroadcastBody__small-close"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a playing broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="pause voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_12"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 4a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Zm8 0a2 2 0 0 0-2 2v12a2 2 0 1 0 4 0V6a2 2 0 0 0-2-2Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_8 mx_VoiceBroadcastBody__small-close"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`<VoiceBroadcastSmallPlaybackBody /> when rendering a stopped broadcast should render as expected 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastBody mx_VoiceBroadcastBody--pip mx_VoiceBroadcastBody--small"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
data-testid="room-avatar"
|
||||
>
|
||||
room avatar:
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_content"
|
||||
>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room_wrapper"
|
||||
>
|
||||
<div
|
||||
class="mx_VoiceBroadcastHeader_room"
|
||||
>
|
||||
My room
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Change input device"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastHeader_line"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8 6a4 4 0 1 1 8 0v6a4 4 0 0 1-8 0V6Z"
|
||||
/>
|
||||
<path
|
||||
d="M5 11a1 1 0 0 1 1 1 6 6 0 0 0 12 0 1 1 0 1 1 2 0 8.001 8.001 0 0 1-7 7.938V21a1 1 0 1 1-2 0v-1.062A8.001 8.001 0 0 1 4 12a1 1 0 0 1 1-1Z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
@user:example.com
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
aria-label="play voice broadcast"
|
||||
class="mx_AccessibleButton mx_VoiceBroadcastControl mx_VoiceBroadcastControl-play"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_16"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="m8.98 4.677 9.921 5.58c1.36.764 1.36 2.722 0 3.486l-9.92 5.58C7.647 20.073 6 19.11 6 17.58V6.42c0-1.53 1.647-2.493 2.98-1.743Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
class="mx_AccessibleButton"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
class="mx_Icon mx_Icon_8 mx_VoiceBroadcastBody__small-close"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
viewBox="0 0 24 24"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M6.293 6.293a1 1 0 0 1 1.414 0L12 10.586l4.293-4.293a1 1 0 1 1 1.414 1.414L13.414 12l4.293 4.293a1 1 0 0 1-1.414 1.414L12 13.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L10.586 12 6.293 7.707a1 1 0 0 1 0-1.414Z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,747 +0,0 @@
|
|||
/*
|
||||
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 { screen } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { MatrixClient, MatrixEvent, MatrixEventEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
|
||||
import { Playback, PlaybackState } from "../../../../src/audio/Playback";
|
||||
import { PlaybackManager } from "../../../../src/audio/PlaybackManager";
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
|
||||
import { MediaEventHelper } from "../../../../src/utils/MediaEventHelper";
|
||||
import {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastLiveness,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybackEvent,
|
||||
VoiceBroadcastPlaybackState,
|
||||
VoiceBroadcastRecording,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import {
|
||||
filterConsole,
|
||||
flushPromises,
|
||||
flushPromisesWithFakeTimers,
|
||||
stubClient,
|
||||
waitEnoughCyclesForModal,
|
||||
} from "../../../test-utils";
|
||||
import { createTestPlayback } from "../../../test-utils/audio";
|
||||
import { mkVoiceBroadcastChunkEvent, mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
import { LazyValue } from "../../../../src/utils/LazyValue";
|
||||
|
||||
jest.mock("../../../../src/utils/MediaEventHelper", () => ({
|
||||
MediaEventHelper: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastPlayback", () => {
|
||||
const userId = "@user:example.com";
|
||||
let deviceId: string;
|
||||
const roomId = "!room:example.com";
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let playback: VoiceBroadcastPlayback;
|
||||
let onStateChanged: (state: VoiceBroadcastPlaybackState) => void;
|
||||
let chunk1Event: MatrixEvent;
|
||||
let deplayedChunk1Event: MatrixEvent;
|
||||
let chunk2Event: MatrixEvent;
|
||||
let chunk2BEvent: MatrixEvent;
|
||||
let chunk3Event: MatrixEvent;
|
||||
const chunk1Length = 2300;
|
||||
const chunk2Length = 4200;
|
||||
const chunk3Length = 6900;
|
||||
const chunk1Data = new ArrayBuffer(2);
|
||||
const chunk2Data = new ArrayBuffer(3);
|
||||
const chunk3Data = new ArrayBuffer(3);
|
||||
let delayedChunk1Helper: MediaEventHelper;
|
||||
let chunk1Helper: MediaEventHelper;
|
||||
let chunk2Helper: MediaEventHelper;
|
||||
let chunk3Helper: MediaEventHelper;
|
||||
let chunk1Playback: Playback;
|
||||
let chunk2Playback: Playback;
|
||||
let chunk3Playback: Playback;
|
||||
let middleOfSecondChunk!: number;
|
||||
let middleOfThirdChunk!: number;
|
||||
|
||||
const queryConfirmListeningDialog = () => {
|
||||
return screen.queryByText(
|
||||
"If you start listening to this live broadcast, your current live broadcast recording will be ended.",
|
||||
);
|
||||
};
|
||||
|
||||
const itShouldSetTheStateTo = (state: VoiceBroadcastPlaybackState) => {
|
||||
it(`should set the state to ${state}`, () => {
|
||||
expect(playback.getState()).toBe(state);
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldEmitAStateChangedEvent = (state: VoiceBroadcastPlaybackState) => {
|
||||
it(`should emit a ${state} state changed event`, () => {
|
||||
expect(mocked(onStateChanged)).toHaveBeenCalledWith(state, playback);
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldHaveLiveness = (liveness: VoiceBroadcastLiveness): void => {
|
||||
it(`should have liveness ${liveness}`, () => {
|
||||
expect(playback.getLiveness()).toBe(liveness);
|
||||
});
|
||||
};
|
||||
|
||||
const startPlayback = () => {
|
||||
beforeEach(() => {
|
||||
playback.start();
|
||||
});
|
||||
};
|
||||
|
||||
const pausePlayback = () => {
|
||||
beforeEach(() => {
|
||||
playback.pause();
|
||||
});
|
||||
};
|
||||
|
||||
const stopPlayback = () => {
|
||||
beforeEach(() => {
|
||||
playback.stop();
|
||||
});
|
||||
};
|
||||
|
||||
const mkChunkHelper = (data: ArrayBuffer): MediaEventHelper => {
|
||||
return {
|
||||
sourceBlob: {
|
||||
cachedValue: new Blob(),
|
||||
done: false,
|
||||
value: {
|
||||
// @ts-ignore
|
||||
arrayBuffer: jest.fn().mockResolvedValue(data),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const mkDeplayedChunkHelper = (data: ArrayBuffer): MediaEventHelper => {
|
||||
const deferred = defer<LazyValue<Blob>>();
|
||||
|
||||
setTimeout(() => {
|
||||
deferred.resolve({
|
||||
// @ts-ignore
|
||||
arrayBuffer: jest.fn().mockResolvedValue(data),
|
||||
});
|
||||
}, 7500);
|
||||
|
||||
return {
|
||||
sourceBlob: {
|
||||
cachedValue: new Blob(),
|
||||
done: false,
|
||||
// @ts-ignore
|
||||
value: deferred.promise,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const simulateFirstChunkArrived = async (): Promise<void> => {
|
||||
jest.advanceTimersByTime(10000);
|
||||
await flushPromisesWithFakeTimers();
|
||||
};
|
||||
|
||||
const mkInfoEvent = (state: VoiceBroadcastInfoState) => {
|
||||
return mkVoiceBroadcastInfoStateEvent(roomId, state, userId, deviceId);
|
||||
};
|
||||
|
||||
const mkPlayback = async (fakeTimers = false): Promise<VoiceBroadcastPlayback> => {
|
||||
const playback = new VoiceBroadcastPlayback(
|
||||
infoEvent,
|
||||
client,
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore,
|
||||
);
|
||||
jest.spyOn(playback, "removeAllListeners");
|
||||
jest.spyOn(playback, "destroy");
|
||||
playback.on(VoiceBroadcastPlaybackEvent.StateChanged, onStateChanged);
|
||||
if (fakeTimers) {
|
||||
await flushPromisesWithFakeTimers();
|
||||
} else {
|
||||
await flushPromises();
|
||||
}
|
||||
return playback;
|
||||
};
|
||||
|
||||
const setUpChunkEvents = (chunkEvents: MatrixEvent[]) => {
|
||||
mocked(client.relations).mockResolvedValueOnce({
|
||||
events: chunkEvents,
|
||||
});
|
||||
};
|
||||
|
||||
const createChunkEvents = () => {
|
||||
chunk1Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk1Length, 1);
|
||||
deplayedChunk1Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk1Length, 1);
|
||||
chunk2Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk2Length, 2);
|
||||
chunk2Event.setTxnId("tx-id-1");
|
||||
chunk2BEvent = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk2Length, 2);
|
||||
chunk2BEvent.setTxnId("tx-id-1");
|
||||
chunk3Event = mkVoiceBroadcastChunkEvent(infoEvent.getId()!, userId, roomId, chunk3Length, 3);
|
||||
|
||||
chunk1Helper = mkChunkHelper(chunk1Data);
|
||||
delayedChunk1Helper = mkDeplayedChunkHelper(chunk1Data);
|
||||
chunk2Helper = mkChunkHelper(chunk2Data);
|
||||
chunk3Helper = mkChunkHelper(chunk3Data);
|
||||
|
||||
chunk1Playback = createTestPlayback();
|
||||
chunk2Playback = createTestPlayback();
|
||||
chunk3Playback = createTestPlayback();
|
||||
|
||||
middleOfSecondChunk = (chunk1Length + chunk2Length / 2) / 1000;
|
||||
middleOfThirdChunk = (chunk1Length + chunk2Length + chunk3Length / 2) / 1000;
|
||||
|
||||
jest.spyOn(PlaybackManager.instance, "createPlaybackInstance").mockImplementation(
|
||||
(buffer: ArrayBuffer, _waveForm?: number[]) => {
|
||||
if (buffer === chunk1Data) return chunk1Playback;
|
||||
if (buffer === chunk2Data) return chunk2Playback;
|
||||
if (buffer === chunk3Data) return chunk3Playback;
|
||||
|
||||
throw new Error("unexpected buffer");
|
||||
},
|
||||
);
|
||||
|
||||
mocked(MediaEventHelper).mockImplementation((event: MatrixEvent): any => {
|
||||
if (event === chunk1Event) return chunk1Helper;
|
||||
if (event === deplayedChunk1Event) return delayedChunk1Helper;
|
||||
if (event === chunk2Event) return chunk2Helper;
|
||||
if (event === chunk3Event) return chunk3Helper;
|
||||
});
|
||||
};
|
||||
|
||||
filterConsole(
|
||||
// expected for some tests
|
||||
"Unable to load broadcast playback",
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
deviceId = client.getDeviceId() || "";
|
||||
room = new Room(roomId, client, client.getSafeUserId());
|
||||
mocked(client.getRoom).mockImplementation((roomId: string): Room | null => {
|
||||
if (roomId === room.roomId) return room;
|
||||
return null;
|
||||
});
|
||||
onStateChanged = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.getCurrent()?.stop();
|
||||
SdkContextClass.instance.voiceBroadcastPlaybacksStore.clearCurrent();
|
||||
await SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()?.stop();
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.clearCurrent();
|
||||
playback.destroy();
|
||||
});
|
||||
|
||||
describe(`when there is a ${VoiceBroadcastInfoState.Resumed} broadcast without chunks yet`, () => {
|
||||
beforeEach(async () => {
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||
createChunkEvents();
|
||||
room.addLiveEvents([infoEvent], { addToState: true });
|
||||
playback = await mkPlayback();
|
||||
});
|
||||
|
||||
describe("and calling start", () => {
|
||||
startPlayback();
|
||||
|
||||
itShouldHaveLiveness("live");
|
||||
|
||||
it("should be in buffering state", () => {
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Buffering);
|
||||
});
|
||||
|
||||
it("should have duration 0", () => {
|
||||
expect(playback.durationSeconds).toBe(0);
|
||||
});
|
||||
|
||||
it("should be at time 0", () => {
|
||||
expect(playback.timeSeconds).toBe(0);
|
||||
});
|
||||
|
||||
describe("and calling stop", () => {
|
||||
stopPlayback();
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
|
||||
describe("and calling pause", () => {
|
||||
pausePlayback();
|
||||
// stopped voice broadcasts cannot be paused
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling pause", () => {
|
||||
pausePlayback();
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused);
|
||||
});
|
||||
|
||||
describe("and receiving the first chunk", () => {
|
||||
beforeEach(() => {
|
||||
room.relations.aggregateChildEvent(chunk1Event);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
|
||||
itShouldHaveLiveness("live");
|
||||
|
||||
it("should update the duration", () => {
|
||||
expect(playback.durationSeconds).toBe(2.3);
|
||||
});
|
||||
|
||||
it("should play the first chunk", () => {
|
||||
expect(chunk1Playback.play).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and receiving the first undecryptable chunk", () => {
|
||||
beforeEach(() => {
|
||||
jest.spyOn(chunk1Event, "isDecryptionFailure").mockReturnValue(true);
|
||||
room.relations.aggregateChildEvent(chunk1Event);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Error);
|
||||
|
||||
it("should not update the duration", () => {
|
||||
expect(playback.durationSeconds).toBe(0);
|
||||
});
|
||||
|
||||
describe("and the chunk is decrypted", () => {
|
||||
beforeEach(() => {
|
||||
mocked(chunk1Event.isDecryptionFailure).mockReturnValue(false);
|
||||
chunk1Event.emit(MatrixEventEvent.Decrypted, chunk1Event);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused);
|
||||
|
||||
it("should not update the duration", () => {
|
||||
expect(playback.durationSeconds).toBe(2.3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when there is a ${VoiceBroadcastInfoState.Resumed} voice broadcast with some chunks`, () => {
|
||||
beforeEach(async () => {
|
||||
mocked(client.relations).mockResolvedValueOnce({ events: [] });
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Resumed);
|
||||
createChunkEvents();
|
||||
setUpChunkEvents([chunk2Event, chunk1Event]);
|
||||
room.addLiveEvents([infoEvent, chunk1Event, chunk2Event], { addToState: true });
|
||||
room.relations.aggregateChildEvent(chunk2Event);
|
||||
room.relations.aggregateChildEvent(chunk1Event);
|
||||
playback = await mkPlayback();
|
||||
});
|
||||
|
||||
it("durationSeconds should have the length of the known chunks", () => {
|
||||
expect(playback.durationSeconds).toEqual(6.5);
|
||||
});
|
||||
|
||||
describe("and starting a playback with a broken chunk", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(chunk2Playback.prepare).mockRejectedValue("Error decoding chunk");
|
||||
await playback.start();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Error);
|
||||
|
||||
it("start() should keep it in the error state)", async () => {
|
||||
await playback.start();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
|
||||
it("stop() should keep it in the error state)", () => {
|
||||
playback.stop();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
|
||||
it("toggle() should keep it in the error state)", async () => {
|
||||
await playback.toggle();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
|
||||
it("pause() should keep it in the error state)", () => {
|
||||
playback.pause();
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and an event with the same transaction Id occurs", () => {
|
||||
beforeEach(() => {
|
||||
room.addLiveEvents([chunk2BEvent], { addToState: true });
|
||||
room.relations.aggregateChildEvent(chunk2BEvent);
|
||||
});
|
||||
|
||||
it("durationSeconds should not change", () => {
|
||||
expect(playback.durationSeconds).toEqual(6.5);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling start", () => {
|
||||
startPlayback();
|
||||
|
||||
it("should play the last chunk", () => {
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Playing);
|
||||
// assert that the last chunk is played first
|
||||
expect(chunk2Playback.play).toHaveBeenCalled();
|
||||
expect(chunk1Playback.play).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe(
|
||||
"and receiving a stop info event with last_chunk_sequence = 2 and " +
|
||||
"the playback of the last available chunk ends",
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
const stoppedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.deviceId!,
|
||||
infoEvent,
|
||||
2,
|
||||
);
|
||||
room.addLiveEvents([stoppedEvent], { addToState: true });
|
||||
room.relations.aggregateChildEvent(stoppedEvent);
|
||||
chunk2Playback.emit(PlaybackState.Stopped);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
},
|
||||
);
|
||||
|
||||
describe(
|
||||
"and receiving a stop info event with last_chunk_sequence = 3 and " +
|
||||
"the playback of the last available chunk ends",
|
||||
() => {
|
||||
beforeEach(() => {
|
||||
const stoppedEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Stopped,
|
||||
client.getSafeUserId(),
|
||||
client.deviceId!,
|
||||
infoEvent,
|
||||
3,
|
||||
);
|
||||
room.addLiveEvents([stoppedEvent], { addToState: true });
|
||||
room.relations.aggregateChildEvent(stoppedEvent);
|
||||
chunk2Playback.emit(PlaybackState.Stopped);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering);
|
||||
|
||||
describe("and the next chunk arrives", () => {
|
||||
beforeEach(() => {
|
||||
room.addLiveEvents([chunk3Event], { addToState: true });
|
||||
room.relations.aggregateChildEvent(chunk3Event);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
|
||||
|
||||
it("should play the next chunk", () => {
|
||||
expect(chunk3Playback.play).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe("and the info event is deleted", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent.makeRedacted(new MatrixEvent({}), room);
|
||||
});
|
||||
|
||||
it("should stop and destroy the playback", () => {
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped);
|
||||
expect(playback.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and currently recording a broadcast", () => {
|
||||
let recording: VoiceBroadcastRecording;
|
||||
|
||||
beforeEach(async () => {
|
||||
recording = new VoiceBroadcastRecording(
|
||||
mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getSafeUserId(),
|
||||
client.deviceId,
|
||||
),
|
||||
client,
|
||||
);
|
||||
jest.spyOn(recording, "stop");
|
||||
SdkContextClass.instance.voiceBroadcastRecordingsStore.setCurrent(recording);
|
||||
playback.start();
|
||||
await waitEnoughCyclesForModal();
|
||||
});
|
||||
|
||||
it("should display a confirm modal", () => {
|
||||
expect(queryConfirmListeningDialog()).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("when confirming the dialog", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("Yes, end my recording"));
|
||||
});
|
||||
|
||||
it("should stop the recording", () => {
|
||||
expect(recording.stop).toHaveBeenCalled();
|
||||
expect(SdkContextClass.instance.voiceBroadcastRecordingsStore.getCurrent()).toBeNull();
|
||||
});
|
||||
|
||||
it("should not start the playback", () => {
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Playing);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when not confirming the dialog", () => {
|
||||
beforeEach(async () => {
|
||||
await userEvent.click(screen.getByText("No"));
|
||||
});
|
||||
|
||||
it("should not stop the recording", () => {
|
||||
expect(recording.stop).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should start the playback", () => {
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is a stopped voice broadcast", () => {
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
infoEvent = mkInfoEvent(VoiceBroadcastInfoState.Stopped);
|
||||
createChunkEvents();
|
||||
// use delayed first chunk here to simulate loading time
|
||||
setUpChunkEvents([chunk2Event, deplayedChunk1Event, chunk3Event]);
|
||||
room.addLiveEvents([infoEvent, deplayedChunk1Event, chunk2Event, chunk3Event], { addToState: true });
|
||||
playback = await mkPlayback(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("should expose the info event", () => {
|
||||
expect(playback.infoEvent).toBe(infoEvent);
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
|
||||
describe("and calling start", () => {
|
||||
startPlayback();
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Buffering);
|
||||
|
||||
describe("and the first chunk data has been loaded", () => {
|
||||
beforeEach(async () => {
|
||||
await simulateFirstChunkArrived();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
|
||||
|
||||
it("should play the chunks beginning with the first one", () => {
|
||||
// assert that the first chunk is being played
|
||||
expect(chunk1Playback.play).toHaveBeenCalled();
|
||||
expect(chunk2Playback.play).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and calling start again", () => {
|
||||
it("should not play the first chunk a second time", () => {
|
||||
expect(chunk1Playback.play).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the chunk playback progresses", () => {
|
||||
beforeEach(() => {
|
||||
chunk1Playback.clockInfo.liveData.update([11]);
|
||||
});
|
||||
|
||||
it("should update the time", () => {
|
||||
expect(playback.timeSeconds).toBe(11);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the chunk playback progresses across the actual time", () => {
|
||||
// This can be the case if the meta data is out of sync with the actual audio data.
|
||||
|
||||
beforeEach(() => {
|
||||
chunk1Playback.clockInfo.liveData.update([15]);
|
||||
});
|
||||
|
||||
it("should update the time", () => {
|
||||
expect(playback.timeSeconds).toBe(15);
|
||||
expect(playback.timeLeftSeconds).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and skipping to the middle of the second chunk", () => {
|
||||
const middleOfSecondChunk = (chunk1Length + chunk2Length / 2) / 1000;
|
||||
|
||||
beforeEach(async () => {
|
||||
await playback.skipTo(middleOfSecondChunk);
|
||||
});
|
||||
|
||||
it("should play the second chunk", () => {
|
||||
expect(chunk1Playback.stop).toHaveBeenCalled();
|
||||
expect(chunk1Playback.destroy).toHaveBeenCalled();
|
||||
expect(chunk2Playback.play).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update the time", () => {
|
||||
expect(playback.timeSeconds).toBe(middleOfSecondChunk);
|
||||
});
|
||||
|
||||
describe("and skipping to the start", () => {
|
||||
beforeEach(async () => {
|
||||
await playback.skipTo(0);
|
||||
});
|
||||
|
||||
it("should play the first chunk", () => {
|
||||
expect(chunk2Playback.stop).toHaveBeenCalled();
|
||||
expect(chunk2Playback.destroy).toHaveBeenCalled();
|
||||
expect(chunk1Playback.play).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should update the time", () => {
|
||||
expect(playback.timeSeconds).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and skipping multiple times", () => {
|
||||
beforeEach(async () => {
|
||||
return Promise.all([
|
||||
playback.skipTo(middleOfSecondChunk),
|
||||
playback.skipTo(middleOfThirdChunk),
|
||||
playback.skipTo(0),
|
||||
]);
|
||||
});
|
||||
|
||||
it("should only skip to the first and last position", () => {
|
||||
expect(chunk1Playback.stop).toHaveBeenCalled();
|
||||
expect(chunk1Playback.destroy).toHaveBeenCalled();
|
||||
expect(chunk2Playback.play).toHaveBeenCalled();
|
||||
|
||||
expect(chunk3Playback.play).not.toHaveBeenCalled();
|
||||
|
||||
expect(chunk2Playback.stop).toHaveBeenCalled();
|
||||
expect(chunk2Playback.destroy).toHaveBeenCalled();
|
||||
expect(chunk1Playback.play).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the first chunk ends", () => {
|
||||
beforeEach(() => {
|
||||
chunk1Playback.emit(PlaybackState.Stopped);
|
||||
});
|
||||
|
||||
it("should play until the end", () => {
|
||||
// assert first chunk was unloaded
|
||||
expect(chunk1Playback.destroy).toHaveBeenCalled();
|
||||
|
||||
// assert that the second chunk is being played
|
||||
expect(chunk2Playback.play).toHaveBeenCalled();
|
||||
|
||||
// simulate end of second and third chunk
|
||||
chunk2Playback.emit(PlaybackState.Stopped);
|
||||
chunk3Playback.emit(PlaybackState.Stopped);
|
||||
|
||||
// assert that the entire playback is now in stopped state
|
||||
expect(playback.getState()).toBe(VoiceBroadcastPlaybackState.Stopped);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling pause", () => {
|
||||
pausePlayback();
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused);
|
||||
itShouldEmitAStateChangedEvent(VoiceBroadcastPlaybackState.Paused);
|
||||
});
|
||||
|
||||
describe("and calling stop", () => {
|
||||
stopPlayback();
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
|
||||
it("should stop the playback", () => {
|
||||
expect(chunk1Playback.stop).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and skipping to somewhere in the middle of the first chunk", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(chunk1Playback.play).mockClear();
|
||||
await playback.skipTo(1);
|
||||
});
|
||||
|
||||
it("should not start the playback", () => {
|
||||
expect(chunk1Playback.play).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
playback.destroy();
|
||||
});
|
||||
|
||||
it("should call removeAllListeners", () => {
|
||||
expect(playback.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call destroy on the playbacks", () => {
|
||||
expect(chunk1Playback.destroy).toHaveBeenCalled();
|
||||
expect(chunk2Playback.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling toggle for the first time", () => {
|
||||
beforeEach(async () => {
|
||||
playback.toggle();
|
||||
await simulateFirstChunkArrived();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
|
||||
|
||||
describe("and calling toggle a second time", () => {
|
||||
beforeEach(async () => {
|
||||
await playback.toggle();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Paused);
|
||||
|
||||
describe("and calling toggle a third time", () => {
|
||||
beforeEach(async () => {
|
||||
await playback.toggle();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling stop", () => {
|
||||
stopPlayback();
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Stopped);
|
||||
|
||||
describe("and calling toggle", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(onStateChanged).mockReset();
|
||||
playback.toggle();
|
||||
await simulateFirstChunkArrived();
|
||||
});
|
||||
|
||||
itShouldSetTheStateTo(VoiceBroadcastPlaybackState.Playing);
|
||||
itShouldEmitAStateChangedEvent(VoiceBroadcastPlaybackState.Playing);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
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, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
startNewVoiceBroadcastRecording,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
|
||||
jest.mock("../../../../src/voice-broadcast/utils/startNewVoiceBroadcastRecording");
|
||||
|
||||
describe("VoiceBroadcastPreRecording", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let sender: RoomMember;
|
||||
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let preRecording: VoiceBroadcastPreRecording;
|
||||
let onDismiss: (voiceBroadcastPreRecording: VoiceBroadcastPreRecording) => void;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getUserId() || "");
|
||||
sender = new RoomMember(roomId, client.getUserId() || "");
|
||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
onDismiss = jest.fn();
|
||||
preRecording = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||
preRecording.on("dismiss", onDismiss);
|
||||
});
|
||||
|
||||
describe("start", () => {
|
||||
beforeEach(() => {
|
||||
preRecording.start();
|
||||
});
|
||||
|
||||
it("should start a new voice broadcast recording", () => {
|
||||
expect(startNewVoiceBroadcastRecording).toHaveBeenCalledWith(room, client, playbacksStore, recordingsStore);
|
||||
});
|
||||
|
||||
it("should emit a dismiss event", () => {
|
||||
expect(onDismiss).toHaveBeenCalledWith(preRecording);
|
||||
});
|
||||
});
|
||||
|
||||
describe("cancel", () => {
|
||||
beforeEach(() => {
|
||||
preRecording.cancel();
|
||||
});
|
||||
|
||||
it("should emit a dismiss event", () => {
|
||||
expect(onDismiss).toHaveBeenCalledWith(preRecording);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,660 +0,0 @@
|
|||
/*
|
||||
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,
|
||||
EventTimelineSet,
|
||||
EventType,
|
||||
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
|
||||
MatrixClient,
|
||||
MatrixEvent,
|
||||
MatrixEventEvent,
|
||||
MsgType,
|
||||
RelationType,
|
||||
Room,
|
||||
Relations,
|
||||
SyncState,
|
||||
} from "matrix-js-sdk/src/matrix";
|
||||
import { EncryptedFile } from "matrix-js-sdk/src/types";
|
||||
import fetchMock from "fetch-mock-jest";
|
||||
|
||||
import { uploadFile } from "../../../../src/ContentMessages";
|
||||
import { createVoiceMessageContent } from "../../../../src/utils/createVoiceMessageContent";
|
||||
import {
|
||||
createVoiceBroadcastRecorder,
|
||||
getChunkLength,
|
||||
getMaxBroadcastLength,
|
||||
VoiceBroadcastInfoEventContent,
|
||||
VoiceBroadcastInfoEventType,
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastRecorder,
|
||||
VoiceBroadcastRecorderEvent,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastRecordingEvent,
|
||||
VoiceBroadcastRecordingState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkEvent, mkStubRoom, stubClient } from "../../../test-utils";
|
||||
import dis from "../../../../src/dispatcher/dispatcher";
|
||||
import { VoiceRecording } from "../../../../src/audio/VoiceRecording";
|
||||
import { createAudioContext } from "../../../../src/audio/compat";
|
||||
|
||||
jest.mock("../../../../src/voice-broadcast/audio/VoiceBroadcastRecorder", () => ({
|
||||
...(jest.requireActual("../../../../src/voice-broadcast/audio/VoiceBroadcastRecorder") as object),
|
||||
createVoiceBroadcastRecorder: jest.fn(),
|
||||
}));
|
||||
|
||||
// mock VoiceRecording because it contains all the audio APIs
|
||||
jest.mock("../../../../src/audio/VoiceRecording", () => ({
|
||||
VoiceRecording: jest.fn().mockReturnValue({
|
||||
disableMaxLength: jest.fn(),
|
||||
liveData: {
|
||||
onUpdate: jest.fn(),
|
||||
},
|
||||
off: jest.fn(),
|
||||
on: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
destroy: jest.fn(),
|
||||
contentType: "audio/ogg",
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/ContentMessages", () => ({
|
||||
uploadFile: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/utils/createVoiceMessageContent", () => ({
|
||||
createVoiceMessageContent: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock("../../../../src/audio/compat", () => ({
|
||||
...jest.requireActual("../../../../src/audio/compat"),
|
||||
createAudioContext: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("VoiceBroadcastRecording", () => {
|
||||
const roomId = "!room:example.com";
|
||||
const uploadedUrl = "mxc://example.com/vb";
|
||||
const uploadedFile = { file: true } as unknown as EncryptedFile;
|
||||
const maxLength = getMaxBroadcastLength();
|
||||
let room: Room;
|
||||
let client: MatrixClient;
|
||||
let infoEvent: MatrixEvent;
|
||||
let voiceBroadcastRecording: VoiceBroadcastRecording;
|
||||
let onStateChanged: (state: VoiceBroadcastRecordingState) => void;
|
||||
let voiceBroadcastRecorder: VoiceBroadcastRecorder;
|
||||
let audioElement: HTMLAudioElement;
|
||||
|
||||
const mkVoiceBroadcastInfoEvent = (content: VoiceBroadcastInfoEventContent) => {
|
||||
return mkEvent({
|
||||
event: true,
|
||||
type: VoiceBroadcastInfoEventType,
|
||||
user: client.getSafeUserId(),
|
||||
room: roomId,
|
||||
content,
|
||||
});
|
||||
};
|
||||
|
||||
const setUpVoiceBroadcastRecording = () => {
|
||||
voiceBroadcastRecording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
voiceBroadcastRecording.on(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged);
|
||||
jest.spyOn(voiceBroadcastRecording, "destroy");
|
||||
jest.spyOn(voiceBroadcastRecording, "emit");
|
||||
jest.spyOn(voiceBroadcastRecording, "removeAllListeners");
|
||||
};
|
||||
|
||||
const itShouldBeInState = (state: VoiceBroadcastRecordingState) => {
|
||||
it(`should be in state stopped ${state}`, () => {
|
||||
expect(voiceBroadcastRecording.getState()).toBe(state);
|
||||
});
|
||||
};
|
||||
|
||||
const emitFirsChunkRecorded = () => {
|
||||
voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.ChunkRecorded, {
|
||||
buffer: new Uint8Array([1, 2, 3]),
|
||||
length: 23,
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldSendAnInfoEvent = (state: VoiceBroadcastInfoState, lastChunkSequence: number) => {
|
||||
it(`should send a ${state} info event`, () => {
|
||||
expect(client.sendStateEvent).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
VoiceBroadcastInfoEventType,
|
||||
{
|
||||
device_id: client.getDeviceId(),
|
||||
state,
|
||||
last_chunk_sequence: lastChunkSequence,
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: infoEvent.getId(),
|
||||
},
|
||||
} as VoiceBroadcastInfoEventContent,
|
||||
client.getUserId()!,
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const itShouldSendAVoiceMessage = (data: number[], size: number, duration: number, sequence: number) => {
|
||||
// events contain milliseconds
|
||||
duration *= 1000;
|
||||
|
||||
it("should send a voice message", () => {
|
||||
expect(uploadFile).toHaveBeenCalledWith(
|
||||
client,
|
||||
roomId,
|
||||
new Blob([new Uint8Array(data)], { type: voiceBroadcastRecorder.contentType }),
|
||||
);
|
||||
|
||||
expect(mocked(client.sendMessage)).toHaveBeenCalledWith(roomId, {
|
||||
body: "Voice message",
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
info: {
|
||||
duration,
|
||||
mimetype: "audio/ogg",
|
||||
size,
|
||||
},
|
||||
["m.relates_to"]: {
|
||||
event_id: infoEvent.getId(),
|
||||
rel_type: "m.reference",
|
||||
},
|
||||
msgtype: "m.audio",
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration,
|
||||
waveform: undefined,
|
||||
},
|
||||
["org.matrix.msc1767.file"]: {
|
||||
file: {
|
||||
file: true,
|
||||
},
|
||||
mimetype: "audio/ogg",
|
||||
name: "Voice message.ogg",
|
||||
size,
|
||||
url: "mxc://example.com/vb",
|
||||
},
|
||||
["org.matrix.msc1767.text"]: "Voice message",
|
||||
["org.matrix.msc3245.voice"]: {},
|
||||
url: "mxc://example.com/vb",
|
||||
["io.element.voice_broadcast_chunk"]: {
|
||||
sequence,
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const setUpUploadFileMock = () => {
|
||||
mocked(uploadFile).mockResolvedValue({
|
||||
url: uploadedUrl,
|
||||
file: uploadedFile,
|
||||
});
|
||||
};
|
||||
|
||||
const mockAudioBufferSourceNode = {
|
||||
addEventListener: jest.fn(),
|
||||
connect: jest.fn(),
|
||||
start: jest.fn(),
|
||||
};
|
||||
const mockAudioContext = {
|
||||
decodeAudioData: jest.fn(),
|
||||
suspend: jest.fn(),
|
||||
resume: jest.fn(),
|
||||
createBufferSource: jest.fn().mockReturnValue(mockAudioBufferSourceNode),
|
||||
currentTime: 1337,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = mkStubRoom(roomId, "Test Room", client);
|
||||
mocked(client.getRoom).mockImplementation((getRoomId: string | undefined): Room | null => {
|
||||
if (getRoomId === roomId) {
|
||||
return room;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
onStateChanged = jest.fn();
|
||||
voiceBroadcastRecorder = new VoiceBroadcastRecorder(new VoiceRecording(), getChunkLength());
|
||||
jest.spyOn(voiceBroadcastRecorder, "start");
|
||||
jest.spyOn(voiceBroadcastRecorder, "stop");
|
||||
jest.spyOn(voiceBroadcastRecorder, "destroy");
|
||||
mocked(createVoiceBroadcastRecorder).mockReturnValue(voiceBroadcastRecorder);
|
||||
|
||||
setUpUploadFileMock();
|
||||
|
||||
mocked(createVoiceMessageContent).mockImplementation(
|
||||
(
|
||||
mxc: string | undefined,
|
||||
mimetype: string,
|
||||
duration: number,
|
||||
size: number,
|
||||
file?: EncryptedFile,
|
||||
waveform?: number[],
|
||||
) => {
|
||||
return {
|
||||
body: "Voice message",
|
||||
msgtype: MsgType.Audio,
|
||||
url: mxc,
|
||||
file,
|
||||
info: {
|
||||
duration,
|
||||
mimetype,
|
||||
size,
|
||||
},
|
||||
["org.matrix.msc1767.text"]: "Voice message",
|
||||
["org.matrix.msc1767.file"]: {
|
||||
url: mxc,
|
||||
file,
|
||||
name: "Voice message.ogg",
|
||||
mimetype,
|
||||
size,
|
||||
},
|
||||
["org.matrix.msc1767.audio"]: {
|
||||
duration,
|
||||
// https://github.com/matrix-org/matrix-doc/pull/3246
|
||||
waveform,
|
||||
},
|
||||
["org.matrix.msc3245.voice"]: {}, // No content, this is a rendering hint
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
audioElement = {
|
||||
play: jest.fn(),
|
||||
} as any as HTMLAudioElement;
|
||||
|
||||
jest.spyOn(document, "querySelector").mockImplementation((selector: string) => {
|
||||
if (selector === "audio#errorAudio") {
|
||||
return audioElement;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
mocked(createAudioContext).mockReturnValue(mockAudioContext as unknown as AudioContext);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
voiceBroadcastRecording?.off(VoiceBroadcastRecordingEvent.StateChanged, onStateChanged);
|
||||
});
|
||||
|
||||
describe("when there is an info event without id", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent = mkVoiceBroadcastInfoEvent({
|
||||
device_id: client.getDeviceId()!,
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
});
|
||||
jest.spyOn(infoEvent, "getId").mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
it("should raise an error when creating a broadcast", () => {
|
||||
expect(() => {
|
||||
setUpVoiceBroadcastRecording();
|
||||
}).toThrow("Cannot create broadcast for info event without Id.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when there is an info event without room", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent = mkVoiceBroadcastInfoEvent({
|
||||
device_id: client.getDeviceId()!,
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
});
|
||||
jest.spyOn(infoEvent, "getRoomId").mockReturnValue(undefined);
|
||||
});
|
||||
|
||||
it("should raise an error when creating a broadcast", () => {
|
||||
expect(() => {
|
||||
setUpVoiceBroadcastRecording();
|
||||
}).toThrow(`Cannot create broadcast for unknown room (info event ${infoEvent.getId()})`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when created for a Voice Broadcast Info without relations", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent = mkVoiceBroadcastInfoEvent({
|
||||
device_id: client.getDeviceId()!,
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
});
|
||||
setUpVoiceBroadcastRecording();
|
||||
});
|
||||
|
||||
it("should be in Started state", () => {
|
||||
expect(voiceBroadcastRecording.getState()).toBe(VoiceBroadcastInfoState.Started);
|
||||
});
|
||||
|
||||
describe("and calling stop", () => {
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecording.stop();
|
||||
});
|
||||
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Stopped, 0);
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Stopped);
|
||||
|
||||
it("should emit a stopped state changed event", () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Stopped);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling start", () => {
|
||||
beforeEach(async () => {
|
||||
await voiceBroadcastRecording.start();
|
||||
});
|
||||
|
||||
it("should start the recorder", () => {
|
||||
expect(voiceBroadcastRecorder.start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and the info event is redacted", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent.emit(
|
||||
MatrixEventEvent.BeforeRedaction,
|
||||
infoEvent,
|
||||
mkEvent({
|
||||
event: true,
|
||||
type: EventType.RoomRedaction,
|
||||
user: client.getSafeUserId(),
|
||||
content: {},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Stopped);
|
||||
|
||||
it("should destroy the recording", () => {
|
||||
expect(voiceBroadcastRecording.destroy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and receiving a call action", () => {
|
||||
beforeEach(() => {
|
||||
dis.dispatch(
|
||||
{
|
||||
action: "call_state",
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
|
||||
describe("and a chunk time update occurs", () => {
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, 10);
|
||||
});
|
||||
|
||||
it("should update time left", () => {
|
||||
expect(voiceBroadcastRecording.getTimeLeft()).toBe(maxLength - 10);
|
||||
expect(voiceBroadcastRecording.emit).toHaveBeenCalledWith(
|
||||
VoiceBroadcastRecordingEvent.TimeLeftChanged,
|
||||
maxLength - 10,
|
||||
);
|
||||
});
|
||||
|
||||
describe("and a chunk time update occurs, that would increase time left", () => {
|
||||
beforeEach(() => {
|
||||
mocked(voiceBroadcastRecording.emit).mockClear();
|
||||
voiceBroadcastRecorder.emit(VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated, 5);
|
||||
});
|
||||
|
||||
it("should not change time left", () => {
|
||||
expect(voiceBroadcastRecording.getTimeLeft()).toBe(maxLength - 10);
|
||||
expect(voiceBroadcastRecording.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and a chunk has been recorded", () => {
|
||||
beforeEach(async () => {
|
||||
emitFirsChunkRecorded();
|
||||
});
|
||||
|
||||
itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1);
|
||||
|
||||
describe("and another chunk has been recorded, that exceeds the max time", () => {
|
||||
beforeEach(() => {
|
||||
mocked(voiceBroadcastRecorder.stop).mockResolvedValue({
|
||||
buffer: new Uint8Array([23, 24, 25]),
|
||||
length: getMaxBroadcastLength(),
|
||||
});
|
||||
voiceBroadcastRecorder.emit(
|
||||
VoiceBroadcastRecorderEvent.CurrentChunkLengthUpdated,
|
||||
getMaxBroadcastLength(),
|
||||
);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Stopped);
|
||||
itShouldSendAVoiceMessage([23, 24, 25], 3, getMaxBroadcastLength(), 2);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Stopped, 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling stop", () => {
|
||||
beforeEach(async () => {
|
||||
mocked(voiceBroadcastRecorder.stop).mockResolvedValue({
|
||||
buffer: new Uint8Array([4, 5, 6]),
|
||||
length: 42,
|
||||
});
|
||||
await voiceBroadcastRecording.stop();
|
||||
});
|
||||
|
||||
itShouldSendAVoiceMessage([4, 5, 6], 3, 42, 1);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Stopped, 1);
|
||||
});
|
||||
|
||||
describe.each([
|
||||
["pause", async () => voiceBroadcastRecording.pause()],
|
||||
["toggle", async () => voiceBroadcastRecording.toggle()],
|
||||
])("and calling %s", (_case: string, action: () => Promise<void>) => {
|
||||
beforeEach(async () => {
|
||||
await action();
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Paused);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Paused, 0);
|
||||
|
||||
it("should stop the recorder", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.stop)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should emit a paused state changed event", () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and there is no connection", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendStateEvent).mockImplementation(() => {
|
||||
throw new Error();
|
||||
});
|
||||
});
|
||||
|
||||
describe.each([
|
||||
["pause", async () => voiceBroadcastRecording.pause()],
|
||||
["toggle", async () => voiceBroadcastRecording.toggle()],
|
||||
])("and calling %s", (_case: string, action: () => Promise<void>) => {
|
||||
beforeEach(async () => {
|
||||
await action();
|
||||
});
|
||||
|
||||
itShouldBeInState("connection_error");
|
||||
|
||||
describe("and the connection is back", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendStateEvent).mockResolvedValue({ event_id: "e1" });
|
||||
client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Paused);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
voiceBroadcastRecording.destroy();
|
||||
});
|
||||
|
||||
it("should stop the recorder and remove all listeners", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.stop)).toHaveBeenCalled();
|
||||
expect(mocked(voiceBroadcastRecorder.destroy)).toHaveBeenCalled();
|
||||
expect(mocked(voiceBroadcastRecording.removeAllListeners)).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and a chunk has been recorded and the upload fails", () => {
|
||||
beforeEach(() => {
|
||||
mocked(uploadFile).mockRejectedValue("Error");
|
||||
emitFirsChunkRecorded();
|
||||
});
|
||||
|
||||
itShouldBeInState("connection_error");
|
||||
|
||||
describe("and the connection is back", () => {
|
||||
beforeEach(() => {
|
||||
setUpUploadFileMock();
|
||||
client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Paused);
|
||||
itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and audible notifications are disabled", () => {
|
||||
beforeEach(() => {
|
||||
const notificationSettings = mkEvent({
|
||||
event: true,
|
||||
type: `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${client.getDeviceId()}`,
|
||||
user: client.getSafeUserId(),
|
||||
content: {
|
||||
is_silenced: true,
|
||||
},
|
||||
});
|
||||
mocked(client.getAccountData).mockReturnValue(notificationSettings);
|
||||
});
|
||||
|
||||
describe("and a chunk has been recorded and sending the voice message fails", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendMessage).mockRejectedValue("Error");
|
||||
emitFirsChunkRecorded();
|
||||
});
|
||||
|
||||
itShouldBeInState("connection_error");
|
||||
|
||||
it("should not play a notification", () => {
|
||||
expect(audioElement.play).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and a chunk has been recorded and sending the voice message fails", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendMessage).mockRejectedValue("Error");
|
||||
emitFirsChunkRecorded();
|
||||
fetchMock.get("media/error.mp3", 200);
|
||||
});
|
||||
|
||||
itShouldBeInState("connection_error");
|
||||
|
||||
it("should play a notification", () => {
|
||||
expect(mockAudioBufferSourceNode.start).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe("and the connection is back", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendMessage).mockClear();
|
||||
mocked(client.sendMessage).mockResolvedValue({ event_id: "e23" });
|
||||
client.emit(ClientEvent.Sync, SyncState.Catchup, SyncState.Error);
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Paused);
|
||||
itShouldSendAVoiceMessage([1, 2, 3], 3, 23, 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("and it is in paused state", () => {
|
||||
beforeEach(async () => {
|
||||
await voiceBroadcastRecording.pause();
|
||||
});
|
||||
|
||||
describe.each([
|
||||
["resume", async () => voiceBroadcastRecording.resume()],
|
||||
["toggle", async () => voiceBroadcastRecording.toggle()],
|
||||
])("and calling %s", (_case: string, action: () => Promise<void>) => {
|
||||
beforeEach(async () => {
|
||||
await action();
|
||||
});
|
||||
|
||||
itShouldBeInState(VoiceBroadcastInfoState.Resumed);
|
||||
itShouldSendAnInfoEvent(VoiceBroadcastInfoState.Resumed, 0);
|
||||
|
||||
it("should start the recorder", () => {
|
||||
expect(mocked(voiceBroadcastRecorder.start)).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(`should emit a ${VoiceBroadcastInfoState.Resumed} state changed event`, () => {
|
||||
expect(onStateChanged).toHaveBeenCalledWith(VoiceBroadcastInfoState.Resumed);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("when created for a Voice Broadcast Info with a Stopped relation", () => {
|
||||
beforeEach(() => {
|
||||
infoEvent = mkVoiceBroadcastInfoEvent({
|
||||
device_id: client.getDeviceId()!,
|
||||
state: VoiceBroadcastInfoState.Started,
|
||||
chunk_length: 120,
|
||||
});
|
||||
|
||||
const relationsContainer = {
|
||||
getRelations: jest.fn(),
|
||||
} as unknown as Relations;
|
||||
mocked(relationsContainer.getRelations).mockReturnValue([
|
||||
mkVoiceBroadcastInfoEvent({
|
||||
device_id: client.getDeviceId()!,
|
||||
state: VoiceBroadcastInfoState.Stopped,
|
||||
["m.relates_to"]: {
|
||||
rel_type: RelationType.Reference,
|
||||
event_id: infoEvent.getId()!,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const timelineSet = {
|
||||
relations: {
|
||||
getChildEventsForEvent: jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
(eventId: string, relationType: RelationType | string, eventType: EventType | string) => {
|
||||
if (
|
||||
eventId === infoEvent.getId() &&
|
||||
relationType === RelationType.Reference &&
|
||||
eventType === VoiceBroadcastInfoEventType
|
||||
) {
|
||||
return relationsContainer;
|
||||
}
|
||||
},
|
||||
),
|
||||
},
|
||||
} as unknown as EventTimelineSet;
|
||||
mocked(room.getUnfilteredTimelineSet).mockReturnValue(timelineSet);
|
||||
|
||||
setUpVoiceBroadcastRecording();
|
||||
});
|
||||
|
||||
it("should be in Stopped state", () => {
|
||||
expect(voiceBroadcastRecording.getState()).toBe(VoiceBroadcastInfoState.Stopped);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
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 {
|
||||
VoiceBroadcastInfoState,
|
||||
VoiceBroadcastPlayback,
|
||||
VoiceBroadcastPlaybackEvent,
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPlaybacksStoreEvent,
|
||||
VoiceBroadcastPlaybackState,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkStubRoom, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
|
||||
describe("VoiceBroadcastPlaybacksStore", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let userId: string;
|
||||
let deviceId: string;
|
||||
let room: Room;
|
||||
let infoEvent1: MatrixEvent;
|
||||
let infoEvent2: MatrixEvent;
|
||||
let playback1: VoiceBroadcastPlayback;
|
||||
let playback2: VoiceBroadcastPlayback;
|
||||
let playbacks: VoiceBroadcastPlaybacksStore;
|
||||
let onCurrentChanged: (playback: VoiceBroadcastPlayback | null) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
userId = client.getUserId() || "";
|
||||
deviceId = client.getDeviceId() || "";
|
||||
mocked(client.relations).mockClear();
|
||||
mocked(client.relations).mockResolvedValue({ events: [] });
|
||||
|
||||
room = mkStubRoom(roomId, "test room", client);
|
||||
mocked(client.getRoom).mockImplementation((roomId: string): Room | null => {
|
||||
if (roomId === room.roomId) {
|
||||
return room;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
infoEvent1 = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId);
|
||||
infoEvent2 = mkVoiceBroadcastInfoStateEvent(roomId, VoiceBroadcastInfoState.Started, userId, deviceId);
|
||||
const recordings = new VoiceBroadcastRecordingsStore();
|
||||
playback1 = new VoiceBroadcastPlayback(infoEvent1, client, recordings);
|
||||
jest.spyOn(playback1, "off");
|
||||
playback2 = new VoiceBroadcastPlayback(infoEvent2, client, recordings);
|
||||
jest.spyOn(playback2, "off");
|
||||
|
||||
playbacks = new VoiceBroadcastPlaybacksStore(recordings);
|
||||
jest.spyOn(playbacks, "removeAllListeners");
|
||||
onCurrentChanged = jest.fn();
|
||||
playbacks.on(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, onCurrentChanged);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
playbacks.off(VoiceBroadcastPlaybacksStoreEvent.CurrentChanged, onCurrentChanged);
|
||||
});
|
||||
|
||||
describe("when setting a current Voice Broadcast playback", () => {
|
||||
beforeEach(() => {
|
||||
playbacks.setCurrent(playback1);
|
||||
});
|
||||
|
||||
it("should return it as current", () => {
|
||||
expect(playbacks.getCurrent()).toBe(playback1);
|
||||
});
|
||||
|
||||
it("should return it by id", () => {
|
||||
expect(playbacks.getByInfoEvent(infoEvent1, client)).toBe(playback1);
|
||||
});
|
||||
|
||||
it("should emit a CurrentChanged event", () => {
|
||||
expect(onCurrentChanged).toHaveBeenCalledWith(playback1);
|
||||
});
|
||||
|
||||
describe("and setting the same again", () => {
|
||||
beforeEach(() => {
|
||||
mocked(onCurrentChanged).mockClear();
|
||||
playbacks.setCurrent(playback1);
|
||||
});
|
||||
|
||||
it("should not emit a CurrentChanged event", () => {
|
||||
expect(onCurrentChanged).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and setting another playback and start both", () => {
|
||||
beforeEach(() => {
|
||||
playbacks.setCurrent(playback2);
|
||||
playback1.start();
|
||||
playback2.start();
|
||||
});
|
||||
|
||||
it("should set playback1 to paused", () => {
|
||||
expect(playback1.getState()).toBe(VoiceBroadcastPlaybackState.Paused);
|
||||
});
|
||||
|
||||
it("should set playback2 to buffering", () => {
|
||||
// buffering because there are no chunks, yet
|
||||
expect(playback2.getState()).toBe(VoiceBroadcastPlaybackState.Buffering);
|
||||
});
|
||||
|
||||
describe("and calling destroy", () => {
|
||||
beforeEach(() => {
|
||||
playbacks.destroy();
|
||||
});
|
||||
|
||||
it("should remove all listeners", () => {
|
||||
expect(playbacks.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should deregister the listeners on the playbacks", () => {
|
||||
expect(playback1.off).toHaveBeenCalledWith(
|
||||
VoiceBroadcastPlaybackEvent.StateChanged,
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(playback2.off).toHaveBeenCalledWith(
|
||||
VoiceBroadcastPlaybackEvent.StateChanged,
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getByInfoEventId", () => {
|
||||
let returnedPlayback: VoiceBroadcastPlayback;
|
||||
|
||||
describe("when retrieving a known playback", () => {
|
||||
beforeEach(() => {
|
||||
playbacks.setCurrent(playback1);
|
||||
returnedPlayback = playbacks.getByInfoEvent(infoEvent1, client);
|
||||
});
|
||||
|
||||
it("should return the playback", () => {
|
||||
expect(returnedPlayback).toBe(playback1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when retrieving an unknown playback", () => {
|
||||
beforeEach(() => {
|
||||
returnedPlayback = playbacks.getByInfoEvent(infoEvent1, client);
|
||||
});
|
||||
|
||||
it("should return the playback", () => {
|
||||
expect(returnedPlayback.infoEvent).toBe(infoEvent1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
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, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import {
|
||||
VoiceBroadcastPlaybacksStore,
|
||||
VoiceBroadcastPreRecording,
|
||||
VoiceBroadcastPreRecordingStore,
|
||||
VoiceBroadcastRecordingsStore,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
|
||||
describe("VoiceBroadcastPreRecordingStore", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let sender: RoomMember;
|
||||
let playbacksStore: VoiceBroadcastPlaybacksStore;
|
||||
let recordingsStore: VoiceBroadcastRecordingsStore;
|
||||
let store: VoiceBroadcastPreRecordingStore;
|
||||
let preRecording1: VoiceBroadcastPreRecording;
|
||||
|
||||
beforeAll(() => {
|
||||
client = stubClient();
|
||||
room = new Room(roomId, client, client.getUserId() || "");
|
||||
sender = new RoomMember(roomId, client.getUserId() || "");
|
||||
recordingsStore = new VoiceBroadcastRecordingsStore();
|
||||
playbacksStore = new VoiceBroadcastPlaybacksStore(recordingsStore);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = new VoiceBroadcastPreRecordingStore();
|
||||
jest.spyOn(store, "emit");
|
||||
jest.spyOn(store, "removeAllListeners");
|
||||
preRecording1 = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||
jest.spyOn(preRecording1, "off");
|
||||
});
|
||||
|
||||
it("getCurrent() should return null", () => {
|
||||
expect(store.getCurrent()).toBeNull();
|
||||
});
|
||||
|
||||
it("clearCurrent() should work", () => {
|
||||
store.clearCurrent();
|
||||
expect(store.getCurrent()).toBeNull();
|
||||
});
|
||||
|
||||
describe("when setting a current recording", () => {
|
||||
beforeEach(() => {
|
||||
store.setCurrent(preRecording1);
|
||||
});
|
||||
|
||||
it("getCurrent() should return the recording", () => {
|
||||
expect(store.getCurrent()).toBe(preRecording1);
|
||||
});
|
||||
|
||||
it("should emit a changed event with the recording", () => {
|
||||
expect(store.emit).toHaveBeenCalledWith("changed", preRecording1);
|
||||
});
|
||||
|
||||
describe("and calling destroy()", () => {
|
||||
beforeEach(() => {
|
||||
store.destroy();
|
||||
});
|
||||
|
||||
it("should remove all listeners", () => {
|
||||
expect(store.removeAllListeners).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should deregister from the pre-recordings", () => {
|
||||
expect(preRecording1.off).toHaveBeenCalledWith("dismiss", expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
describe("and cancelling the pre-recording", () => {
|
||||
beforeEach(() => {
|
||||
preRecording1.cancel();
|
||||
});
|
||||
|
||||
it("should clear the current recording", () => {
|
||||
expect(store.getCurrent()).toBeNull();
|
||||
});
|
||||
|
||||
it("should emit a changed event with null", () => {
|
||||
expect(store.emit).toHaveBeenCalledWith("changed", null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and setting the same pre-recording again", () => {
|
||||
beforeEach(() => {
|
||||
mocked(store.emit).mockClear();
|
||||
store.setCurrent(preRecording1);
|
||||
});
|
||||
|
||||
it("should not emit a changed event", () => {
|
||||
expect(store.emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and setting another pre-recording", () => {
|
||||
let preRecording2: VoiceBroadcastPreRecording;
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(store.emit).mockClear();
|
||||
mocked(preRecording1.off).mockClear();
|
||||
preRecording2 = new VoiceBroadcastPreRecording(room, sender, client, playbacksStore, recordingsStore);
|
||||
store.setCurrent(preRecording2);
|
||||
});
|
||||
|
||||
it("should deregister from the current pre-recording", () => {
|
||||
expect(preRecording1.off).toHaveBeenCalledWith("dismiss", expect.any(Function));
|
||||
});
|
||||
|
||||
it("getCurrent() should return the new recording", () => {
|
||||
expect(store.getCurrent()).toBe(preRecording2);
|
||||
});
|
||||
|
||||
it("should emit a changed event with the new recording", () => {
|
||||
expect(store.emit).toHaveBeenCalledWith("changed", preRecording2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,167 +0,0 @@
|
|||
/*
|
||||
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 {
|
||||
VoiceBroadcastRecordingsStore,
|
||||
VoiceBroadcastRecordingsStoreEvent,
|
||||
VoiceBroadcastRecording,
|
||||
VoiceBroadcastInfoState,
|
||||
} from "../../../../src/voice-broadcast";
|
||||
import { mkStubRoom, stubClient } from "../../../test-utils";
|
||||
import { mkVoiceBroadcastInfoStateEvent } from "../utils/test-utils";
|
||||
|
||||
describe("VoiceBroadcastRecordingsStore", () => {
|
||||
const roomId = "!room:example.com";
|
||||
let client: MatrixClient;
|
||||
let room: Room;
|
||||
let infoEvent: MatrixEvent;
|
||||
let otherInfoEvent: MatrixEvent;
|
||||
let recording: VoiceBroadcastRecording;
|
||||
let otherRecording: VoiceBroadcastRecording;
|
||||
let recordings: VoiceBroadcastRecordingsStore;
|
||||
let onCurrentChanged: (recording: VoiceBroadcastRecording | null) => void;
|
||||
|
||||
beforeEach(() => {
|
||||
client = stubClient();
|
||||
room = mkStubRoom(roomId, "test room", client);
|
||||
mocked(client.getRoom).mockImplementation((roomId: string) => {
|
||||
if (roomId === room.roomId) {
|
||||
return room;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
infoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
otherInfoEvent = mkVoiceBroadcastInfoStateEvent(
|
||||
roomId,
|
||||
VoiceBroadcastInfoState.Started,
|
||||
client.getUserId()!,
|
||||
client.getDeviceId()!,
|
||||
);
|
||||
recording = new VoiceBroadcastRecording(infoEvent, client);
|
||||
otherRecording = new VoiceBroadcastRecording(otherInfoEvent, client);
|
||||
recordings = new VoiceBroadcastRecordingsStore();
|
||||
onCurrentChanged = jest.fn();
|
||||
recordings.on(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
recording.destroy();
|
||||
recordings.off(VoiceBroadcastRecordingsStoreEvent.CurrentChanged, onCurrentChanged);
|
||||
});
|
||||
|
||||
it("when setting a recording without info event Id, it should raise an error", () => {
|
||||
infoEvent.event.event_id = undefined;
|
||||
expect(() => {
|
||||
recordings.setCurrent(recording);
|
||||
}).toThrow("Got broadcast info event without Id");
|
||||
});
|
||||
|
||||
describe("when setting a current Voice Broadcast recording", () => {
|
||||
beforeEach(() => {
|
||||
recordings.setCurrent(recording);
|
||||
});
|
||||
|
||||
it("should return it as current", () => {
|
||||
expect(recordings.hasCurrent()).toBe(true);
|
||||
expect(recordings.getCurrent()).toBe(recording);
|
||||
});
|
||||
|
||||
it("should return it by id", () => {
|
||||
expect(recordings.getByInfoEvent(infoEvent, client)).toBe(recording);
|
||||
});
|
||||
|
||||
it("should emit a CurrentChanged event", () => {
|
||||
expect(onCurrentChanged).toHaveBeenCalledWith(recording);
|
||||
});
|
||||
|
||||
describe("and setting the same again", () => {
|
||||
beforeEach(() => {
|
||||
mocked(onCurrentChanged).mockClear();
|
||||
recordings.setCurrent(recording);
|
||||
});
|
||||
|
||||
it("should not emit a CurrentChanged event", () => {
|
||||
expect(onCurrentChanged).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and calling clearCurrent()", () => {
|
||||
beforeEach(() => {
|
||||
recordings.clearCurrent();
|
||||
});
|
||||
|
||||
it("should clear the current recording", () => {
|
||||
expect(recordings.hasCurrent()).toBe(false);
|
||||
expect(recordings.getCurrent()).toBeNull();
|
||||
});
|
||||
|
||||
it("should emit a current changed event", () => {
|
||||
expect(onCurrentChanged).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
it("and calling it again should work", () => {
|
||||
recordings.clearCurrent();
|
||||
expect(recordings.getCurrent()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("and setting another recording and stopping the previous recording", () => {
|
||||
beforeEach(() => {
|
||||
recordings.setCurrent(otherRecording);
|
||||
recording.stop();
|
||||
});
|
||||
|
||||
it("should keep the current recording", () => {
|
||||
expect(recordings.getCurrent()).toBe(otherRecording);
|
||||
});
|
||||
});
|
||||
|
||||
describe("and the recording stops", () => {
|
||||
beforeEach(() => {
|
||||
recording.stop();
|
||||
});
|
||||
|
||||
it("should clear the current recording", () => {
|
||||
expect(recordings.getCurrent()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getByInfoEventId", () => {
|
||||
let returnedRecording: VoiceBroadcastRecording;
|
||||
|
||||
describe("when retrieving a known recording", () => {
|
||||
beforeEach(() => {
|
||||
recordings.setCurrent(recording);
|
||||
returnedRecording = recordings.getByInfoEvent(infoEvent, client);
|
||||
});
|
||||
|
||||
it("should return the recording", () => {
|
||||
expect(returnedRecording).toBe(recording);
|
||||
});
|
||||
});
|
||||
|
||||
describe("when retrieving an unknown recording", () => {
|
||||
beforeEach(() => {
|
||||
returnedRecording = recordings.getByInfoEvent(infoEvent, client);
|
||||
});
|
||||
|
||||
it("should return the recording", () => {
|
||||
expect(returnedRecording.infoEvent).toBe(infoEvent);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,142 +0,0 @@
|
|||
/*
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,24 +0,0 @@
|
|||
// 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,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -1,116 +0,0 @@
|
|||
// 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,
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
|
@ -1,42 +0,0 @@
|
|||
// 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>
|
||||
`;
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
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");
|
||||
});
|
||||
});
|
|
@ -1,116 +0,0 @@
|
|||
/*
|
||||
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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,195 +0,0 @@
|
|||
/*
|
||||
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], { addToState: true });
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
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], { addToState: true });
|
||||
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], { addToState: true });
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
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], { addToState: true });
|
||||
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());
|
||||
});
|
||||
});
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
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);
|
||||
});
|
||||
});
|
|
@ -1,138 +0,0 @@
|
|||
/*
|
||||
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();
|
||||
},
|
||||
);
|
||||
});
|
|
@ -1,234 +0,0 @@
|
|||
/*
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
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,
|
||||
);
|
||||
};
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
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
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
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