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 / scene.test.ts
scene.test.ts 4.7K
1// The scene description, read off a synthetic node.2//3// `readScene` takes the smallest structural interface a real `Element`4// satisfies, so the contract in docs/app.md §4 can be tested without a5// browser: the node below is a hand-written stand-in for what6// `internal/app/templates/viewport.tmpl` renders, and it is the same shape7// the templates wave produces.89import test from "node:test";10import { strict as assert } from "node:assert";11import { EMPTY_SCENE, readCamera, readScene, sceneChange } from "../scene/read.ts";12import type { StateNode } from "../scene/read.ts";1314/** A stand-in for the rendered `#atlas-viewport-state` node. */15function node(16 attributes: Record<string, string>,17 children: { className: string; value: string }[] = [],18): StateNode {19 return {20 getAttribute: (name) => attributes[name] ?? null,21 querySelectorAll: (selector) => children22 .filter((child) => selector === `data.${child.className}`)23 .map((child) => ({ getAttribute: (name: string) => name === "value" ? child.value : null })),24 };25}2627const full = () => node({28 "data-volume": "bend-or",29 "data-base": "/data/v/bend-or/f0feba1cd00c",30 "data-world": "2026-08-02",31 "data-lens": "Basemap",32 "data-lens-index": "0",33 "data-surface": "plane",34 "data-selected": "1849",35 "data-search": "shrine",36 "data-grid-system": "geohash",37 "data-grid-cell": "9q5c",38 "data-subgrid": "1",39 "data-camera": "4096,-4096,1.34,0",40}, [41 { className: "hidden-collection", value: "12" },42 { className: "hidden-collection", value: "31" },43 { className: "highlighted-feature", value: "77" },44 { className: "label-override", value: "39191589=quiet" },45]);4647test("every field of the state node arrives", () => {48 const scene = readScene(full());49 assert.equal(scene.volume, "bend-or");50 assert.equal(scene.base, "/data/v/bend-or/f0feba1cd00c");51 assert.equal(scene.world, "2026-08-02");52 assert.equal(scene.lens, "Basemap");53 assert.equal(scene.surface, "plane");54 assert.equal(scene.selected, "1849");55 assert.equal(scene.search, "shrine");56 assert.equal(scene.gridSystem, "geohash");57 assert.equal(scene.gridCell, "9q5c");58 assert.equal(scene.subgrid, 1);59 assert.deepEqual([...scene.hidden], ["12", "31"]);60 assert.deepEqual([...scene.highlighted], ["77"]);61 assert.equal(scene.overrides.get("39191589"), "quiet");62 assert.deepEqual(scene.camera, { x: 4096, y: -4096, zoom: 1.34, rotation: 0 });63});6465test("a page with no volume open reads as the empty scene", () => {66 assert.deepEqual(readScene(null), EMPTY_SCENE);67 const bare = readScene(node({}));68 assert.equal(bare.volume, "");69 assert.equal(bare.surface, "plane");70 assert.equal(bare.camera, null);71 assert.equal(bare.hidden.size, 0);72});7374test("a camera is four numbers or it is absent", () => {75 assert.deepEqual(readCamera("1,2,3,4"), { x: 1, y: 2, zoom: 3, rotation: 4 });76 // Until the seam has reported one, there is none — and half a camera77 // points somewhere nobody chose, so it is treated as none too.78 assert.equal(readCamera(""), null);79 assert.equal(readCamera("1,2,3"), null);80 assert.equal(readCamera("1,2,3,x"), null);81});8283test("an override is read whole or not at all", () => {84 const scene = readScene(node({}, [85 { className: "label-override", value: "1=always" },86 { className: "label-override", value: "2=shouting" },87 { className: "label-override", value: "=quiet" },88 { className: "label-override", value: "3" },89 ]));90 assert.deepEqual([...scene.overrides], [["1", "always"]]);91});9293test("what moved is named, so a swap reconciles rather than rebuilds", () => {94 const was = readScene(full());95 assert.equal(sceneChange(was, readScene(full())).any, false);9697 const hidden = readScene(node({ ...attributesOf(full()), "data-search": "shrine" }, [98 { className: "hidden-collection", value: "12" },99 ]));100 const change = sceneChange(was, hidden);101 assert.equal(change.filters, true);102 assert.equal(change.world, false);103 assert.equal(change.lens, false);104105 // A different world is a different everything: a lens change rides along,106 // because the lens list belongs to the world.107 const elsewhere = sceneChange(was, readScene(node({ "data-volume": "bend-or", "data-world": "other" })));108 assert.equal(elsewhere.world, true);109 assert.equal(elsewhere.lens, true);110});111112function attributesOf(source: StateNode): Record<string, string> {113 const names = [114 "data-volume", "data-base", "data-world", "data-lens", "data-lens-index",115 "data-surface", "data-selected", "data-search", "data-grid-system",116 "data-grid-cell", "data-subgrid", "data-camera",117 ];118 const out: Record<string, string> = {};119 for (const name of names) {120 const value = source.getAttribute(name);121 if (value !== null) out[name] = value;122 }123 return out;124}