mirror of
https://github.com/vector-im/element-call.git
synced 2026-05-01 09:54:37 +00:00
Test that the local user can see their own screen share
To make this test work I had to extend the mocking of the CallViewModel tests to make a local connection object exist.
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
||||
|
||||
import { mockConfig } from "./utils/test";
|
||||
|
||||
const sentryInitSpy = vi.fn();
|
||||
const sentryInitSpy = vi.hoisted(() => vi.fn());
|
||||
|
||||
// Place the mock after the spy is defined
|
||||
vi.mock("@sentry/react", () => ({
|
||||
|
||||
@@ -36,7 +36,6 @@ import { deepCompare } from "matrix-js-sdk/lib/utils";
|
||||
|
||||
import { type Layout } from "../layout-types.ts";
|
||||
import {
|
||||
mockLocalParticipant,
|
||||
mockMatrixRoomMember,
|
||||
mockRemoteParticipant,
|
||||
withTestScheduler,
|
||||
@@ -61,7 +60,10 @@ import {
|
||||
import { MediaDevices } from "../MediaDevices.ts";
|
||||
import { getValue } from "../../utils/observable.ts";
|
||||
import { type Behavior, constant } from "../Behavior.ts";
|
||||
import { withCallViewModel as withCallViewModelInMode } from "./CallViewModelTestUtils.ts";
|
||||
import {
|
||||
localParticipant,
|
||||
withCallViewModel as withCallViewModelInMode,
|
||||
} from "./CallViewModelTestUtils.ts";
|
||||
import { MatrixRTCMode } from "../../settings/settings.ts";
|
||||
import { initializeWidget } from "../../widget.ts";
|
||||
|
||||
@@ -104,7 +106,6 @@ const dave = mockMatrixRoomMember(daveRtcMember, { rawDisplayName: "Dave" });
|
||||
|
||||
const daveId = `${dave.userId}:${daveRtcMember.deviceId}`;
|
||||
|
||||
const localParticipant = mockLocalParticipant({ identity: "" });
|
||||
const bobParticipant = mockRemoteParticipant({ identity: bobId });
|
||||
const daveParticipant = mockRemoteParticipant({ identity: daveId });
|
||||
|
||||
@@ -269,7 +270,7 @@ describe.each([
|
||||
});
|
||||
});
|
||||
|
||||
test("screen sharing activates spotlight layout", () => {
|
||||
test("remote screen sharing activates spotlight layout", () => {
|
||||
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
|
||||
@@ -350,6 +351,76 @@ describe.each([
|
||||
});
|
||||
});
|
||||
|
||||
test("local screen sharing stays in grid layout", () => {
|
||||
withTestScheduler(({ behavior, expectObservable }) => {
|
||||
// Local participant shares their screen, then stops sharing
|
||||
const sharingInputMarbles = " nyn";
|
||||
// Layout should show the screen share but stay in type: "grid"
|
||||
const expectedLayoutMarbles = "aba";
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: constant([aliceParticipant, bobParticipant]),
|
||||
rtcMembers$: constant([localRtcMember, aliceRtcMember, bobRtcMember]),
|
||||
sharingScreen: new Map([
|
||||
[localParticipant, behavior(sharingInputMarbles, yesNo)],
|
||||
]),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(summarizeLayout$(vm.layout$)).toBe(
|
||||
expectedLayoutMarbles,
|
||||
{
|
||||
a: {
|
||||
type: "grid",
|
||||
spotlight: undefined,
|
||||
grid: [`${localId}:0`, `${aliceId}:0`, `${bobId}:0`],
|
||||
},
|
||||
b: {
|
||||
type: "grid",
|
||||
spotlight: [`${localId}:0:screen-share`],
|
||||
grid: [`${localId}:0`, `${aliceId}:0`, `${bobId}:0`],
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("local screen sharing in one-on-one call activates grid layout", () => {
|
||||
withTestScheduler(({ behavior, expectObservable }) => {
|
||||
// Local participant shares their screen, then stops sharing
|
||||
const sharingInputMarbles = " nyn";
|
||||
// Layout should switch to grid layout then back to one-on-one layout
|
||||
const expectedLayoutMarbles = "aba";
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: constant([aliceParticipant]),
|
||||
rtcMembers$: constant([localRtcMember, aliceRtcMember]),
|
||||
sharingScreen: new Map([
|
||||
[localParticipant, behavior(sharingInputMarbles, yesNo)],
|
||||
]),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(summarizeLayout$(vm.layout$)).toBe(
|
||||
expectedLayoutMarbles,
|
||||
{
|
||||
a: {
|
||||
type: "one-on-one",
|
||||
pip: `${localId}:0`,
|
||||
spotlight: `${aliceId}:0`,
|
||||
},
|
||||
b: {
|
||||
type: "grid",
|
||||
spotlight: [`${localId}:0:screen-share`],
|
||||
grid: [`${localId}:0`, `${aliceId}:0`],
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("participants stay in the same order unless to appear/disappear", () => {
|
||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||
const visibilityInputMarbles = "a";
|
||||
|
||||
@@ -105,12 +105,16 @@ import {
|
||||
import {
|
||||
createLocalTransport$,
|
||||
JwtEndpointVersion,
|
||||
type LocalTransport,
|
||||
} from "./localMember/LocalTransport.ts";
|
||||
import {
|
||||
createMemberships$,
|
||||
membershipsAndTransports$,
|
||||
} from "../SessionBehaviors.ts";
|
||||
import { ECConnectionFactory } from "./remoteMembers/ConnectionFactory.ts";
|
||||
import {
|
||||
type ConnectionFactory,
|
||||
ECConnectionFactory,
|
||||
} from "./remoteMembers/ConnectionFactory.ts";
|
||||
import {
|
||||
type ConnectionManagerData,
|
||||
createConnectionManager$,
|
||||
@@ -170,6 +174,10 @@ export interface CallViewModelOptions {
|
||||
connectionState$?: Behavior<ConnectionState>;
|
||||
/** Optional behavior overriding the computed window size, mainly for testing purposes. */
|
||||
windowSize$?: Behavior<{ width: number; height: number }>;
|
||||
/** Optional value overriding the local transport, for testing purposes. */
|
||||
localTransport?: LocalTransport;
|
||||
/** Optional value overriding the connection factory, for testing purposes. */
|
||||
connectionFactory?: ConnectionFactory;
|
||||
/** The version & compatibility mode of MatrixRTC that we should use. */
|
||||
matrixRTCMode$?: Behavior<MatrixRTCMode>;
|
||||
}
|
||||
@@ -441,6 +449,7 @@ export function createCallViewModel$(
|
||||
// Re-create LocalTransport whenever the mode changes
|
||||
(mode) => ({ keys: [mode], data: undefined }),
|
||||
(scope, _data$, mode) =>
|
||||
options.localTransport ??
|
||||
createLocalTransport$({
|
||||
scope: scope,
|
||||
memberships$: memberships$,
|
||||
@@ -467,17 +476,19 @@ export function createCallViewModel$(
|
||||
),
|
||||
);
|
||||
|
||||
const connectionFactory = new ECConnectionFactory(
|
||||
client,
|
||||
matrixRoom.roomId,
|
||||
mediaDevices,
|
||||
trackProcessorState$,
|
||||
livekitKeyProvider,
|
||||
getUrlParams().controlledAudioDevices,
|
||||
options.livekitRoomFactory,
|
||||
getUrlParams().echoCancellation,
|
||||
getUrlParams().noiseSuppression,
|
||||
);
|
||||
const connectionFactory =
|
||||
options.connectionFactory ??
|
||||
new ECConnectionFactory(
|
||||
client,
|
||||
matrixRoom.roomId,
|
||||
mediaDevices,
|
||||
trackProcessorState$,
|
||||
livekitKeyProvider,
|
||||
getUrlParams().controlledAudioDevices,
|
||||
options.livekitRoomFactory,
|
||||
getUrlParams().echoCancellation,
|
||||
getUrlParams().noiseSuppression,
|
||||
);
|
||||
|
||||
const connectionManager = createConnectionManager$({
|
||||
scope: scope,
|
||||
|
||||
@@ -30,7 +30,10 @@ import {
|
||||
type CallViewModelOptions,
|
||||
} from "./CallViewModel";
|
||||
import {
|
||||
exampleSfuConfig,
|
||||
exampleTransport,
|
||||
mockConfig,
|
||||
MockConnection,
|
||||
mockLivekitRoom,
|
||||
mockLocalParticipant,
|
||||
mockMatrixRoom,
|
||||
@@ -75,7 +78,7 @@ export interface CallViewModelInputs {
|
||||
windowSize$: Behavior<{ width: number; height: number }>;
|
||||
}
|
||||
|
||||
const localParticipant = mockLocalParticipant({ identity: "" });
|
||||
export const localParticipant = mockLocalParticipant({ identity: "" });
|
||||
|
||||
export function withCallViewModel(mode: MatrixRTCMode) {
|
||||
return (
|
||||
@@ -180,6 +183,13 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
||||
);
|
||||
const reactions$ = new BehaviorSubject<Record<string, ReactionInfo>>({});
|
||||
|
||||
const livekitRoomFactory = (): LivekitRoom =>
|
||||
mockLivekitRoom({
|
||||
localParticipant,
|
||||
disconnect: async () => Promise.resolve(),
|
||||
setE2EEEnabled: async () => Promise.resolve(),
|
||||
});
|
||||
|
||||
const vm = createCallViewModel$(
|
||||
testScope(),
|
||||
rtcSession.asMockedSession(),
|
||||
@@ -189,14 +199,38 @@ export function withCallViewModel(mode: MatrixRTCMode) {
|
||||
{
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
autoLeaveWhenOthersLeft: false,
|
||||
livekitRoomFactory: (): LivekitRoom =>
|
||||
mockLivekitRoom({
|
||||
localParticipant,
|
||||
disconnect: async () => Promise.resolve(),
|
||||
setE2EEEnabled: async () => Promise.resolve(),
|
||||
}),
|
||||
livekitRoomFactory,
|
||||
connectionState$,
|
||||
windowSize$,
|
||||
localTransport: {
|
||||
active$: constant({
|
||||
transport: exampleTransport,
|
||||
sfuConfig: exampleSfuConfig,
|
||||
}),
|
||||
advertised$: constant(exampleTransport),
|
||||
},
|
||||
connectionFactory: {
|
||||
createConnection(
|
||||
scope,
|
||||
transport,
|
||||
ownMembershipIdentity,
|
||||
logger,
|
||||
sfuConfig,
|
||||
) {
|
||||
return new MockConnection(
|
||||
{
|
||||
scope,
|
||||
transport,
|
||||
ownMembershipIdentity,
|
||||
existingSFUConfig: sfuConfig,
|
||||
client: room.client,
|
||||
roomId: room.roomId,
|
||||
livekitRoomFactory,
|
||||
},
|
||||
logger,
|
||||
);
|
||||
},
|
||||
},
|
||||
matrixRTCMode$: constant(mode),
|
||||
...options,
|
||||
},
|
||||
|
||||
@@ -74,6 +74,8 @@ import {
|
||||
createRemoteScreenShare,
|
||||
type RemoteScreenShareViewModel,
|
||||
} from "../state/media/RemoteScreenShareViewModel";
|
||||
import { Connection } from "../state/CallViewModel/remoteMembers/Connection";
|
||||
import { type SFUConfig } from "../livekit/openIDSFU";
|
||||
|
||||
export function withFakeTimers(continuation: () => void): void {
|
||||
vi.useFakeTimers();
|
||||
@@ -210,6 +212,13 @@ export const exampleTransport: LivekitTransport = {
|
||||
livekit_alias: "!alias:example.org",
|
||||
};
|
||||
|
||||
export const exampleSfuConfig: SFUConfig = {
|
||||
jwt: "foo",
|
||||
livekitAlias: "bar",
|
||||
livekitIdentity: "baz",
|
||||
url: "bro",
|
||||
};
|
||||
|
||||
export function mockRtcMembership(
|
||||
user: string | RoomMember,
|
||||
deviceId: string,
|
||||
@@ -564,3 +573,8 @@ export function mockMuteStates(
|
||||
videoEnabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
export class MockConnection extends Connection {
|
||||
public async start(): Promise<void> {}
|
||||
public async stop(): Promise<void> {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user