Files
element-call-Github/src/utils/test.ts
Robin 016ba676dd Test CallViewModel
This adds tests for a couple of the less trivial bits of code in CallViewModel. Testing them helped me uncover why focus switches still weren't being smooth! (It was because I was using RxJS's sample operator when I really wanted withLatestFrom.)
2024-09-12 15:53:13 -04:00

201 lines
4.5 KiB
TypeScript

/*
Copyright 2023, 2024 New Vector Ltd.
SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/
import { map } from "rxjs";
import { RunHelpers, TestScheduler } from "rxjs/testing";
import { expect, vi } from "vitest";
import { RoomMember, Room as MatrixRoom } from "matrix-js-sdk/src/matrix";
import {
LocalParticipant,
LocalTrackPublication,
RemoteParticipant,
RemoteTrackPublication,
Room as LivekitRoom,
} from "livekit-client";
import {
LocalUserMediaViewModel,
RemoteUserMediaViewModel,
} from "../state/MediaViewModel";
export function withFakeTimers(continuation: () => void): void {
vi.useFakeTimers();
try {
continuation();
} finally {
vi.useRealTimers();
}
}
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;
}
/**
* Run Observables with a scheduler that virtualizes time, for testing purposes.
*/
export function withTestScheduler(
continuation: (helpers: OurRunHelpers) => void,
): void {
new TestScheduler((actual, expected) => {
expect(actual).deep.equals(expected);
}).run((helpers) =>
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);
},
}),
);
}
export function mockMember(member: Partial<RoomMember>): RoomMember {
return {
on() {
return this;
},
off() {
return this;
},
addListener() {
return this;
},
removeListener() {
return this;
},
...member,
} as RoomMember;
}
export function mockMatrixRoom(room: Partial<MatrixRoom>): MatrixRoom {
return {
on() {
return this as MatrixRoom;
},
off() {
return this as MatrixRoom;
},
addEventListener() {
return this as MatrixRoom;
},
removeEventListener() {
return this as MatrixRoom;
},
...room,
} as Partial<MatrixRoom> as MatrixRoom;
}
export function mockLivekitRoom(room: Partial<LivekitRoom>): LivekitRoom {
return {
on() {
return this as LivekitRoom;
},
off() {
return this as LivekitRoom;
},
addEventListener() {
return this as LivekitRoom;
},
removeEventListener() {
return this as LivekitRoom;
},
...room,
} as Partial<LivekitRoom> as LivekitRoom;
}
export function mockLocalParticipant(
participant: Partial<LocalParticipant>,
): LocalParticipant {
return {
isLocal: true,
getTrackPublication: () =>
({}) as Partial<LocalTrackPublication> as LocalTrackPublication,
on() {
return this as LocalParticipant;
},
off() {
return this as LocalParticipant;
},
addListener() {
return this as LocalParticipant;
},
removeListener() {
return this as LocalParticipant;
},
...participant,
} as Partial<LocalParticipant> as LocalParticipant;
}
export async function withLocalMedia(
member: Partial<RoomMember>,
continuation: (vm: LocalUserMediaViewModel) => void | Promise<void>,
): Promise<void> {
const vm = new LocalUserMediaViewModel(
"local",
mockMember(member),
mockLocalParticipant({}),
true,
);
try {
await continuation(vm);
} finally {
vm.destroy();
}
}
export function mockRemoteParticipant(
participant: Partial<RemoteParticipant>,
): RemoteParticipant {
return {
isLocal: false,
setVolume() {},
getTrackPublication: () =>
({}) as Partial<RemoteTrackPublication> as RemoteTrackPublication,
on() {
return this;
},
off() {
return this;
},
addListener() {
return this;
},
removeListener() {
return this;
},
...participant,
} as RemoteParticipant;
}
export async function withRemoteMedia(
member: Partial<RoomMember>,
participant: Partial<RemoteParticipant>,
continuation: (vm: RemoteUserMediaViewModel) => void | Promise<void>,
): Promise<void> {
const vm = new RemoteUserMediaViewModel(
"remote",
mockMember(member),
mockRemoteParticipant(participant),
true,
);
try {
await continuation(vm);
} finally {
vm.destroy();
}
}