open-standards/joule-code/joule-code-rs/crates/joule-moq-web/web/room/termination.test.mjs
2026-10-22 13:51:45 -04:00

101 lines
5.8 KiB
JavaScript

// The page's stream-termination rules (./termination.js), in Node: the declaration it reads
// (capability-constraints §4.1), the refresh rate it measures, which reason is due
// (stream-termination §4.3, §6.2) and how objects gather into segments (§4.5).
//
// node --test web/room/termination.test.mjs
import test from "node:test";
import assert from "node:assert/strict";
import { declarationFrom, refreshFromIntervals, dueReason, freshState, observe, unseen, Gatherer, isActivation, bitDepthOf, ladderOf, SIGNAL, SEEN, UNSEEN } from "./termination.js";
test("the declaration: CSS pixels times the ratio, sides without orientation, depth per component", () => {
assert.deepEqual(declarationFrom({ width: 1440, height: 900, dpr: 2, colorDepth: 30, hdr: true, refreshMilli: 120000 }),
{ long: 2880, short: 1800, refresh_milli: 120000, bit_depth: 10, hdr: true, long_mm: 0, short_mm: 0 });
// Portrait, a fractional ratio rounded to the nearest pixel, and the 24 a user agent gives
// when it does not say: 8 bits.
assert.deepEqual(declarationFrom({ width: 390, height: 844, dpr: 2.625, colorDepth: 24, hdr: false, refreshMilli: 60000 }),
{ long: 2216, short: 1024, refresh_milli: 60000, bit_depth: 8, hdr: false, long_mm: 0, short_mm: 0 });
});
test("the refresh rate: the nearest whole rate to the median interval", () => {
assert.equal(refreshFromIntervals(Array(60).fill(16667)), 60000);
// A 59.94 Hz panel declares 60, under which a 59.94 rendition is within.
assert.equal(refreshFromIntervals(Array(60).fill(16683)), 60000);
assert.equal(refreshFromIntervals([8333, 8334, 8333, 50000, 8333]), 120000, "one late frame does not move the median");
assert.equal(refreshFromIntervals([]), null);
});
const video = { media: "video", start: "user", unseen_grace_ms: 3000, idle_ms: 0, autoplay_grace_ms: 0 };
const S = 1_000_000;
test("hidden is due after the grace, and not once the page is visible again", () => {
let st = freshState(0);
st = observe(st, SIGNAL.visibility, UNSEEN, 5 * S);
assert.equal(dueReason(video, st, 8 * S - 1), null);
assert.equal(dueReason(video, st, 8 * S), "hidden");
// A repeated observation does not move `since`.
assert.equal(dueReason(video, observe(st, SIGNAL.visibility, UNSEEN, 7 * S), 8 * S), "hidden");
assert.equal(dueReason(video, observe(st, SIGNAL.visibility, SEEN, 6 * S), 20 * S), null);
assert.equal(unseen(st), true);
});
test("occluded likewise; audio is never ended for being unseen", () => {
const st = observe(freshState(0), SIGNAL.occlusion, UNSEEN, S);
assert.equal(dueReason(video, st, 4 * S), "occluded");
assert.equal(dueReason({ ...video, media: "audio" }, observe(st, SIGNAL.visibility, UNSEEN, S), 60 * S), null);
});
test("idle after the timeout from the last interaction; autoplay unattended only with none", () => {
const idle = { ...video, idle_ms: 30000 };
let st = observe(freshState(0), SIGNAL.interaction, 1, 4 * S);
assert.equal(dueReason(idle, st, 34 * S - 1), null);
assert.equal(dueReason(idle, st, 34 * S), "idle");
const auto = { ...video, start: "autoplay", autoplay_grace_ms: 20000 };
assert.equal(dueReason(auto, freshState(0), 20 * S), "autoplay_unattended");
assert.equal(dueReason(auto, st, 25 * S), null, "an interaction makes it attended");
assert.equal(dueReason({ ...auto, start: "user" }, freshState(0), 25 * S), null);
});
test("objects gather into segments of at least the span, closed by the next object, never overlapping", () => {
const g = new Gatherer(2 * S);
const o = (k, r = 0) => ({ hash: k.toString(16).padStart(64, "0"), bytes: 100, mediaUs: k * 100_000, durUs: 100_000, rendition: r });
let closed = [];
for (let k = 0; k < 25; k++) closed = closed.concat(g.add(o(k)));
assert.equal(closed.length, 1);
assert.deepEqual([closed[0].media_start, closed[0].media_end, closed[0].hashes.length], [0, 2 * S, 20]);
assert.deepEqual(g.add(o(24)), [], "a repeated object is not gathered again");
assert.deepEqual(g.add(o(10)), [], "nor one before the last segment's end");
const r = g.add(o(25, 1));
assert.equal(r.length, 1, "a change of rendition closes the segment");
assert.deepEqual([r[0].media_start, r[0].media_end, r[0].rendition], [2 * S, 2_500_000, 0]);
const last = g.take();
assert.deepEqual([last.media_start, last.media_end, last.rendition], [2_500_000, 2_600_000, 1]);
assert.equal(g.take(), null);
// A gap longer than the span closes the segment where its last object ends.
const h = new Gatherer(2 * S);
h.add(o(0));
h.add(o(1));
const gapped = h.add(o(60));
assert.deepEqual([gapped[0].media_start, gapped[0].media_end, gapped[0].hashes.length], [0, 200_000, 2]);
// With nothing gathered, an object before the last segment's end starts no segment.
assert.deepEqual(g.add(o(20, 1)), []);
assert.equal(g.take(), null, "a segment would overlap the one before it");
});
test("activation-triggering input events (HTML §6.4)", () => {
const e = (type, extra = {}) => ({ type, isTrusted: true, ...extra });
assert.equal(isActivation(e("keydown", { key: "a" })), true);
assert.equal(isActivation(e("keydown", { key: "Escape" })), false);
assert.equal(isActivation(e("pointerdown", { pointerType: "mouse" })), true);
assert.equal(isActivation(e("pointerdown", { pointerType: "touch" })), false);
assert.equal(isActivation(e("pointerup", { pointerType: "touch" })), true);
assert.equal(isActivation({ ...e("mousedown"), isTrusted: false }), false, "a script's event is not the viewer's");
});
test("the ladder from a catalog's tracks", () => {
assert.equal(bitDepthOf("vp09.00.10.08"), 8);
assert.equal(bitDepthOf("av01.0.08M.10"), 10);
assert.equal(bitDepthOf("vp8"), 8);
assert.deepEqual(ladderOf([{ width: 640, height: 480, framerate: 30, codec: "vp09.00.10.08" }]),
[{ width: 640, height: 480, frame_rate_milli: 30000, bit_depth: 8, transfer: 1 }]);
});