test(stories): cover scrolly step text and back-to-top; fix build-output note

S8 asserts scrolly-section splits its slot content into visible step panels
(the blank-text-column regression had no coverage: S3 only checked the image
column existed, and the silent early return threw nothing for S4 to catch).
S9 covers back-to-top now that main.js solely owns it, including the history
entry the removed inline copy lacked.

CLAUDE.md attributed css-compiled/ and fonts/ to css/style.css and
css/tokens.css. They are built from the js/src/ entrypoints' CSS and font
imports; css/ is hand-authored and served directly, needing no rebuild.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 23:16:22 +02:00
co-authored by Claude Opus 5
parent e106da0206
commit 143ec135c0
2 changed files with 75 additions and 3 deletions
+72 -1
View File
@@ -1,5 +1,5 @@
// @ts-check
// Tests: S1S7 — story mode rendering and navigation
// Tests: S1S9 — story mode rendering and navigation
// Requires demo data: run `make demo-load` before this suite.
const { test, expect } = require('@playwright/test');
@@ -85,3 +85,74 @@ test('S7: story body back link has back-pill class', async ({ page }) => {
await expect(bodyBack).toBeAttached();
await expect(bodyBack).toHaveText(/← Back/);
});
// ── S8: Scrolly-section text panels actually render beside the pinned image ───
// The server ships the panel text inside .scrolly__steps-content, which CSS hides
// (style.css: `display: none`). Only the inline Scrollama block in story.html.twig
// splits it into visible .scrolly-step divs — and it early-returns silently if the
// main.js bundle (which sets window.scrollama) hasn't executed yet. S3 asserted the
// image column exists; nothing asserted the text column was non-empty.
test('S8: scrolly-section builds visible step panels from its slot content', async ({ page }) => {
await page.goto(STORY_SCROLLY);
await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 });
// The bundle must have published scrollama before the inline block ran
expect(
await page.evaluate(() => typeof window.scrollama !== 'undefined'),
'window.scrollama published by main.js bundle'
).toBe(true);
// Every scrolly-section must have produced at least one step
const sections = page.locator('.scrolly');
const sectionCount = await sections.count();
expect(sectionCount, 'Two scrolly-sections').toBe(2);
for (let i = 0; i < sectionCount; i++) {
const section = sections.nth(i);
const steps = section.locator('.scrolly-step');
expect(
await steps.count(),
`scrolly-section ${i} split its slot content into steps`
).toBeGreaterThan(0);
}
// …and the text must be readable, not left hidden in the raw slot.
// Scroll each step into view so its reveal transition completes.
const firstStep = page.locator('.scrolly').first().locator('.scrolly-step').first();
await firstStep.scrollIntoViewIfNeeded();
await page.waitForTimeout(800);
await expect(firstStep.locator('.scrolly-step__inner')).toBeVisible();
const text = (await firstStep.innerText()).trim();
expect(text.length, 'First step panel renders non-empty text').toBeGreaterThan(20);
});
// ── S9: Back-to-top is wired once, by main.js, and pushes a history entry ─────
// The inline duplicate in story.html.twig was removed; initBackToTop() in
// js/src/main.js now solely owns #story-totop. That makes the button depend on
// the bundle having loaded, so assert the observable behaviour end to end.
test('S9: story back-to-top reveals on scroll, returns to top, and pushes history', async ({ page }) => {
await page.goto(STORY_SCROLLY);
await expect(page.locator('.story-hero__img')).toBeVisible({ timeout: 8000 });
const btn = page.locator('#story-totop');
await expect(btn).toBeAttached();
// Hidden until scrolled past the 0.8 * viewport threshold
await expect(btn).not.toHaveClass(/is-visible/);
const historyBefore = await page.evaluate(() => history.length);
await page.evaluate(() => window.scrollTo(0, window.innerHeight * 2));
await expect(btn).toHaveClass(/is-visible/, { timeout: 3000 });
await btn.click();
await expect
.poll(() => page.evaluate(() => window.scrollY), { timeout: 3000 })
.toBeLessThan(10);
// main.js's variant pushes a history entry; the removed inline copy did not
expect(
await page.evaluate(() => history.length),
'Back-to-top pushed a history entry'
).toBeGreaterThan(historyBefore);
});