94 lines
3.3 KiB
TypeScript
94 lines
3.3 KiB
TypeScript
/*
|
|||
|
|
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 { 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<keyof RTCRtpEncodingParameters>
|
||
|
|
>();
|
||
|
|
|
||
|
|
// 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<keyof RTCRtpEncodingParameters>
|
||
|
|
>();
|
||
|
|
|
||
|
|
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<keyof RTCRtpEncodingParameters>
|
||
|
|
>();
|
||
|
|
expect(buildPatch(undefined, { maxBitrate: 1000 }, writtenKeys)).toEqual(
|
||
|
|
{},
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|