From 4455a5ed015066f903c224f24f717cd61aca6882 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 26 May 2026 22:43:02 +0000 Subject: [PATCH] feat: create BaseLayout component with global styles and skip navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created src/layouts/BaseLayout.astro as the site's base layout component - Imports tokens.css and fonts.css for design system and typography - Accepts Props {title: string, description?: string} - Computes page title (appends ' — Crash Test Dev' unless title is already 'Crash Test Dev') - Reads import.meta.env.BASE_URL for RSS link generation - HTML structure: doctype, html lang=en, head with charset/viewport/description meta, computed title, RSS link alternate - Body: skip-to-content link, named nav slot, main#main-content with default slot - Global styles: CSS reset, body typography, link styles, skip-link, main container, ::selection - Updated src/pages/index.astro to use BaseLayout with inline style placeholders demonstrating tokens/fonts - Added tests/check-base-layout.mjs build-output test with 23 assertions, all passing --- src/layouts/BaseLayout.astro | 101 +++++++++++++++++++++++++ src/pages/index.astro | 23 +++--- tests/check-base-layout.mjs | 138 +++++++++++++++++++++++++++++++++++ 3 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 src/layouts/BaseLayout.astro create mode 100644 tests/check-base-layout.mjs diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro new file mode 100644 index 0000000..c6491f0 --- /dev/null +++ b/src/layouts/BaseLayout.astro @@ -0,0 +1,101 @@ +--- +import '../styles/tokens.css'; +import '../styles/fonts.css'; + +interface Props { + title: string; + description?: string; +} + +const { title, description } = Astro.props; + +const siteTitle = title === 'Crash Test Dev' ? title : `${title} — Crash Test Dev`; +const base = import.meta.env.BASE_URL; +--- + + + + + + + {description && } + {siteTitle} + + + + + +
+ +
+ + + + \ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index 8c648df..46403cd 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,14 +1,15 @@ --- +import BaseLayout from '../layouts/BaseLayout.astro'; --- - - - - - Crash Test Dev - - -

Crash Test Dev

-

Blog by Ken Chau

- - \ No newline at end of file + +

+ Crash Test Dev +

+

+ Blog by Ken Chau +

+

+ Tokens and fonts are working. +

+
diff --git a/tests/check-base-layout.mjs b/tests/check-base-layout.mjs new file mode 100644 index 0000000..56729ac --- /dev/null +++ b/tests/check-base-layout.mjs @@ -0,0 +1,138 @@ +/** + * Build-output test for BaseLayout.astro + * Runs after `npm run build` and checks the dist HTML for expected structure. + */ +import { readFileSync, existsSync } from 'fs'; +import { resolve } from 'path'; + +const distDir = resolve('dist'); +const indexPath = resolve(distDir, 'index.html'); + +let failures = 0; +let passes = 0; + +function assert(condition, label) { + if (condition) { + console.log(` ✓ ${label}`); + passes++; + } else { + console.error(` ✗ FAIL: ${label}`); + failures++; + } +} + +// ── Source file existence checks ── +console.log('\n--- Source file checks ---'); + +assert( + existsSync(resolve('src/layouts/BaseLayout.astro')), + 'src/layouts/BaseLayout.astro exists' +); + +assert( + existsSync(resolve('src/pages/index.astro')), + 'src/pages/index.astro exists' +); + +// ── Source content checks ── +console.log('\n--- Source content checks ---'); + +if (existsSync(resolve('src/layouts/BaseLayout.astro'))) { + const layoutSrc = readFileSync(resolve('src/layouts/BaseLayout.astro'), 'utf-8'); + + assert( + layoutSrc.includes("import '../styles/tokens.css'") || layoutSrc.includes("import '../styles/tokens.css';"), + 'BaseLayout imports tokens.css' + ); + assert( + layoutSrc.includes("import '../styles/fonts.css'") || layoutSrc.includes("import '../styles/fonts.css';"), + 'BaseLayout imports fonts.css' + ); + assert( + /title:\s*string/.test(layoutSrc), + 'Props interface has title: string' + ); + assert( + /description\?:\s*string/.test(layoutSrc), + 'Props interface has description?: string' + ); + assert( + layoutSrc.includes('skip-link'), + 'BaseLayout has skip-link class' + ); + assert( + layoutSrc.includes("skip-to-content") || layoutSrc.includes("Skip to content"), + 'BaseLayout has skip-to-content link text' + ); + assert( + layoutSrc.includes('') || layoutSrc.includes(""), + 'BaseLayout has named nav slot' + ); + assert( + layoutSrc.includes('id="main-content"') || layoutSrc.includes("id='main-content'"), + 'BaseLayout has main with id=main-content' + ); + assert( + layoutSrc.includes('Crash Test Dev'), + 'BaseLayout references site name for title computation' + ); + assert( + layoutSrc.includes('import.meta.env.BASE_URL'), + 'BaseLayout reads import.meta.env.BASE_URL' + ); +} + +if (existsSync(resolve('src/pages/index.astro'))) { + const indexSrc = readFileSync(resolve('src/pages/index.astro'), 'utf-8'); + + assert( + indexSrc.includes('BaseLayout'), + 'index.astro uses BaseLayout' + ); +} + +// ── Build output checks ── +console.log('\n--- Build output checks ---'); + +assert(existsSync(indexPath), 'dist/index.html exists'); + +if (existsSync(indexPath)) { + const html = readFileSync(indexPath, 'utf-8'); + + assert(html.includes('') || html.includes(''), 'Has doctype'); + assert(html.includes('lang="en"') || html.includes("lang='en'"), 'html has lang=en'); + assert(html.includes('charset'), 'Has charset meta'); + assert(html.includes('viewport'), 'Has viewport meta'); + // Astro extracts CSS into separate files; check both HTML and linked CSS bundles + const { readdirSync } = await import('fs'); + const astroDir = resolve(distDir, '_astro'); + let distCss = ''; + if (existsSync(astroDir)) { + for (const f of readdirSync(astroDir)) { + if (f.endsWith('.css')) { + distCss += readFileSync(resolve(astroDir, f), 'utf-8'); + } + } + } + assert( + html.includes('Source Serif') || distCss.includes('Source Serif'), + 'dist output (HTML or CSS) contains Source Serif reference' + ); + assert(html.includes('skip-link') || html.includes('Skip to content'), 'Has skip link'); + assert(html.includes('main-content'), 'Has main-content id'); + assert( + html.includes('Crash Test Dev'), + 'Title contains Crash Test Dev' + ); + assert( + html.includes('application/rss+xml') || html.includes('rss'), + 'Has RSS link' + ); +} + +// ── Summary ── +console.log(`\n--- Results: ${passes} passed, ${failures} failed ---`); + +if (failures > 0) { + process.exit(1); +} \ No newline at end of file