/* 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 { describe, expect, test, vi } from "vitest"; import { applyProportionalMaxBitrate, buildPatch, patchSender, } from "./lotusQuality"; function makeSender( initialEncodings: RTCRtpEncodingParameters[] = [{}], ): RTCRtpSender { let encodings = initialEncodings; return { getParameters: vi.fn(() => ({ encodings })), setParameters: vi.fn(async (params: RTCRtpSendParameters) => { await Promise.resolve(); encodings = params.encodings ?? []; }), } as unknown as RTCRtpSender; } describe("lotusQuality set_quality -> clear (#11)", () => { test("clearing a previously-set cap actively unsets it on the sender", async () => { const sender = makeSender(); const writtenKeys = new WeakMap< RTCRtpSender, Set >(); // Set: audioMaxBitrate = 64000. const setPatch = buildPatch(sender, { maxBitrate: 64_000 }, writtenKeys); expect(setPatch).toEqual({ maxBitrate: 64_000 }); await patchSender(sender, setPatch, writtenKeys); expect(sender.getParameters().encodings[0].maxBitrate).toBe(64_000); expect(writtenKeys.get(sender)).toEqual(new Set(["maxBitrate"])); // Clear: host sends `null`, so the caller now wants an empty desired // patch. buildPatch must still emit an explicit `undefined` for the key // it previously wrote, instead of an empty patch that leaves the stale // cap on the sender. const clearPatch = buildPatch(sender, {}, writtenKeys); expect(clearPatch).toEqual({ maxBitrate: undefined }); await patchSender(sender, clearPatch, writtenKeys); const finalEncoding = sender.getParameters().encodings[0]; expect(finalEncoding.maxBitrate).toBeUndefined(); expect("maxBitrate" in finalEncoding).toBe(true); // explicitly cleared, not merely absent expect(writtenKeys.has(sender)).toBe(false); }); test("clearing one of several caps leaves the others (and their tracking) intact", async () => { const sender = makeSender(); const writtenKeys = new WeakMap< RTCRtpSender, Set >(); const setPatch = buildPatch( sender, { maxBitrate: 500_000, maxFramerate: 24 }, writtenKeys, ); await patchSender(sender, setPatch, writtenKeys); expect(writtenKeys.get(sender)).toEqual( new Set(["maxBitrate", "maxFramerate"]), ); // Only maxFramerate is still desired; maxBitrate should be actively // cleared. const clearPatch = buildPatch(sender, { maxFramerate: 24 }, writtenKeys); expect(clearPatch).toEqual({ maxFramerate: 24, maxBitrate: undefined }); await patchSender(sender, clearPatch, writtenKeys); const finalEncoding = sender.getParameters().encodings[0]; expect(finalEncoding.maxBitrate).toBeUndefined(); expect(finalEncoding.maxFramerate).toBe(24); expect(writtenKeys.get(sender)).toEqual(new Set(["maxFramerate"])); }); test("no sender means an empty patch and no call", () => { const writtenKeys = new WeakMap< RTCRtpSender, Set >(); expect(buildPatch(undefined, { maxBitrate: 1000 }, writtenKeys)).toEqual( {}, ); }); }); describe("lotusQuality screenshare simulcast cap (#12)", () => { test("caps the aggregate across a 3-layer simulcast publication, not per-layer", async () => { // A typical VP8 simulcast screenshare publication: low/medium/high // layers with existing presets, ordered low-to-high like LiveKit // publishes them. const sender = makeSender([ { maxBitrate: 150_000 }, { maxBitrate: 500_000 }, { maxBitrate: 1_500_000 }, ]); const writtenKeys = new WeakMap< RTCRtpSender, Set >(); const cap = 750_000; const patch = buildPatch(sender, { maxBitrate: cap }, writtenKeys); await patchSender(sender, patch, writtenKeys); const encodings = sender.getParameters().encodings; expect(encodings).toHaveLength(3); // Each layer keeps its old proportion of the total, but the aggregate // across all layers must not exceed the requested cap — that's the bug: // writing `cap` into every layer let the aggregate reach ~3x the cap. const oldTotal = 150_000 + 500_000 + 1_500_000; expect(encodings[0].maxBitrate).toBe( Math.floor((150_000 / oldTotal) * cap), ); expect(encodings[1].maxBitrate).toBe( Math.floor((500_000 / oldTotal) * cap), ); expect(encodings[2].maxBitrate).toBe( Math.floor((1_500_000 / oldTotal) * cap), ); // The highest layer still ends up with the largest share. expect(encodings[2].maxBitrate).toBeGreaterThan(encodings[1].maxBitrate!); expect(encodings[1].maxBitrate).toBeGreaterThan(encodings[0].maxBitrate!); const aggregate = encodings.reduce( (sum, enc) => sum + (enc.maxBitrate ?? 0), 0, ); expect(aggregate).toBeLessThanOrEqual(cap); }); test("a single encoding keeps the previous (non-scaled) behaviour", () => { const encodings: RTCRtpEncodingParameters[] = [{ maxBitrate: 2_000_000 }]; applyProportionalMaxBitrate(encodings, 500_000); expect(encodings).toEqual([{ maxBitrate: 500_000 }]); }); test("falls back to capping only the highest layer when there are no prior ratios", () => { const encodings: RTCRtpEncodingParameters[] = [ {}, // no prior cap on any layer to derive a ratio from {}, ]; applyProportionalMaxBitrate(encodings, 500_000); expect(encodings[0].maxBitrate).toBeUndefined(); expect(encodings[1].maxBitrate).toBe(500_000); }); });