1// The application, walked. These specs assert arrangement, not pixels: the2// URL a doorway lands on, the island a page carries, the controls a topbar3// offers, the session the next page remembers. The registry underneath is4// dist/e2e/bundles — the committed corpus, packed by tests/e2e/prep — so5// every expectation below is a fact about bytes in this repository.6import { expect, test, type Page } from "@playwright/test";78// The corpus contract, spelled out rather than discovered, so that a volume9// quietly failing to serve is a red test and not a shorter loop.10const MARS = { slug: "mars", world: "global", title: "Mars", lenses: 2 };11const BEND = { slug: "bend-or", world: "2026-08-02", title: "Bend, Oregon", lenses: 1 };12// The included Earth volume: the committed bundle the desktop embeds,13// installed into the e2e registry by tests/e2e/prep beside the corpus. It is14// the one entry with real rasters.15const EARTH = { slug: "earth", world: "earth", title: "Earth", lenses: 1 };1617// island reads the page's own account of the arrangement: the inert JSON18// script node every explorer page carries.19async function island(page: Page) {20 const text = await page.locator("#atlas-session-island").textContent();21 expect(text, "the page carries no session island").toBeTruthy();22 return JSON.parse(text!) as {23 last: string;24 entry: null | { volume: string; world: string; lens: number };25 };26}2728interface SeamSnapshot {29 world: string;30 tileStats: { loaded: number; errors: number };31 sync: { drawn: number };32}3334async function seamSnapshot(page: Page): Promise<SeamSnapshot | null> {35 return page.evaluate(() => {36 const host = window as unknown as {37 __atlasSeam?: { snapshot(): SeamSnapshot };38 };39 return host.__atlasSeam?.snapshot() ?? null;40 });41}4243test.describe("the doorways", () => {44 test("the root is a doorway to a real world URL", async ({ page }) => {45 await page.goto("/");46 await expect(page).toHaveURL(/\/v\/[a-z0-9-]+\/[a-z0-9-]+$/);47 });4849 test("open names a volume and lands on its world", async ({ page }) => {50 await page.goto(`/open?volume=${MARS.slug}&world=${MARS.world}`);51 await expect(page).toHaveURL(`/v/${MARS.slug}/${MARS.world}`);52 });5354 test("the catalog serves every corpus volume", async ({ request }) => {55 const response = await request.get("/data/catalog.json");56 expect(response.ok()).toBeTruthy();57 const catalog = (await response.json()) as {58 volumes: { slug: string; stamp: string; worlds: unknown[] }[];59 };60 const slugs = catalog.volumes.map((v) => v.slug).sort();61 expect(slugs).toEqual([BEND.slug, EARTH.slug, MARS.slug].sort());62 for (const volume of catalog.volumes) {63 expect(volume.stamp).toMatch(/^[0-9a-f]{64}$/);64 expect(volume.worlds.length).toBeGreaterThan(0);65 }66 });6768 test("a volume nobody installed is not found", async ({ page }) => {69 const response = await page.goto("/v/nowhere/nothing");70 expect(response?.status()).toBe(404);71 });7273 // The raster leg, end to end: the included Earth carries real tiles, so the74 // data plane can be asked for imagery and answer with an image — the one75 // assertion the tile-less corpus volumes can never carry.76 test("the included Earth serves a real raster tile", async ({ request }) => {77 const response = await request.get("/data/catalog.json");78 const catalog = (await response.json()) as {79 volumes: { slug: string; stamp: string }[];80 };81 const earth = catalog.volumes.find((v) => v.slug === EARTH.slug);82 expect(earth, "the catalog does not list earth").toBeTruthy();83 const tile = await request.get(84 `/data/v/${EARTH.slug}/${earth!.stamp.slice(0, 12)}/tiles/earth/0/0/0.jpg`,85 );86 expect(tile.ok()).toBeTruthy();87 expect(tile.headers()["content-type"]).toBe("image/jpeg");88 expect((await tile.body()).length).toBeGreaterThan(1000);89 });90});9192test.describe("the explorer page", () => {93 for (const volume of [MARS, BEND, EARTH]) {94 test(`${volume.slug} arrives arranged`, async ({ page }) => {95 await page.goto(`/v/${volume.slug}/${volume.world}`);9697 // The page's own account agrees with its address.98 const state = await island(page);99 expect(state.entry?.volume).toBe(volume.slug);100 expect(state.entry?.world).toBe(volume.world);101102 // The topbar's selects say where the reader is and what else there is.103 // A crumb with one option is a fact, not a choice: every installed104 // volume carries a single world, so the world crumb reads but does not105 // edit, while the volume crumb (three volumes installed) stays a real106 // select.107 await expect(page.locator("#volume-select")).toHaveValue(volume.slug);108 await expect(page.locator("#volume-select")).toBeEnabled();109 await expect(page.locator("#world-select")).toHaveValue(volume.world);110 await expect(page.locator("#world-select")).toBeDisabled();111 const lensField = page.locator("#lens-field");112 if (volume.lenses > 1) {113 await expect(lensField).toBeVisible();114 await expect(page.locator("#lens-select")).toBeEnabled();115 await expect(page.locator("#lens-select option")).toHaveCount(volume.lenses);116 } else {117 await expect(lensField).toBeHidden();118 }119120 // The viewport is the seam's: the custom elements are there, and the121 // chart has booted far enough to put a canvas on the page. No tile122 // assertions — the corpus commits no rasters, and surviving that is123 // the render suite's own proof.124 await expect(page.locator("atlas-viewport")).toHaveCount(1);125 await expect(page.locator("atlas-chart")).toHaveCount(1);126 await expect(page.locator("atlas-globe")).toHaveCount(1);127 await expect(page.locator("atlas-chart canvas, #map canvas").first()).toBeAttached({128 timeout: 15_000,129 });130131 // The keyboard contract rides every page.132 await expect(page.locator("#atlas-shortcuts")).toBeAttached();133 });134 }135136 test("a lens is addressed by name and remembered", async ({ page }) => {137 await page.goto(`/v/${MARS.slug}/${MARS.world}`);138 const options = page.locator("#lens-select option");139 await expect(options).toHaveCount(MARS.lenses);140 const second = await options.nth(1).getAttribute("value");141 expect(second).toBeTruthy();142143 await page.locator("#lens-select").selectOption(second!);144 // The session answer swaps the shell and republishes the island.145 await expect146 .poll(async () => (await island(page)).entry?.lens, {147 message: "the island never recorded the lens change",148 })149 .toBe(1);150 // And the keyboard goes back to the map: a crumb that kept focus would151 // spend the reader's next letter walking its own options.152 await expect153 .poll(() => page.evaluate(() => document.activeElement?.id ?? ""), {154 message: "the crumb kept the keyboard after it was answered",155 })156 .toBe("map");157158 // A fresh visit to the same world resumes the same lens.159 await page.goto(`/v/${MARS.slug}/${MARS.world}`);160 expect((await island(page)).entry?.lens).toBe(1);161 });162163 test("the last volume is where the root door leads", async ({ page }) => {164 await page.goto(`/v/${MARS.slug}/${MARS.world}`);165 await expect(page.locator("#atlas-shell")).toBeAttached();166 await page.goto("/");167 await expect(page).toHaveURL(`/v/${MARS.slug}/${MARS.world}`);168 });169170 test("native features arrive through demand pages and are drawn", async ({ page }) => {171 const requested: string[] = [];172 page.on("request", (request) => requested.push(request.url()));173174 await page.goto(`/v/${BEND.slug}/${BEND.world}`);175 await expect.poll(async () => (await seamSnapshot(page))?.sync.drawn ?? 0, {176 message: "the native feature pages never produced anything drawable",177 timeout: 15_000,178 }).toBeGreaterThan(0);179180 const paths = requested.map((raw) => new URL(raw).pathname);181 expect(paths.some((path) => path.endsWith("/outline.json"))).toBeTruthy();182 expect(paths.some((path) => path.includes("/features/") && path.endsWith(".json"))).toBeTruthy();183 expect(paths.some((path) => path.endsWith("/data/features.pack"))).toBeFalsy();184 });185186 test("Earth draws raster tiles without load errors", async ({ page }) => {187 await page.goto(`/v/${EARTH.slug}/${EARTH.world}`);188 await expect.poll(async () => (await seamSnapshot(page))?.tileStats.loaded ?? 0, {189 message: "Earth's raster never loaded a tile",190 timeout: 15_000,191 }).toBeGreaterThan(0);192193 const snapshot = await seamSnapshot(page);194 expect(snapshot?.world).toBe(EARTH.title);195 expect(snapshot?.tileStats.errors).toBe(0);196 });197});