web/room/remote.js: input statements, floor statements and latency records written byte for byte, every statement read, the host's rules and the floor replayed over a room's records, the table from keyboard codes to HID usages; and RemoteViewer, which draws the host's screen (the page's WebCodecs decoder, AVC or HEVC in Annex B, every frame verified against its receipt first), shows what the host captures and who holds the floor, batches pointer, keys, composed text and pasted text into input statements under the grant's classes, asks for and gives back the floor, and signs a latency record of each applied input's frame at the display refresh that showed it. remote.test.mjs checks it against wai/live-conformance/remote/, the host agent's key table among it. The page draws a room with no composition descriptor, and names a screen track by its namespace. e2e: the remote scenario runs wai-remote-host with a pattern source (the platform's hardware encoder on macOS), two pages, the floor asked for and given, a click and a key applied with their latency records, an input with no control power refused, and the session the host records verified by remote_desktop_verify.py. Co-Authored-By: Claude Opus 5.5 <[email protected]>
64 lines
3.1 KiB
JavaScript
64 lines
3.1 KiB
JavaScript
// The room page's markup (`node --test web/room/markup.test.mjs`): every element it opens is
|
|
// closed, in order, so no section nests inside another by accident; and each mode's section
|
|
// holds its own controls and nothing of another's. A section left open nests everything after it,
|
|
// so a section the page hides for one mode would hide the Log and the binding table with it.
|
|
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const html = fs.readFileSync(fileURLToPath(new URL("./index.html", import.meta.url)), "utf8");
|
|
|
|
// HTML's void elements, which have no end tag.
|
|
const VOID = new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
|
|
|
|
// The elements of `src` by `id`, each with the ids of the elements it is inside, outermost first;
|
|
// and every tag that does not balance.
|
|
function parse(src) {
|
|
const body = src.replace(/<!--[\s\S]*?-->/g, "").replace(/<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "").replace(/<!doctype[^>]*>/i, "");
|
|
const stack = [];
|
|
const ids = new Map();
|
|
const errors = [];
|
|
const tag = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)\b([^>]*)>/g;
|
|
let m;
|
|
while ((m = tag.exec(body))) {
|
|
const [, close, raw, attrs] = m;
|
|
const name = raw.toLowerCase();
|
|
if (close) {
|
|
const top = stack.pop();
|
|
if (!top || top.name !== name) errors.push(`</${name}> closes <${top ? top.name : "nothing"}>`);
|
|
continue;
|
|
}
|
|
if (VOID.has(name) || /\/\s*$/.test(attrs)) continue;
|
|
const id = (attrs.match(/\bid="([^"]*)"/) || [])[1];
|
|
if (id) ids.set(id, stack.map((e) => e.id).filter(Boolean));
|
|
stack.push({ name, id });
|
|
}
|
|
for (const e of stack) errors.push(`<${e.name}${e.id ? ` id="${e.id}"` : ""}> is never closed`);
|
|
return { ids, errors };
|
|
}
|
|
|
|
const { ids, errors } = parse(html);
|
|
|
|
test("every element the room page opens is closed, in order", () => {
|
|
assert.deepEqual(errors, []);
|
|
});
|
|
|
|
test("the Log, the binding table and the co-watch section are outside the co-performance section", () => {
|
|
for (const id of ["log", "binding-debug", "cowatch", "cowatch-media", "cw-sync"]) {
|
|
assert.ok(ids.has(id), `#${id} is on the page`);
|
|
assert.ok(!ids.get(id).includes("co"), `#${id} is inside ${ids.get(id).map((x) => `#${x}`).join(" > ")}`);
|
|
}
|
|
});
|
|
|
|
test("the remote desktop's controls are in its own section, outside the others", () => {
|
|
for (const id of ["remote-canvas", "remote-request", "remote-release", "remote-state"]) {
|
|
assert.ok(ids.get(id).includes("remote"), `#${id} is inside #remote`);
|
|
assert.ok(!ids.get(id).includes("co") && !ids.get(id).includes("cowatch"), `#${id} is inside another mode's section`);
|
|
}
|
|
});
|
|
|
|
test("each mode's controls are inside its own section", () => {
|
|
for (const id of ["co-lead", "co-input", "co-turns", "co-fidelity"]) assert.ok(ids.get(id).includes("co"), `#${id} is inside #co`);
|
|
for (const id of ["cw-play", "cw-pause", "cw-rate", "cw-sync"]) assert.ok(ids.get(id).includes("cowatch") && !ids.get(id).includes("co"), `#${id} is inside #cowatch alone`);
|
|
});
|