2023-09-27 17:34:41 -04:00
|
|
|
/*
|
2024-09-06 10:22:13 +02:00
|
|
|
Copyright 2023, 2024 New Vector Ltd.
|
2023-09-27 17:34:41 -04:00
|
|
|
|
2024-09-06 10:22:13 +02:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
Please see LICENSE in the repository root for full details.
|
2023-09-27 17:34:41 -04:00
|
|
|
*/
|
2024-11-18 22:39:59 +00:00
|
|
|
import { map, Observable, of, SchedulerLike } from "rxjs";
|
2024-08-30 19:09:42 -04:00
|
|
|
import { RunHelpers, TestScheduler } from "rxjs/testing";
|
|
|
|
|
import { expect, vi } from "vitest";
|
2024-09-11 01:03:23 -04:00
|
|
|
import { RoomMember, Room as MatrixRoom } from "matrix-js-sdk/src/matrix";
|
2024-09-10 17:35:50 -04:00
|
|
|
import {
|
|
|
|
|
LocalParticipant,
|
|
|
|
|
LocalTrackPublication,
|
|
|
|
|
RemoteParticipant,
|
|
|
|
|
RemoteTrackPublication,
|
2024-09-11 01:03:23 -04:00
|
|
|
Room as LivekitRoom,
|
2024-09-10 17:35:50 -04:00
|
|
|
} from "livekit-client";
|
2024-09-06 17:27:08 -04:00
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
LocalUserMediaViewModel,
|
|
|
|
|
RemoteUserMediaViewModel,
|
|
|
|
|
} from "../state/MediaViewModel";
|
2024-11-04 09:11:44 +00:00
|
|
|
import { E2eeType } from "../e2ee/e2eeType";
|
2023-09-27 17:34:41 -04:00
|
|
|
|
|
|
|
|
export function withFakeTimers(continuation: () => void): void {
|
2024-02-02 19:00:38 -06:00
|
|
|
vi.useFakeTimers();
|
2023-09-27 17:34:41 -04:00
|
|
|
try {
|
|
|
|
|
continuation();
|
|
|
|
|
} finally {
|
2024-02-02 19:00:38 -06:00
|
|
|
vi.useRealTimers();
|
2023-09-27 17:34:41 -04:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-30 19:09:42 -04:00
|
|
|
|
|
|
|
|
export interface OurRunHelpers extends RunHelpers {
|
|
|
|
|
/**
|
|
|
|
|
* Schedules a sequence of actions to happen, as described by a marble
|
|
|
|
|
* diagram.
|
|
|
|
|
*/
|
|
|
|
|
schedule: (marbles: string, actions: Record<string, () => void>) => void;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-18 22:39:59 +00:00
|
|
|
interface TestRunnerGlobal {
|
|
|
|
|
rxjsTestScheduler?: SchedulerLike;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-30 19:09:42 -04:00
|
|
|
/**
|
|
|
|
|
* Run Observables with a scheduler that virtualizes time, for testing purposes.
|
|
|
|
|
*/
|
|
|
|
|
export function withTestScheduler(
|
|
|
|
|
continuation: (helpers: OurRunHelpers) => void,
|
|
|
|
|
): void {
|
2024-11-18 22:39:59 +00:00
|
|
|
const scheduler = new TestScheduler((actual, expected) => {
|
2024-08-30 19:09:42 -04:00
|
|
|
expect(actual).deep.equals(expected);
|
2024-11-18 22:39:59 +00:00
|
|
|
});
|
|
|
|
|
// we set the test scheduler as a global so that you can watch it in a debugger
|
|
|
|
|
// and get the frame number. e.g. `rxjsTestScheduler?.now()`
|
|
|
|
|
(global as unknown as TestRunnerGlobal).rxjsTestScheduler = scheduler;
|
|
|
|
|
scheduler.run((helpers) =>
|
2024-08-30 19:09:42 -04:00
|
|
|
continuation({
|
|
|
|
|
...helpers,
|
|
|
|
|
schedule(marbles, actions) {
|
|
|
|
|
const actionsObservable = helpers
|
|
|
|
|
.cold(marbles)
|
|
|
|
|
.pipe(map((value) => actions[value]()));
|
|
|
|
|
const results = Object.fromEntries(
|
|
|
|
|
Object.keys(actions).map((value) => [value, undefined] as const),
|
|
|
|
|
);
|
|
|
|
|
// Run the actions and verify that none of them error
|
|
|
|
|
helpers.expectObservable(actionsObservable).toBe(marbles, results);
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-09-06 17:27:08 -04:00
|
|
|
|
2024-09-18 23:05:31 -04:00
|
|
|
interface EmitterMock<T> {
|
|
|
|
|
on: () => T;
|
|
|
|
|
off: () => T;
|
|
|
|
|
addListener: () => T;
|
|
|
|
|
removeListener: () => T;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mockEmitter<T>(): EmitterMock<T> {
|
2024-09-10 17:35:50 -04:00
|
|
|
return {
|
2024-09-18 23:05:31 -04:00
|
|
|
on(): T {
|
|
|
|
|
return this as T;
|
2024-09-10 17:35:50 -04:00
|
|
|
},
|
2024-09-18 23:05:31 -04:00
|
|
|
off(): T {
|
|
|
|
|
return this as T;
|
2024-09-10 17:35:50 -04:00
|
|
|
},
|
2024-09-18 23:05:31 -04:00
|
|
|
addListener(): T {
|
|
|
|
|
return this as T;
|
2024-09-10 17:35:50 -04:00
|
|
|
},
|
2024-09-18 23:05:31 -04:00
|
|
|
removeListener(): T {
|
|
|
|
|
return this as T;
|
2024-09-10 17:35:50 -04:00
|
|
|
},
|
2024-09-18 23:05:31 -04:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-18 23:10:30 -04:00
|
|
|
// Maybe it'd be good to move this to matrix-js-sdk? Our testing needs are
|
|
|
|
|
// rather simple, but if one util to mock a member is good enough for us, maybe
|
|
|
|
|
// it's useful for matrix-js-sdk consumers in general.
|
2024-11-21 11:02:05 +00:00
|
|
|
export function mockMatrixRoomMember(member: Partial<RoomMember>): RoomMember {
|
2024-09-18 23:05:31 -04:00
|
|
|
return { ...mockEmitter(), ...member } as RoomMember;
|
2024-09-10 17:35:50 -04:00
|
|
|
}
|
|
|
|
|
|
2024-09-11 01:03:23 -04:00
|
|
|
export function mockMatrixRoom(room: Partial<MatrixRoom>): MatrixRoom {
|
2024-09-18 23:05:31 -04:00
|
|
|
return { ...mockEmitter(), ...room } as Partial<MatrixRoom> as MatrixRoom;
|
2024-09-11 01:03:23 -04:00
|
|
|
}
|
|
|
|
|
|
2024-11-06 11:12:46 +00:00
|
|
|
export function mockLivekitRoom(
|
|
|
|
|
room: Partial<LivekitRoom>,
|
|
|
|
|
{
|
|
|
|
|
remoteParticipants,
|
|
|
|
|
}: { remoteParticipants?: Observable<RemoteParticipant[]> } = {},
|
|
|
|
|
): LivekitRoom {
|
|
|
|
|
const livekitRoom = {
|
|
|
|
|
...mockEmitter(),
|
|
|
|
|
...room,
|
|
|
|
|
} as Partial<LivekitRoom> as LivekitRoom;
|
|
|
|
|
if (remoteParticipants) {
|
|
|
|
|
livekitRoom.remoteParticipants = new Map();
|
|
|
|
|
remoteParticipants.subscribe((newRemoteParticipants) => {
|
|
|
|
|
livekitRoom.remoteParticipants.clear();
|
|
|
|
|
newRemoteParticipants.forEach((p) => {
|
|
|
|
|
livekitRoom.remoteParticipants.set(p.identity, p);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return livekitRoom;
|
2024-09-11 01:03:23 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mockLocalParticipant(
|
|
|
|
|
participant: Partial<LocalParticipant>,
|
|
|
|
|
): LocalParticipant {
|
|
|
|
|
return {
|
|
|
|
|
isLocal: true,
|
|
|
|
|
getTrackPublication: () =>
|
|
|
|
|
({}) as Partial<LocalTrackPublication> as LocalTrackPublication,
|
2024-09-18 23:05:31 -04:00
|
|
|
...mockEmitter(),
|
2024-09-11 01:03:23 -04:00
|
|
|
...participant,
|
|
|
|
|
} as Partial<LocalParticipant> as LocalParticipant;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 17:27:08 -04:00
|
|
|
export async function withLocalMedia(
|
2024-09-10 17:35:50 -04:00
|
|
|
member: Partial<RoomMember>,
|
2024-09-10 17:46:40 -04:00
|
|
|
continuation: (vm: LocalUserMediaViewModel) => void | Promise<void>,
|
2024-09-06 17:27:08 -04:00
|
|
|
): Promise<void> {
|
2024-11-06 11:12:46 +00:00
|
|
|
const localParticipant = mockLocalParticipant({});
|
2024-09-06 17:27:08 -04:00
|
|
|
const vm = new LocalUserMediaViewModel(
|
2024-09-10 17:35:50 -04:00
|
|
|
"local",
|
2024-11-21 11:02:05 +00:00
|
|
|
mockMatrixRoomMember(member),
|
2024-11-06 11:12:46 +00:00
|
|
|
localParticipant,
|
2024-11-04 09:11:44 +00:00
|
|
|
{
|
|
|
|
|
kind: E2eeType.PER_PARTICIPANT,
|
|
|
|
|
},
|
2024-11-06 11:12:46 +00:00
|
|
|
mockLivekitRoom({ localParticipant }),
|
2024-09-06 17:27:08 -04:00
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
await continuation(vm);
|
|
|
|
|
} finally {
|
|
|
|
|
vm.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-11 01:03:23 -04:00
|
|
|
export function mockRemoteParticipant(
|
|
|
|
|
participant: Partial<RemoteParticipant>,
|
|
|
|
|
): RemoteParticipant {
|
|
|
|
|
return {
|
|
|
|
|
isLocal: false,
|
|
|
|
|
setVolume() {},
|
|
|
|
|
getTrackPublication: () =>
|
|
|
|
|
({}) as Partial<RemoteTrackPublication> as RemoteTrackPublication,
|
2024-09-18 23:05:31 -04:00
|
|
|
...mockEmitter(),
|
2024-09-11 01:03:23 -04:00
|
|
|
...participant,
|
|
|
|
|
} as RemoteParticipant;
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-06 17:27:08 -04:00
|
|
|
export async function withRemoteMedia(
|
|
|
|
|
member: Partial<RoomMember>,
|
|
|
|
|
participant: Partial<RemoteParticipant>,
|
2024-09-10 17:46:40 -04:00
|
|
|
continuation: (vm: RemoteUserMediaViewModel) => void | Promise<void>,
|
2024-09-06 17:27:08 -04:00
|
|
|
): Promise<void> {
|
2024-11-06 11:12:46 +00:00
|
|
|
const remoteParticipant = mockRemoteParticipant(participant);
|
2024-09-06 17:27:08 -04:00
|
|
|
const vm = new RemoteUserMediaViewModel(
|
2024-09-10 17:35:50 -04:00
|
|
|
"remote",
|
2024-11-21 11:02:05 +00:00
|
|
|
mockMatrixRoomMember(member),
|
2024-11-06 11:12:46 +00:00
|
|
|
remoteParticipant,
|
2024-11-04 09:11:44 +00:00
|
|
|
{
|
|
|
|
|
kind: E2eeType.PER_PARTICIPANT,
|
|
|
|
|
},
|
2024-11-06 11:12:46 +00:00
|
|
|
mockLivekitRoom({}, { remoteParticipants: of([remoteParticipant]) }),
|
2024-09-06 17:27:08 -04:00
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
await continuation(vm);
|
|
|
|
|
} finally {
|
|
|
|
|
vm.destroy();
|
|
|
|
|
}
|
|
|
|
|
}
|