// Covers the click-to-place/edit marker popup flow (Leaflet L.popup() + Alpine's $refs // DOM-node-reuse trick in map.js's init()/openMarkerForm) — flagged throughout MCMapper's // development as verified only at the unit-test/manual-curl level, never actually clicked // through in a real browser. See README's e2e section. import { test, expect, type Page } from "@playwright/test"; import { readE2eState } from "../config"; const state = readE2eState(); const SESSION_STORAGE_KEY = "mcmapper_session"; async function waitForMapReady(page: Page) { await page.goto("/"); await expect(page.getByTestId("place-marker-toggle")).toBeVisible(); // Leaflet adds this class to the same #map div passed to L.map() — waiting for it means the // map/tile layer/view are fully initialized before we try to click on it. await page.locator("#map.leaflet-container").waitFor(); } async function placeMarkerAt(page: Page, offset: { x: number; y: number }) { await page.getByTestId("place-marker-toggle").click(); const map = page.locator("#map"); const box = await map.boundingBox(); if (!box) throw new Error("#map has no bounding box"); await map.click({ position: offset }); // Not `.leaflet-popup` — Leaflet doesn't remove a previously-closed popup's container from the // DOM, only the shared markerFormEl content node (moved between popups, see map.js's // openMarkerForm doc comment) is guaranteed unique and reflects the currently-open popup. await expect(page.getByTestId("marker-form")).toBeVisible(); return box; } test.describe("marker popup — anonymous (localStorage)", () => { test.use({ storageState: { cookies: [], origins: [] } }); test("clicking the map opens a popup pre-filled with defaults", async ({ page }) => { await waitForMapReady(page); await placeMarkerAt(page, { x: 400, y: 300 }); await expect(page.getByTestId("marker-y")).toHaveValue("60"); await expect(page.getByTestId("marker-x")).not.toHaveValue(""); await expect(page.getByTestId("marker-z")).not.toHaveValue(""); const color = await page.getByTestId("marker-color-input").inputValue(); expect(color).toMatch(/^#[0-9a-f]{6}$/i); }); test("creating a marker adds it to the list and survives a reload", async ({ page }) => { await waitForMapReady(page); await placeMarkerAt(page, { x: 420, y: 280 }); await page.getByTestId("marker-name-input").fill("Test Marker"); await page.getByTestId("marker-save").click(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="Test Marker"]')).toBeVisible(); await page.reload(); await expect(page.getByTestId("place-marker-toggle")).toBeVisible(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="Test Marker"]')).toBeVisible(); }); test("editing a marker via the popup updates its name", async ({ page }) => { await waitForMapReady(page); await placeMarkerAt(page, { x: 440, y: 260 }); await page.getByTestId("marker-name-input").fill("Before Edit"); await page.getByTestId("marker-save").click(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="Before Edit"]')).toBeVisible(); await page.locator('[data-testid="marker-row"][data-marker-name="Before Edit"]').getByTestId("marker-edit").click(); await expect(page.getByTestId("marker-form")).toBeVisible(); await expect(page.getByTestId("marker-name-input")).toHaveValue("Before Edit"); await page.getByTestId("marker-name-input").fill("After Edit"); await page.getByTestId("marker-save").click(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="After Edit"]')).toBeVisible(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="Before Edit"]')).toHaveCount(0); }); test("deleting a marker removes it from the list", async ({ page }) => { await waitForMapReady(page); await placeMarkerAt(page, { x: 460, y: 240 }); await page.getByTestId("marker-name-input").fill("To Delete"); await page.getByTestId("marker-save").click(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="To Delete"]')).toBeVisible(); await page.locator('[data-testid="marker-row"][data-marker-name="To Delete"]').getByTestId("marker-delete").click(); await expect(page.locator('[data-testid="marker-row"][data-marker-name="To Delete"]')).toHaveCount(0); }); }); test.describe("marker popup — linked account (server-synced across devices)", () => { async function injectSession(page: Page) { await page.addInitScript( ([key, token]) => localStorage.setItem(key, token), [SESSION_STORAGE_KEY, state.sessionToken], ); } test("a marker created while linked is visible in a separate browser context with the same session", async ({ browser }) => { const contextA = await browser.newContext(); const pageA = await contextA.newPage(); await injectSession(pageA); await waitForMapReady(pageA); await expect(pageA.getByTestId("account-linked")).toBeVisible(); await placeMarkerAt(pageA, { x: 400, y: 300 }); const markerName = `Synced Marker ${Date.now()}`; await pageA.getByTestId("marker-name-input").fill(markerName); await pageA.getByTestId("marker-save").click(); await expect(pageA.locator(`[data-testid="marker-row"][data-marker-name="${markerName}"]`)).toBeVisible(); await contextA.close(); // A fresh context has its own localStorage — the only way this marker can show up here is // if it was actually persisted server-side (accounts.markers table), proving the // cross-device sync the user asked for, not just localStorage. const contextB = await browser.newContext(); const pageB = await contextB.newPage(); await injectSession(pageB); await waitForMapReady(pageB); await expect(pageB.getByTestId("account-linked")).toBeVisible(); await expect(pageB.locator(`[data-testid="marker-row"][data-marker-name="${markerName}"]`)).toBeVisible(); await contextB.close(); }); });