@@ -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 EventEmitter from "events" ;
1010import {
1111 BehaviorSubject ,
@@ -32,6 +32,9 @@ import {
3232 Status ,
3333 type CallMembership ,
3434 type MatrixRTCSession ,
35+ type IRTCNotificationContent ,
36+ type ICallNotifyContent ,
37+ MatrixRTCSessionEvent ,
3538} from "matrix-js-sdk/lib/matrixrtc" ;
3639import { deepCompare } from "matrix-js-sdk/lib/utils" ;
3740
@@ -1228,6 +1231,215 @@ test("autoLeaveWhenOthersLeft$ doesn't emits when autoLeaveWhenOthersLeft option
12281231 } ) ;
12291232} ) ;
12301233
1234+ describe ( "waitForNotificationAnswer$" , ( ) => {
1235+ test ( "unknown -> ringing -> timeout when notified and nobody joins" , ( ) => {
1236+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1237+ // No one ever joins (only local user)
1238+ withCallViewModel (
1239+ {
1240+ remoteParticipants$ : scope . behavior ( hot ( "a" , { a : [ ] } ) , [ ] ) ,
1241+ rtcMembers$ : scope . behavior ( hot ( "a" , { a : [ localRtcMember ] } ) , [ ] ) ,
1242+ connectionState$ : of ( ConnectionState . Connected ) ,
1243+ speaking : new Map ( ) ,
1244+ mediaDevices : mockMediaDevices ( { } ) ,
1245+ } ,
1246+ ( vm , rtcSession ) => {
1247+ // Fire a call notification at 10ms with lifetime 30ms
1248+ schedule ( " 10ms r" , {
1249+ r : ( ) => {
1250+ rtcSession . emit (
1251+ MatrixRTCSessionEvent . DidSendCallNotification ,
1252+ { lifetime : 30 } as unknown as IRTCNotificationContent ,
1253+ { } as unknown as ICallNotifyContent ,
1254+ ) ;
1255+ } ,
1256+ } ) ;
1257+
1258+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe (
1259+ "a 9ms b 29ms c" ,
1260+ { a : "unknown" , b : "ringing" , c : "timeout" } ,
1261+ ) ;
1262+ } ,
1263+ {
1264+ waitForNotificationAnswer : true ,
1265+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1266+ } ,
1267+ ) ;
1268+ } ) ;
1269+ } ) ;
1270+
1271+ test ( "ringing -> success if someone joins before timeout" , ( ) => {
1272+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1273+ // Someone joins at 20ms (both LiveKit participant and MatrixRTC member)
1274+ const remote$ = scope . behavior (
1275+ hot ( "a--b" , { a : [ ] , b : [ aliceParticipant ] } ) ,
1276+ [ ] ,
1277+ ) ;
1278+ const rtc$ = scope . behavior (
1279+ hot ( "a--b" , {
1280+ a : [ localRtcMember ] ,
1281+ b : [ localRtcMember , aliceRtcMember ] ,
1282+ } ) ,
1283+ [ ] ,
1284+ ) ;
1285+
1286+ withCallViewModel (
1287+ {
1288+ remoteParticipants$ : remote$ ,
1289+ rtcMembers$ : rtc$ ,
1290+ connectionState$ : of ( ConnectionState . Connected ) ,
1291+ speaking : new Map ( ) ,
1292+ mediaDevices : mockMediaDevices ( { } ) ,
1293+ } ,
1294+ ( vm , rtcSession ) => {
1295+ // Notify at 5ms so we enter ringing, then success at 20ms
1296+ schedule ( " 5ms r" , {
1297+ r : ( ) => {
1298+ rtcSession . emit (
1299+ MatrixRTCSessionEvent . DidSendCallNotification ,
1300+ { lifetime : 100 } as unknown as IRTCNotificationContent ,
1301+ { } as unknown as ICallNotifyContent ,
1302+ ) ;
1303+ } ,
1304+ } ) ;
1305+
1306+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "a 2ms c" , {
1307+ a : "unknown" ,
1308+ c : "success" ,
1309+ } ) ;
1310+ } ,
1311+ {
1312+ waitForNotificationAnswer : true ,
1313+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1314+ } ,
1315+ ) ;
1316+ } ) ;
1317+ } ) ;
1318+
1319+ test ( "success when someone joins before we notify" , ( ) => {
1320+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1321+ // Join at 10ms, notify later at 20ms (state should stay success)
1322+ const remote$ = scope . behavior (
1323+ hot ( "a-b" , { a : [ ] , b : [ aliceParticipant ] } ) ,
1324+ [ ] ,
1325+ ) ;
1326+ const rtc$ = scope . behavior (
1327+ hot ( "a-b" , {
1328+ a : [ localRtcMember ] ,
1329+ b : [ localRtcMember , aliceRtcMember ] ,
1330+ } ) ,
1331+ [ ] ,
1332+ ) ;
1333+
1334+ withCallViewModel (
1335+ {
1336+ remoteParticipants$ : remote$ ,
1337+ rtcMembers$ : rtc$ ,
1338+ connectionState$ : of ( ConnectionState . Connected ) ,
1339+ speaking : new Map ( ) ,
1340+ mediaDevices : mockMediaDevices ( { } ) ,
1341+ } ,
1342+ ( vm , rtcSession ) => {
1343+ schedule ( " 20ms r" , {
1344+ r : ( ) => {
1345+ rtcSession . emit (
1346+ MatrixRTCSessionEvent . DidSendCallNotification ,
1347+ { lifetime : 50 } as unknown as IRTCNotificationContent ,
1348+ { } as unknown as ICallNotifyContent ,
1349+ ) ;
1350+ } ,
1351+ } ) ;
1352+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "a 1ms b" , {
1353+ a : "unknown" ,
1354+ b : "success" ,
1355+ } ) ;
1356+ } ,
1357+ {
1358+ waitForNotificationAnswer : true ,
1359+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1360+ } ,
1361+ ) ;
1362+ } ) ;
1363+ } ) ;
1364+
1365+ test ( "notify without lifetime -> immediate timeout" , ( ) => {
1366+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1367+ withCallViewModel (
1368+ {
1369+ remoteParticipants$ : scope . behavior ( hot ( "a" , { a : [ ] } ) , [ ] ) ,
1370+ rtcMembers$ : scope . behavior ( hot ( "a" , { a : [ localRtcMember ] } ) , [ ] ) ,
1371+ connectionState$ : of ( ConnectionState . Connected ) ,
1372+ speaking : new Map ( ) ,
1373+ mediaDevices : mockMediaDevices ( { } ) ,
1374+ } ,
1375+ ( vm , rtcSession ) => {
1376+ schedule ( " 10ms r" , {
1377+ r : ( ) => {
1378+ rtcSession . emit (
1379+ MatrixRTCSessionEvent . DidSendCallNotification ,
1380+ { lifetime : 0 } as unknown as IRTCNotificationContent , // no lifetime
1381+ { } as unknown as ICallNotifyContent ,
1382+ ) ;
1383+ } ,
1384+ } ) ;
1385+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "a 9ms b" , {
1386+ a : "unknown" ,
1387+ b : "timeout" ,
1388+ } ) ;
1389+ } ,
1390+ {
1391+ waitForNotificationAnswer : true ,
1392+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1393+ } ,
1394+ ) ;
1395+ } ) ;
1396+ } ) ;
1397+
1398+ test ( "stays null when waitForNotificationAnswer=false" , ( ) => {
1399+ withTestScheduler ( ( { hot, schedule, expectObservable, scope } ) => {
1400+ const remote$ = scope . behavior (
1401+ hot ( "a--b" , { a : [ ] , b : [ aliceParticipant ] } ) ,
1402+ [ ] ,
1403+ ) ;
1404+ const rtc$ = scope . behavior (
1405+ hot ( "a--b" , {
1406+ a : [ localRtcMember ] ,
1407+ b : [ localRtcMember , aliceRtcMember ] ,
1408+ } ) ,
1409+ [ ] ,
1410+ ) ;
1411+
1412+ withCallViewModel (
1413+ {
1414+ remoteParticipants$ : remote$ ,
1415+ rtcMembers$ : rtc$ ,
1416+ connectionState$ : of ( ConnectionState . Connected ) ,
1417+ speaking : new Map ( ) ,
1418+ mediaDevices : mockMediaDevices ( { } ) ,
1419+ } ,
1420+ ( vm , rtcSession ) => {
1421+ schedule ( " 5ms r" , {
1422+ r : ( ) => {
1423+ rtcSession . emit (
1424+ MatrixRTCSessionEvent . DidSendCallNotification ,
1425+ { lifetime : 30 } as unknown as IRTCNotificationContent ,
1426+ { } as unknown as ICallNotifyContent ,
1427+ ) ;
1428+ } ,
1429+ } ) ;
1430+ expectObservable ( vm . waitForNotificationAnswer$ ) . toBe ( "(n)" , {
1431+ n : null ,
1432+ } ) ;
1433+ } ,
1434+ {
1435+ waitForNotificationAnswer : false ,
1436+ encryptionSystem : { kind : E2eeType . PER_PARTICIPANT } ,
1437+ } ,
1438+ ) ;
1439+ } ) ;
1440+ } ) ;
1441+ } ) ;
1442+
12311443test ( "audio output changes when toggling earpiece mode" , ( ) => {
12321444 withTestScheduler ( ( { schedule, expectObservable } ) => {
12331445 getUrlParams . mockReturnValue ( { controlledAudioDevices : true } ) ;
0 commit comments