test: re-point suite off retired standalone views
Phase-1 fallout: delete tests for deleted pages, re-point moved-behavior tests to the trip/home page where the content now lives. - maps.spec: keep M4/M7/M8 (home+trip maps); drop M1-3,5,6,9-11 (/map, /dailies & /stories mini-maps) - map-ux: re-point MUX4/5 sort toggle to #trip-sort-toggle on trip page - nav: N1->trip page, N2->home, drop N3 (/stats) + N5 (obsolete skip) - dailies: T1->trip page; T2->trip page with oldest-first assertion; fix T3-T6 stale entry-detail selectors (.entry-* -> .journal-post-*, pre-existing failures from an earlier entry redesign) - stories: S1/S5 -> trip feed story cards ([data-type=story]) - gpx-journey: load MapUtils from the trip page instead of /map - a11y: A4 photo strips -> trip page; drop AX3 (covered by AX2) - post/helpers: verify posted entry on the trip page (ACTIVE_TRIP_URL) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -69,7 +69,7 @@ test('A3f: clicking Cycling toggle sets aria-expanded="true" then back to false'
|
||||
|
||||
// ── A4: Photo strip keyboard navigation ───────────────────────────────────────
|
||||
test('A4a: all photo strips have role=region and aria-label', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo/dailies');
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
const strips = page.locator('.journal-photo-strip');
|
||||
const count = await strips.count();
|
||||
if (count === 0) return;
|
||||
@@ -80,7 +80,7 @@ test('A4a: all photo strips have role=region and aria-label', async ({ page }) =
|
||||
});
|
||||
|
||||
test('A4b: multi-slide photo strips have accessible prev/next controls', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo/dailies');
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
const multiCount = await page.locator('.journal-photo-strip').evaluateAll(
|
||||
els => els.filter(el => parseInt(el.dataset.slides, 10) >= 2).length
|
||||
);
|
||||
@@ -133,7 +133,6 @@ function axeScan(id, url) {
|
||||
|
||||
axeScan('AX1', '/');
|
||||
axeScan('AX2', '/trips/italy-2026-demo');
|
||||
axeScan('AX3', '/trips/italy-2026-demo/dailies');
|
||||
axeScan('AX4', '/trips/italy-2026-demo/dailies/2026-09-01-0700-setting-off-from-campiglia.entry');
|
||||
axeScan('AX5', '/trips');
|
||||
|
||||
|
||||
@@ -12,17 +12,19 @@ const KNOWN_COUNTRY = 'Italy';
|
||||
const NEWER_SLUG = '2023-10-18-hunting-the-mother-of-georgia-from-above.entry'; // newest date in that trip
|
||||
const OLDER_SLUG = '2023-08-28-welcome-to-my-central-asian-picture-diary.entry'; // oldest date in that trip
|
||||
|
||||
// ── T1: Dailies page loads ─────────────────────────────────────────────────────
|
||||
test('T1: /trips/italy-2026-demo/dailies loads and shows at least one entry card', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo/dailies');
|
||||
// ── T1: Trip page journal feed loads ──────────────────────────────────────────
|
||||
// The journal feed moved onto the trip page when the standalone /dailies view was retired.
|
||||
test('T1: trip page loads and shows at least one journal entry card', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
await expect(page.locator('.journal-post').first()).toBeVisible();
|
||||
await expect(page.locator('.site-header')).toBeVisible();
|
||||
});
|
||||
|
||||
// ── T2: Entries are newest-first ──────────────────────────────────────────────
|
||||
// ── T2: Trip feed default order is oldest-first ───────────────────────────────
|
||||
// The trip page sorts the feed oldest→newest by default (home uses newest-first).
|
||||
// Verify using two known real entries from central-asia-2023 (22 entries, stable order).
|
||||
test('T2: dailies shows newer entries before older entries', async ({ page }) => {
|
||||
await page.goto('/trips/central-asia-2023/dailies');
|
||||
test('T2: trip feed shows older entries before newer entries (oldest-first default)', async ({ page }) => {
|
||||
await page.goto('/trips/central-asia-2023');
|
||||
|
||||
// Use attribute selector to handle dots in slug names (CSS dots are class selectors)
|
||||
const newerCard = page.locator(`.journal-post[id="entry-${NEWER_SLUG}"]`);
|
||||
@@ -31,7 +33,7 @@ test('T2: dailies shows newer entries before older entries', async ({ page }) =>
|
||||
await expect(newerCard).toBeVisible();
|
||||
await expect(olderCard).toBeVisible();
|
||||
|
||||
// The newer entry should appear higher in the DOM (lower index)
|
||||
// The older entry should appear higher in the DOM (lower index)
|
||||
const newerIdx = await newerCard.evaluate(el => {
|
||||
return [...document.querySelectorAll('.journal-post')].findIndex(c => c.id === el.id);
|
||||
});
|
||||
@@ -39,36 +41,36 @@ test('T2: dailies shows newer entries before older entries', async ({ page }) =>
|
||||
return [...document.querySelectorAll('.journal-post')].findIndex(c => c.id === el.id);
|
||||
});
|
||||
|
||||
expect(newerIdx).toBeLessThan(olderIdx);
|
||||
expect(olderIdx).toBeLessThan(newerIdx);
|
||||
});
|
||||
|
||||
// ── T3: Individual entry page loads ───────────────────────────────────────────
|
||||
test('T3: individual entry page loads at /trips/italy-2026-demo/dailies/{slug}', async ({ page }) => {
|
||||
await page.goto(`/trips/italy-2026-demo/dailies/${KNOWN_SLUG}`);
|
||||
await expect(page.locator('article.entry')).toBeVisible();
|
||||
await expect(page.locator('article.journal-post')).toBeVisible();
|
||||
await expect(page.locator('.site-header')).toBeVisible();
|
||||
});
|
||||
|
||||
// ── T4: Entry page shows title, date, and content ─────────────────────────────
|
||||
test('T4: entry page shows title and body content', async ({ page }) => {
|
||||
await page.goto(`/trips/italy-2026-demo/dailies/${KNOWN_SLUG}`);
|
||||
await expect(page.locator('.entry-title')).toContainText(KNOWN_TITLE);
|
||||
await expect(page.locator('.entry-body')).not.toBeEmpty();
|
||||
await expect(page.locator('time.entry-date')).toBeVisible();
|
||||
await expect(page.locator('.journal-post-title')).toContainText(KNOWN_TITLE);
|
||||
await expect(page.locator('.journal-post-body')).not.toBeEmpty();
|
||||
await expect(page.locator('.journal-post-meta time')).toBeVisible();
|
||||
});
|
||||
|
||||
// ── T5: Entry page shows location when present ────────────────────────────────
|
||||
test('T5: entry page shows city and country when set', async ({ page }) => {
|
||||
await page.goto(`/trips/italy-2026-demo/dailies/${KNOWN_SLUG}`);
|
||||
await expect(page.locator('.entry-location')).toContainText(KNOWN_CITY);
|
||||
await expect(page.locator('.entry-location')).toContainText(KNOWN_COUNTRY);
|
||||
await expect(page.locator('.journal-post-location')).toContainText(KNOWN_CITY);
|
||||
await expect(page.locator('.journal-post-location')).toContainText(KNOWN_COUNTRY);
|
||||
});
|
||||
|
||||
// ── T6: Entry page has a fixed top back pill and a footer back pill ───────────────
|
||||
test('T6: entry page has fixed back pill at top and back pill in footer', async ({ page }) => {
|
||||
const KNOWN_ENTRY = `/trips/italy-2026-demo/dailies/${KNOWN_SLUG}`;
|
||||
await page.goto(KNOWN_ENTRY);
|
||||
await expect(page.locator('article.entry')).toBeVisible();
|
||||
await expect(page.locator('article.journal-post')).toBeVisible();
|
||||
const topPill = page.locator('.entry-back-fixed');
|
||||
await expect(topPill).toBeVisible();
|
||||
await expect(topPill).toHaveText(/← Back/);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// @ts-check
|
||||
// Tests: G1–G5 — buildJourneySegments algorithm correctness
|
||||
// These tests load the italy-2026-demo map page (which has GPX) to get MapUtils in scope,
|
||||
// then call the functions with synthetic data via page.evaluate.
|
||||
// These tests load the italy-2026-demo trip page (which has GPX + the map bundle) to get
|
||||
// MapUtils in scope, then call the functions with synthetic data via page.evaluate.
|
||||
// Requires demo data: run `make demo-load` before this suite.
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
async function getMapUtils(page) {
|
||||
await page.goto('/trips/italy-2026-demo/map');
|
||||
await expect(page.locator('canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
await expect(page.locator('#trip-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
|
||||
// G1: No GPX → all pairs connected in one segment
|
||||
|
||||
+10
-9
@@ -62,19 +62,20 @@ const USER_DIR = resolveUserDir();
|
||||
const TRACKER_DIR = resolveDailiesDir(USER_DIR) || path.join(USER_DIR, 'pages/01.trips/italy-2026-demo/01.dailies');
|
||||
|
||||
/**
|
||||
* The Grav route to the active dailies listing page,
|
||||
* read from the post-form.md pageconfig.parent value.
|
||||
* Falls back to '/trips/italy-2026-demo/dailies'.
|
||||
* The Grav route to the active trip page, derived from the post-form.md
|
||||
* pageconfig.parent value (the dailies container route, minus the trailing
|
||||
* `/dailies`). Posted entries surface in this page's journal feed.
|
||||
* Falls back to '/trips/italy-2026-demo'.
|
||||
*/
|
||||
function resolveActiveDailiesUrl() {
|
||||
function resolveActiveTripUrl() {
|
||||
const postFormPath = path.join(USER_DIR, 'pages/02.post/post-form.md');
|
||||
if (!fs.existsSync(postFormPath)) return '/trips/italy-2026-demo/dailies';
|
||||
if (!fs.existsSync(postFormPath)) return '/trips/italy-2026-demo';
|
||||
const content = fs.readFileSync(postFormPath, 'utf-8');
|
||||
const m = content.match(/parent:\s*['"]?(\/trips\/[^'"]+\/dailies)['"]?/);
|
||||
return m ? m[1] : '/trips/italy-2026-demo/dailies';
|
||||
const m = content.match(/parent:\s*['"]?(\/trips\/[^'"]+)\/dailies['"]?/);
|
||||
return m ? m[1] : '/trips/italy-2026-demo';
|
||||
}
|
||||
|
||||
const DAILIES_URL = resolveActiveDailiesUrl();
|
||||
const ACTIVE_TRIP_URL = resolveActiveTripUrl();
|
||||
|
||||
/**
|
||||
* Wait for all filepond items to finish XHR upload.
|
||||
@@ -136,4 +137,4 @@ function readEntryMd(entryDir) {
|
||||
return fs.readFileSync(path.join(entryDir, name), 'utf-8');
|
||||
}
|
||||
|
||||
module.exports = { waitForFilePondUpload, postEntry, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR, DAILIES_URL };
|
||||
module.exports = { waitForFilePondUpload, postEntry, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR, ACTIVE_TRIP_URL };
|
||||
|
||||
@@ -48,11 +48,13 @@ test('MUX3: trip page map has a fullscreen toggle button', async ({ page }) => {
|
||||
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');
|
||||
// ── MUX4: Trip feed sort toggle reverses entry order ─────────────────────────
|
||||
// The sort toggle moved onto the trip page (#trip-sort-toggle) when the standalone
|
||||
// /dailies feed view was retired.
|
||||
test('MUX4: trip feed sort toggle reverses the feed entry order', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
|
||||
const sortBtn = page.locator('#feed-sort-toggle');
|
||||
const sortBtn = page.locator('#trip-sort-toggle');
|
||||
await expect(sortBtn).toBeVisible();
|
||||
|
||||
const firstBefore = await page.locator('[data-type]').first().getAttribute('id');
|
||||
@@ -67,21 +69,22 @@ test('MUX4: dailies sort toggle reverses the feed entry order', async ({ page })
|
||||
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');
|
||||
// ── MUX5: Trip feed sort toggle reverses story card order ────────────────────
|
||||
// Story cards live in the trip feed as [data-type="story"] (the /stories grid was retired).
|
||||
test('MUX5: trip feed sort toggle reverses the story card order', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
|
||||
const sortBtn = page.locator('#feed-sort-toggle');
|
||||
const sortBtn = page.locator('#trip-sort-toggle');
|
||||
await expect(sortBtn).toBeVisible();
|
||||
|
||||
const firstBefore = await page.locator('.story-card').first().getAttribute('id');
|
||||
const firstBefore = await page.locator('[data-type="story"]').first().getAttribute('id');
|
||||
|
||||
await sortBtn.click();
|
||||
|
||||
const firstAfter = await page.locator('.story-card').first().getAttribute('id');
|
||||
const firstAfter = await page.locator('[data-type="story"]').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');
|
||||
const firstRestored = await page.locator('[data-type="story"]').first().getAttribute('id');
|
||||
expect(firstRestored, 'Story order restored after second toggle').toBe(firstBefore);
|
||||
});
|
||||
|
||||
+5
-104
@@ -1,38 +1,11 @@
|
||||
// @ts-check
|
||||
// Tests: M1–M4 — MapLibre GL canvas renders on all three map surfaces
|
||||
// Tests: M4, M7, M8 — MapLibre GL renders on the two live map surfaces (home + trip page).
|
||||
// The standalone /map, /dailies mini-map and /stories mini-map surfaces were retired
|
||||
// (see docs/working/plans/2026-07-04-standalone-page-cleanup.md); their coverage now
|
||||
// lives on the trip and home maps, both driven by MapUtils.initEntryMap().
|
||||
// Requires demo data: run `make demo-load` before this suite.
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
// ── M1: Full map page renders MapLibre canvas ─────────────────────────────────
|
||||
test('M1: /map page 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/map');
|
||||
await expect(page.locator('canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
expect(errors, 'No JS errors on map page').toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── M2: Full map page — dot markers are in the DOM ───────────────────────────
|
||||
test('M2: /map page has at least one dot marker', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo/map');
|
||||
await expect(page.locator('canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
// Markers are added in map.on('load') — wait for first to appear in the DOM
|
||||
await expect(page.locator('.maplibregl-marker').first()).toBeVisible({ timeout: 15000 });
|
||||
const markerCount = await page.locator('.maplibregl-marker').count();
|
||||
expect(markerCount, 'At least one marker present').toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// ── M3: Dailies mini-map renders MapLibre canvas ─────────────────────────────
|
||||
test('M3: Dailies 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/dailies');
|
||||
await expect(page.locator('#feed-map canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
expect(errors, 'No JS errors on dailies page').toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── M4: Home map renders MapLibre canvas ─────────────────────────────────────
|
||||
test('M4: Home page map renders MapLibre GL canvas without JS errors', async ({ page }) => {
|
||||
const errors = [];
|
||||
@@ -43,42 +16,6 @@ test('M4: Home page map renders MapLibre GL canvas without JS errors', async ({
|
||||
expect(errors, 'No JS errors on home page').toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── M5: Italy map — no JS errors with GPX present ────────────────────────────
|
||||
test('M5: Italy map page renders without JS errors (GPX present)', async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
|
||||
await page.goto('/trips/italy-2026-demo/map');
|
||||
await expect(page.locator('canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
// Wait for markers to confirm map.on('load') completed
|
||||
await expect(page.locator('.maplibregl-marker').first()).toBeVisible({ timeout: 15000 });
|
||||
// Give Promise.all time to resolve
|
||||
await page.waitForTimeout(3000);
|
||||
|
||||
expect(errors, 'No JS errors on Italy map page').toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── M6: Italy map — journey source exists after GPX loads ────────────────────
|
||||
test('M6: Italy map has a journey MapLibre source after GPX settles', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo/map');
|
||||
await expect(page.locator('canvas.maplibregl-canvas')).toBeVisible({ timeout: 10000 });
|
||||
await expect(page.locator('.maplibregl-marker').first()).toBeVisible({ timeout: 15000 });
|
||||
|
||||
// Wait until the journey source appears — addJourneySegments runs inside Promise.all.then()
|
||||
// `var map = ...` in map.html.twig is a plain <script> var → available as window.map.
|
||||
await page.waitForFunction(function () {
|
||||
return window.map &&
|
||||
(window.map.getSource('journey') !== undefined ||
|
||||
window.map.getSource('journey-0') !== undefined);
|
||||
}, { timeout: 15000 });
|
||||
|
||||
const hasSource = await page.evaluate(function () {
|
||||
return !!(window.map.getSource('journey') || window.map.getSource('journey-0'));
|
||||
});
|
||||
|
||||
expect(hasSource).toBe(true);
|
||||
});
|
||||
|
||||
// ── M7: Clicking a trip-page map marker adds is-highlighted to the entry card ──
|
||||
test('M7: clicking map marker briefly highlights the corresponding entry card', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
@@ -103,7 +40,7 @@ test('M7: clicking map marker briefly highlights the corresponding entry card',
|
||||
|
||||
// ── M8: Home map has GPX journey source on active trip ────────────────────────
|
||||
test('M8: home map has a journey source after GPX settles (active trip)', async ({ page }) => {
|
||||
// Requires travelling: true in user/config/site.yaml (set in Task 1).
|
||||
// Requires travelling: true in user/config/site.yaml.
|
||||
// Requires GPX files attached to the active trip (italy-2026-demo has 7).
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
@@ -125,39 +62,3 @@ 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);
|
||||
});
|
||||
|
||||
+10
-25
@@ -1,47 +1,32 @@
|
||||
// @ts-check
|
||||
// Tests: N1–N5 — page loads and navigation links
|
||||
// Tests: N1–N4 — page loads and navigation links.
|
||||
// The standalone /map, /stats and /dailies views were retired; their content now
|
||||
// lives on the trip page. N1/N2 are smoke tests for the two live landing surfaces.
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
// ── N1: /trips/italy-2026-demo/dailies renders ───────────────────────────────
|
||||
test('N1: /trips/italy-2026-demo/dailies page loads with site header', async ({ page }) => {
|
||||
// ── N1: Trip page renders with site header ───────────────────────────────────
|
||||
test('N1: trip page loads with site header and title', async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
|
||||
await page.goto('/trips/italy-2026-demo/dailies');
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
await expect(page.locator('.site-header')).toBeVisible();
|
||||
await expect(page).toHaveTitle(/Into the East/i);
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── N2: /trips/italy-2026-demo/map renders without JS errors ─────────────────
|
||||
test('N2: /trips/italy-2026-demo/map page loads without JS errors', async ({ page }) => {
|
||||
// ── N2: Home page renders without JS errors ──────────────────────────────────
|
||||
test('N2: home page loads with site header and no JS errors', async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
|
||||
await page.goto('/trips/italy-2026-demo/map');
|
||||
await page.goto('/');
|
||||
await expect(page.locator('.site-header')).toBeVisible();
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── N3: /trips/italy-2026-demo/stats renders ─────────────────────────────────
|
||||
test('N3: /trips/italy-2026-demo/stats page loads with site header', async ({ page }) => {
|
||||
const errors = [];
|
||||
page.on('pageerror', e => errors.push(e.message));
|
||||
|
||||
await page.goto('/trips/italy-2026-demo/stats');
|
||||
await expect(page.locator('.site-header')).toBeVisible();
|
||||
expect(errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── N4: trip page has Journal filter button (replaced nav link) ───────────────
|
||||
// ── N4: trip page has Journal filter button (replaced the old sub-page nav link) ─
|
||||
test('N4: trip page filter bar has Journal button', async ({ page }) => {
|
||||
await page.goto('/trips/italy-2026-demo');
|
||||
await expect(page.locator('.trip-filter-btn[data-filter="journal"]')).toBeVisible();
|
||||
});
|
||||
|
||||
// ── N5: "Map" nav link goes to /map ──────────────────────────────────────────
|
||||
test.skip('N5: Map nav link navigates to /map', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.click('nav a[href*="map"]');
|
||||
await expect(page).toHaveURL(/\/map/);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
const { test, expect } = require('@playwright/test');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { waitForFilePondUpload, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR, DAILIES_URL } = require('../helpers');
|
||||
const { waitForFilePondUpload, cleanupEntry, findEntry, readEntryMd, TRACKER_DIR, ACTIVE_TRIP_URL } = require('../helpers');
|
||||
|
||||
const TEST_PHOTO = path.join(__dirname, '../../fixtures/test-photo.jpg');
|
||||
|
||||
@@ -16,7 +16,7 @@ test.afterAll(() => {
|
||||
});
|
||||
|
||||
// ── P1: Post without photo ─────────────────────────────────────────────────────
|
||||
test('P1: post text-only entry → created on disk and visible on /dailies', async ({ page }) => {
|
||||
test('P1: post text-only entry → created on disk and visible in trip feed', async ({ page }) => {
|
||||
const tag = `p1-${Date.now()}`;
|
||||
const title = `UI Test ${tag}`;
|
||||
|
||||
@@ -39,12 +39,12 @@ test('P1: post text-only entry → created on disk and visible on /dailies', asy
|
||||
const photos = fs.readdirSync(entryDir).filter(f => /\.(jpg|jpeg|png|webp|heic)$/i.test(f));
|
||||
expect(photos.length, 'Text-only entry should have no photos').toBe(0);
|
||||
|
||||
await page.goto(DAILIES_URL);
|
||||
await page.goto(ACTIVE_TRIP_URL);
|
||||
await expect(page.locator('body')).toContainText(tag);
|
||||
});
|
||||
|
||||
// ── P2: Post with photo ────────────────────────────────────────────────────────
|
||||
test.skip('P2: post entry with photo → photo saved in entry folder and visible on /dailies', async ({ page }) => {
|
||||
test.skip('P2: post entry with photo → photo saved in entry folder and visible in trip feed', async ({ page }) => {
|
||||
const tag = `p2-${Date.now()}`;
|
||||
const title = `UI Test ${tag}`;
|
||||
|
||||
@@ -70,7 +70,7 @@ test.skip('P2: post entry with photo → photo saved in entry folder and visible
|
||||
const photos = fs.readdirSync(entryDir).filter(f => /\.(jpg|jpeg|png|webp|heic)$/i.test(f));
|
||||
expect(photos.length, 'At least one photo should be saved').toBeGreaterThan(0);
|
||||
|
||||
await page.goto(DAILIES_URL);
|
||||
await page.goto(ACTIVE_TRIP_URL);
|
||||
await expect(page.locator('body')).toContainText(tag);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
// Requires demo data: run `make demo-load` before this suite.
|
||||
const { test, expect } = require('@playwright/test');
|
||||
|
||||
const STORIES_URL = '/trips/italy-2026-demo/stories';
|
||||
const TRIP_URL = '/trips/italy-2026-demo'; // stories now surface in the trip feed
|
||||
const STORY_GALLERY = '/trips/italy-2026-demo/stories/val-dorcia-at-dawn'; // gallery-led: snap-gallery × 2, chapter-break, text-only pull-quote
|
||||
const STORY_SCROLLY = '/trips/italy-2026-demo/stories/sorano-rock-and-time'; // scrolly-led: scrolly-section × 2, chapter-break, pull-quote with image
|
||||
const DEMO_STORY = '/trips/italy-2026-demo/stories/val-dorcia-at-dawn'; // used for cross-trip hero sanity check
|
||||
|
||||
// ── S1: Stories listing shows cards ──────────────────────────────────────────
|
||||
test('S1: stories listing renders at least 3 story cards', async ({ page }) => {
|
||||
await page.goto(STORIES_URL);
|
||||
const cards = page.locator('.story-card');
|
||||
// ── S1: Trip feed shows story cards ──────────────────────────────────────────
|
||||
test('S1: trip feed renders at least 3 story cards', async ({ page }) => {
|
||||
await page.goto(TRIP_URL);
|
||||
const cards = page.locator('[data-type="story"]');
|
||||
await expect(cards.first()).toBeVisible({ timeout: 5000 });
|
||||
const count = await cards.count();
|
||||
expect(count, 'At least 3 story cards').toBeGreaterThanOrEqual(3);
|
||||
@@ -55,16 +55,16 @@ test('S4: scrolly story page loads without JS errors', async ({ page }) => {
|
||||
expect(errors, 'No JS errors on story page').toHaveLength(0);
|
||||
});
|
||||
|
||||
// ── S5: Back button returns to stories listing ────────────────────────────────
|
||||
test('S5: back button navigates back to stories listing', async ({ page }) => {
|
||||
// Establish history: listing → story → back
|
||||
await page.goto(STORIES_URL);
|
||||
await page.locator('.story-card').first().click();
|
||||
// ── S5: Back button returns to the originating trip page ─────────────────────
|
||||
test('S5: story back button navigates back to the trip page', async ({ page }) => {
|
||||
// Establish history: trip page → story → back
|
||||
await page.goto(TRIP_URL);
|
||||
await page.locator('[data-type="story"]').first().click();
|
||||
await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 });
|
||||
await page.locator('.story-escape').click();
|
||||
// After history.back(), URL should be the stories listing
|
||||
await expect(page).toHaveURL(/italy-2026-demo\/stories$/);
|
||||
await expect(page.locator('.story-card').first()).toBeVisible();
|
||||
// story-escape runs history.back() when history exists → back to the trip page
|
||||
await expect(page).toHaveURL(/\/trips\/italy-2026-demo$/);
|
||||
await expect(page.locator('.journal-post').first()).toBeVisible();
|
||||
});
|
||||
|
||||
// ── S6: Demo story — hero image sanity check ─────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user