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