clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

main
atlas / render / test / report.test.ts
report.test.ts 5.9K
1// The three reports that leave the seam, checked against the page they write to.2//3// `render/data/report.ts` is the one module in the lane that touches the4// document and never touches the network, and both halves of that are worth5// pinning: it fills fields the application rendered and raises an event the6// application listens for, and it does nothing else at all. So the page here7// is a stub of exactly what `internal/app/templates/shell.tmpl` renders -- two8// hidden forms and their fields -- and the assertions are about what lands in9// them and what is dispatched afterwards.10//11// The rule the pick tests exist for is the miss. A click on open water is not12// a request to close the card the reader is reading; the card is put away by13// Escape and by its own button, and a report that posted an empty identity14// would take it away on every stray click. The cell's report keeps the same15// rule for a reason of its own: an empty address *is* a place -- the root --16// but neither surface that reports one can mean it, and the sphere's descent17// answers empty for "there is nothing below this".1819import test from "node:test";20import { strict as assert } from "node:assert";21import { reportCamera, reportGridPick, reportPick } from "../data/report.ts";2223interface Field {24 value: string;25}2627/** What the page offers the report, and what it heard back. */28interface Page {29 readonly fields: Map<string, Field>;30 readonly events: CustomEvent[];31}3233/**34 * A page carrying the named nodes and nothing else. `mount([])` is the host35 * that rendered no such form: the report is lost, and nothing throws.36 */37function mount(ids: string[]): Page {38 const fields = new Map<string, Field>();39 const nodes = new Map<string, unknown>();40 for (const id of ids) {41 const field: Field = { value: "" };42 fields.set(id, field);43 nodes.set(id, field);44 }45 // The forms themselves are only ever looked up, never written.46 if (ids.some((id) => id.startsWith("#atlas-pick"))) nodes.set("#atlas-pick", {});47 if (ids.some((id) => id.startsWith("#atlas-camera"))) nodes.set("#atlas-camera", {});48 if (ids.some((id) => id.startsWith("#atlas-grid-pick"))) nodes.set("#atlas-grid-pick", {});49 const events: CustomEvent[] = [];50 globalThis.document = {51 querySelector: (selector: string) => nodes.get(selector) ?? null,52 } as unknown as Document;53 globalThis.window = {54 dispatchEvent: (event: Event) => {55 events.push(event as CustomEvent);56 return true;57 },58 } as unknown as Window & typeof globalThis;59 return { fields, events };60}6162test("a pick fills the field the page rendered and says so", () => {63 const page = mount(["#atlas-pick-feature"]);64 reportPick({ feature: "1849", kind: "area" });65 assert.equal(page.fields.get("#atlas-pick-feature")?.value, "1849");66 assert.equal(page.events.length, 1);67 const raised = page.events[0];68 assert.equal(raised?.type, "atlas:pick");69 // It is heard on the window, where the form listens, so it does not travel.70 assert.equal(raised?.bubbles, false);71});7273test("the kind travels in the event and posts no field", () => {74 const page = mount(["#atlas-pick-feature"]);75 reportPick({ feature: "1849", kind: "point" });76 const detail = page.events[0]?.detail as { feature: string; kind: string };77 assert.deepEqual(detail, { feature: "1849", kind: "point" });78 // One field posts, which is the identity. The session looks the rest up.79 assert.equal(page.fields.size, 1);80});8182test("a miss posts nothing and leaves the open card alone", () => {83 const page = mount(["#atlas-pick-feature"]);84 const field = page.fields.get("#atlas-pick-feature");85 assert.ok(field);86 field.value = "1849";87 reportPick({ feature: "", kind: "" });88 assert.equal(field.value, "1849", "a miss overwrote the selection that was posted");89 assert.equal(page.events.length, 0, "a miss posted a request");90});9192test("a page that renders no pick form loses the report and nothing else", () => {93 const page = mount([]);94 assert.doesNotThrow(() => reportPick({ feature: "1849", kind: "point" }));95 assert.equal(page.events.length, 0);96});9798test("a cell fills the address the page rendered and says so", () => {99 const page = mount(["#atlas-grid-pick-cell"]);100 reportGridPick("9qb");101 assert.equal(page.fields.get("#atlas-grid-pick-cell")?.value, "9qb");102 const raised = page.events[0];103 assert.equal(raised?.type, "atlas:grid-pick");104 assert.equal(raised?.bubbles, false);105 assert.deepEqual(raised?.detail, { cell: "9qb" });106});107108test("an empty address is a miss, not the root", () => {109 const page = mount(["#atlas-grid-pick-cell"]);110 const field = page.fields.get("#atlas-grid-pick-cell");111 assert.ok(field);112 field.value = "9qb";113 reportGridPick("");114 assert.equal(field.value, "9qb", "a miss overwrote the address that was posted");115 assert.equal(page.events.length, 0, "the telescope's floor was posted as the root");116});117118test("a page that renders no grid-pick form loses the report and nothing else", () => {119 const page = mount([]);120 assert.doesNotThrow(() => reportGridPick("9qb"));121 assert.equal(page.events.length, 0);122});123124test("the camera fills all five fields and says so", () => {125 const page = mount([126 "#atlas-camera-world", "#atlas-camera-x", "#atlas-camera-y",127 "#atlas-camera-zoom", "#atlas-camera-rotation",128 ]);129 reportCamera({130 volume: "neon-harbor", world: "old-quarter", x: 120.5, y: -40, zoom: 6.25, rotation: 0.5,131 });132 assert.equal(page.fields.get("#atlas-camera-world")?.value, "old-quarter");133 assert.equal(page.fields.get("#atlas-camera-zoom")?.value, "6.25");134 assert.equal(page.fields.get("#atlas-camera-rotation")?.value, "0.5");135 assert.equal(page.events[0]?.type, "atlas:camera");136});137138test("a page that renders no camera form loses the report and nothing else", () => {139 const page = mount([]);140 assert.doesNotThrow(() => reportCamera({141 volume: "neon-harbor", world: "old-quarter", x: 0, y: 0, zoom: 1, rotation: 0,142 }));143 assert.equal(page.events.length, 0);144});