@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
55Please see LICENSE in the repository root for full details.
66*/
77
8- import { test , vi , onTestFinished , it } from "vitest" ;
8+ import { test , vi , onTestFinished , it , describe } from "vitest" ;
99import {
1010 BehaviorSubject ,
1111 combineLatest ,
@@ -30,6 +30,9 @@ import * as ComponentsCore from "@livekit/components-core";
3030import {
3131 type CallMembership ,
3232 type MatrixRTCSession ,
33+ type IRTCNotificationContent ,
34+ type ICallNotifyContent ,
35+ MatrixRTCSessionEvent ,
3336} from "matrix-js-sdk/lib/matrixrtc" ;
3437import { deepCompare } from "matrix-js-sdk/lib/utils" ;
3538
@@ -1199,6 +1202,205 @@ test("autoLeaveWhenOthersLeft$ doesn't emits when autoLeaveWhenOthersLeft option
11991202 } ) ;
12001203} ) ;
12011204
1205+ describe ( "waitForNotificationAnswer$" , ( ) => {
1206+ test ( "unknown -> ringing -> timeout when notified and nobody joins" , ( ) => {
1207+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1208+ // No one ever joins (only local user)
1209+ withCallViewModel (
1210+ scope . behavior ( hot ( "a" , { a : [ ] } ) , [ ] ) , // remote participants
1211+ scope . behavior ( hot ( "a" , { a : [ localRtcMember ] } ) , [ ] ) , // rtc members
1212+ of ( ConnectionState . Connected ) ,
1213+ new Map ( ) ,
1214+ mockMediaDevices ( { } ) ,
1215+ ( vm , rtcSession ) => {
1216+ // Fire a call notification at 10ms with lifetime 30ms
1217+ schedule ( " 10ms r" , {
1218+ r : ( ) => {
1219+ rtcSession . emit (
1220+ MatrixRTCSessionEvent . DidSendCallNotification ,
1221+ { lifetime : 30 } as unknown as IRTCNotificationContent ,
1222+ { } as unknown as ICallNotifyContent ,
1223+ ) ;
1224+ } ,
1225+ } ) ;
1226+
1227+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe (
1228+ "a 9ms b 29ms c" ,
1229+ { a : "unknown" , b : "ringing" , c : "timeout" } ,
1230+ ) ;
1231+ } ,
1232+ {
1233+ waitForNotificationAnswer : true ,
1234+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1235+ } ,
1236+ ) ;
1237+ } ) ;
1238+ } ) ;
1239+
1240+ test ( "ringing -> success if someone joins before timeout" , ( ) => {
1241+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1242+ // Someone joins at 20ms (both LiveKit participant and MatrixRTC member)
1243+ const remote$ = scope . behavior (
1244+ hot ( "a--b" , { a : [ ] , b : [ aliceParticipant ] } ) ,
1245+ [ ] ,
1246+ ) ;
1247+ const rtc$ = scope . behavior (
1248+ hot ( "a--b" , {
1249+ a : [ localRtcMember ] ,
1250+ b : [ localRtcMember , aliceRtcMember ] ,
1251+ } ) ,
1252+ [ ] ,
1253+ ) ;
1254+
1255+ withCallViewModel (
1256+ remote$ ,
1257+ rtc$ ,
1258+ of ( ConnectionState . Connected ) ,
1259+ new Map ( ) ,
1260+ mockMediaDevices ( { } ) ,
1261+ ( vm , rtcSession ) => {
1262+ // Notify at 5ms so we enter ringing, then success at 20ms
1263+ schedule ( " 5ms r" , {
1264+ r : ( ) => {
1265+ rtcSession . emit (
1266+ MatrixRTCSessionEvent . DidSendCallNotification ,
1267+ { lifetime : 100 } as unknown as IRTCNotificationContent ,
1268+ { } as unknown as ICallNotifyContent ,
1269+ ) ;
1270+ } ,
1271+ } ) ;
1272+
1273+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "a 2ms c" , {
1274+ a : "unknown" ,
1275+ c : "success" ,
1276+ } ) ;
1277+ } ,
1278+ {
1279+ waitForNotificationAnswer : true ,
1280+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1281+ } ,
1282+ ) ;
1283+ } ) ;
1284+ } ) ;
1285+
1286+ test ( "success when someone joins before we notify" , ( ) => {
1287+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1288+ // Join at 10ms, notify later at 20ms (state should stay success)
1289+ const remote$ = scope . behavior (
1290+ hot ( "a-b" , { a : [ ] , b : [ aliceParticipant ] } ) ,
1291+ [ ] ,
1292+ ) ;
1293+ const rtc$ = scope . behavior (
1294+ hot ( "a-b" , {
1295+ a : [ localRtcMember ] ,
1296+ b : [ localRtcMember , aliceRtcMember ] ,
1297+ } ) ,
1298+ [ ] ,
1299+ ) ;
1300+
1301+ withCallViewModel (
1302+ remote$ ,
1303+ rtc$ ,
1304+ of ( ConnectionState . Connected ) ,
1305+ new Map ( ) ,
1306+ mockMediaDevices ( { } ) ,
1307+ ( vm , rtcSession ) => {
1308+ schedule ( " 20ms r" , {
1309+ r : ( ) => {
1310+ rtcSession . emit (
1311+ MatrixRTCSessionEvent . DidSendCallNotification ,
1312+ { lifetime : 50 } as unknown as IRTCNotificationContent ,
1313+ { } as unknown as ICallNotifyContent ,
1314+ ) ;
1315+ } ,
1316+ } ) ;
1317+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "a 1ms b" , {
1318+ a : "unknown" ,
1319+ b : "success" ,
1320+ } ) ;
1321+ } ,
1322+ {
1323+ waitForNotificationAnswer : true ,
1324+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1325+ } ,
1326+ ) ;
1327+ } ) ;
1328+ } ) ;
1329+
1330+ test ( "notify without lifetime -> immediate timeout" , ( ) => {
1331+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1332+ withCallViewModel (
1333+ scope . behavior ( hot ( "a" , { a : [ ] } ) , [ ] ) ,
1334+ scope . behavior ( hot ( "a" , { a : [ localRtcMember ] } ) , [ ] ) ,
1335+ of ( ConnectionState . Connected ) ,
1336+ new Map ( ) ,
1337+ mockMediaDevices ( { } ) ,
1338+ ( vm , rtcSession ) => {
1339+ schedule ( " 10ms r" , {
1340+ r : ( ) => {
1341+ rtcSession . emit (
1342+ MatrixRTCSessionEvent . DidSendCallNotification ,
1343+ { lifetime : 0 } as unknown as IRTCNotificationContent , // no lifetime
1344+ { } as unknown as ICallNotifyContent ,
1345+ ) ;
1346+ } ,
1347+ } ) ;
1348+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "a 9ms b" , {
1349+ a : "unknown" ,
1350+ b : "timeout" ,
1351+ } ) ;
1352+ } ,
1353+ {
1354+ waitForNotificationAnswer : true ,
1355+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1356+ } ,
1357+ ) ;
1358+ } ) ;
1359+ } ) ;
1360+
1361+ test ( "stays null when waitForNotificationAnswer=false" , ( ) => {
1362+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1363+ const remote$ = scope . behavior (
1364+ hot ( "a--b" , { a : [ ] , b : [ aliceParticipant ] } ) ,
1365+ [ ] ,
1366+ ) ;
1367+ const rtc$ = scope . behavior (
1368+ hot ( "a--b" , {
1369+ a : [ localRtcMember ] ,
1370+ b : [ localRtcMember , aliceRtcMember ] ,
1371+ } ) ,
1372+ [ ] ,
1373+ ) ;
1374+
1375+ withCallViewModel (
1376+ remote$ ,
1377+ rtc$ ,
1378+ of ( ConnectionState . Connected ) ,
1379+ new Map ( ) ,
1380+ mockMediaDevices ( { } ) ,
1381+ ( vm , rtcSession ) => {
1382+ schedule ( " 5ms r" , {
1383+ r : ( ) => {
1384+ rtcSession . emit (
1385+ MatrixRTCSessionEvent . DidSendCallNotification ,
1386+ { lifetime : 30 } as unknown as IRTCNotificationContent ,
1387+ { } as unknown as ICallNotifyContent ,
1388+ ) ;
1389+ } ,
1390+ } ) ;
1391+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "(n)" , {
1392+ n : null ,
1393+ } ) ;
1394+ } ,
1395+ {
1396+ waitForNotificationAnswer : false ,
1397+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1398+ } ,
1399+ ) ;
1400+ } ) ;
1401+ } ) ;
1402+ } ) ;
1403+
12021404test ( "audio output changes when toggling earpiece mode" , ( ) => {
12031405 withTestScheduler ( ( { schedule, expectObservable } ) => {
12041406 getUrlParams . mockReturnValue ( { controlledAudioDevices : true } ) ;
0 commit comments