diff --git a/tests/ui/stories.spec.js b/tests/ui/stories.spec.js new file mode 100644 index 0000000..20e1ebe --- /dev/null +++ b/tests/ui/stories.spec.js @@ -0,0 +1,75 @@ +// @ts-check +// Tests: S1–S6 — story mode rendering and navigation +// Requires demo data: run `make demo-load` before this suite. +const { test, expect } = require('@playwright/test'); + +const STORIES_URL = '/trips/italy-2025/stories'; +const STORY_GALLERY = '/trips/italy-2025/stories/val-dorcia-dawn'; // gallery-led: snap-gallery × 2, chapter-break, text-only pull-quote +const STORY_SCROLLY = '/trips/italy-2025/stories/long-climb-montalcino'; // scrolly-led: scrolly-section × 2, chapter-break, pull-quote with image +const JAPAN_STORY = '/trips/japan-korea-2026/stories/the-thousand-gates'; + +// ── 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'); + await expect(cards.first()).toBeVisible({ timeout: 5000 }); + const count = await cards.count(); + expect(count, 'At least 3 story cards').toBeGreaterThanOrEqual(3); +}); + +// ── S2: Gallery-led story — hero image + snap-gallery + chapter-break + text-only pull-quote ── +test('S2: gallery-led story renders hero, snap-gallery, chapter-break, text-only pull-quote', async ({ page }) => { + await page.goto(STORY_GALLERY); + // Hero: real image rendered, no placeholder + await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); + await expect(page.locator('.story-hero__img-placeholder')).toHaveCount(0); + // Snap-gallery (there are two in this story) + const galleries = page.locator('.pgallery'); + await expect(galleries.first()).toBeVisible(); + expect(await galleries.count(), 'Two snap-galleries').toBe(2); + // Chapter-break + await expect(page.locator('.chapter-break')).toBeVisible(); + // Text-only pull-quote (no background image variant) + await expect(page.locator('.pull-quote__inner--no-image')).toBeVisible(); +}); + +// ── S3: Scrolly-led story — two scrolly-sections + pull-quote with image ───── +test('S3: scrolly-led story renders two scrolly-sections and pull-quote with background image', async ({ page }) => { + await page.goto(STORY_SCROLLY); + await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); + // Two scrolly-sections + const scrollySections = page.locator('.scrolly'); + await expect(scrollySections.first()).toBeVisible(); + expect(await scrollySections.count(), 'Two scrolly-sections').toBe(2); + // Pull-quote with background image + await expect(page.locator('.pull-quote__bg')).toBeVisible(); +}); + +// ── S4: Scrolly story loads without JS errors (Scrollama CDN) ──────────────── +test('S4: scrolly story page loads without JS errors', async ({ page }) => { + const errors = []; + page.on('pageerror', e => errors.push(e.message)); + await page.goto(STORY_SCROLLY); + await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); + await page.waitForTimeout(1000); + 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(); + 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-2025\/stories$/); + await expect(page.locator('.story-card').first()).toBeVisible(); +}); + +// ── S6: Japan story — cross-trip hero image sanity check ───────────────────── +test('S6: Japan story renders hero image without placeholder', async ({ page }) => { + await page.goto(JAPAN_STORY); + await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 }); + await expect(page.locator('.story-hero__img-placeholder')).toHaveCount(0); +});