From 00d6bb0e371bca7795f95319e3d6edb09769a479 Mon Sep 17 00:00:00 2001 From: Mischa Date: Mon, 22 Jun 2026 01:48:04 +0200 Subject: [PATCH] test: add M9-M11 stories map + MUX1-5 panel/sort/fullscreen regression tests --- tests/ui/maps/map-ux.spec.js | 87 ++++++++++++++++++++++++++++++++++++ tests/ui/maps/maps.spec.js | 36 +++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tests/ui/maps/map-ux.spec.js diff --git a/tests/ui/maps/map-ux.spec.js b/tests/ui/maps/map-ux.spec.js new file mode 100644 index 0000000..cce2fd8 --- /dev/null +++ b/tests/ui/maps/map-ux.spec.js @@ -0,0 +1,87 @@ +// @ts-check +// Tests: MUX1–MUX5 — Map UX features: panel toggles, sort toggle, fullscreen button +// Requires demo data: `make demo-load` before running. +const { test, expect } = require('@playwright/test'); + +// ── MUX1: Trip stats panel toggles open and closed ────────────────────────── +test('MUX1: trip stats panel opens and closes on button click', async ({ page }) => { + await page.goto('/trips/italy-2026-demo'); + + const statsBtn = page.locator('#trip-stats-toggle'); + const statsBlock = page.locator('#trip-stats-block'); + + await expect(statsBtn).toBeVisible(); + await expect(statsBlock).not.toHaveClass(/is-open/); + + await statsBtn.click(); + await expect(statsBlock).toHaveClass(/is-open/); + await expect(page.locator('.trip-stats-grid')).toBeVisible(); + + await statsBtn.click(); + await expect(statsBlock).not.toHaveClass(/is-open/); +}); + +// ── MUX2: Trip cycling panel toggles open and closed ──────────────────────── +test('MUX2: trip cycling panel opens and closes on button click', async ({ page }) => { + await page.goto('/trips/italy-2026-demo'); + + const cyclingBtn = page.locator('#trip-cycling-toggle'); + const cyclingBlock = page.locator('#trip-cycling-block'); + + await expect(cyclingBtn).toBeVisible(); + await expect(cyclingBlock).not.toHaveClass(/is-open/); + + await cyclingBtn.click(); + await expect(cyclingBlock).toHaveClass(/is-open/); + + await cyclingBtn.click(); + await expect(cyclingBlock).not.toHaveClass(/is-open/); +}); + +// ── MUX3: Trip page map has a fullscreen button in the DOM ──────────────────── +test('MUX3: trip page map has a fullscreen toggle button', async ({ page }) => { + await page.goto('/trips/italy-2026-demo'); + await expect(page.locator('#trip-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 }); + + const fsBtn = page.locator('#trip-map-fullscreen'); + await expect(fsBtn).toBeAttached(); + await expect(fsBtn).toHaveAttribute('aria-label', 'Expand map'); +}); + +// ── MUX4: Dailies sort toggle reverses entry order ─────────────────────────── +test('MUX4: dailies sort toggle reverses the feed entry order', async ({ page }) => { + await page.goto('/trips/italy-2026-demo/dailies'); + + const sortBtn = page.locator('#feed-sort-toggle'); + await expect(sortBtn).toBeVisible(); + + const firstBefore = await page.locator('[data-type]').first().getAttribute('id'); + + await sortBtn.click(); + + const firstAfter = await page.locator('[data-type]').first().getAttribute('id'); + expect(firstAfter, 'Entry order reversed after sort').not.toBe(firstBefore); + + await sortBtn.click(); + const firstRestored = await page.locator('[data-type]').first().getAttribute('id'); + expect(firstRestored, 'Entry order restored after second toggle').toBe(firstBefore); +}); + +// ── MUX5: Stories sort toggle reverses story card order ───────────────────── +test('MUX5: stories sort toggle reverses the story card order', async ({ page }) => { + await page.goto('/trips/italy-2026-demo/stories'); + + const sortBtn = page.locator('#feed-sort-toggle'); + await expect(sortBtn).toBeVisible(); + + const firstBefore = await page.locator('.story-card').first().getAttribute('id'); + + await sortBtn.click(); + + const firstAfter = await page.locator('.story-card').first().getAttribute('id'); + expect(firstAfter, 'Story order reversed after sort').not.toBe(firstBefore); + + await sortBtn.click(); + const firstRestored = await page.locator('.story-card').first().getAttribute('id'); + expect(firstRestored, 'Story order restored after second toggle').toBe(firstBefore); +}); diff --git a/tests/ui/maps/maps.spec.js b/tests/ui/maps/maps.spec.js index 615e768..82d3bf4 100644 --- a/tests/ui/maps/maps.spec.js +++ b/tests/ui/maps/maps.spec.js @@ -125,3 +125,39 @@ test('M8: home map has a journey source after GPX settles (active trip)', async expect(hasSource, 'Home map has a journey or GPX source').toBe(true); expect(errors, 'No JS errors on home page').toHaveLength(0); }); + +// ── M9: Stories mini-map renders MapLibre canvas ────────────────────────────── +test('M9: Stories mini-map renders MapLibre GL canvas without JS errors', async ({ page }) => { + const errors = []; + page.on('pageerror', e => errors.push(e.message)); + + await page.goto('/trips/italy-2026-demo/stories'); + await expect(page.locator('#stories-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 }); + expect(errors, 'No JS errors on stories page').toHaveLength(0); +}); + +// ── M10: Stories mini-map has at least one story marker ────────────────────── +test('M10: Stories mini-map has at least one story marker', async ({ page }) => { + await page.goto('/trips/italy-2026-demo/stories'); + await expect(page.locator('#stories-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 }); + await expect(page.locator('#stories-map .maplibregl-marker').first()).toBeVisible({ timeout: 15000 }); + + const markerCount = await page.locator('#stories-map .maplibregl-marker').count(); + expect(markerCount, 'At least one story marker').toBeGreaterThan(0); +}); + +// ── M11: Dailies attribution control starts collapsed ───────────────────────── +test('M11: Dailies mini-map attribution starts collapsed (no open attribute)', async ({ page }) => { + await page.goto('/trips/italy-2026-demo/dailies'); + await expect(page.locator('#feed-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 }); + // Wait for markers (added in map.on('load')) to ensure the load callback has run, + // which is also where removeAttribute('open') executes. + await expect(page.locator('#feed-map .maplibregl-marker').first()).toBeVisible({ timeout: 15000 }); + await expect(page.locator('#feed-map .maplibregl-ctrl-attrib')).toBeVisible({ timeout: 10000 }); + + const hasOpen = await page.evaluate(function () { + var attrib = document.querySelector('#feed-map .maplibregl-ctrl-attrib'); + return attrib ? attrib.hasAttribute('open') : null; + }); + expect(hasOpen, 'Attribution is collapsed (no open attribute)').toBe(false); +});