Merge branch 'toger5/waitForNotificationAnswer' into toger5/call-pickup-state-decline-event
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
RoomEvent as MatrixRoomEvent,
|
||||
MatrixEvent,
|
||||
type IRoomTimelineData,
|
||||
EventType,
|
||||
} from "matrix-js-sdk";
|
||||
import {
|
||||
ConnectionState,
|
||||
@@ -318,7 +319,7 @@ function withCallViewModel(
|
||||
|
||||
const roomEventSelectorSpy = vi
|
||||
.spyOn(ComponentsCore, "roomEventSelector")
|
||||
.mockImplementation((room, eventType) => of());
|
||||
.mockImplementation((_room, _eventType) => of());
|
||||
|
||||
const livekitRoom = mockLivekitRoom(
|
||||
{ localParticipant },
|
||||
@@ -1078,9 +1079,9 @@ it("should rank raised hands above video feeds and below speakers and presenters
|
||||
});
|
||||
|
||||
function nooneEverThere$<T>(
|
||||
hot: (marbles: string, values: Record<string, T[]>) => Observable<T[]>,
|
||||
): Observable<T[]> {
|
||||
return hot("a-b-c-d", {
|
||||
behavior: (marbles: string, values: Record<string, T[]>) => Behavior<T[]>,
|
||||
): Behavior<T[]> {
|
||||
return behavior("a-b-c-d", {
|
||||
a: [], // Start empty
|
||||
b: [], // Alice joins
|
||||
c: [], // Alice still there
|
||||
@@ -1089,12 +1090,12 @@ function nooneEverThere$<T>(
|
||||
}
|
||||
|
||||
function participantJoinLeave$(
|
||||
hot: (
|
||||
behavior: (
|
||||
marbles: string,
|
||||
values: Record<string, RemoteParticipant[]>,
|
||||
) => Observable<RemoteParticipant[]>,
|
||||
): Observable<RemoteParticipant[]> {
|
||||
return hot("a-b-c-d", {
|
||||
) => Behavior<RemoteParticipant[]>,
|
||||
): Behavior<RemoteParticipant[]> {
|
||||
return behavior("a-b-c-d", {
|
||||
a: [], // Start empty
|
||||
b: [aliceParticipant], // Alice joins
|
||||
c: [aliceParticipant], // Alice still there
|
||||
@@ -1103,12 +1104,12 @@ function participantJoinLeave$(
|
||||
}
|
||||
|
||||
function rtcMemberJoinLeave$(
|
||||
hot: (
|
||||
behavior: (
|
||||
marbles: string,
|
||||
values: Record<string, CallMembership[]>,
|
||||
) => Observable<CallMembership[]>,
|
||||
): Observable<CallMembership[]> {
|
||||
return hot("a-b-c-d", {
|
||||
) => Behavior<CallMembership[]>,
|
||||
): Behavior<CallMembership[]> {
|
||||
return behavior("a-b-c-d", {
|
||||
a: [localRtcMember], // Start empty
|
||||
b: [localRtcMember, aliceRtcMember], // Alice joins
|
||||
c: [localRtcMember, aliceRtcMember], // Alice still there
|
||||
@@ -1116,47 +1117,15 @@ function rtcMemberJoinLeave$(
|
||||
});
|
||||
}
|
||||
|
||||
test("allOthersLeft$ emits only when someone joined and then all others left", () => {
|
||||
withTestScheduler(({ hot, expectObservable, scope }) => {
|
||||
// Test scenario 1: No one ever joins - should only emit initial false and never emit again
|
||||
withCallViewModel(
|
||||
{ remoteParticipants$: scope.behavior(nooneEverThere$(hot), []) },
|
||||
(vm) => {
|
||||
expectObservable(vm.allOthersLeft$).toBe("n------", { n: false });
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("allOthersLeft$ emits true when someone joined and then all others left", () => {
|
||||
withTestScheduler(({ hot, expectObservable, scope }) => {
|
||||
test("autoLeave$ emits only when autoLeaveWhenOthersLeft option is enabled", () => {
|
||||
withTestScheduler(({ behavior, expectObservable }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(participantJoinLeave$(hot), []),
|
||||
rtcMembers$: scope.behavior(rtcMemberJoinLeave$(hot), []),
|
||||
remoteParticipants$: participantJoinLeave$(behavior),
|
||||
rtcMembers$: rtcMemberJoinLeave$(behavior),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(vm.allOthersLeft$).toBe(
|
||||
"n-----u", // false initially, then at frame 6: true then false emissions in same frame
|
||||
{ n: false, u: true }, // map(() => {})
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("autoLeaveWhenOthersLeft$ emits only when autoLeaveWhenOthersLeft option is enabled", () => {
|
||||
withTestScheduler(({ hot, expectObservable, scope }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(participantJoinLeave$(hot), []),
|
||||
rtcMembers$: scope.behavior(rtcMemberJoinLeave$(hot), []),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(vm.autoLeaveWhenOthersLeft$).toBe(
|
||||
"------e", // false initially, then at frame 6: true then false emissions in same frame
|
||||
{ e: undefined },
|
||||
);
|
||||
expectObservable(vm.autoLeave$).toBe("------(e|)", { e: undefined });
|
||||
},
|
||||
{
|
||||
autoLeaveWhenOthersLeft: true,
|
||||
@@ -1166,15 +1135,15 @@ test("autoLeaveWhenOthersLeft$ emits only when autoLeaveWhenOthersLeft option is
|
||||
});
|
||||
});
|
||||
|
||||
test("autoLeaveWhenOthersLeft$ never emits autoLeaveWhenOthersLeft option is enabled but no-one is there", () => {
|
||||
withTestScheduler(({ hot, expectObservable, scope }) => {
|
||||
test("autoLeave$ never emits autoLeaveWhenOthersLeft option is enabled but no-one is there", () => {
|
||||
withTestScheduler(({ behavior, expectObservable }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(nooneEverThere$(hot), []),
|
||||
rtcMembers$: scope.behavior(nooneEverThere$(hot), []),
|
||||
remoteParticipants$: nooneEverThere$(behavior),
|
||||
rtcMembers$: nooneEverThere$(behavior),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(vm.autoLeaveWhenOthersLeft$).toBe("-------");
|
||||
expectObservable(vm.autoLeave$).toBe("-");
|
||||
},
|
||||
{
|
||||
autoLeaveWhenOthersLeft: true,
|
||||
@@ -1184,15 +1153,15 @@ test("autoLeaveWhenOthersLeft$ never emits autoLeaveWhenOthersLeft option is ena
|
||||
});
|
||||
});
|
||||
|
||||
test("autoLeaveWhenOthersLeft$ doesn't emit when autoLeaveWhenOthersLeft option is disabled and all others left", () => {
|
||||
withTestScheduler(({ hot, expectObservable, scope }) => {
|
||||
test("autoLeave$ doesn't emit when autoLeaveWhenOthersLeft option is disabled and all others left", () => {
|
||||
withTestScheduler(({ behavior, expectObservable }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(participantJoinLeave$(hot), []),
|
||||
rtcMembers$: scope.behavior(rtcMemberJoinLeave$(hot), []),
|
||||
remoteParticipants$: participantJoinLeave$(behavior),
|
||||
rtcMembers$: rtcMemberJoinLeave$(behavior),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(vm.autoLeaveWhenOthersLeft$).toBe("-------");
|
||||
expectObservable(vm.autoLeave$).toBe("-");
|
||||
},
|
||||
{
|
||||
autoLeaveWhenOthersLeft: false,
|
||||
@@ -1202,31 +1171,25 @@ test("autoLeaveWhenOthersLeft$ doesn't emit when autoLeaveWhenOthersLeft option
|
||||
});
|
||||
});
|
||||
|
||||
test("autoLeaveWhenOthersLeft$ doesn't emits when autoLeaveWhenOthersLeft option is enabled and all others left", () => {
|
||||
withTestScheduler(({ hot, expectObservable, scope }) => {
|
||||
test("autoLeave$ emits when autoLeaveWhenOthersLeft option is enabled and all others left", () => {
|
||||
withTestScheduler(({ behavior, expectObservable }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(
|
||||
hot("a-b-c-d", {
|
||||
a: [], // Alone
|
||||
b: [aliceParticipant], // Alice joins
|
||||
c: [aliceParticipant],
|
||||
d: [], // Local joins with a second device
|
||||
}),
|
||||
[], //Alice leaves
|
||||
),
|
||||
rtcMembers$: scope.behavior(
|
||||
hot("a-b-c-d", {
|
||||
a: [localRtcMember], // Start empty
|
||||
b: [localRtcMember, aliceRtcMember], // Alice joins
|
||||
c: [localRtcMember, aliceRtcMember, localRtcMemberDevice2], // Alice still there
|
||||
d: [localRtcMember, localRtcMemberDevice2], // The second Alice leaves
|
||||
}),
|
||||
[],
|
||||
),
|
||||
remoteParticipants$: behavior("a-b-c-d", {
|
||||
a: [], // Alone
|
||||
b: [aliceParticipant], // Alice joins
|
||||
c: [aliceParticipant],
|
||||
d: [], // Local joins with a second device
|
||||
}),
|
||||
rtcMembers$: behavior("a-b-c-d", {
|
||||
a: [localRtcMember], // Start empty
|
||||
b: [localRtcMember, aliceRtcMember], // Alice joins
|
||||
c: [localRtcMember, aliceRtcMember, localRtcMemberDevice2], // Alice still there
|
||||
d: [localRtcMember, localRtcMemberDevice2], // The second Alice leaves
|
||||
}),
|
||||
},
|
||||
(vm) => {
|
||||
expectObservable(vm.autoLeaveWhenOthersLeft$).toBe("------e", {
|
||||
expectObservable(vm.autoLeave$).toBe("------(e|)", {
|
||||
e: undefined,
|
||||
});
|
||||
},
|
||||
@@ -1238,18 +1201,12 @@ test("autoLeaveWhenOthersLeft$ doesn't emits when autoLeaveWhenOthersLeft option
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldWaitForCallPickup$", () => {
|
||||
describe("waitForCallPickup$", () => {
|
||||
test("unknown -> ringing -> timeout when notified and nobody joins", () => {
|
||||
withTestScheduler(({ hot, schedule, expectObservable, scope }) => {
|
||||
withTestScheduler(({ schedule, expectObservable }) => {
|
||||
// No one ever joins (only local user)
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(hot("a", { a: [] }), []),
|
||||
rtcMembers$: scope.behavior(hot("a", { a: [localRtcMember] }), []),
|
||||
connectionState$: of(ConnectionState.Connected),
|
||||
speaking: new Map(),
|
||||
mediaDevices: mockMediaDevices({}),
|
||||
},
|
||||
{ remoteParticipants$: constant([]) },
|
||||
(vm, rtcSession) => {
|
||||
// Fire a call notification at 10ms with lifetime 30ms
|
||||
schedule(" 10ms r", {
|
||||
@@ -1273,7 +1230,7 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
@@ -1281,25 +1238,18 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
|
||||
test("ringing -> success if someone joins before timeout", () => {
|
||||
withTestScheduler(({ hot, schedule, expectObservable, scope }) => {
|
||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||
// Someone joins at 20ms (both LiveKit participant and MatrixRTC member)
|
||||
const remote$ = scope.behavior(
|
||||
hot("a--b", { a: [], b: [aliceParticipant] }),
|
||||
[],
|
||||
);
|
||||
const rtc$ = scope.behavior(
|
||||
hot("a--b", {
|
||||
a: [localRtcMember],
|
||||
b: [localRtcMember, aliceRtcMember],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: remote$,
|
||||
rtcMembers$: rtc$,
|
||||
connectionState$: of(ConnectionState.Connected),
|
||||
remoteParticipants$: behavior("a 19ms b", {
|
||||
a: [],
|
||||
b: [aliceParticipant],
|
||||
}),
|
||||
rtcMembers$: behavior("a 19ms b", {
|
||||
a: [localRtcMember],
|
||||
b: [localRtcMember, aliceRtcMember],
|
||||
}),
|
||||
},
|
||||
(vm, rtcSession) => {
|
||||
// Notify at 5ms so we enter ringing, then success at 20ms
|
||||
@@ -1320,13 +1270,14 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expectObservable(vm.callPickupState$).toBe("a 2ms c", {
|
||||
expectObservable(vm.callPickupState$).toBe("a 4ms b 14ms c", {
|
||||
a: "unknown",
|
||||
b: "ringing",
|
||||
c: "success",
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
@@ -1334,27 +1285,18 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
|
||||
test("success when someone joins before we notify", () => {
|
||||
withTestScheduler(({ hot, schedule, expectObservable, scope }) => {
|
||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||
// Join at 10ms, notify later at 20ms (state should stay success)
|
||||
const remote$ = scope.behavior(
|
||||
hot("a-b", { a: [], b: [aliceParticipant] }),
|
||||
[],
|
||||
);
|
||||
const rtc$ = scope.behavior(
|
||||
hot("a-b", {
|
||||
a: [localRtcMember],
|
||||
b: [localRtcMember, aliceRtcMember],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: remote$,
|
||||
rtcMembers$: rtc$,
|
||||
connectionState$: of(ConnectionState.Connected),
|
||||
speaking: new Map(),
|
||||
mediaDevices: mockMediaDevices({}),
|
||||
remoteParticipants$: behavior("a 9ms b", {
|
||||
a: [],
|
||||
b: [aliceParticipant],
|
||||
}),
|
||||
rtcMembers$: behavior("a 9ms b", {
|
||||
a: [localRtcMember],
|
||||
b: [localRtcMember, aliceRtcMember],
|
||||
}),
|
||||
},
|
||||
(vm, rtcSession) => {
|
||||
schedule(" 20ms r", {
|
||||
@@ -1370,13 +1312,13 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
);
|
||||
},
|
||||
});
|
||||
expectObservable(vm.callPickupState$).toBe("a 1ms b", {
|
||||
expectObservable(vm.callPickupState$).toBe("a 9ms b", {
|
||||
a: "unknown",
|
||||
b: "success",
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
@@ -1384,21 +1326,15 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
|
||||
test("notify without lifetime -> immediate timeout", () => {
|
||||
withTestScheduler(({ hot, schedule, expectObservable, scope }) => {
|
||||
withTestScheduler(({ schedule, expectObservable }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: scope.behavior(hot("a", { a: [] }), []),
|
||||
rtcMembers$: scope.behavior(hot("a", { a: [localRtcMember] }), []),
|
||||
connectionState$: of(ConnectionState.Connected),
|
||||
speaking: new Map(),
|
||||
mediaDevices: mockMediaDevices({}),
|
||||
},
|
||||
{},
|
||||
(vm, rtcSession) => {
|
||||
schedule(" 10ms r", {
|
||||
r: () => {
|
||||
rtcSession.emit(
|
||||
MatrixRTCSessionEvent.DidSendCallNotification,
|
||||
{ event_id: "$notif4", lifetime: 0 } as unknown as {
|
||||
{ event_id: "$notif4" } as unknown as {
|
||||
event_id: string;
|
||||
} & IRTCNotificationContent, // no lifetime
|
||||
{ event_id: "$notif4" } as unknown as {
|
||||
@@ -1413,34 +1349,25 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("stays null when shouldWaitForCallPickup=false", () => {
|
||||
withTestScheduler(({ hot, schedule, expectObservable, scope }) => {
|
||||
const remote$ = scope.behavior(
|
||||
hot("a--b", { a: [], b: [aliceParticipant] }),
|
||||
[],
|
||||
);
|
||||
const rtc$ = scope.behavior(
|
||||
hot("a--b", {
|
||||
a: [localRtcMember],
|
||||
b: [localRtcMember, aliceRtcMember],
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
test("stays null when waitForCallPickup=false", () => {
|
||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: remote$,
|
||||
rtcMembers$: rtc$,
|
||||
connectionState$: of(ConnectionState.Connected),
|
||||
speaking: new Map(),
|
||||
mediaDevices: mockMediaDevices({}),
|
||||
remoteParticipants$: behavior("a--b", {
|
||||
a: [],
|
||||
b: [aliceParticipant],
|
||||
}),
|
||||
rtcMembers$: behavior("a--b", {
|
||||
a: [localRtcMember],
|
||||
b: [localRtcMember, aliceRtcMember],
|
||||
}),
|
||||
},
|
||||
(vm, rtcSession) => {
|
||||
schedule(" 5ms r", {
|
||||
@@ -1461,7 +1388,7 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: false,
|
||||
waitForCallPickup: false,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
@@ -1494,7 +1421,15 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
// Emit decline timeline event with id matching the notification
|
||||
rtcSession.room.emit(
|
||||
MatrixRoomEvent.Timeline,
|
||||
new MatrixEvent({ event_id: "$decl1", type: "m.rtc.decline" }),
|
||||
new MatrixEvent({
|
||||
type: EventType.RTCDecline,
|
||||
content: {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.reference",
|
||||
event_id: "$decl1",
|
||||
},
|
||||
},
|
||||
}),
|
||||
rtcSession.room,
|
||||
undefined,
|
||||
false,
|
||||
@@ -1509,7 +1444,7 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
@@ -1557,7 +1492,7 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
@@ -1604,7 +1539,7 @@ describe("shouldWaitForCallPickup$", () => {
|
||||
});
|
||||
},
|
||||
{
|
||||
shouldWaitForCallPickup: true,
|
||||
waitForCallPickup: true,
|
||||
encryptionSystem: { kind: E2eeType.PER_PARTICIPANT },
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user