diff --git a/plugins/voyage/tests/playground/voyage-playground.test.mjs b/plugins/voyage/tests/playground/voyage-playground.test.mjs index 2aea598..7506f0d 100644 --- a/plugins/voyage/tests/playground/voyage-playground.test.mjs +++ b/plugins/voyage/tests/playground/voyage-playground.test.mjs @@ -502,3 +502,151 @@ test('voyage-playground.html loadProjectDirectory wires isProjectPathSafe filter // aria-live announce must fire when something is filtered assert.match(text, /announce\(filteredCount/, 'filteredCount announce required'); }); + +// ===================================================================== +// v4.3 Step 28 — Group A static-HTML assertions (Wave 7). +// +// SC1 10-element checklist (one test per element), SC3 webkitdirectory + +// drag-drop attributes, SC6 export-bundle markers, SC7 no-CDN tag-level +// checks. All assertions read voyage-playground.html as text — no DOM, +// no browser. The HTML is FROZEN in Session 6; if any assertion fails +// the test must be adjusted to reflect actual state, not the HTML. +// ===================================================================== + +// --- SC1 element 1 — header / app-shell topbar ----------------------- +test('SC1.1 header — app-shell topbar with breadcrumb (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /class="app-header__breadcrumb"/, 'breadcrumb required'); + assert.match(text, /aria-label="Brødsmuler"/, 'breadcrumb aria-label required'); +}); + +// --- SC1 element 2 — breadcrumb interactive return path -------------- +test('SC1.2 breadcrumb — clickable returns to dashboard (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /breadcrumb-click/, 'breadcrumb-click handler required'); +}); + +// --- SC1 element 3 — theme bootstrap IIFE ---------------------------- +test('SC1.3 theme bootstrap — IIFE sets data-theme + colorScheme (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /]+data-theme="dark"/, 'html data-theme=dark required'); + assert.match(text, /prefers-color-scheme:\s*dark/, 'OS preference fallback required'); + assert.match(text, /setAttribute\('data-theme'/, 'data-theme setter required'); +}); + +// --- SC1 element 4 — onboarding-grid (redefined as fleet-grid) ------- +// Per scope-guardian SC-GAP-1 (Assumptions row #21): voyage redefines +// onboarding-grid as fleet-grid. Operator-signed-off; /trekreview may +// flag this for revision. +test('SC1.4 onboarding-grid equivalent — fleet-grid pattern (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /class="fleet-grid"/, 'fleet-grid container required'); + assert.match(text, /fleet-tile/, 'fleet-tile children required'); +}); + +// --- SC1 element 5 — A11Y panel built from DS-primitives ------------- +test('SC1.5 A11Y panel — guide-panel--info + key-stats + findings (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /guide-panel guide-panel--info/, 'guide-panel--info required'); + assert.match(text, /class="key-stats"/, 'key-stats severity-grid required'); + assert.match(text, /class="findings__items"/, 'findings__items list required'); + assert.match(text, /wireA11yToggle/, 'A11Y wiring function required'); +}); + +// --- SC1 element 6 — screenshots-spor convention --------------------- +// Per scope-guardian SC-GAP-2 (Assumptions row #22): hooks + dir convention +// instead of inline gallery. Operator-signed-off. +test('SC1.6 screenshots-spor — window.__voyage hooks + docs convention (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /window\.__voyage\s*=/, 'window.__voyage namespace required'); + assert.match(text, /docs\/screenshots\/README\.md/, 'docs/screenshots reference required'); + // Companion file must exist + const SCREENSHOTS_README = join(ROOT, 'docs', 'screenshots', 'README.md'); + assert.ok(existsSync(SCREENSHOTS_README), 'docs/screenshots/README.md must exist'); +}); + +// --- SC1 element 7 — body typography ----------------------------------- +test('SC1.7 body typography — DS font-size + family tokens (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /var\(--font-size-/, 'DS font-size token required'); + assert.match(text, /var\(--font-family-mono\)/, 'DS font-family-mono token required'); +}); + +// --- SC1 element 8 — spacing rhythm ------------------------------------ +test('SC1.8 spacing rhythm — DS --space-N tokens used (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + // Expect at least 5 distinct --space-N references (rhythm, not one-off) + const matches = text.match(/var\(--space-\d/g) || []; + assert.ok(matches.length >= 5, `expected ≥5 --space-N tokens, got ${matches.length}`); +}); + +// --- SC1 element 9 — color-token fidelity ------------------------------ +test('SC1.9 color-token fidelity — voyage-scope tokens + DS colors (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + // Voyage-scope tokens were added in Step 1 (DS base.css) and consumed by playground + assert.match(text, /badge--scope-voyage|--color-scope-voyage/, 'voyage-scope token usage required'); +}); + +// --- SC1 element 10 — dark-mode parity -------------------------------- +test('SC1.10 dark-mode parity — explicit dark default + bootstrap (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + // html element ships data-theme=dark, theme-bootstrap respects user setting + assert.match(text, /]+data-theme="dark"/, 'dark default required'); + assert.match(text, /voyage-theme|voyage_theme/, 'theme persistence key required'); +}); + +// --- SC3 — webkitdirectory + drag-drop attribute presence ------------- +test('SC3 webkitdirectory — input declares directory attribute (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + // The input element on line 849 has webkitdirectory as an HTML attribute + assert.match(text, /\bwebkitdirectory\b/, 'webkitdirectory attribute required'); +}); + +test('SC3 drag-drop — webkitGetAsEntry recursive walk (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /webkitGetAsEntry/, 'webkitGetAsEntry recursive entry-point required'); + assert.match(text, /addEventListener\('dragenter/, 'dragenter handler required'); + assert.match(text, /addEventListener\('dragover/, 'dragover handler required'); + assert.match(text, /addEventListener\('dragleave/, 'dragleave handler required'); +}); + +// --- SC6 export-bundle markers ---------------------------------------- +test('SC6 export — buildAnnotatedMarkdown function exists (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /function\s+buildAnnotatedMarkdown\s*\(/, 'buildAnnotatedMarkdown required'); +}); + +test('SC6 export — download filename pattern annotated-{target}.md (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /'annotated-'\s*\+\s*target\s*\+\s*'\.md'/, 'filename pattern required'); +}); + +test('SC6 export — Blob + clipboard.writeText flows wired (v4.3 Step 28)', () => { + const text = readFileSync(HTML, 'utf-8'); + assert.match(text, /new\s+Blob\(/, 'Blob construction required'); + assert.match(text, /clipboard\.writeText/, 'clipboard copy flow required'); +}); + +// --- SC7 no-CDN tag-level checks --------------------------------------- +test('SC7 no-CDN — every