diff --git a/src/state/CallViewModel/CallViewModel.test.ts b/src/state/CallViewModel/CallViewModel.test.ts index 1fecd112..93e1d991 100644 --- a/src/state/CallViewModel/CallViewModel.test.ts +++ b/src/state/CallViewModel/CallViewModel.test.ts @@ -105,15 +105,7 @@ const dave = mockMatrixRoomMember(daveRtcMember, { rawDisplayName: "Dave" }); const daveId = `${dave.userId}:${daveRtcMember.deviceId}`; const localParticipant = mockLocalParticipant({ identity: "" }); -const aliceSharingScreen = mockRemoteParticipant({ - identity: aliceId, - isScreenShareEnabled: true, -}); const bobParticipant = mockRemoteParticipant({ identity: bobId }); -const bobSharingScreen = mockRemoteParticipant({ - identity: bobId, - isScreenShareEnabled: true, -}); const daveParticipant = mockRemoteParticipant({ identity: daveId }); export interface GridLayoutSummary { @@ -281,7 +273,8 @@ describe.each([ withTestScheduler(({ behavior, schedule, expectObservable }) => { // 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 - 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 // switch back to grid at the end const modeInputMarbles = " -----s--g"; @@ -292,13 +285,12 @@ describe.each([ const expectedShowSpeakingMarbles = "y----nyny"; withCallViewModel( { - remoteParticipants$: behavior(participantInputMarbles, { - a: [aliceParticipant, bobParticipant], - b: [aliceSharingScreen, bobParticipant], - c: [aliceSharingScreen, bobSharingScreen], - d: [aliceParticipant, bobSharingScreen], - }), + remoteParticipants$: constant([aliceParticipant, bobParticipant]), rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]), + sharingScreen: new Map([ + [aliceParticipant, behavior(aliceSharingInputMarbles, yesNo)], + [bobParticipant, behavior(bobSharingInputMarbles, yesNo)], + ]), }, (vm) => { schedule(modeInputMarbles, { @@ -688,7 +680,7 @@ describe.each([ withCallViewModel( { remoteParticipants$: constant([ - aliceSharingScreen, + aliceParticipant, bobParticipant, daveParticipant, ]), @@ -702,6 +694,7 @@ describe.each([ [bobParticipant, behavior(bSpeakingInputMarbles, yesNo)], [daveParticipant, behavior(dSpeakingInputMarbles, yesNo)], ]), + sharingScreen: new Map([[aliceParticipant, constant(true)]]), }, (vm) => { schedule(modeInputMarbles, { @@ -856,26 +849,30 @@ describe.each([ withTestScheduler(({ behavior, expectObservable }) => { // iterate through a number of combinations of participants and MatrixRTC memberships // 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 - const expectedLayoutMarbles = "a-bc-b"; + const expectedLayoutMarbles = " a-bc-b"; withCallViewModel( { - remoteParticipants$: behavior(scenarioInputMarbles, { + remoteParticipants$: behavior(participantInputMarbles, { a: [], b: [bobParticipant], c: [aliceParticipant, bobParticipant], d: [aliceParticipant, daveParticipant, bobParticipant], - e: [aliceParticipant, daveParticipant, bobSharingScreen], }), - rtcMembers$: behavior(scenarioInputMarbles, { + rtcMembers$: behavior(participantInputMarbles, { a: [localRtcMember], b: [localRtcMember], c: [localRtcMember, aliceRtcMember], d: [localRtcMember, aliceRtcMember, daveRtcMember], e: [localRtcMember, aliceRtcMember, daveRtcMember], }), + sharingScreen: new Map([ + [bobParticipant, behavior(bobSharingInputMarbles, yesNo)], + ]), }, (vm) => { vm.setGridMode("grid"); diff --git a/src/state/CallViewModel/CallViewModelTestUtils.ts b/src/state/CallViewModel/CallViewModelTestUtils.ts index 09a43fc3..431e691d 100644 --- a/src/state/CallViewModel/CallViewModelTestUtils.ts +++ b/src/state/CallViewModel/CallViewModelTestUtils.ts @@ -15,7 +15,7 @@ import { type Room as LivekitRoom, } from "livekit-client"; 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 { ClientEvent, type RoomMember, type MatrixClient } from "matrix-js-sdk"; import EventEmitter from "events"; @@ -68,7 +68,8 @@ export interface CallViewModelInputs { rtcMembers$: Behavior[]>; roomMembers: RoomMember[]; livekitConnectionState$: Behavior; - speaking: Map>; + speaking: Map>; + sharingScreen: Map>; mediaDevices: MediaDevices; initialSyncState: SyncState; windowSize$: Behavior<{ width: number; height: number }>; @@ -94,6 +95,7 @@ export function withCallViewModel(mode: MatrixRTCMode) { ConnectionState.Connected, ), speaking = new Map(), + sharingScreen = new Map(), mediaDevices = mockMediaDevices({}), initialSyncState = SyncState.Syncing, windowSize$ = constant({ width: 1000, height: 800 }), @@ -154,13 +156,19 @@ export function withCallViewModel(mode: MatrixRTCMode) { const eventsSpy = vi .spyOn(ComponentsCore, "observeParticipantEvents") .mockImplementation((p, ...eventTypes) => { - if (eventTypes.includes(ParticipantEvent.IsSpeakingChanged)) { - return (speaking.get(p) ?? of(false)).pipe( - map((s): Participant => ({ ...p, isSpeaking: s }) as Participant), - ); - } else { - return of(p); - } + return combineLatest([ + (eventTypes.includes(ParticipantEvent.IsSpeakingChanged) && + speaking.get(p)) || + constant(false), + (eventTypes.includes(ParticipantEvent.TrackPublished) && + sharingScreen.get(p)) || + constant(false), + ]).pipe( + map( + ([isSpeaking, isScreenShareEnabled]) => + ({ ...p, isSpeaking, isScreenShareEnabled }) as Participant, + ), + ); }); const roomEventSelectorSpy = vi