/* Copyright 2026 Lotus Guild SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { Room as LivekitRoom } from "livekit-client"; import { BehaviorSubject } from "rxjs"; import { logger } from "matrix-js-sdk/lib/logger"; import EventEmitter from "events"; import { ObservableScope } from "../state/ObservableScope.ts"; import { ECConnectionFactory } from "../state/CallViewModel/remoteMembers/ConnectionFactory.ts"; import type { OpenIDClientParts } from "../livekit/openIDSFU.ts"; import { exampleTransport, mockMediaDevices, ownMemberMock, } from "../utils/test.ts"; import type { ProcessorState } from "../livekit/TrackProcessorContext.tsx"; import { autoGainControlSetting, echoCancellationSetting, noiseSuppressionSetting, } from "../settings/settings.ts"; // [lotus] Upstream v0.25.0 moved the audio-capture constraints to Settings. // The Lotus host still drives them per-call via URL params (it turns browser // noiseSuppression/AGC OFF for the in-source ML denoise tier), so // ConnectionFactory ANDs the Setting with the URL param. These tests pin that // contract. const { getUrlParams } = vi.hoisted(() => ({ getUrlParams: vi.fn() })); vi.mock("../UrlParams", () => ({ getUrlParams })); vi.mock("livekit-client", async (importOriginal) => ({ ...(await importOriginal()), Room: vi.fn().mockImplementation(function (this: LivekitRoom) { const emitter = new EventEmitter(); return { on: emitter.on.bind(emitter), off: emitter.off.bind(emitter), emit: emitter.emit.bind(emitter), disconnect: vi.fn(), remoteParticipants: new Map(), } as unknown as LivekitRoom; }), })); let testScope: ObservableScope; const mockClient: OpenIDClientParts = { getOpenIdToken: vi.fn().mockReturnValue(""), getDeviceId: vi.fn().mockReturnValue("DEV000"), }; function createRoom(): void { new ECConnectionFactory( mockClient, "!roomid:example.org", mockMediaDevices({}), new BehaviorSubject({ supported: true, processor: undefined, }), undefined, false, ).createConnection(testScope, exampleTransport, ownMemberMock, logger); } function capturedAudioDefaults(): Record { const RoomConstructor = vi.mocked(LivekitRoom); const options = RoomConstructor.mock.calls.at(-1)?.[0]; return (options?.audioCaptureDefaults ?? {}) as Record; } beforeEach(() => { testScope = new ObservableScope(); echoCancellationSetting.setValue(true); noiseSuppressionSetting.setValue(true); autoGainControlSetting.setValue(true); }); afterEach(() => { testScope.end(); vi.mocked(LivekitRoom).mockClear(); }); describe("[lotus] audio-capture URL param overrides", () => { test("with params defaulted to true, the Settings govern (upstream behaviour)", () => { getUrlParams.mockReturnValue({ echoCancellation: true, noiseSuppression: true, autoGainControl: true, }); autoGainControlSetting.setValue(false); createRoom(); expect(capturedAudioDefaults()).toMatchObject({ echoCancellation: true, noiseSuppression: true, autoGainControl: false, }); }); test("host-passed noiseSuppression=false / autoGainControl=false force OFF regardless of Settings", () => { getUrlParams.mockReturnValue({ echoCancellation: true, noiseSuppression: false, autoGainControl: false, }); createRoom(); expect(capturedAudioDefaults()).toMatchObject({ echoCancellation: true, noiseSuppression: false, autoGainControl: false, }); }); test("a URL param cannot force a constraint ON when the Setting is off", () => { getUrlParams.mockReturnValue({ echoCancellation: true, noiseSuppression: true, autoGainControl: true, }); echoCancellationSetting.setValue(false); createRoom(); expect(capturedAudioDefaults()).toMatchObject({ echoCancellation: false }); }); });