mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-30 19:39:22 +00:00
Make screen sharing work more like speaking marbles in tests
This approach is more flexible in that it allows even the local participant to share their screen in CallViewModel tests, and more rigorous in that it ensures that application code is reacting specifically to track publications.
This commit is contained in:
@@ -105,15 +105,7 @@ const dave = mockMatrixRoomMember(daveRtcMember, { rawDisplayName: "Dave" });
|
|||||||
const daveId = `${dave.userId}:${daveRtcMember.deviceId}`;
|
const daveId = `${dave.userId}:${daveRtcMember.deviceId}`;
|
||||||
|
|
||||||
const localParticipant = mockLocalParticipant({ identity: "" });
|
const localParticipant = mockLocalParticipant({ identity: "" });
|
||||||
const aliceSharingScreen = mockRemoteParticipant({
|
|
||||||
identity: aliceId,
|
|
||||||
isScreenShareEnabled: true,
|
|
||||||
});
|
|
||||||
const bobParticipant = mockRemoteParticipant({ identity: bobId });
|
const bobParticipant = mockRemoteParticipant({ identity: bobId });
|
||||||
const bobSharingScreen = mockRemoteParticipant({
|
|
||||||
identity: bobId,
|
|
||||||
isScreenShareEnabled: true,
|
|
||||||
});
|
|
||||||
const daveParticipant = mockRemoteParticipant({ identity: daveId });
|
const daveParticipant = mockRemoteParticipant({ identity: daveId });
|
||||||
|
|
||||||
export interface GridLayoutSummary {
|
export interface GridLayoutSummary {
|
||||||
@@ -281,7 +273,8 @@ describe.each([
|
|||||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||||
// Start with no screen shares, then have Alice and Bob share their screens,
|
// Start with no screen shares, then have Alice and Bob share their screens,
|
||||||
// then return to no screen shares, then have just Alice share for a bit
|
// then return to no screen shares, then have just Alice share for a bit
|
||||||
const participantInputMarbles = " abcda-ba";
|
const aliceSharingInputMarbles = " ny-n--yn";
|
||||||
|
const bobSharingInputMarbles = " n-y-n---";
|
||||||
// While there are no screen shares, switch to spotlight manually, and then
|
// While there are no screen shares, switch to spotlight manually, and then
|
||||||
// switch back to grid at the end
|
// switch back to grid at the end
|
||||||
const modeInputMarbles = " -----s--g";
|
const modeInputMarbles = " -----s--g";
|
||||||
@@ -292,13 +285,12 @@ describe.each([
|
|||||||
const expectedShowSpeakingMarbles = "y----nyny";
|
const expectedShowSpeakingMarbles = "y----nyny";
|
||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
{
|
{
|
||||||
remoteParticipants$: behavior(participantInputMarbles, {
|
remoteParticipants$: constant([aliceParticipant, bobParticipant]),
|
||||||
a: [aliceParticipant, bobParticipant],
|
|
||||||
b: [aliceSharingScreen, bobParticipant],
|
|
||||||
c: [aliceSharingScreen, bobSharingScreen],
|
|
||||||
d: [aliceParticipant, bobSharingScreen],
|
|
||||||
}),
|
|
||||||
rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]),
|
rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]),
|
||||||
|
sharingScreen: new Map([
|
||||||
|
[aliceParticipant, behavior(aliceSharingInputMarbles, yesNo)],
|
||||||
|
[bobParticipant, behavior(bobSharingInputMarbles, yesNo)],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
(vm) => {
|
(vm) => {
|
||||||
schedule(modeInputMarbles, {
|
schedule(modeInputMarbles, {
|
||||||
@@ -688,7 +680,7 @@ describe.each([
|
|||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
{
|
{
|
||||||
remoteParticipants$: constant([
|
remoteParticipants$: constant([
|
||||||
aliceSharingScreen,
|
aliceParticipant,
|
||||||
bobParticipant,
|
bobParticipant,
|
||||||
daveParticipant,
|
daveParticipant,
|
||||||
]),
|
]),
|
||||||
@@ -702,6 +694,7 @@ describe.each([
|
|||||||
[bobParticipant, behavior(bSpeakingInputMarbles, yesNo)],
|
[bobParticipant, behavior(bSpeakingInputMarbles, yesNo)],
|
||||||
[daveParticipant, behavior(dSpeakingInputMarbles, yesNo)],
|
[daveParticipant, behavior(dSpeakingInputMarbles, yesNo)],
|
||||||
]),
|
]),
|
||||||
|
sharingScreen: new Map([[aliceParticipant, constant(true)]]),
|
||||||
},
|
},
|
||||||
(vm) => {
|
(vm) => {
|
||||||
schedule(modeInputMarbles, {
|
schedule(modeInputMarbles, {
|
||||||
@@ -856,26 +849,30 @@ describe.each([
|
|||||||
withTestScheduler(({ behavior, expectObservable }) => {
|
withTestScheduler(({ behavior, expectObservable }) => {
|
||||||
// iterate through a number of combinations of participants and MatrixRTC memberships
|
// iterate through a number of combinations of participants and MatrixRTC memberships
|
||||||
// Bob never has an MatrixRTC membership
|
// Bob never has an MatrixRTC membership
|
||||||
const scenarioInputMarbles = " abcdec";
|
const participantInputMarbles = "abcd-c";
|
||||||
|
// Bob even tries to share his screen at the end
|
||||||
|
const bobSharingInputMarbles = " n---yn";
|
||||||
// Bob should never be visible
|
// Bob should never be visible
|
||||||
const expectedLayoutMarbles = "a-bc-b";
|
const expectedLayoutMarbles = " a-bc-b";
|
||||||
|
|
||||||
withCallViewModel(
|
withCallViewModel(
|
||||||
{
|
{
|
||||||
remoteParticipants$: behavior(scenarioInputMarbles, {
|
remoteParticipants$: behavior(participantInputMarbles, {
|
||||||
a: [],
|
a: [],
|
||||||
b: [bobParticipant],
|
b: [bobParticipant],
|
||||||
c: [aliceParticipant, bobParticipant],
|
c: [aliceParticipant, bobParticipant],
|
||||||
d: [aliceParticipant, daveParticipant, bobParticipant],
|
d: [aliceParticipant, daveParticipant, bobParticipant],
|
||||||
e: [aliceParticipant, daveParticipant, bobSharingScreen],
|
|
||||||
}),
|
}),
|
||||||
rtcMembers$: behavior(scenarioInputMarbles, {
|
rtcMembers$: behavior(participantInputMarbles, {
|
||||||
a: [localRtcMember],
|
a: [localRtcMember],
|
||||||
b: [localRtcMember],
|
b: [localRtcMember],
|
||||||
c: [localRtcMember, aliceRtcMember],
|
c: [localRtcMember, aliceRtcMember],
|
||||||
d: [localRtcMember, aliceRtcMember, daveRtcMember],
|
d: [localRtcMember, aliceRtcMember, daveRtcMember],
|
||||||
e: [localRtcMember, aliceRtcMember, daveRtcMember],
|
e: [localRtcMember, aliceRtcMember, daveRtcMember],
|
||||||
}),
|
}),
|
||||||
|
sharingScreen: new Map([
|
||||||
|
[bobParticipant, behavior(bobSharingInputMarbles, yesNo)],
|
||||||
|
]),
|
||||||
},
|
},
|
||||||
(vm) => {
|
(vm) => {
|
||||||
vm.setGridMode("grid");
|
vm.setGridMode("grid");
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
type Room as LivekitRoom,
|
type Room as LivekitRoom,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { SyncState } from "matrix-js-sdk/lib/sync";
|
import { SyncState } from "matrix-js-sdk/lib/sync";
|
||||||
import { BehaviorSubject, type Observable, map, of } from "rxjs";
|
import { BehaviorSubject, combineLatest, map, of } from "rxjs";
|
||||||
import { onTestFinished, vi } from "vitest";
|
import { onTestFinished, vi } from "vitest";
|
||||||
import { ClientEvent, type RoomMember, type MatrixClient } from "matrix-js-sdk";
|
import { ClientEvent, type RoomMember, type MatrixClient } from "matrix-js-sdk";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
@@ -68,7 +68,8 @@ export interface CallViewModelInputs {
|
|||||||
rtcMembers$: Behavior<Partial<CallMembership>[]>;
|
rtcMembers$: Behavior<Partial<CallMembership>[]>;
|
||||||
roomMembers: RoomMember[];
|
roomMembers: RoomMember[];
|
||||||
livekitConnectionState$: Behavior<ConnectionState>;
|
livekitConnectionState$: Behavior<ConnectionState>;
|
||||||
speaking: Map<Participant, Observable<boolean>>;
|
speaking: Map<Participant, Behavior<boolean>>;
|
||||||
|
sharingScreen: Map<Participant, Behavior<boolean>>;
|
||||||
mediaDevices: MediaDevices;
|
mediaDevices: MediaDevices;
|
||||||
initialSyncState: SyncState;
|
initialSyncState: SyncState;
|
||||||
windowSize$: Behavior<{ width: number; height: number }>;
|
windowSize$: Behavior<{ width: number; height: number }>;
|
||||||
@@ -94,6 +95,7 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
ConnectionState.Connected,
|
ConnectionState.Connected,
|
||||||
),
|
),
|
||||||
speaking = new Map(),
|
speaking = new Map(),
|
||||||
|
sharingScreen = new Map(),
|
||||||
mediaDevices = mockMediaDevices({}),
|
mediaDevices = mockMediaDevices({}),
|
||||||
initialSyncState = SyncState.Syncing,
|
initialSyncState = SyncState.Syncing,
|
||||||
windowSize$ = constant({ width: 1000, height: 800 }),
|
windowSize$ = constant({ width: 1000, height: 800 }),
|
||||||
@@ -154,13 +156,19 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
|||||||
const eventsSpy = vi
|
const eventsSpy = vi
|
||||||
.spyOn(ComponentsCore, "observeParticipantEvents")
|
.spyOn(ComponentsCore, "observeParticipantEvents")
|
||||||
.mockImplementation((p, ...eventTypes) => {
|
.mockImplementation((p, ...eventTypes) => {
|
||||||
if (eventTypes.includes(ParticipantEvent.IsSpeakingChanged)) {
|
return combineLatest([
|
||||||
return (speaking.get(p) ?? of(false)).pipe(
|
(eventTypes.includes(ParticipantEvent.IsSpeakingChanged) &&
|
||||||
map((s): Participant => ({ ...p, isSpeaking: s }) as Participant),
|
speaking.get(p)) ||
|
||||||
);
|
constant(false),
|
||||||
} else {
|
(eventTypes.includes(ParticipantEvent.TrackPublished) &&
|
||||||
return of(p);
|
sharingScreen.get(p)) ||
|
||||||
}
|
constant(false),
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([isSpeaking, isScreenShareEnabled]) =>
|
||||||
|
({ ...p, isSpeaking, isScreenShareEnabled }) as Participant,
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const roomEventSelectorSpy = vi
|
const roomEventSelectorSpy = vi
|
||||||
|
|||||||
Reference in New Issue
Block a user