mirror of
https://github.com/vector-im/element-web.git
synced 2025-12-08 02:41:32 +01:00
Watch for a 'join' action to know when the call is connected (#29492)
Previously we were watching for changes to the room state to know when you become connected to a call. However, the room state might not change if you had a stuck membership event prior to re-joining the call. It's going to be more reliable to watch for the 'join' action that Element Call sends, and use that to track the connection state.
This commit is contained in:
parent
6a1c0502aa
commit
4b4cb896eb
@ -23,14 +23,12 @@ import { type IWidgetApiRequest, type ClientWidgetApi, type IWidgetData } from "
|
||||
import {
|
||||
type MatrixRTCSession,
|
||||
MatrixRTCSessionEvent,
|
||||
type CallMembership,
|
||||
MatrixRTCSessionManagerEvents,
|
||||
} from "matrix-js-sdk/src/matrixrtc";
|
||||
|
||||
import type EventEmitter from "events";
|
||||
import type { IApp } from "../stores/WidgetStore";
|
||||
import SettingsStore from "../settings/SettingsStore";
|
||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../MediaDeviceHandler";
|
||||
import { timeout } from "../utils/promise";
|
||||
import WidgetUtils from "../utils/WidgetUtils";
|
||||
import { WidgetType } from "../widgets/WidgetType";
|
||||
@ -193,18 +191,6 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
*/
|
||||
public abstract clean(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Contacts the widget to connect to the call or prompt the user to connect to the call.
|
||||
* @param {MediaDeviceInfo | null} audioInput The audio input to use, or
|
||||
* null to start muted.
|
||||
* @param {MediaDeviceInfo | null} audioInput The video input to use, or
|
||||
* null to start muted.
|
||||
*/
|
||||
protected abstract performConnection(
|
||||
audioInput: MediaDeviceInfo | null,
|
||||
videoInput: MediaDeviceInfo | null,
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Contacts the widget to disconnect from the call.
|
||||
*/
|
||||
@ -212,28 +198,10 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
|
||||
/**
|
||||
* Starts the communication between the widget and the call.
|
||||
* The call then waits for the necessary requirements to actually perform the connection
|
||||
* or connects right away depending on the call type. (Jitsi, Legacy, ElementCall...)
|
||||
* It uses the media devices set in MediaDeviceHandler.
|
||||
* The widget associated with the call must be active
|
||||
* for this to succeed.
|
||||
* The widget associated with the call must be active for this to succeed.
|
||||
* Only call this if the call state is: ConnectionState.Disconnected.
|
||||
*/
|
||||
public async start(): Promise<void> {
|
||||
const { [MediaDeviceKindEnum.AudioInput]: audioInputs, [MediaDeviceKindEnum.VideoInput]: videoInputs } =
|
||||
(await MediaDeviceHandler.getDevices())!;
|
||||
|
||||
let audioInput: MediaDeviceInfo | null = null;
|
||||
if (!MediaDeviceHandler.startWithAudioMuted) {
|
||||
const deviceId = MediaDeviceHandler.getAudioInput();
|
||||
audioInput = audioInputs.find((d) => d.deviceId === deviceId) ?? audioInputs[0] ?? null;
|
||||
}
|
||||
let videoInput: MediaDeviceInfo | null = null;
|
||||
if (!MediaDeviceHandler.startWithVideoMuted) {
|
||||
const deviceId = MediaDeviceHandler.getVideoInput();
|
||||
videoInput = videoInputs.find((d) => d.deviceId === deviceId) ?? videoInputs[0] ?? null;
|
||||
}
|
||||
|
||||
const messagingStore = WidgetMessagingStore.instance;
|
||||
this.messaging = messagingStore.getMessagingForUid(this.widgetUid) ?? null;
|
||||
if (!this.messaging) {
|
||||
@ -254,13 +222,23 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
throw new Error(`Failed to bind call widget in room ${this.roomId}: ${e}`);
|
||||
}
|
||||
}
|
||||
await this.performConnection(audioInput, videoInput);
|
||||
}
|
||||
|
||||
protected setConnected(): void {
|
||||
this.room.on(RoomEvent.MyMembership, this.onMyMembership);
|
||||
window.addEventListener("beforeunload", this.beforeUnload);
|
||||
this.connectionState = ConnectionState.Connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually marks the call as disconnected.
|
||||
*/
|
||||
protected setDisconnected(): void {
|
||||
this.room.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
window.removeEventListener("beforeunload", this.beforeUnload);
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the user from the call.
|
||||
*/
|
||||
@ -273,15 +251,6 @@ export abstract class Call extends TypedEventEmitter<CallEvent, CallEventHandler
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually marks the call as disconnected.
|
||||
*/
|
||||
public setDisconnected(): void {
|
||||
this.room.off(RoomEvent.MyMembership, this.onMyMembership);
|
||||
window.removeEventListener("beforeunload", this.beforeUnload);
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops further communication with the widget and tells the UI to close.
|
||||
*/
|
||||
@ -467,66 +436,10 @@ export class JitsiCall extends Call {
|
||||
});
|
||||
}
|
||||
|
||||
protected async performConnection(
|
||||
audioInput: MediaDeviceInfo | null,
|
||||
videoInput: MediaDeviceInfo | null,
|
||||
): Promise<void> {
|
||||
// Ensure that the messaging doesn't get stopped while we're waiting for responses
|
||||
const dontStopMessaging = new Promise<void>((resolve, reject) => {
|
||||
const messagingStore = WidgetMessagingStore.instance;
|
||||
|
||||
const listener = (uid: string): void => {
|
||||
if (uid === this.widgetUid) {
|
||||
cleanup();
|
||||
reject(new Error("Messaging stopped"));
|
||||
}
|
||||
};
|
||||
const done = (): void => {
|
||||
cleanup();
|
||||
resolve();
|
||||
};
|
||||
const cleanup = (): void => {
|
||||
messagingStore.off(WidgetMessagingStoreEvent.StopMessaging, listener);
|
||||
this.off(CallEvent.ConnectionState, done);
|
||||
};
|
||||
|
||||
messagingStore.on(WidgetMessagingStoreEvent.StopMessaging, listener);
|
||||
this.on(CallEvent.ConnectionState, done);
|
||||
});
|
||||
|
||||
// Empirically, it's possible for Jitsi Meet to crash instantly at startup,
|
||||
// sending a hangup event that races with the rest of this method, so we need
|
||||
// to add the hangup listener now rather than later
|
||||
public async start(): Promise<void> {
|
||||
await super.start();
|
||||
this.messaging!.on(`action:${ElementWidgetActions.JoinCall}`, this.onJoin);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
|
||||
// Actually perform the join
|
||||
const response = waitForEvent(
|
||||
this.messaging!,
|
||||
`action:${ElementWidgetActions.JoinCall}`,
|
||||
(ev: CustomEvent<IWidgetApiRequest>) => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
return true;
|
||||
},
|
||||
);
|
||||
const request = this.messaging!.transport.send(ElementWidgetActions.JoinCall, {
|
||||
audioInput: audioInput?.label ?? null,
|
||||
videoInput: videoInput?.label ?? null,
|
||||
});
|
||||
try {
|
||||
await Promise.race([Promise.all([request, response]), dontStopMessaging]);
|
||||
} catch (e) {
|
||||
// If it timed out, clean up our advance preparations
|
||||
this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
|
||||
if (this.messaging!.transport.ready) {
|
||||
// The messaging still exists, which means Jitsi might still be going in the background
|
||||
this.messaging!.transport.send(ElementWidgetActions.HangupCall, { force: true });
|
||||
}
|
||||
|
||||
throw new Error(`Failed to join call in room ${this.roomId}: ${e}`);
|
||||
}
|
||||
|
||||
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Dock, this.onDock);
|
||||
ActiveWidgetStore.instance.on(ActiveWidgetStoreEvent.Undock, this.onUndock);
|
||||
}
|
||||
@ -549,18 +462,17 @@ export class JitsiCall extends Call {
|
||||
}
|
||||
}
|
||||
|
||||
public setDisconnected(): void {
|
||||
// During tests this.messaging can be undefined
|
||||
this.messaging?.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
public close(): void {
|
||||
this.messaging!.off(`action:${ElementWidgetActions.JoinCall}`, this.onJoin);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
ActiveWidgetStore.instance.off(ActiveWidgetStoreEvent.Dock, this.onDock);
|
||||
ActiveWidgetStore.instance.off(ActiveWidgetStoreEvent.Undock, this.onUndock);
|
||||
|
||||
super.setDisconnected();
|
||||
super.close();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.room.off(RoomStateEvent.Update, this.onRoomState);
|
||||
this.on(CallEvent.ConnectionState, this.onConnectionState);
|
||||
this.off(CallEvent.ConnectionState, this.onConnectionState);
|
||||
if (this.participantsExpirationTimer !== null) {
|
||||
clearTimeout(this.participantsExpirationTimer);
|
||||
this.participantsExpirationTimer = null;
|
||||
@ -612,27 +524,21 @@ export class JitsiCall extends Call {
|
||||
await this.messaging!.transport.send(ElementWidgetActions.SpotlightLayout, {});
|
||||
};
|
||||
|
||||
private readonly onJoin = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.setConnected();
|
||||
};
|
||||
|
||||
private readonly onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
// If we're already in the middle of a client-initiated disconnection,
|
||||
// ignore the event
|
||||
if (this.connectionState === ConnectionState.Disconnecting) return;
|
||||
|
||||
ev.preventDefault();
|
||||
|
||||
// In case this hangup is caused by Jitsi Meet crashing at startup,
|
||||
// wait for the connection event in order to avoid racing
|
||||
if (this.connectionState === ConnectionState.Disconnected) {
|
||||
await waitForEvent(this, CallEvent.ConnectionState);
|
||||
}
|
||||
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.setDisconnected();
|
||||
this.close();
|
||||
// In video rooms we immediately want to restart the call after hangup
|
||||
// The lobby will be shown again and it connects to all signals from Jitsi.
|
||||
if (isVideoRoom(this.room)) {
|
||||
this.start();
|
||||
}
|
||||
if (!isVideoRoom(this.room)) this.close();
|
||||
};
|
||||
}
|
||||
|
||||
@ -860,54 +766,38 @@ export class ElementCall extends Call {
|
||||
ElementCall.createOrGetCallWidget(room.roomId, room.client, skipLobby, isVideoRoom(room));
|
||||
}
|
||||
|
||||
protected async performConnection(
|
||||
audioInput: MediaDeviceInfo | null,
|
||||
videoInput: MediaDeviceInfo | null,
|
||||
): Promise<void> {
|
||||
public async start(): Promise<void> {
|
||||
await super.start();
|
||||
this.messaging!.on(`action:${ElementWidgetActions.JoinCall}`, this.onJoin);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
this.messaging!.once(`action:${ElementWidgetActions.Close}`, this.onClose);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.Close}`, this.onClose);
|
||||
this.messaging!.on(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
|
||||
|
||||
// TODO: Watch for a widget action telling us that the join button was clicked, rather than
|
||||
// relying on the MatrixRTC session state, to set the state to connecting
|
||||
const session = this.client.matrixRTC.getActiveRoomSession(this.room);
|
||||
if (session) {
|
||||
await waitForEvent(
|
||||
session,
|
||||
MatrixRTCSessionEvent.MembershipsChanged,
|
||||
(_, newMemberships: CallMembership[]) =>
|
||||
newMemberships.some((m) => m.sender === this.client.getUserId()),
|
||||
false, // allow user to wait as long as they want (no timeout)
|
||||
);
|
||||
} else {
|
||||
await waitForEvent(
|
||||
this.client.matrixRTC,
|
||||
MatrixRTCSessionManagerEvents.SessionStarted,
|
||||
(roomId: string, session: MatrixRTCSession) =>
|
||||
this.session.callId === session.callId && roomId === this.roomId,
|
||||
false, // allow user to wait as long as they want (no timeout)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async performDisconnection(): Promise<void> {
|
||||
try {
|
||||
await this.messaging!.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
await waitForEvent(
|
||||
this.session,
|
||||
MatrixRTCSessionEvent.MembershipsChanged,
|
||||
(_, newMemberships: CallMembership[]) =>
|
||||
!newMemberships.some((m) => m.sender === this.client.getUserId()),
|
||||
const response = waitForEvent(
|
||||
this.messaging!,
|
||||
`action:${ElementWidgetActions.HangupCall}`,
|
||||
(ev: CustomEvent<IWidgetApiRequest>) => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
return true;
|
||||
},
|
||||
);
|
||||
const request = this.messaging!.transport.send(ElementWidgetActions.HangupCall, {});
|
||||
try {
|
||||
await Promise.all([request, response]);
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to hangup call in room ${this.roomId}: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
public setDisconnected(): void {
|
||||
public close(): void {
|
||||
this.messaging!.off(`action:${ElementWidgetActions.JoinCall}`, this.onJoin);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.HangupCall}`, this.onHangup);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.Close}`, this.onClose);
|
||||
this.messaging!.off(`action:${ElementWidgetActions.DeviceMute}`, this.onDeviceMute);
|
||||
super.setDisconnected();
|
||||
super.close();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
@ -954,22 +844,27 @@ export class ElementCall extends Call {
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
};
|
||||
|
||||
private readonly onJoin = (ev: CustomEvent<IWidgetApiRequest>): void => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.setConnected();
|
||||
};
|
||||
|
||||
private readonly onHangup = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
// If we're already in the middle of a client-initiated disconnection,
|
||||
// ignore the event
|
||||
if (this.connectionState === ConnectionState.Disconnecting) return;
|
||||
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
this.setDisconnected();
|
||||
// In video rooms we immediately want to reconnect after hangup
|
||||
// This starts the lobby again and connects to all signals from EC.
|
||||
if (isVideoRoom(this.room)) {
|
||||
this.start();
|
||||
}
|
||||
};
|
||||
|
||||
private readonly onClose = async (ev: CustomEvent<IWidgetApiRequest>): Promise<void> => {
|
||||
ev.preventDefault();
|
||||
this.messaging!.transport.reply(ev.detail, {}); // ack
|
||||
// User is done with the call; tell the UI to close it
|
||||
this.close();
|
||||
this.setDisconnected(); // Just in case the widget forgot to emit a hangup action (maybe it's in an error state)
|
||||
this.close(); // User is done with the call; tell the UI to close it
|
||||
};
|
||||
|
||||
public clean(): Promise<void> {
|
||||
|
||||
@ -79,7 +79,6 @@ export class MockedCall extends Call {
|
||||
// No action needed for any of the following methods since this is just a mock
|
||||
public async clean(): Promise<void> {}
|
||||
// Public to allow spying
|
||||
public async performConnection(): Promise<void> {}
|
||||
public async performDisconnection(): Promise<void> {}
|
||||
|
||||
public destroy() {
|
||||
|
||||
@ -151,7 +151,7 @@ describe("CallEvent", () => {
|
||||
}),
|
||||
);
|
||||
defaultDispatcher.unregister(dispatcherRef);
|
||||
await act(() => call.start());
|
||||
act(() => call.setConnectionState(ConnectionState.Connected));
|
||||
|
||||
// Test that the leave button works
|
||||
fireEvent.click(screen.getByRole("button", { name: "Leave" }));
|
||||
|
||||
@ -46,6 +46,7 @@ import { UIComponent } from "../../../../../src/settings/UIFeature";
|
||||
import { MessagePreviewStore } from "../../../../../src/stores/room-list/MessagePreviewStore";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { ConnectionState } from "../../../../../src/models/Call";
|
||||
|
||||
jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
|
||||
shouldShowComponent: jest.fn(),
|
||||
@ -215,7 +216,7 @@ describe("RoomTile", () => {
|
||||
it("tracks connection state", async () => {
|
||||
renderRoomTile();
|
||||
screen.getByText("Video");
|
||||
await act(() => call.start());
|
||||
act(() => call.setConnectionState(ConnectionState.Connected));
|
||||
screen.getByText("Joined");
|
||||
await act(() => call.disconnect());
|
||||
screen.getByText("Video");
|
||||
|
||||
@ -41,7 +41,6 @@ import {
|
||||
ElementCall,
|
||||
} from "../../../src/models/Call";
|
||||
import { stubClient, mkEvent, mkRoomMember, setupAsyncStoreWithClient, mockPlatformPeg } from "../../test-utils";
|
||||
import MediaDeviceHandler, { MediaDeviceKindEnum } from "../../../src/MediaDeviceHandler";
|
||||
import { MatrixClientPeg } from "../../../src/MatrixClientPeg";
|
||||
import WidgetStore from "../../../src/stores/WidgetStore";
|
||||
import { WidgetMessagingStore } from "../../../src/stores/widgets/WidgetMessagingStore";
|
||||
@ -55,18 +54,6 @@ import RoomListStore from "../../../src/stores/room-list/RoomListStore.ts";
|
||||
import { DefaultTagID } from "../../../src/stores/room-list/models.ts";
|
||||
import DMRoomMap from "../../../src/utils/DMRoomMap.ts";
|
||||
|
||||
jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({
|
||||
[MediaDeviceKindEnum.AudioInput]: [
|
||||
{ deviceId: "1", groupId: "1", kind: "audioinput", label: "Headphones", toJSON: () => {} },
|
||||
],
|
||||
[MediaDeviceKindEnum.VideoInput]: [
|
||||
{ deviceId: "2", groupId: "2", kind: "videoinput", label: "Built-in webcam", toJSON: () => {} },
|
||||
],
|
||||
[MediaDeviceKindEnum.AudioOutput]: [],
|
||||
});
|
||||
jest.spyOn(MediaDeviceHandler, "getAudioInput").mockReturnValue("1");
|
||||
jest.spyOn(MediaDeviceHandler, "getVideoInput").mockReturnValue("2");
|
||||
|
||||
const enabledSettings = new Set(["feature_group_calls", "feature_video_rooms", "feature_element_call_video_rooms"]);
|
||||
jest.spyOn(SettingsStore, "getValue").mockImplementation(
|
||||
(settingName): any => enabledSettings.has(settingName) || undefined,
|
||||
@ -140,14 +127,7 @@ const cleanUpClientRoomAndStores = (client: MatrixClient, room: Room) => {
|
||||
client.reEmitter.stopReEmitting(room, [RoomStateEvent.Events]);
|
||||
};
|
||||
|
||||
const setUpWidget = (
|
||||
call: Call,
|
||||
): {
|
||||
widget: Widget;
|
||||
messaging: Mocked<ClientWidgetApi>;
|
||||
audioMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
videoMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
} => {
|
||||
const setUpWidget = (call: Call): { widget: Widget; messaging: Mocked<ClientWidgetApi> } => {
|
||||
call.widget.data = { ...call.widget, skipLobby: true };
|
||||
const widget = new Widget(call.widget);
|
||||
|
||||
@ -165,23 +145,45 @@ const setUpWidget = (
|
||||
} as unknown as Mocked<ClientWidgetApi>;
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, call.roomId, messaging);
|
||||
|
||||
const audioMutedSpy = jest.spyOn(MediaDeviceHandler, "startWithAudioMuted", "get");
|
||||
const videoMutedSpy = jest.spyOn(MediaDeviceHandler, "startWithVideoMuted", "get");
|
||||
|
||||
return { widget, messaging, audioMutedSpy, videoMutedSpy };
|
||||
return { widget, messaging };
|
||||
};
|
||||
|
||||
const cleanUpCallAndWidget = (
|
||||
call: Call,
|
||||
widget: Widget,
|
||||
audioMutedSpy: jest.SpyInstance<boolean, []>,
|
||||
videoMutedSpy: jest.SpyInstance<boolean, []>,
|
||||
) => {
|
||||
async function connect(call: Call, messaging: Mocked<ClientWidgetApi>, startWidget = true): Promise<void> {
|
||||
async function sessionConnect() {
|
||||
await new Promise<void>((r) => {
|
||||
setTimeout(() => r(), 400);
|
||||
});
|
||||
messaging.emit(`action:${ElementWidgetActions.JoinCall}`, new CustomEvent("widgetapirequest", {}));
|
||||
}
|
||||
async function runTimers() {
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(500);
|
||||
}
|
||||
sessionConnect();
|
||||
await Promise.all([...(startWidget ? [call.start()] : []), runTimers()]);
|
||||
}
|
||||
|
||||
async function disconnect(call: Call, messaging: Mocked<ClientWidgetApi>): Promise<void> {
|
||||
async function sessionDisconnect() {
|
||||
await new Promise<void>((r) => {
|
||||
setTimeout(() => r(), 400);
|
||||
});
|
||||
messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
|
||||
}
|
||||
async function runTimers() {
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(500);
|
||||
}
|
||||
sessionDisconnect();
|
||||
const promise = call.disconnect();
|
||||
runTimers();
|
||||
await promise;
|
||||
}
|
||||
|
||||
const cleanUpCallAndWidget = (call: Call, widget: Widget) => {
|
||||
call.destroy();
|
||||
jest.clearAllMocks();
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, call.roomId);
|
||||
audioMutedSpy.mockRestore();
|
||||
videoMutedSpy.mockRestore();
|
||||
};
|
||||
|
||||
describe("JitsiCall", () => {
|
||||
@ -225,8 +227,6 @@ describe("JitsiCall", () => {
|
||||
let call: JitsiCall;
|
||||
let widget: Widget;
|
||||
let messaging: Mocked<ClientWidgetApi>;
|
||||
let audioMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
let videoMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
@ -237,7 +237,7 @@ describe("JitsiCall", () => {
|
||||
if (maybeCall === null) throw new Error("Failed to create call");
|
||||
call = maybeCall;
|
||||
|
||||
({ widget, messaging, audioMutedSpy, videoMutedSpy } = setUpWidget(call));
|
||||
({ widget, messaging } = setUpWidget(call));
|
||||
|
||||
mocked(messaging.transport).send.mockImplementation(async (action, data): Promise<any> => {
|
||||
if (action === ElementWidgetActions.JoinCall) {
|
||||
@ -255,102 +255,37 @@ describe("JitsiCall", () => {
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget, audioMutedSpy, videoMutedSpy));
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget));
|
||||
|
||||
it("connects muted", async () => {
|
||||
it("connects", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
audioMutedSpy.mockReturnValue(true);
|
||||
videoMutedSpy.mockReturnValue(true);
|
||||
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.JoinCall, {
|
||||
audioInput: null,
|
||||
videoInput: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("connects unmuted", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
audioMutedSpy.mockReturnValue(false);
|
||||
videoMutedSpy.mockReturnValue(false);
|
||||
|
||||
await call.start();
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.JoinCall, {
|
||||
audioInput: "Headphones",
|
||||
videoInput: "Built-in webcam",
|
||||
});
|
||||
});
|
||||
|
||||
it("waits for messaging when connecting", async () => {
|
||||
it("waits for messaging when starting", async () => {
|
||||
// Temporarily remove the messaging to simulate connecting while the
|
||||
// widget is still initializing
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
|
||||
const connect = call.start();
|
||||
const startup = call.start();
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, messaging);
|
||||
await connect;
|
||||
await startup;
|
||||
await connect(call, messaging, false);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
});
|
||||
|
||||
it("doesn't stop messaging when connecting", async () => {
|
||||
// Temporarily remove the messaging to simulate connecting while the
|
||||
// widget is still initializing
|
||||
jest.useFakeTimers();
|
||||
const oldSendMock = messaging.transport.send;
|
||||
mocked(messaging.transport).send.mockImplementation(async (action: string): Promise<any> => {
|
||||
if (action === ElementWidgetActions.JoinCall) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
messaging.emit(
|
||||
`action:${ElementWidgetActions.JoinCall}`,
|
||||
new CustomEvent("widgetapirequest", { detail: {} }),
|
||||
);
|
||||
}
|
||||
});
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
|
||||
const connect = call.start();
|
||||
async function runTimers() {
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(1000);
|
||||
}
|
||||
async function runStopMessaging() {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||
}
|
||||
runStopMessaging();
|
||||
runTimers();
|
||||
let connectError;
|
||||
try {
|
||||
await connect;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
connectError = e;
|
||||
}
|
||||
expect(connectError).toBeDefined();
|
||||
// const connect2 = await connect;
|
||||
// expect(connect2).toThrow();
|
||||
messaging.transport.send = oldSendMock;
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("fails to connect if the widget returns an error", async () => {
|
||||
mocked(messaging.transport).send.mockRejectedValue(new Error("never!!1! >:("));
|
||||
await expect(call.start()).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it("fails to disconnect if the widget returns an error", async () => {
|
||||
await call.start();
|
||||
mocked(messaging.transport).send.mockRejectedValue(new Error("never!!1! >:("));
|
||||
await connect(call, messaging);
|
||||
mocked(messaging.transport).send.mockRejectedValue(new Error("never!"));
|
||||
await expect(call.disconnect()).rejects.toBeDefined();
|
||||
});
|
||||
|
||||
it("handles remote disconnection", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
|
||||
const callback = jest.fn();
|
||||
@ -358,7 +293,6 @@ describe("JitsiCall", () => {
|
||||
call.on(CallEvent.ConnectionState, callback);
|
||||
|
||||
messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
|
||||
messaging.emit(`action:${ElementWidgetActions.Close}`, new CustomEvent("widgetapirequest", {}));
|
||||
await waitFor(() => {
|
||||
expect(callback).toHaveBeenNthCalledWith(1, ConnectionState.Disconnected, ConnectionState.Connected);
|
||||
});
|
||||
@ -368,14 +302,14 @@ describe("JitsiCall", () => {
|
||||
|
||||
it("disconnects", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
await call.disconnect();
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
|
||||
it("disconnects when we leave the room", async () => {
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
room.emit(RoomEvent.MyMembership, room, KnownMembership.Leave);
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
@ -383,14 +317,14 @@ describe("JitsiCall", () => {
|
||||
|
||||
it("reconnects after disconnect in video rooms", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
await call.disconnect();
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
|
||||
it("remains connected if we stay in the room", async () => {
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
room.emit(RoomEvent.MyMembership, room, KnownMembership.Join);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
@ -416,7 +350,7 @@ describe("JitsiCall", () => {
|
||||
|
||||
// Now, stub out client.sendStateEvent so we can test our local echo
|
||||
client.sendStateEvent.mockReset();
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
expect(call.participants).toEqual(
|
||||
new Map([
|
||||
[alice, new Set(["alices_device"])],
|
||||
@ -429,8 +363,8 @@ describe("JitsiCall", () => {
|
||||
});
|
||||
|
||||
it("updates room state when connecting and disconnecting", async () => {
|
||||
await connect(call, messaging);
|
||||
const now1 = Date.now();
|
||||
await call.start();
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
@ -457,7 +391,7 @@ describe("JitsiCall", () => {
|
||||
});
|
||||
|
||||
it("repeatedly updates room state while connected", async () => {
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(client.sendStateEvent).toHaveBeenLastCalledWith(
|
||||
@ -487,7 +421,7 @@ describe("JitsiCall", () => {
|
||||
const onConnectionState = jest.fn();
|
||||
call.on(CallEvent.ConnectionState, onConnectionState);
|
||||
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
await call.disconnect();
|
||||
expect(onConnectionState.mock.calls).toEqual([
|
||||
[ConnectionState.Connected, ConnectionState.Disconnected],
|
||||
@ -502,7 +436,7 @@ describe("JitsiCall", () => {
|
||||
const onParticipants = jest.fn();
|
||||
call.on(CallEvent.Participants, onParticipants);
|
||||
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
await call.disconnect();
|
||||
expect(onParticipants.mock.calls).toEqual([
|
||||
[new Map([[alice, new Set(["alices_device"])]]), new Map()],
|
||||
@ -515,7 +449,7 @@ describe("JitsiCall", () => {
|
||||
});
|
||||
|
||||
it("switches to spotlight layout when the widget becomes a PiP", async () => {
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
ActiveWidgetStore.instance.emit(ActiveWidgetStoreEvent.Undock);
|
||||
expect(messaging.transport.send).toHaveBeenCalledWith(ElementWidgetActions.SpotlightLayout, {});
|
||||
ActiveWidgetStore.instance.emit(ActiveWidgetStoreEvent.Dock);
|
||||
@ -559,7 +493,7 @@ describe("JitsiCall", () => {
|
||||
});
|
||||
|
||||
it("doesn't clean up valid devices", async () => {
|
||||
await call.start();
|
||||
await connect(call, messaging);
|
||||
await client.sendStateEvent(
|
||||
room.roomId,
|
||||
JitsiCall.MEMBER_EVENT_TYPE,
|
||||
@ -624,47 +558,6 @@ describe("ElementCall", () => {
|
||||
jest.spyOn(room, "getJoinedMembers").mockReturnValue(memberIds.map((id) => ({ userId: id }) as RoomMember));
|
||||
}
|
||||
|
||||
const callConnectProcedure = async (call: ElementCall, startWidget = true): Promise<void> => {
|
||||
async function sessionConnect() {
|
||||
await new Promise<void>((r) => {
|
||||
setTimeout(() => r(), 400);
|
||||
});
|
||||
client.matrixRTC.emit(MatrixRTCSessionManagerEvents.SessionStarted, call.roomId, {
|
||||
sessionId: undefined,
|
||||
} as unknown as MatrixRTCSession);
|
||||
call.session?.emit(
|
||||
MatrixRTCSessionEvent.MembershipsChanged,
|
||||
[],
|
||||
[{ sender: client.getUserId() } as CallMembership],
|
||||
);
|
||||
}
|
||||
async function runTimers() {
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(500);
|
||||
}
|
||||
sessionConnect();
|
||||
await Promise.all([...(startWidget ? [call.start()] : []), runTimers()]);
|
||||
};
|
||||
const callDisconnectionProcedure: (call: ElementCall) => Promise<void> = async (call) => {
|
||||
async function sessionDisconnect() {
|
||||
await new Promise<void>((r) => {
|
||||
setTimeout(() => r(), 400);
|
||||
});
|
||||
client.matrixRTC.emit(MatrixRTCSessionManagerEvents.SessionStarted, call.roomId, {
|
||||
sessionId: undefined,
|
||||
} as unknown as MatrixRTCSession);
|
||||
call.session?.emit(MatrixRTCSessionEvent.MembershipsChanged, [], []);
|
||||
}
|
||||
async function runTimers() {
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(500);
|
||||
}
|
||||
sessionDisconnect();
|
||||
const promise = call.disconnect();
|
||||
runTimers();
|
||||
await promise;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers();
|
||||
({ client, room, alice } = setUpClientRoomAndStores());
|
||||
@ -886,8 +779,6 @@ describe("ElementCall", () => {
|
||||
let call: ElementCall;
|
||||
let widget: Widget;
|
||||
let messaging: Mocked<ClientWidgetApi>;
|
||||
let audioMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
let videoMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
@ -898,27 +789,28 @@ describe("ElementCall", () => {
|
||||
if (maybeCall === null) throw new Error("Failed to create call");
|
||||
call = maybeCall;
|
||||
|
||||
({ widget, messaging, audioMutedSpy, videoMutedSpy } = setUpWidget(call));
|
||||
({ widget, messaging } = setUpWidget(call));
|
||||
});
|
||||
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget, audioMutedSpy, videoMutedSpy));
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget));
|
||||
// TODO refactor initial device configuration to use the EW settings.
|
||||
// Add tests for passing EW device configuration to the widget.
|
||||
it("waits for messaging when connecting", async () => {
|
||||
it("waits for messaging when starting", async () => {
|
||||
// Temporarily remove the messaging to simulate connecting while the
|
||||
// widget is still initializing
|
||||
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
|
||||
const connect = callConnectProcedure(call);
|
||||
const startup = call.start();
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, room.roomId, messaging);
|
||||
await connect;
|
||||
await startup;
|
||||
await connect(call, messaging, false);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
});
|
||||
|
||||
it("fails to disconnect if the widget returns an error", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
mocked(messaging.transport).send.mockRejectedValue(new Error("never!!1! >:("));
|
||||
await expect(call.disconnect()).rejects.toBeDefined();
|
||||
});
|
||||
@ -926,7 +818,7 @@ describe("ElementCall", () => {
|
||||
it("handles remote disconnection", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
|
||||
messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
|
||||
@ -936,35 +828,35 @@ describe("ElementCall", () => {
|
||||
|
||||
it("disconnects", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
await callDisconnectionProcedure(call);
|
||||
await disconnect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
|
||||
it("disconnects when we leave the room", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
room.emit(RoomEvent.MyMembership, room, KnownMembership.Leave);
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
|
||||
it("remains connected if we stay in the room", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
room.emit(RoomEvent.MyMembership, room, KnownMembership.Join);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
});
|
||||
|
||||
it("disconnects if the widget dies", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
WidgetMessagingStore.instance.stopMessaging(widget, room.roomId);
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
});
|
||||
|
||||
it("acknowledges mute_device widget action", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
const preventDefault = jest.fn();
|
||||
const mockEv = {
|
||||
preventDefault,
|
||||
@ -980,8 +872,8 @@ describe("ElementCall", () => {
|
||||
const onConnectionState = jest.fn();
|
||||
call.on(CallEvent.ConnectionState, onConnectionState);
|
||||
|
||||
await callConnectProcedure(call);
|
||||
await callDisconnectionProcedure(call);
|
||||
await connect(call, messaging);
|
||||
await disconnect(call, messaging);
|
||||
expect(onConnectionState.mock.calls).toEqual([
|
||||
[ConnectionState.Connected, ConnectionState.Disconnected],
|
||||
[ConnectionState.Disconnecting, ConnectionState.Connected],
|
||||
@ -1003,10 +895,10 @@ describe("ElementCall", () => {
|
||||
});
|
||||
|
||||
it("ends the call immediately if the session ended", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
const onDestroy = jest.fn();
|
||||
call.on(CallEvent.Destroy, onDestroy);
|
||||
await callDisconnectionProcedure(call);
|
||||
await disconnect(call, messaging);
|
||||
// this will be called automatically
|
||||
// disconnect -> widget sends state event -> session manager notices no-one left
|
||||
client.matrixRTC.emit(
|
||||
@ -1048,8 +940,6 @@ describe("ElementCall", () => {
|
||||
let call: ElementCall;
|
||||
let widget: Widget;
|
||||
let messaging: Mocked<ClientWidgetApi>;
|
||||
let audioMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
let videoMutedSpy: jest.SpyInstance<boolean, []>;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
@ -1062,64 +952,29 @@ describe("ElementCall", () => {
|
||||
if (maybeCall === null) throw new Error("Failed to create call");
|
||||
call = maybeCall;
|
||||
|
||||
({ widget, messaging, audioMutedSpy, videoMutedSpy } = setUpWidget(call));
|
||||
({ widget, messaging } = setUpWidget(call));
|
||||
});
|
||||
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget, audioMutedSpy, videoMutedSpy));
|
||||
afterEach(() => cleanUpCallAndWidget(call, widget));
|
||||
|
||||
it("doesn't end the call when the last participant leaves", async () => {
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
const onDestroy = jest.fn();
|
||||
call.on(CallEvent.Destroy, onDestroy);
|
||||
await callDisconnectionProcedure(call);
|
||||
await disconnect(call, messaging);
|
||||
expect(onDestroy).not.toHaveBeenCalled();
|
||||
call.off(CallEvent.Destroy, onDestroy);
|
||||
});
|
||||
|
||||
it("connect to call with ongoing session", async () => {
|
||||
// Mock membership getter used by `roomSessionForRoom`.
|
||||
// This makes sure the roomSession will not be empty.
|
||||
jest.spyOn(MatrixRTCSession, "callMembershipsForRoom").mockImplementation(() => [
|
||||
{ fakeVal: "fake membership", getMsUntilExpiry: () => 1000 } as unknown as CallMembership,
|
||||
]);
|
||||
// Create ongoing session
|
||||
const roomSession = MatrixRTCSession.roomSessionForRoom(client, room);
|
||||
const roomSessionEmitSpy = jest.spyOn(roomSession, "emit");
|
||||
|
||||
// Make sure the created session ends up in the call.
|
||||
// `getActiveRoomSession` will be used during `call.connect`
|
||||
// `getRoomSession` will be used during `Call.get`
|
||||
client.matrixRTC.getActiveRoomSession.mockImplementation(() => {
|
||||
return roomSession;
|
||||
});
|
||||
client.matrixRTC.getRoomSession.mockImplementation(() => {
|
||||
return roomSession;
|
||||
});
|
||||
|
||||
ElementCall.create(room);
|
||||
const call = Call.get(room);
|
||||
if (!(call instanceof ElementCall)) throw new Error("Failed to create call");
|
||||
expect(call.session).toBe(roomSession);
|
||||
await callConnectProcedure(call);
|
||||
expect(roomSessionEmitSpy).toHaveBeenCalledWith(
|
||||
"memberships_changed",
|
||||
[],
|
||||
[{ sender: "@alice:example.org" }],
|
||||
);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
call.destroy();
|
||||
});
|
||||
|
||||
it("handles remote disconnection and reconnect right after", async () => {
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
await callConnectProcedure(call);
|
||||
await connect(call, messaging);
|
||||
expect(call.connectionState).toBe(ConnectionState.Connected);
|
||||
|
||||
messaging.emit(`action:${ElementWidgetActions.HangupCall}`, new CustomEvent("widgetapirequest", {}));
|
||||
messaging.emit(`action:${ElementWidgetActions.Close}`, new CustomEvent("widgetapirequest", {}));
|
||||
// We should now be able to reconnect without manually starting the widget
|
||||
expect(call.connectionState).toBe(ConnectionState.Disconnected);
|
||||
await callConnectProcedure(call, false);
|
||||
await connect(call, messaging, false);
|
||||
await waitFor(() => expect(call.connectionState).toBe(ConnectionState.Connected), { interval: 5 });
|
||||
});
|
||||
});
|
||||
|
||||
@ -28,6 +28,7 @@ import "../../../../../src/stores/room-list/RoomListStore"; // must be imported
|
||||
import { Algorithm } from "../../../../../src/stores/room-list/algorithms/Algorithm";
|
||||
import { CallStore } from "../../../../../src/stores/CallStore";
|
||||
import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMessagingStore";
|
||||
import { ConnectionState } from "../../../../../src/models/Call";
|
||||
|
||||
describe("Algorithm", () => {
|
||||
useMockedCalls();
|
||||
@ -83,7 +84,7 @@ describe("Algorithm", () => {
|
||||
|
||||
MockedCall.create(roomWithCall, "1");
|
||||
const call = CallStore.instance.getCall(roomWithCall.roomId);
|
||||
if (call === null) throw new Error("Failed to create call");
|
||||
if (!(call instanceof MockedCall)) throw new Error("Failed to create call");
|
||||
|
||||
const widget = new Widget(call.widget);
|
||||
WidgetMessagingStore.instance.storeMessaging(widget, roomWithCall.roomId, {
|
||||
@ -93,7 +94,7 @@ describe("Algorithm", () => {
|
||||
// End of setup
|
||||
|
||||
expect(algorithm.getOrderedRooms()[DefaultTagID.Untagged]).toEqual([room, roomWithCall]);
|
||||
await call.start();
|
||||
call.setConnectionState(ConnectionState.Connected);
|
||||
expect(algorithm.getOrderedRooms()[DefaultTagID.Untagged]).toEqual([roomWithCall, room]);
|
||||
await call.disconnect();
|
||||
expect(algorithm.getOrderedRooms()[DefaultTagID.Untagged]).toEqual([room, roomWithCall]);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user