diff --git a/sdk/src/RTCSlot.test.ts b/sdk/src/RTCSlot.test.ts new file mode 100644 index 000000000..1f3b9e9b3 --- /dev/null +++ b/sdk/src/RTCSlot.test.ts @@ -0,0 +1,71 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE in the repository root for full details. +*/ + +import { describe, expect, it } from "vitest"; +import { type MatrixClient, type Room } from "matrix-js-sdk"; +import { EventType } from "matrix-js-sdk/lib/@types/event"; +import { type MatrixEvent } from "matrix-js-sdk/lib/models/event"; +import { + type RoomState, + RoomStateEvent, +} from "matrix-js-sdk/lib/models/room-state"; + +import { createRTCSlot } from "./RTCSlot"; +import { type RTCSlotOptions } from "./api"; +import { MatrixRTCMode } from "./config"; +import { E2eeType } from "./encryption"; +import { mockEmitter, MockRTCSession, testScope } from "./utils/test"; + +const options: RTCSlotOptions = { + matrixRTCMode: MatrixRTCMode.Compatibility, + encryptionSystem: { kind: E2eeType.NONE }, +}; + +function setup(): { + client: MatrixClient; + room: Room; + session: MockRTCSession; +} { + const room = { + ...mockEmitter(), + roomId: "!room:example.org", + getMembersWithMembership: (): [] => [], + } as unknown as Room; + const session = new MockRTCSession(room); + const client = { + getUserId: (): string => "@alice:example.org", + getDeviceId: (): string => "AAAA", + matrixRTC: { getRoomSession: (): MockRTCSession => session }, + } as unknown as MatrixClient; + return { client, room, session }; +} + +describe("createRTCSlot", () => { + describe("status$", () => { + it("is undefined while the room has no slot event", () => { + const { client, room } = setup(); + const slot = createRTCSlot(testScope(), client, room, options); + expect(slot.status$.value).toBeUndefined(); + }); + + it("follows the slot event", () => { + const { client, room, session } = setup(); + session.rtcSlot = { status: "open" }; + const slot = createRTCSlot(testScope(), client, room, options); + expect(slot.status$.value).toBe("open"); + + session.rtcSlot = { status: "closed" }; + room.emit( + RoomStateEvent.Events, + { getType: (): string => EventType.RTCSlot } as MatrixEvent, + {} as RoomState, + null, + ); + expect(slot.status$.value).toBe("closed"); + }); + }); +}); diff --git a/sdk/src/RTCSlot.ts b/sdk/src/RTCSlot.ts index 734596ea5..eb6dc2967 100644 --- a/sdk/src/RTCSlot.ts +++ b/sdk/src/RTCSlot.ts @@ -15,7 +15,15 @@ import { } from "matrix-js-sdk/lib/matrixrtc"; import { type MatrixEvent } from "matrix-js-sdk/lib/models/event"; import { RoomStateEvent } from "matrix-js-sdk/lib/models/room-state"; -import { BehaviorSubject, filter, fromEvent, map, of, switchMap } from "rxjs"; +import { + BehaviorSubject, + filter, + fromEvent, + map, + of, + startWith, + switchMap, +} from "rxjs"; import { ObservableScope } from "./reactive/ObservableScope"; import { generateItems } from "./reactive/observable"; @@ -108,12 +116,14 @@ export function createRTCSlot( ), ); + // The status is undefined while the room has no slot event, and an explicit + // undefined is not an initial value to `behavior`, so it goes in the stream const status$ = scope.behavior( fromEvent(room, RoomStateEvent.Events, (event: MatrixEvent) => event).pipe( filter((event) => event.getType() === EventType.RTCSlot), map(() => slotStatus(jsSdkSession)), + startWith(slotStatus(jsSdkSession)), ), - slotStatus(jsSdkSession), ); const slot: RTCSlot = { diff --git a/sdk/src/utils/test.ts b/sdk/src/utils/test.ts index bc2cbb020..d7af9cbd1 100644 --- a/sdk/src/utils/test.ts +++ b/sdk/src/utils/test.ts @@ -273,6 +273,12 @@ export class MockRTCSession extends TypedEventEmitter< return this.joined; } + /** The slot event's content; undefined while the room has none. */ + public rtcSlot: ReturnType = undefined; + public getRtcSlot(): ReturnType { + return this.rtcSlot; + } + public isKeyRotationSuppressed = false; public withMemberships(