From d4b7eeb403a7c82f75a592f870237394a5ec0643 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 26 May 2026 22:46:16 +0000 Subject: [PATCH] feat: add NavBar component with site name, Posts link, and responsive layout Created NavBar.astro component with: - Site name link 'Crash Test Dev' and Posts nav link with active state detection - aria-label='Site navigation' and aria-current='page' accessibility attributes - Responsive stacking at 599px breakpoint - Design token-based styling (structure font, border-subtle, etc.) - Updated index.astro to include NavBar in nav slot - Added comprehensive build-output tests (24 assertions) Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- src/components/NavBar.astro | 98 +++++++++++++++++++++ src/pages/index.astro | 2 + tests/check-navbar.mjs | 164 ++++++++++++++++++++++++++++++++++++ 3 files changed, 264 insertions(+) create mode 100644 src/components/NavBar.astro create mode 100644 tests/check-navbar.mjs diff --git a/src/components/NavBar.astro b/src/components/NavBar.astro new file mode 100644 index 0000000..37ba9cd --- /dev/null +++ b/src/components/NavBar.astro @@ -0,0 +1,98 @@ +--- +const currentPath = Astro.url.pathname; +const base = import.meta.env.BASE_URL; + +const navLinks = [ + { label: 'Posts', href: base }, +]; + +function isActive(href: string): boolean { + return currentPath === href || currentPath.startsWith(base + 'posts/'); +} +--- + + + + \ No newline at end of file diff --git a/src/pages/index.astro b/src/pages/index.astro index 46403cd..a4ef0f2 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,8 +1,10 @@ --- import BaseLayout from '../layouts/BaseLayout.astro'; +import NavBar from '../components/NavBar.astro'; --- +

Crash Test Dev

diff --git a/tests/check-navbar.mjs b/tests/check-navbar.mjs new file mode 100644 index 0000000..e97017a --- /dev/null +++ b/tests/check-navbar.mjs @@ -0,0 +1,164 @@ +/** + * Build-output test for NavBar.astro + * Runs after `npm run build` and checks source + dist HTML for expected structure. + */ +import { readFileSync, existsSync, readdirSync } 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/components/NavBar.astro')), + 'src/components/NavBar.astro exists' +); + +// ── Source content checks ── +console.log('\n--- NavBar source content checks ---'); + +if (existsSync(resolve('src/components/NavBar.astro'))) { + const navSrc = readFileSync(resolve('src/components/NavBar.astro'), 'utf-8'); + + assert( + navSrc.includes('Astro.url.pathname'), + 'NavBar reads Astro.url.pathname' + ); + assert( + navSrc.includes('import.meta.env.BASE_URL'), + 'NavBar reads import.meta.env.BASE_URL' + ); + assert( + navSrc.includes("'Posts'") || navSrc.includes('"Posts"'), + 'navLinks has Posts entry' + ); + assert( + /aria-label\s*=\s*['"]Site navigation['"]/.test(navSrc), + 'nav has aria-label="Site navigation"' + ); + assert( + navSrc.includes('aria-current'), + 'NavBar uses aria-current attribute' + ); + assert( + navSrc.includes('Crash Test Dev'), + 'NavBar has site name "Crash Test Dev"' + ); + assert( + navSrc.includes('site-name'), + 'NavBar has .site-name class' + ); + assert( + navSrc.includes('nav-links'), + 'NavBar has .nav-links class' + ); + assert( + navSrc.includes('nav-link'), + 'NavBar has .nav-link class' + ); + assert( + navSrc.includes('nav-inner'), + 'NavBar has .nav-inner class' + ); + assert( + navSrc.includes('isActive') || navSrc.includes('is_active'), + 'NavBar has isActive function' + ); + assert( + navSrc.includes('border-bottom') || navSrc.includes('border-bottom'), + 'nav has border-bottom style' + ); + assert( + navSrc.includes('--font-structure'), + 'Uses structure font' + ); + assert( + navSrc.includes('max-width: var(--width-container)') || navSrc.includes('width-container'), + '.nav-inner uses container max-width' + ); + assert( + navSrc.includes('599px'), + 'Has responsive breakpoint at 599px' + ); +} + +// ── index.astro includes NavBar ── +console.log('\n--- index.astro integration checks ---'); + +if (existsSync(resolve('src/pages/index.astro'))) { + const indexSrc = readFileSync(resolve('src/pages/index.astro'), 'utf-8'); + + assert( + indexSrc.includes('NavBar'), + 'index.astro imports/uses NavBar' + ); + assert( + indexSrc.includes("slot='nav'") || indexSrc.includes('slot="nav"') || indexSrc.includes("slot='nav'") || indexSrc.includes('slot=\"nav\"'), + 'NavBar is placed in nav slot' + ); +} + +// ── Build output checks ── +console.log('\n--- Build output checks ---'); + +assert(existsSync(indexPath), 'dist/crashtestdev/index.html exists'); + +if (existsSync(indexPath)) { + const html = readFileSync(indexPath, 'utf-8'); + + assert( + html.includes(' element' + ); + assert( + html.includes('Crash Test Dev'), + 'dist HTML contains "Crash Test Dev"' + ); + assert( + html.includes('Posts'), + 'dist HTML contains "Posts" nav link' + ); + assert( + /aria-label\s*=\s*["']Site navigation["']/.test(html), + 'dist HTML has aria-label="Site navigation"' + ); + + // Check CSS is present in dist (HTML or linked CSS) + let distCss = ''; + // Astro may output CSS in dist/_astro/ or dist/crashtestdev/_astro/ + for (const cssDir of [resolve(distDir, '_astro'), resolve(distDir, 'crashtestdev', '_astro')]) { + if (existsSync(cssDir)) { + for (const f of readdirSync(cssDir)) { + if (f.endsWith('.css')) { + distCss += readFileSync(resolve(cssDir, f), 'utf-8'); + } + } + } + } + assert( + html.includes('nav-inner') || distCss.includes('nav-inner'), + 'dist output contains nav-inner styling' + ); +} + +// ── Summary ── +console.log(`\n--- Results: ${passes} passed, ${failures} failed ---`); + +if (failures > 0) { + process.exit(1); +} \ No newline at end of file