test(02-05): playwright e2e for PIPE-07 — full Phase-2 loop
- tests/e2e/season1-loop.spec.ts: PIPE-07 smoke covering load → Begin → plant rosemary → fast-forward FakeClock 3min → harvest → fragment-reveal modal → close → journal-icon visible → open journal → fragment present → reload page → fragment persists. Sidesteps Phaser canvas pixel-clicking via window.__tlgStore command dispatch (test-only window slot, production-guarded by import.meta.env.PROD). - playwright.config.ts: bumped webServer timeout 30s → 60s for cold Vite startup; pinned port 5273 + --strictPort to avoid collisions with other dev servers on the user's machine; reuseExistingServer false so the spec always starts a fresh Vite against this project. - package.json: added test:e2e script (npx playwright test). Not added to npm run ci — Playwright is slower than Vitest; manual run before /gsd-verify-work + future v1 release pipeline. - src/content/loader.ts (Rule 3 — Blocking): replaced gray-matter with a 15-line parseFrontmatter helper. gray-matter pulls in Node's Buffer global which is undefined in Vite's browser bundle; the build emits a 'Module "buffer" externalized' warning that masks the runtime ReferenceError. Surfaced under Vite dev mode while running the e2e — Plan 02-03's Markdown loader path (lura-first- letter.md + winter-rose-night.md) was effectively broken in real browsers since shipping. parseFrontmatter handles the strict '---<yaml>---<body>' shape the .md fragments use; bundle dropped from 2.2MB to 1.9MB as a side effect of dropping the unused dep. - deferred-items.md: tracks the gray-matter package.json cleanup (the dep is now unused but kept in package.json for now, scoped out of this plan). - npx playwright test exits 0 (1 spec, 1.5s test runtime); npm run ci exits 0; 308/308 vitest still green. PIPE-07 satisfied end-to-end.
This commit is contained in:
+34
-7
@@ -1,4 +1,3 @@
|
||||
import grayMatter from 'gray-matter';
|
||||
import { parse as parseYAML } from 'yaml';
|
||||
import {
|
||||
SeasonContentSchema,
|
||||
@@ -8,6 +7,31 @@ import {
|
||||
type UiStrings,
|
||||
} from './schemas/index.ts';
|
||||
|
||||
/**
|
||||
* Minimal frontmatter splitter — replaces gray-matter for the Markdown
|
||||
* fragment loader. gray-matter pulls in `Buffer` (Node-only), which
|
||||
* breaks under Vite's browser bundle (Plan 02-05 found this — gray-matter
|
||||
* was working only at build time because Vite externalized buffer with
|
||||
* a warning, but the runtime ReferenceError surfaced in dev mode).
|
||||
*
|
||||
* The Markdown fragments under /content/seasons/<slug>/fragments/*.md
|
||||
* have a strict shape: `---\n<yaml>\n---\n<body>`. This parser handles
|
||||
* exactly that shape; anything else throws so the build / module-eval
|
||||
* fail loudly per PIPE-01.
|
||||
*/
|
||||
function parseFrontmatter(raw: string): { data: unknown; content: string } {
|
||||
// Strip a leading UTF-8 BOM if present.
|
||||
const text = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
||||
// Match `---\n<yaml>\n---\n<rest>` (allow CRLF line endings too).
|
||||
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
||||
if (!match) {
|
||||
return { data: {}, content: text };
|
||||
}
|
||||
const [, yamlBlock, body] = match;
|
||||
const data = parseYAML(yamlBlock ?? '');
|
||||
return { data: data ?? {}, content: body ?? '' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Vite-native content pipeline (PIPE-01). The glob patterns MUST be
|
||||
* string literals at the call site — Vite's plugin walks the AST at build
|
||||
@@ -47,8 +71,8 @@ function loadYamlFragments(): Fragment[] {
|
||||
|
||||
function loadMdFragments(): Fragment[] {
|
||||
return Object.entries(mdFiles).map(([path, raw]) => {
|
||||
const { data, content } = grayMatter(raw);
|
||||
const merged = { ...data, body: content.trim() };
|
||||
const { data, content } = parseFrontmatter(raw);
|
||||
const merged = { ...(data as Record<string, unknown>), body: content.trim() };
|
||||
const parsed = FragmentSchema.safeParse(merged);
|
||||
if (!parsed.success) {
|
||||
throw new Error(`[content] schema violation in ${path}\n${parsed.error.message}`);
|
||||
@@ -108,8 +132,8 @@ export async function loadSeasonFragments(seasonId: number): Promise<Fragment[]>
|
||||
const mdOut: Fragment[] = [];
|
||||
for (const [path, loader] of mdMatch) {
|
||||
const raw = (await loader()) as string;
|
||||
const { data, content } = grayMatter(raw);
|
||||
const merged = { ...data, body: content.trim() };
|
||||
const { data, content } = parseFrontmatter(raw);
|
||||
const merged = { ...(data as Record<string, unknown>), body: content.trim() };
|
||||
const parsed = FragmentSchema.safeParse(merged);
|
||||
if (!parsed.success) {
|
||||
throw new Error(`[content] schema violation in ${path}\n${parsed.error.message}`);
|
||||
@@ -165,8 +189,11 @@ export function loadFragmentsFromGlob(
|
||||
return parsed.data.fragments;
|
||||
});
|
||||
const md = Object.entries(mdGlob).map(([path, raw]) => {
|
||||
const { data, content } = grayMatter(raw);
|
||||
const parsed = FragmentSchema.safeParse({ ...data, body: content.trim() });
|
||||
const { data, content } = parseFrontmatter(raw);
|
||||
const parsed = FragmentSchema.safeParse({
|
||||
...(data as Record<string, unknown>),
|
||||
body: content.trim(),
|
||||
});
|
||||
if (!parsed.success) {
|
||||
throw new Error(`[content] schema violation in ${path}: ${parsed.error.message}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user