import test from "node:test"; import assert from "node:assert/strict"; import { initializeAgent } from "../src/scripts/agent.js"; function eventTarget(extra = {}) { const listeners = new Map(); return { ...extra, addEventListener: (key, fn) => listeners.set(key, fn), removeEventListener: (key) => listeners.delete(key), emit: (key, event = {}) => listeners.get(key)?.(event), }; } function fixture(search = "", reduced = false) { let intersection; let serial = 0; let disconnected = false; const frames = new Map(); const replay = eventTarget({ disabled: false }); const announcement = { textContent: "" }; const fields = [1, 1, 2, 2, 2, 2, 2, 2, 3].map((group) => ({ dataset: { agentField: String(group) }, })); const slots = new Map( ["assignment", "queue-note", "status"].map((name) => [ `[data-agent-${name}]`, { textContent: "" }, ]), ); const root = { dataset: {}, parentElement: { querySelector: (selector) => selector === "[data-agent-replay]" ? replay : announcement, }, querySelector: (selector) => selector === "[data-agent-replay]" ? replay : slots.get(selector), querySelectorAll: () => fields, }; const media = eventTarget({ matches: reduced }); const document = eventTarget({ hidden: false }); const env = { location: { search }, innerHeight: 800, document, matchMedia: () => media, requestAnimationFrame: (callback) => { frames.set(++serial, callback); return serial; }, cancelAnimationFrame: (id) => frames.delete(id), IntersectionObserver: class { constructor(callback) { intersection = callback; } observe() {} disconnect() { disconnected = true; } }, }; const dispose = initializeAgent(root, env); return { root, replay, announcement, fields, slots, media, document, dispose, frames: () => frames.size, disconnected: () => disconnected, visible(height = 600, shown = 600) { intersection([ { isIntersecting: shown > 0, boundingClientRect: { height }, intersectionRect: { height: shown }, }, ]); }, tick(time) { const item = frames.entries().next().value; if (item) { frames.delete(item[0]); item[1](time); } }, }; } test("controller renders grouped fields once, pauses offscreen/hidden, and replays", () => { const app = fixture(); assert.equal(app.root.dataset.agentPhase, "queued"); assert.equal(app.frames(), 0); assert.ok(app.fields.every((field) => field.dataset.filled === "false")); app.visible(); app.tick(0); app.tick(800); assert.equal(app.root.dataset.agentPhase, "assigned"); app.visible(600, 200); assert.equal(app.frames(), 0); app.tick(9000); app.visible(); app.tick(10000); app.tick(10900); assert.equal(app.root.dataset.agentPhase, "filling"); assert.equal( app.fields.filter((field) => field.dataset.filled === "true").length, 2, ); app.document.hidden = true; app.document.emit("visibilitychange"); assert.equal(app.frames(), 0); app.document.hidden = false; app.document.emit("visibilitychange"); app.tick(50000); app.tick(51100); assert.equal( app.fields.filter((field) => field.dataset.filled === "true").length, 8, ); app.tick(52100); assert.ok(app.fields.every((field) => field.dataset.filled === "true")); app.tick(53300); assert.equal(app.root.dataset.agentPhase, "review"); assert.equal(app.frames(), 0); assert.equal(app.replay.disabled, false); app.visible(600, 0); app.visible(); assert.equal( app.frames(), 0, "completed animation does not restart on reentry", ); app.replay.emit("click"); assert.equal(app.root.dataset.agentPhase, "queued"); assert.equal(app.frames(), 1); app.dispose(); assert.equal(app.frames(), 0); assert.equal(app.disconnected(), true); }); test("capture states remain fixed and replay cannot trigger animation", () => { for (const phase of ["queued", "assigned", "filling", "review"]) { const app = fixture(`?capture=1&phase=${phase}`); app.visible(); app.replay.emit("click"); app.tick(10000); assert.equal(app.root.dataset.agentPhase, phase); assert.equal(app.frames(), 0); assert.equal(app.replay.disabled, true); app.dispose(); } }); test("reduced motion is settled on load and when preference changes during playback", () => { const reduced = fixture("", true); assert.equal(reduced.root.dataset.agentPhase, "review"); reduced.visible(); assert.equal(reduced.frames(), 0); reduced.dispose(); const app = fixture(); app.visible(1100, 399); assert.equal(app.frames(), 0); app.visible(1100, 400); app.tick(0); app.tick(1800); app.media.emit("change", { matches: false }); assert.equal(app.root.dataset.agentPhase, "filling"); app.media.emit("change", { matches: true }); assert.equal(app.root.dataset.agentPhase, "review"); assert.equal(app.frames(), 0); assert.equal(app.replay.disabled, true); app.dispose(); });