// Covers the region-select drag tool (mousedown/mousemove/mouseup handlers wired in map.js's // startRegionSelect/updateRegionPreview/finishRegionSelect) and the client-side glTF export // pipeline (export-worker.js) — the second UI surface flagged throughout MCMapper's Phase 5 work // as verified only via a headless pipeline run against seeded data, never an actual browser // drag gesture. See README's e2e section. // // The seeded dataset (see global-setup.ts) fills a 5x5-chunk solid terrain footprint centered on // the world origin — where the map's default view is centered — so a drag anywhere near the // visible center reliably selects real, meshable terrain without computing exact Leaflet // pixel<->world math. import { test, expect, type Page } from "@playwright/test"; async function waitForMapReady(page: Page) { await page.goto("/"); await expect(page.getByTestId("region-toggle")).toBeVisible(); await page.locator("#map.leaflet-container").waitFor(); } async function dragOnMap(page: Page, fromFrac: number, toFrac: number) { const box = await page.locator("#map").boundingBox(); if (!box) throw new Error("#map has no bounding box"); const start = { x: box.x + box.width * fromFrac, y: box.y + box.height * fromFrac }; const end = { x: box.x + box.width * toFrac, y: box.y + box.height * toFrac }; await page.mouse.move(start.x, start.y); await page.mouse.down(); await page.mouse.move((start.x + end.x) / 2, (start.y + end.y) / 2, { steps: 5 }); await page.mouse.move(end.x, end.y, { steps: 5 }); await page.mouse.up(); } test("dragging on the map selects a region and reports the chunk count", async ({ page }) => { await waitForMapReady(page); await page.getByTestId("region-toggle").click(); await dragOnMap(page, 0.4, 0.6); await expect(page.getByTestId("region-status")).toHaveText(/^\d+ chunks? selected$/); await expect(page.getByTestId("region-export")).toBeVisible(); }); test("exporting a selected region downloads a non-empty .glb file", async ({ page }) => { await waitForMapReady(page); await page.getByTestId("region-toggle").click(); await dragOnMap(page, 0.35, 0.65); await expect(page.getByTestId("region-status")).toHaveText(/^\d+ chunks? selected$/); const [download] = await Promise.all([ page.waitForEvent("download"), page.getByTestId("region-export").click(), ]); expect(download.suggestedFilename()).toMatch(/\.glb$/); const path = await download.path(); expect(path).not.toBeNull(); const { statSync } = await import("node:fs"); const size = statSync(path!).size; // A well-formed GLB has at minimum a 12-byte header + JSON chunk header — real seeded terrain // should produce something far larger (actual mesh geometry), so this also catches an // accidentally-empty export, not just a missing file. expect(size).toBeGreaterThan(200); }); test("an oversized selection is rejected with a clear message instead of exporting", async ({ page }) => { await waitForMapReady(page); // Zoom out to the map's minimum (minZoom: -4, see map.js's init()) so many more chunks fit in // the same viewport, making it easy to drag past the 64-chunk cap without needing to compute // exact pixel<->world math. Leaflet's zoom-out control gains `leaflet-disabled` once at // minZoom — waiting for that (rather than a fixed click count) avoids racing the zoom // animation, which silently drops clicks fired faster than it can keep up. const zoomOut = page.locator(".leaflet-control-zoom-out"); for (let i = 0; i < 8; i++) { if (await zoomOut.evaluate((el) => el.classList.contains("leaflet-disabled"))) break; await zoomOut.click(); await page.waitForTimeout(300); } await page.getByTestId("region-toggle").click(); // Not 0.02 — that lands the mousedown on Leaflet's own top-left zoom control, which stops // event propagation before it reaches the map's mousedown listener (startRegionSelect never // fires). Starting past that corner still covers a huge area at minZoom. await dragOnMap(page, 0.15, 0.95); await expect(page.getByTestId("region-status")).toHaveText(/too large/); // x-show hides via CSS rather than removing the node, so assert visibility, not DOM count. await expect(page.getByTestId("region-export")).not.toBeVisible(); });