mirror of
https://github.com/vector-im/element-call.git
synced 2026-03-28 06:50:26 +00:00
prettier !
This commit is contained in:
@@ -5,21 +5,37 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { afterEach, describe, expect, it, type Mock, type MockedObject, vi } from "vitest";
|
||||
import {
|
||||
afterEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
type Mock,
|
||||
type MockedObject,
|
||||
vi,
|
||||
} from "vitest";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
import {
|
||||
ConnectionState,
|
||||
type LocalParticipant,
|
||||
type RemoteParticipant,
|
||||
type Room as LivekitRoom,
|
||||
RoomEvent, type RoomOptions
|
||||
RoomEvent,
|
||||
type RoomOptions,
|
||||
} from "livekit-client";
|
||||
import fetchMock from "fetch-mock";
|
||||
import EventEmitter from "events";
|
||||
import { type IOpenIDToken } from "matrix-js-sdk";
|
||||
|
||||
import type { CallMembership, LivekitTransport } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { type ConnectionOpts, type FocusConnectionState, RemoteConnection } from "./Connection.ts";
|
||||
import type {
|
||||
CallMembership,
|
||||
LivekitTransport,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import {
|
||||
type ConnectionOpts,
|
||||
type FocusConnectionState,
|
||||
RemoteConnection,
|
||||
} from "./Connection.ts";
|
||||
import { ObservableScope } from "./ObservableScope.ts";
|
||||
import { type OpenIDClientParts } from "../livekit/openIDSFU.ts";
|
||||
import { FailToGetOpenIdToken } from "../utils/errors.ts";
|
||||
@@ -38,28 +54,30 @@ let localParticipantEventEmiter: EventEmitter;
|
||||
let fakeLocalParticipant: MockedObject<LocalParticipant>;
|
||||
|
||||
let fakeRoomEventEmiter: EventEmitter;
|
||||
let fakeMembershipsFocusMap$: BehaviorSubject<{ membership: CallMembership; transport: LivekitTransport }[]>;
|
||||
let fakeMembershipsFocusMap$: BehaviorSubject<
|
||||
{ membership: CallMembership; transport: LivekitTransport }[]
|
||||
>;
|
||||
|
||||
const livekitFocus: LivekitTransport = {
|
||||
livekit_alias: "!roomID:example.org",
|
||||
livekit_service_url: "https://matrix-rtc.example.org/livekit/jwt",
|
||||
type: "livekit"
|
||||
type: "livekit",
|
||||
};
|
||||
|
||||
function setupTest(): void {
|
||||
testScope = new ObservableScope();
|
||||
client = vi.mocked<OpenIDClientParts>({
|
||||
getOpenIdToken: vi.fn().mockResolvedValue(
|
||||
{
|
||||
"access_token": "rYsmGUEwNjKgJYyeNUkZseJN",
|
||||
"token_type": "Bearer",
|
||||
"matrix_server_name": "example.org",
|
||||
"expires_in": 3600
|
||||
}
|
||||
),
|
||||
getDeviceId: vi.fn().mockReturnValue("ABCDEF")
|
||||
getOpenIdToken: vi.fn().mockResolvedValue({
|
||||
access_token: "rYsmGUEwNjKgJYyeNUkZseJN",
|
||||
token_type: "Bearer",
|
||||
matrix_server_name: "example.org",
|
||||
expires_in: 3600,
|
||||
}),
|
||||
getDeviceId: vi.fn().mockReturnValue("ABCDEF"),
|
||||
} as unknown as OpenIDClientParts);
|
||||
fakeMembershipsFocusMap$ = new BehaviorSubject<{ membership: CallMembership; transport: LivekitTransport }[]>([]);
|
||||
fakeMembershipsFocusMap$ = new BehaviorSubject<
|
||||
{ membership: CallMembership; transport: LivekitTransport }[]
|
||||
>([]);
|
||||
|
||||
localParticipantEventEmiter = new EventEmitter();
|
||||
|
||||
@@ -69,9 +87,15 @@ function setupTest(): void {
|
||||
getTrackPublication: vi.fn().mockReturnValue(undefined),
|
||||
on: localParticipantEventEmiter.on.bind(localParticipantEventEmiter),
|
||||
off: localParticipantEventEmiter.off.bind(localParticipantEventEmiter),
|
||||
addListener: localParticipantEventEmiter.addListener.bind(localParticipantEventEmiter),
|
||||
removeListener: localParticipantEventEmiter.removeListener.bind(localParticipantEventEmiter),
|
||||
removeAllListeners: localParticipantEventEmiter.removeAllListeners.bind(localParticipantEventEmiter)
|
||||
addListener: localParticipantEventEmiter.addListener.bind(
|
||||
localParticipantEventEmiter,
|
||||
),
|
||||
removeListener: localParticipantEventEmiter.removeListener.bind(
|
||||
localParticipantEventEmiter,
|
||||
),
|
||||
removeAllListeners: localParticipantEventEmiter.removeAllListeners.bind(
|
||||
localParticipantEventEmiter,
|
||||
),
|
||||
} as unknown as LocalParticipant);
|
||||
fakeRoomEventEmiter = new EventEmitter();
|
||||
|
||||
@@ -84,56 +108,45 @@ function setupTest(): void {
|
||||
on: fakeRoomEventEmiter.on.bind(fakeRoomEventEmiter),
|
||||
off: fakeRoomEventEmiter.off.bind(fakeRoomEventEmiter),
|
||||
addListener: fakeRoomEventEmiter.addListener.bind(fakeRoomEventEmiter),
|
||||
removeListener: fakeRoomEventEmiter.removeListener.bind(fakeRoomEventEmiter),
|
||||
removeAllListeners: fakeRoomEventEmiter.removeAllListeners.bind(fakeRoomEventEmiter),
|
||||
setE2EEEnabled: vi.fn().mockResolvedValue(undefined)
|
||||
removeListener:
|
||||
fakeRoomEventEmiter.removeListener.bind(fakeRoomEventEmiter),
|
||||
removeAllListeners:
|
||||
fakeRoomEventEmiter.removeAllListeners.bind(fakeRoomEventEmiter),
|
||||
setE2EEEnabled: vi.fn().mockResolvedValue(undefined),
|
||||
} as unknown as LivekitRoom);
|
||||
|
||||
}
|
||||
|
||||
function setupRemoteConnection(): RemoteConnection {
|
||||
|
||||
const opts: ConnectionOpts = {
|
||||
client: client,
|
||||
transport: livekitFocus,
|
||||
remoteTransports$: fakeMembershipsFocusMap$,
|
||||
scope: testScope,
|
||||
livekitRoomFactory: () => fakeLivekitRoom
|
||||
livekitRoomFactory: () => fakeLivekitRoom,
|
||||
};
|
||||
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`,
|
||||
() => {
|
||||
return {
|
||||
status: 200,
|
||||
body:
|
||||
{
|
||||
"url": "wss://matrix-rtc.m.localhost/livekit/sfu",
|
||||
"jwt": "ATOKEN"
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`, () => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
url: "wss://matrix-rtc.m.localhost/livekit/sfu",
|
||||
jwt: "ATOKEN",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
fakeLivekitRoom
|
||||
.connect
|
||||
.mockResolvedValue(undefined);
|
||||
fakeLivekitRoom.connect.mockResolvedValue(undefined);
|
||||
|
||||
return new RemoteConnection(
|
||||
opts,
|
||||
undefined
|
||||
);
|
||||
return new RemoteConnection(opts, undefined);
|
||||
}
|
||||
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.clearAllMocks();
|
||||
fetchMock.reset();
|
||||
});
|
||||
|
||||
|
||||
describe("Start connection states", () => {
|
||||
|
||||
it("start in initialized state", () => {
|
||||
setupTest();
|
||||
|
||||
@@ -142,15 +155,13 @@ describe("Start connection states", () => {
|
||||
transport: livekitFocus,
|
||||
remoteTransports$: fakeMembershipsFocusMap$,
|
||||
scope: testScope,
|
||||
livekitRoomFactory: () => fakeLivekitRoom
|
||||
livekitRoomFactory: () => fakeLivekitRoom,
|
||||
};
|
||||
const connection = new RemoteConnection(
|
||||
opts,
|
||||
undefined
|
||||
);
|
||||
const connection = new RemoteConnection(opts, undefined);
|
||||
|
||||
expect(connection.focusedConnectionState$.getValue().state)
|
||||
.toEqual("Initialized");
|
||||
expect(connection.focusedConnectionState$.getValue().state).toEqual(
|
||||
"Initialized",
|
||||
);
|
||||
});
|
||||
|
||||
it("fail to getOpenId token then error state", async () => {
|
||||
@@ -162,31 +173,27 @@ describe("Start connection states", () => {
|
||||
transport: livekitFocus,
|
||||
remoteTransports$: fakeMembershipsFocusMap$,
|
||||
scope: testScope,
|
||||
livekitRoomFactory: () => fakeLivekitRoom
|
||||
livekitRoomFactory: () => fakeLivekitRoom,
|
||||
};
|
||||
|
||||
|
||||
const connection = new RemoteConnection(
|
||||
opts,
|
||||
undefined
|
||||
);
|
||||
const connection = new RemoteConnection(opts, undefined);
|
||||
|
||||
const capturedStates: FocusConnectionState[] = [];
|
||||
connection.focusedConnectionState$.subscribe((value) => {
|
||||
capturedStates.push(value);
|
||||
});
|
||||
|
||||
|
||||
const deferred = Promise.withResolvers<IOpenIDToken>();
|
||||
|
||||
client.getOpenIdToken.mockImplementation(async (): Promise<IOpenIDToken> => {
|
||||
return await deferred.promise;
|
||||
});
|
||||
client.getOpenIdToken.mockImplementation(
|
||||
async (): Promise<IOpenIDToken> => {
|
||||
return await deferred.promise;
|
||||
},
|
||||
);
|
||||
|
||||
connection.start()
|
||||
.catch(() => {
|
||||
// expected to throw
|
||||
});
|
||||
connection.start().catch(() => {
|
||||
// expected to throw
|
||||
});
|
||||
|
||||
let capturedState = capturedStates.pop();
|
||||
expect(capturedState).toBeDefined();
|
||||
@@ -199,11 +206,14 @@ describe("Start connection states", () => {
|
||||
capturedState = capturedStates.pop();
|
||||
if (capturedState!.state === "FailedToStart") {
|
||||
expect(capturedState!.error.message).toEqual("Something went wrong");
|
||||
expect(capturedState!.focus.livekit_alias).toEqual(livekitFocus.livekit_alias);
|
||||
expect(capturedState!.focus.livekit_alias).toEqual(
|
||||
livekitFocus.livekit_alias,
|
||||
);
|
||||
} else {
|
||||
expect.fail("Expected FailedToStart state but got " + capturedState?.state);
|
||||
expect.fail(
|
||||
"Expected FailedToStart state but got " + capturedState?.state,
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it("fail to get JWT token and error state", async () => {
|
||||
@@ -215,13 +225,10 @@ describe("Start connection states", () => {
|
||||
transport: livekitFocus,
|
||||
remoteTransports$: fakeMembershipsFocusMap$,
|
||||
scope: testScope,
|
||||
livekitRoomFactory: () => fakeLivekitRoom
|
||||
livekitRoomFactory: () => fakeLivekitRoom,
|
||||
};
|
||||
|
||||
const connection = new RemoteConnection(
|
||||
opts,
|
||||
undefined
|
||||
);
|
||||
const connection = new RemoteConnection(opts, undefined);
|
||||
|
||||
const capturedStates: FocusConnectionState[] = [];
|
||||
connection.focusedConnectionState$.subscribe((value) => {
|
||||
@@ -230,21 +237,17 @@ describe("Start connection states", () => {
|
||||
|
||||
const deferredSFU = Promise.withResolvers<void>();
|
||||
// mock the /sfu/get call
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`,
|
||||
async () => {
|
||||
await deferredSFU.promise;
|
||||
return {
|
||||
status: 500,
|
||||
body: "Internal Server Error"
|
||||
};
|
||||
}
|
||||
);
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`, async () => {
|
||||
await deferredSFU.promise;
|
||||
return {
|
||||
status: 500,
|
||||
body: "Internal Server Error",
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
connection.start()
|
||||
.catch(() => {
|
||||
// expected to throw
|
||||
});
|
||||
connection.start().catch(() => {
|
||||
// expected to throw
|
||||
});
|
||||
|
||||
let capturedState = capturedStates.pop();
|
||||
expect(capturedState).toBeDefined();
|
||||
@@ -256,15 +259,19 @@ describe("Start connection states", () => {
|
||||
capturedState = capturedStates.pop();
|
||||
|
||||
if (capturedState?.state === "FailedToStart") {
|
||||
expect(capturedState?.error.message).toContain("SFU Config fetch failed with exception Error");
|
||||
expect(capturedState?.focus.livekit_alias).toEqual(livekitFocus.livekit_alias);
|
||||
expect(capturedState?.error.message).toContain(
|
||||
"SFU Config fetch failed with exception Error",
|
||||
);
|
||||
expect(capturedState?.focus.livekit_alias).toEqual(
|
||||
livekitFocus.livekit_alias,
|
||||
);
|
||||
} else {
|
||||
expect.fail("Expected FailedToStart state but got " + capturedState?.state);
|
||||
expect.fail(
|
||||
"Expected FailedToStart state but got " + capturedState?.state,
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
it("fail to connect to livekit error state", async () => {
|
||||
setupTest();
|
||||
vi.useFakeTimers();
|
||||
@@ -274,46 +281,36 @@ describe("Start connection states", () => {
|
||||
transport: livekitFocus,
|
||||
remoteTransports$: fakeMembershipsFocusMap$,
|
||||
scope: testScope,
|
||||
livekitRoomFactory: () => fakeLivekitRoom
|
||||
livekitRoomFactory: () => fakeLivekitRoom,
|
||||
};
|
||||
|
||||
const connection = new RemoteConnection(
|
||||
opts,
|
||||
undefined
|
||||
);
|
||||
const connection = new RemoteConnection(opts, undefined);
|
||||
|
||||
const capturedStates: FocusConnectionState[] = [];
|
||||
connection.focusedConnectionState$.subscribe((value) => {
|
||||
capturedStates.push(value);
|
||||
});
|
||||
|
||||
|
||||
const deferredSFU = Promise.withResolvers<void>();
|
||||
// mock the /sfu/get call
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`,
|
||||
() => {
|
||||
return {
|
||||
status: 200,
|
||||
body:
|
||||
{
|
||||
"url": "wss://matrix-rtc.m.localhost/livekit/sfu",
|
||||
"jwt": "ATOKEN"
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
fetchMock.post(`${livekitFocus.livekit_service_url}/sfu/get`, () => {
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
url: "wss://matrix-rtc.m.localhost/livekit/sfu",
|
||||
jwt: "ATOKEN",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
fakeLivekitRoom
|
||||
.connect
|
||||
.mockImplementation(async () => {
|
||||
await deferredSFU.promise;
|
||||
throw new Error("Failed to connect to livekit");
|
||||
});
|
||||
fakeLivekitRoom.connect.mockImplementation(async () => {
|
||||
await deferredSFU.promise;
|
||||
throw new Error("Failed to connect to livekit");
|
||||
});
|
||||
|
||||
connection.start()
|
||||
.catch(() => {
|
||||
// expected to throw
|
||||
});
|
||||
connection.start().catch(() => {
|
||||
// expected to throw
|
||||
});
|
||||
|
||||
let capturedState = capturedStates.pop();
|
||||
expect(capturedState).toBeDefined();
|
||||
@@ -326,12 +323,17 @@ describe("Start connection states", () => {
|
||||
capturedState = capturedStates.pop();
|
||||
|
||||
if (capturedState && capturedState?.state === "FailedToStart") {
|
||||
expect(capturedState.error.message).toContain("Failed to connect to livekit");
|
||||
expect(capturedState.focus.livekit_alias).toEqual(livekitFocus.livekit_alias);
|
||||
expect(capturedState.error.message).toContain(
|
||||
"Failed to connect to livekit",
|
||||
);
|
||||
expect(capturedState.focus.livekit_alias).toEqual(
|
||||
livekitFocus.livekit_alias,
|
||||
);
|
||||
} else {
|
||||
expect.fail("Expected FailedToStart state but got " + JSON.stringify(capturedState));
|
||||
expect.fail(
|
||||
"Expected FailedToStart state but got " + JSON.stringify(capturedState),
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
it("connection states happy path", async () => {
|
||||
@@ -356,7 +358,6 @@ describe("Start connection states", () => {
|
||||
expect(connectingState?.state).toEqual("ConnectingToLkRoom");
|
||||
const connectedState = capturedState.shift();
|
||||
expect(connectedState?.state).toEqual("ConnectedToLkRoom");
|
||||
|
||||
});
|
||||
|
||||
it("should relay livekit events once connected", async () => {
|
||||
@@ -378,7 +379,7 @@ describe("Start connection states", () => {
|
||||
ConnectionState.SignalReconnecting,
|
||||
ConnectionState.Connecting,
|
||||
ConnectionState.Connected,
|
||||
ConnectionState.Reconnecting
|
||||
ConnectionState.Reconnecting,
|
||||
];
|
||||
for (const state of states) {
|
||||
fakeRoomEventEmiter.emit(RoomEvent.ConnectionStateChanged, state);
|
||||
@@ -387,12 +388,18 @@ describe("Start connection states", () => {
|
||||
for (const state of states) {
|
||||
const s = capturedState.shift();
|
||||
expect(s?.state).toEqual("ConnectedToLkRoom");
|
||||
const connectedState = s as FocusConnectionState & { state: "ConnectedToLkRoom" };
|
||||
const connectedState = s as FocusConnectionState & {
|
||||
state: "ConnectedToLkRoom";
|
||||
};
|
||||
expect(connectedState.connectionState).toEqual(state);
|
||||
|
||||
// should always have the focus info
|
||||
expect(connectedState.focus.livekit_alias).toEqual(livekitFocus.livekit_alias);
|
||||
expect(connectedState.focus.livekit_service_url).toEqual(livekitFocus.livekit_service_url);
|
||||
expect(connectedState.focus.livekit_alias).toEqual(
|
||||
livekitFocus.livekit_alias,
|
||||
);
|
||||
expect(connectedState.focus.livekit_service_url).toEqual(
|
||||
livekitFocus.livekit_service_url,
|
||||
);
|
||||
}
|
||||
|
||||
// If the state is not ConnectedToLkRoom, no events should be relayed anymore
|
||||
@@ -403,10 +410,8 @@ describe("Start connection states", () => {
|
||||
}
|
||||
|
||||
expect(capturedState.length).toEqual(0);
|
||||
|
||||
});
|
||||
|
||||
|
||||
it("shutting down the scope should stop the connection", async () => {
|
||||
setupTest();
|
||||
vi.useFakeTimers();
|
||||
@@ -423,7 +428,6 @@ describe("Start connection states", () => {
|
||||
const stopSpy = vi.spyOn(connection, "stop");
|
||||
testScope.end();
|
||||
|
||||
|
||||
expect(stopSpy).toHaveBeenCalled();
|
||||
expect(fakeLivekitRoom.disconnect).toHaveBeenCalled();
|
||||
|
||||
@@ -437,26 +441,22 @@ describe("Start connection states", () => {
|
||||
|
||||
expect(capturedState.length).toEqual(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function fakeRemoteLivekitParticipant(id: string): RemoteParticipant {
|
||||
return vi.mocked<RemoteParticipant>({
|
||||
identity: id
|
||||
identity: id,
|
||||
} as unknown as RemoteParticipant);
|
||||
}
|
||||
|
||||
function fakeRtcMemberShip(userId: string, deviceId: string): CallMembership {
|
||||
return vi.mocked<CallMembership>({
|
||||
sender: userId,
|
||||
deviceId: deviceId
|
||||
deviceId: deviceId,
|
||||
} as unknown as CallMembership);
|
||||
}
|
||||
|
||||
describe("Publishing participants observations", () => {
|
||||
|
||||
|
||||
it("should emit the list of publishing participants", async () => {
|
||||
setupTest();
|
||||
|
||||
@@ -464,13 +464,24 @@ describe("Publishing participants observations", () => {
|
||||
|
||||
const bobIsAPublisher = Promise.withResolvers<void>();
|
||||
const danIsAPublisher = Promise.withResolvers<void>();
|
||||
const observedPublishers: { participant: RemoteParticipant; membership: CallMembership }[][] = [];
|
||||
const observedPublishers: {
|
||||
participant: RemoteParticipant;
|
||||
membership: CallMembership;
|
||||
}[][] = [];
|
||||
connection.publishingParticipants$.subscribe((publishers) => {
|
||||
observedPublishers.push(publishers);
|
||||
if (publishers.some((p) => p.participant.identity === "@bob:example.org:DEV111")) {
|
||||
if (
|
||||
publishers.some(
|
||||
(p) => p.participant.identity === "@bob:example.org:DEV111",
|
||||
)
|
||||
) {
|
||||
bobIsAPublisher.resolve();
|
||||
}
|
||||
if (publishers.some((p) => p.participant.identity === "@dan:example.org:DEV333")) {
|
||||
if (
|
||||
publishers.some(
|
||||
(p) => p.participant.identity === "@dan:example.org:DEV333",
|
||||
)
|
||||
) {
|
||||
danIsAPublisher.resolve();
|
||||
}
|
||||
});
|
||||
@@ -482,14 +493,13 @@ describe("Publishing participants observations", () => {
|
||||
fakeRemoteLivekitParticipant("@alice:example.org:DEV000"),
|
||||
fakeRemoteLivekitParticipant("@bob:example.org:DEV111"),
|
||||
fakeRemoteLivekitParticipant("@carol:example.org:DEV222"),
|
||||
fakeRemoteLivekitParticipant("@dan:example.org:DEV333")
|
||||
fakeRemoteLivekitParticipant("@dan:example.org:DEV333"),
|
||||
];
|
||||
|
||||
// Let's simulate 3 members on the livekitRoom
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get")
|
||||
.mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p]))
|
||||
);
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get").mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p])),
|
||||
);
|
||||
|
||||
for (const participant of participants) {
|
||||
fakeRoomEventEmiter.emit(RoomEvent.ParticipantConnected, participant);
|
||||
@@ -498,20 +508,27 @@ describe("Publishing participants observations", () => {
|
||||
// At this point there should be no publishers
|
||||
expect(observedPublishers.pop()!.length).toEqual(0);
|
||||
|
||||
|
||||
const otherFocus: LivekitTransport = {
|
||||
livekit_alias: "!roomID:example.org",
|
||||
livekit_service_url: "https://other-matrix-rtc.example.org/livekit/jwt",
|
||||
type: "livekit"
|
||||
type: "livekit",
|
||||
};
|
||||
|
||||
|
||||
const rtcMemberships = [
|
||||
// Say bob is on the same focus
|
||||
{ membership: fakeRtcMemberShip("@bob:example.org", "DEV111"), transport: livekitFocus },
|
||||
{
|
||||
membership: fakeRtcMemberShip("@bob:example.org", "DEV111"),
|
||||
transport: livekitFocus,
|
||||
},
|
||||
// Alice and carol is on a different focus
|
||||
{ membership: fakeRtcMemberShip("@alice:example.org", "DEV000"), transport: otherFocus },
|
||||
{ membership: fakeRtcMemberShip("@carol:example.org", "DEV222"), transport: otherFocus }
|
||||
{
|
||||
membership: fakeRtcMemberShip("@alice:example.org", "DEV000"),
|
||||
transport: otherFocus,
|
||||
},
|
||||
{
|
||||
membership: fakeRtcMemberShip("@carol:example.org", "DEV222"),
|
||||
transport: otherFocus,
|
||||
},
|
||||
// NO DAVE YET
|
||||
];
|
||||
// signal this change in rtc memberships
|
||||
@@ -521,53 +538,74 @@ describe("Publishing participants observations", () => {
|
||||
await bobIsAPublisher.promise;
|
||||
const publishers = observedPublishers.pop();
|
||||
expect(publishers?.length).toEqual(1);
|
||||
expect(publishers?.[0].participant.identity).toEqual("@bob:example.org:DEV111");
|
||||
expect(publishers?.[0].participant.identity).toEqual(
|
||||
"@bob:example.org:DEV111",
|
||||
);
|
||||
|
||||
// Now let's make dan join the rtc memberships
|
||||
rtcMemberships
|
||||
.push({ membership: fakeRtcMemberShip("@dan:example.org", "DEV333"), transport: livekitFocus });
|
||||
rtcMemberships.push({
|
||||
membership: fakeRtcMemberShip("@dan:example.org", "DEV333"),
|
||||
transport: livekitFocus,
|
||||
});
|
||||
fakeMembershipsFocusMap$.next(rtcMemberships);
|
||||
|
||||
// We should have bob and dan has publishers now
|
||||
await danIsAPublisher.promise;
|
||||
const twoPublishers = observedPublishers.pop();
|
||||
expect(twoPublishers?.length).toEqual(2);
|
||||
expect(twoPublishers?.some((p) => p.participant.identity === "@bob:example.org:DEV111")).toBeTruthy();
|
||||
expect(twoPublishers?.some((p) => p.participant.identity === "@dan:example.org:DEV333")).toBeTruthy();
|
||||
expect(
|
||||
twoPublishers?.some(
|
||||
(p) => p.participant.identity === "@bob:example.org:DEV111",
|
||||
),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
twoPublishers?.some(
|
||||
(p) => p.participant.identity === "@dan:example.org:DEV333",
|
||||
),
|
||||
).toBeTruthy();
|
||||
|
||||
// Now let's make bob leave the livekit room
|
||||
participants = participants.filter((p) => p.identity !== "@bob:example.org:DEV111");
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get")
|
||||
.mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p]))
|
||||
);
|
||||
fakeRoomEventEmiter.emit(RoomEvent.ParticipantDisconnected, fakeRemoteLivekitParticipant("@bob:example.org:DEV111"));
|
||||
participants = participants.filter(
|
||||
(p) => p.identity !== "@bob:example.org:DEV111",
|
||||
);
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get").mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p])),
|
||||
);
|
||||
fakeRoomEventEmiter.emit(
|
||||
RoomEvent.ParticipantDisconnected,
|
||||
fakeRemoteLivekitParticipant("@bob:example.org:DEV111"),
|
||||
);
|
||||
|
||||
const updatedPublishers = observedPublishers.pop();
|
||||
expect(updatedPublishers?.length).toEqual(1);
|
||||
expect(updatedPublishers?.some((p) => p.participant.identity === "@dan:example.org:DEV333")).toBeTruthy();
|
||||
expect(
|
||||
updatedPublishers?.some(
|
||||
(p) => p.participant.identity === "@dan:example.org:DEV333",
|
||||
),
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
|
||||
it("should be scoped to parent scope", (): void => {
|
||||
setupTest();
|
||||
|
||||
const connection = setupRemoteConnection();
|
||||
|
||||
let observedPublishers: { participant: RemoteParticipant; membership: CallMembership }[][] = [];
|
||||
let observedPublishers: {
|
||||
participant: RemoteParticipant;
|
||||
membership: CallMembership;
|
||||
}[][] = [];
|
||||
connection.publishingParticipants$.subscribe((publishers) => {
|
||||
observedPublishers.push(publishers);
|
||||
});
|
||||
|
||||
let participants: RemoteParticipant[] = [
|
||||
fakeRemoteLivekitParticipant("@bob:example.org:DEV111")
|
||||
fakeRemoteLivekitParticipant("@bob:example.org:DEV111"),
|
||||
];
|
||||
|
||||
// Let's simulate 3 members on the livekitRoom
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get")
|
||||
.mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p]))
|
||||
);
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get").mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p])),
|
||||
);
|
||||
|
||||
for (const participant of participants) {
|
||||
fakeRoomEventEmiter.emit(RoomEvent.ParticipantConnected, participant);
|
||||
@@ -578,7 +616,10 @@ describe("Publishing participants observations", () => {
|
||||
|
||||
const rtcMemberships = [
|
||||
// Say bob is on the same focus
|
||||
{ membership: fakeRtcMemberShip("@bob:example.org", "DEV111"), transport: livekitFocus }
|
||||
{
|
||||
membership: fakeRtcMemberShip("@bob:example.org", "DEV111"),
|
||||
transport: livekitFocus,
|
||||
},
|
||||
];
|
||||
// signal this change in rtc memberships
|
||||
fakeMembershipsFocusMap$.next(rtcMemberships);
|
||||
@@ -586,27 +627,31 @@ describe("Publishing participants observations", () => {
|
||||
// We should have bob has a publisher now
|
||||
const publishers = observedPublishers.pop();
|
||||
expect(publishers?.length).toEqual(1);
|
||||
expect(publishers?.[0].participant.identity).toEqual("@bob:example.org:DEV111");
|
||||
expect(publishers?.[0].participant.identity).toEqual(
|
||||
"@bob:example.org:DEV111",
|
||||
);
|
||||
|
||||
// end the parent scope
|
||||
testScope.end();
|
||||
observedPublishers = [];
|
||||
|
||||
// SHOULD NOT emit any more publishers as the scope is ended
|
||||
participants = participants.filter((p) => p.identity !== "@bob:example.org:DEV111");
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get")
|
||||
.mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p]))
|
||||
);
|
||||
fakeRoomEventEmiter.emit(RoomEvent.ParticipantDisconnected, fakeRemoteLivekitParticipant("@bob:example.org:DEV111"));
|
||||
participants = participants.filter(
|
||||
(p) => p.identity !== "@bob:example.org:DEV111",
|
||||
);
|
||||
vi.spyOn(fakeLivekitRoom, "remoteParticipants", "get").mockReturnValue(
|
||||
new Map(participants.map((p) => [p.identity, p])),
|
||||
);
|
||||
fakeRoomEventEmiter.emit(
|
||||
RoomEvent.ParticipantDisconnected,
|
||||
fakeRemoteLivekitParticipant("@bob:example.org:DEV111"),
|
||||
);
|
||||
|
||||
expect(observedPublishers.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("PublishConnection", () => {
|
||||
|
||||
// let fakeBlurProcessor: ProcessorWrapper<BackgroundOptions>;
|
||||
let roomFactoryMock: Mock<() => LivekitRoom>;
|
||||
let muteStates: MockedObject<MuteStates>;
|
||||
@@ -616,7 +661,6 @@ describe("PublishConnection", () => {
|
||||
|
||||
roomFactoryMock = vi.fn().mockReturnValue(fakeLivekitRoom);
|
||||
|
||||
|
||||
muteStates = mockMuteStates();
|
||||
|
||||
// fakeBlurProcessor = vi.mocked<ProcessorWrapper<BackgroundOptions>>({
|
||||
@@ -626,20 +670,15 @@ describe("PublishConnection", () => {
|
||||
// getOptions: vi.fn().mockReturnValue({ strength: 0.5 }),
|
||||
// isRunning: vi.fn().mockReturnValue(false)
|
||||
// });
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
describe("Livekit room creation", () => {
|
||||
|
||||
|
||||
function createSetup(): void {
|
||||
setUpPublishConnection();
|
||||
|
||||
const fakeTrackProcessorSubject$ = new BehaviorSubject<ProcessorState>({
|
||||
supported: true,
|
||||
processor: undefined
|
||||
processor: undefined,
|
||||
});
|
||||
|
||||
const opts: ConnectionOpts = {
|
||||
@@ -647,28 +686,25 @@ describe("PublishConnection", () => {
|
||||
transport: livekitFocus,
|
||||
remoteTransports$: fakeMembershipsFocusMap$,
|
||||
scope: testScope,
|
||||
livekitRoomFactory: roomFactoryMock
|
||||
livekitRoomFactory: roomFactoryMock,
|
||||
};
|
||||
|
||||
const audioInput = {
|
||||
available$: of(new Map([["mic1", { id: "mic1" }]])),
|
||||
selected$: new BehaviorSubject({ id: "mic1" }),
|
||||
select(): void {
|
||||
}
|
||||
select(): void {},
|
||||
};
|
||||
|
||||
const videoInput = {
|
||||
available$: of(new Map([["cam1", { id: "cam1" }]])),
|
||||
selected$: new BehaviorSubject({ id: "cam1" }),
|
||||
select(): void {
|
||||
}
|
||||
select(): void {},
|
||||
};
|
||||
|
||||
const audioOutput = {
|
||||
available$: of(new Map([["speaker", { id: "speaker" }]])),
|
||||
selected$: new BehaviorSubject({ id: "speaker" }),
|
||||
select(): void {
|
||||
}
|
||||
select(): void {},
|
||||
};
|
||||
|
||||
// TODO understand what is wrong with our mocking that requires ts-expect-error
|
||||
@@ -678,7 +714,7 @@ describe("PublishConnection", () => {
|
||||
// @ts-expect-error Mocking only
|
||||
videoInput,
|
||||
// @ts-expect-error Mocking only
|
||||
audioOutput
|
||||
audioOutput,
|
||||
});
|
||||
|
||||
new PublishConnection(
|
||||
@@ -686,18 +722,17 @@ describe("PublishConnection", () => {
|
||||
fakeDevices,
|
||||
muteStates,
|
||||
undefined,
|
||||
fakeTrackProcessorSubject$
|
||||
fakeTrackProcessorSubject$,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
it("should create room with proper initial audio and video settings", () => {
|
||||
|
||||
createSetup();
|
||||
|
||||
expect(roomFactoryMock).toHaveBeenCalled();
|
||||
|
||||
const lastCallArgs = roomFactoryMock.mock.calls[roomFactoryMock.mock.calls.length - 1];
|
||||
const lastCallArgs =
|
||||
roomFactoryMock.mock.calls[roomFactoryMock.mock.calls.length - 1];
|
||||
|
||||
const roomOptions = lastCallArgs.pop() as unknown as RoomOptions;
|
||||
expect(roomOptions).toBeDefined();
|
||||
@@ -705,7 +740,6 @@ describe("PublishConnection", () => {
|
||||
expect(roomOptions!.videoCaptureDefaults?.deviceId).toEqual("cam1");
|
||||
expect(roomOptions!.audioCaptureDefaults?.deviceId).toEqual("mic1");
|
||||
expect(roomOptions!.audioOutput?.deviceId).toEqual("speaker");
|
||||
|
||||
});
|
||||
|
||||
it("respect controlledAudioDevices", () => {
|
||||
@@ -719,7 +753,6 @@ describe("PublishConnection", () => {
|
||||
// })
|
||||
// };
|
||||
// });
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user