feat: add playable flight and office screen sharing
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import { createOfficeScreenPanel, type MediaSurfaceDescriptor } from "../media/index.ts";
|
||||
|
||||
type Listener = (event: FakeEvent) => void;
|
||||
class FakeEvent {
|
||||
defaultPrevented = false;
|
||||
readonly type: string;
|
||||
readonly target: FakeElement;
|
||||
readonly key: string;
|
||||
readonly shiftKey: boolean;
|
||||
constructor(
|
||||
type: string,
|
||||
target: FakeElement,
|
||||
key = "",
|
||||
shiftKey = false,
|
||||
) {
|
||||
this.type = type;
|
||||
this.target = target;
|
||||
this.key = key;
|
||||
this.shiftKey = shiftKey;
|
||||
}
|
||||
preventDefault(): void { this.defaultPrevented = true; }
|
||||
}
|
||||
class FakeElement {
|
||||
readonly children: FakeElement[] = [];
|
||||
readonly attributes = new Map<string, string>();
|
||||
readonly listeners = new Map<string, Listener[]>();
|
||||
parentElement: FakeElement | null = null;
|
||||
className = "";
|
||||
textContent = "";
|
||||
hidden = false;
|
||||
disabled = false;
|
||||
type = "";
|
||||
id = "";
|
||||
readonly ownerDocument: FakeDocument;
|
||||
readonly tagName: string;
|
||||
constructor(ownerDocument: FakeDocument, tagName: string) {
|
||||
this.ownerDocument = ownerDocument;
|
||||
this.tagName = tagName;
|
||||
}
|
||||
append(...nodes: FakeElement[]): void { for (const node of nodes) { node.parentElement = this; this.children.push(node); } }
|
||||
setAttribute(name: string, value: string): void { this.attributes.set(name, value); }
|
||||
getAttribute(name: string): string | null { return this.attributes.get(name) ?? null; }
|
||||
addEventListener(type: string, listener: Listener): void {
|
||||
const list = this.listeners.get(type); if (list) list.push(listener); else this.listeners.set(type, [listener]);
|
||||
}
|
||||
dispatch(type: string, init: { key?: string; shiftKey?: boolean } = {}): FakeEvent {
|
||||
const event = new FakeEvent(type, this, init.key, init.shiftKey);
|
||||
for (const listener of this.listeners.get(type) ?? []) listener(event);
|
||||
return event;
|
||||
}
|
||||
focus(): void { this.ownerDocument.activeElement = this; }
|
||||
remove(): void {
|
||||
if (!this.parentElement) return;
|
||||
const index = this.parentElement.children.indexOf(this); if (index >= 0) this.parentElement.children.splice(index, 1);
|
||||
this.parentElement = null;
|
||||
}
|
||||
find(attribute: string, value: string): FakeElement {
|
||||
if (this.attributes.get(attribute) === value) return this;
|
||||
for (const child of this.children) { try { return child.find(attribute, value); } catch { /* next */ } }
|
||||
throw new Error(`missing [${attribute}=${value}]`);
|
||||
}
|
||||
text(): string { return this.textContent + this.children.map((child) => child.text()).join(""); }
|
||||
}
|
||||
class FakeDocument {
|
||||
activeElement: FakeElement | null = null;
|
||||
createElement(tag: string): FakeElement { return new FakeElement(this, tag.toUpperCase()); }
|
||||
}
|
||||
|
||||
const SURFACES: MediaSurfaceDescriptor[] = [
|
||||
{ screenId: "lobby-monitor", officeId: "hq", levelId: "level-1", roomId: "Lobby", kind: "tera:screen.monitor", bound: false },
|
||||
{ screenId: "commons-display", officeId: "hq", levelId: "level-2", roomId: "Commons", kind: "tera:screen.wall-display", bound: true },
|
||||
];
|
||||
|
||||
function setup() {
|
||||
const document = new FakeDocument();
|
||||
const container = document.createElement("div");
|
||||
const selected: string[] = [];
|
||||
const shares: string[] = [];
|
||||
const stops: string[] = [];
|
||||
const opts: [string, boolean][] = [];
|
||||
const panel = createOfficeScreenPanel({
|
||||
container: container as unknown as HTMLElement,
|
||||
surfaces: SURFACES,
|
||||
onSelect: (surface) => selected.push(surface.screenId),
|
||||
onRequestShare: (surface) => shares.push(surface.screenId),
|
||||
onStopShare: (surface) => stops.push(surface.screenId),
|
||||
onViewerOptIn: (surface, value) => opts.push([surface.screenId, value]),
|
||||
});
|
||||
return { document, container, panel, selected, shares, stops, opts };
|
||||
}
|
||||
|
||||
describe("office screen manager panel", () => {
|
||||
it("builds a closed labelled dialog with safe screen, room, and status text", () => {
|
||||
const { container, panel } = setup();
|
||||
const root = panel.root as unknown as FakeElement;
|
||||
assert.equal(container.children.length, 1);
|
||||
assert.equal(root.hidden, true);
|
||||
assert.equal(root.getAttribute("role"), "dialog");
|
||||
assert.equal(root.getAttribute("aria-modal"), "true");
|
||||
assert.match(root.text(), /lobby-monitor/);
|
||||
assert.match(root.text(), /Lobby/);
|
||||
assert.match(root.text(), /Media off/);
|
||||
assert.match(root.text(), /Media active/);
|
||||
});
|
||||
|
||||
it("selects, explicitly opts in, and emits share/stop intent without capture", () => {
|
||||
const { panel, selected, shares, stops, opts } = setup();
|
||||
panel.open();
|
||||
const root = panel.root as unknown as FakeElement;
|
||||
root.find("data-screen-id", "commons-display").dispatch("click");
|
||||
root.find("data-action", "opt-in").dispatch("click");
|
||||
root.find("data-action", "share").dispatch("click");
|
||||
root.find("data-action", "stop").dispatch("click");
|
||||
assert.deepEqual(selected, ["commons-display"]);
|
||||
assert.deepEqual(opts, [["commons-display", true]]);
|
||||
assert.deepEqual(shares, ["commons-display"]);
|
||||
assert.deepEqual(stops, ["commons-display"]);
|
||||
assert.deepEqual(panel.state().optedInScreenIds, ["commons-display"]);
|
||||
assert.equal("mediaDevices" in panel, false);
|
||||
});
|
||||
|
||||
it("updates defensively, removes stale consent, and represents an empty office", () => {
|
||||
const { panel } = setup();
|
||||
const root = panel.root as unknown as FakeElement;
|
||||
root.find("data-action", "opt-in").dispatch("click");
|
||||
const next = [{ ...SURFACES[1]!, screenId: "safe-text-<script>" }];
|
||||
panel.update(next);
|
||||
next[0]!.screenId = "mutated";
|
||||
assert.equal(panel.state().selectedId, "safe-text-<script>");
|
||||
assert.deepEqual(panel.state().optedInScreenIds, []);
|
||||
assert.match(root.text(), /safe-text-<script>/);
|
||||
panel.update([]);
|
||||
assert.equal(panel.state().selectedId, null);
|
||||
assert.match(root.text(), /No authored office screens/);
|
||||
});
|
||||
|
||||
it("traps tab focus, closes on Escape, restores focus, and disposes idempotently", () => {
|
||||
const { document, container, panel } = setup();
|
||||
const trigger = document.createElement("button");
|
||||
trigger.focus();
|
||||
panel.open();
|
||||
const root = panel.root as unknown as FakeElement;
|
||||
const first = root.find("data-screen-id", "lobby-monitor");
|
||||
const last = root.find("data-action", "close");
|
||||
last.focus();
|
||||
assert.equal(root.dispatch("keydown", { key: "Tab" }).defaultPrevented, true);
|
||||
assert.equal(document.activeElement, first);
|
||||
first.focus();
|
||||
assert.equal(root.dispatch("keydown", { key: "Tab", shiftKey: true }).defaultPrevented, true);
|
||||
assert.equal(document.activeElement, last);
|
||||
assert.equal(root.dispatch("keydown", { key: "Escape" }).defaultPrevented, true);
|
||||
assert.equal(panel.state().open, false);
|
||||
assert.equal(document.activeElement, trigger);
|
||||
panel.dispose();
|
||||
panel.dispose();
|
||||
assert.equal(container.children.length, 0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user