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:
@@ -6,7 +6,7 @@ Rules, gotchas, and entry points — the things that must change what you do *be
|
|||||||
|---|---|
|
|---|---|
|
||||||
| How the site hangs together — stack, plugin roles, templates, partial contracts, data flows | [`docs/reference/architecture.md`](docs/reference/architecture.md) |
|
| How the site hangs together — stack, plugin roles, templates, partial contracts, data flows | [`docs/reference/architecture.md`](docs/reference/architecture.md) |
|
||||||
| Domain vocabulary — Trip, Entry, Story, Active Trip | [`CONCEPTS.md`](CONCEPTS.md) |
|
| Domain vocabulary — Trip, Entry, Story, Active Trip | [`CONCEPTS.md`](CONCEPTS.md) |
|
||||||
| Doing something operational — posting, GPX, switching trips, local setup, deploying | [`docs/guides/`](docs/guides/) |
|
| Doing something operational — posting, writing stories, GPX, switching trips, local setup, deploying | [`docs/guides/`](docs/guides/) |
|
||||||
| Test suite layout and conventions | [`docs/reference/testing.md`](docs/reference/testing.md) |
|
| Test suite layout and conventions | [`docs/reference/testing.md`](docs/reference/testing.md) |
|
||||||
| A bug or workflow trap already hit and written up | [`docs/solutions/`](docs/solutions/) — grep the `module`/`tags`/`problem_type` frontmatter; check when working in a documented area |
|
| A bug or workflow trap already hit and written up | [`docs/solutions/`](docs/solutions/) — grep the `module`/`tags`/`problem_type` frontmatter; check when working in a documented area |
|
||||||
| Folder map, prerequisites, the full `make` command list | [`README.md`](README.md) |
|
| Folder map, prerequisites, the full `make` command list | [`README.md`](README.md) |
|
||||||
@@ -20,7 +20,8 @@ The site is Grav (flat-file PHP CMS, no database) in Docker, with content and th
|
|||||||
- **Never SSH to a server directly** — use the `make remote-*` targets, since credentials live in `.env`. If no target covers what you need, ask the user to run it or propose a new target.
|
- **Never SSH to a server directly** — use the `make remote-*` targets, since credentials live in `.env`. If no target covers what you need, ask the user to run it or propose a new target.
|
||||||
- **Never hand-edit build output** — sources and outputs share folders under `user/themes/intotheeast/` (paths below are relative to it), so know which is which. Run `make build-assets` after editing any source.
|
- **Never hand-edit build output** — sources and outputs share folders under `user/themes/intotheeast/` (paths below are relative to it), so know which is which. Run `make build-assets` after editing any source.
|
||||||
- Everything in `js/` is **generated** *except* `js/src/`, `js/maplibre-utils.js` and `js/nav.js`.
|
- Everything in `js/` is **generated** *except* `js/src/`, `js/maplibre-utils.js` and `js/nav.js`.
|
||||||
- `css-compiled/` and `fonts/` are generated (sources: `css/style.css`, `css/tokens.css`); so is `templates/partials/weather-icons.html.twig` (source: `scripts/gen-weather-icons.js`).
|
- `css-compiled/` and `fonts/` are generated by esbuild from the `js/src/` entrypoints' CSS and font imports (fontsource, photoswipe, maplibre-gl) — **not** from `css/`. `css/style.css` and `css/tokens.css` are hand-authored and served directly (`partials/base.html.twig`), so editing them needs no rebuild.
|
||||||
|
- `templates/partials/weather-icons.html.twig` is generated (source: `scripts/gen-weather-icons.js`).
|
||||||
- **Never toggle dev↔prod mode mid-session.** If a caching or config issue appears, fix it at the application level (plugin, template logic) rather than flipping a mode flag — mode switches leave inconsistent state and make bugs harder to reproduce.
|
- **Never toggle dev↔prod mode mid-session.** If a caching or config issue appears, fix it at the application level (plugin, template logic) rather than flipping a mode flag — mode switches leave inconsistent state and make bugs harder to reproduce.
|
||||||
|
|
||||||
## Dev environment
|
## Dev environment
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// @ts-check
|
// @ts-check
|
||||||
// Tests: S1–S7 — story mode rendering and navigation
|
// Tests: S1–S9 — story mode rendering and navigation
|
||||||
// Requires demo data: run `make demo-load` before this suite.
|
// Requires demo data: run `make demo-load` before this suite.
|
||||||
const { test, expect } = require('@playwright/test');
|
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).toBeAttached();
|
||||||
await expect(bodyBack).toHaveText(/← Back/);
|
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);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user