/** * Verification test for Self-Hosted Fonts (Task 3) * * Checks that: * 1. 8 woff2 font files exist in src/fonts/ * 2. src/styles/fonts.css exists with 8 @font-face declarations * 3. Each @font-face has correct font-family, weight, style, src, font-display * 4. src URLs use relative paths to ../fonts/ */ import { readFileSync, existsSync, statSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = join(__dirname, '..'); let passed = 0; let failed = 0; function assert(condition, message) { if (condition) { console.log(` ✓ ${message}`); passed++; } else { console.error(` ✗ ${message}`); failed++; } } // === 1. Font files exist in src/fonts/ === console.log('\n1. Font files exist in src/fonts/'); const expectedFonts = [ 'source-serif-4-400.woff2', 'source-serif-4-600.woff2', 'source-serif-4-400-italic.woff2', 'inter-400.woff2', 'inter-500.woff2', 'inter-600.woff2', 'inter-700.woff2', 'jetbrains-mono-400.woff2', ]; for (const font of expectedFonts) { const fontPath = join(root, 'src/fonts', font); const exists = existsSync(fontPath); assert(exists, `src/fonts/${font} exists`); if (exists) { const stat = statSync(fontPath); assert(stat.size > 1000, `src/fonts/${font} is a real font file (${stat.size} bytes)`); } } // === 2. fonts.css exists === console.log('\n2. fonts.css exists'); const fontsCssPath = join(root, 'src/styles/fonts.css'); assert(existsSync(fontsCssPath), 'src/styles/fonts.css exists'); if (!existsSync(fontsCssPath)) { console.log('\nfonts.css not found, skipping remaining tests.'); console.log(`\n${'='.repeat(40)}`); console.log(`Results: ${passed} passed, ${failed} failed out of ${passed + failed}`); process.exit(1); } const css = readFileSync(fontsCssPath, 'utf8'); // === 3. Has 8 @font-face declarations === console.log('\n3. @font-face count'); const fontFaceBlocks = css.match(/@font-face\s*\{[^}]+\}/g); const fontFaceCount = fontFaceBlocks ? fontFaceBlocks.length : 0; assert(fontFaceCount === 8, `has exactly 8 @font-face declarations (found ${fontFaceCount})`); // === 4. Source Serif 4 declarations (prose voice) === console.log('\n4. Source Serif 4 declarations (prose voice)'); // Regular 400 assert( css.includes("font-family: 'Source Serif 4'") || css.includes('font-family: "Source Serif 4"'), "has font-family: 'Source Serif 4'" ); // Check for 400 normal const ss4_400_block = fontFaceBlocks?.find( (b) => (b.includes("'Source Serif 4'") || b.includes('"Source Serif 4"')) && b.includes('font-weight: 400') && b.includes('font-style: normal') ); assert(!!ss4_400_block, 'Source Serif 4 400 normal declaration exists'); // Check for 600 normal const ss4_600_block = fontFaceBlocks?.find( (b) => (b.includes("'Source Serif 4'") || b.includes('"Source Serif 4"')) && b.includes('font-weight: 600') && b.includes('font-style: normal') ); assert(!!ss4_600_block, 'Source Serif 4 600 normal declaration exists'); // Check for 400 italic const ss4_400i_block = fontFaceBlocks?.find( (b) => (b.includes("'Source Serif 4'") || b.includes('"Source Serif 4"')) && b.includes('font-weight: 400') && b.includes('font-style: italic') ); assert(!!ss4_400i_block, 'Source Serif 4 400 italic declaration exists'); // === 5. Inter declarations (structure voice) === console.log('\n5. Inter declarations (structure voice)'); const interWeights = [400, 500, 600, 700]; for (const weight of interWeights) { const interBlock = fontFaceBlocks?.find( (b) => (b.includes("'Inter'") || b.includes('"Inter"')) && b.includes(`font-weight: ${weight}`) && b.includes('font-style: normal') ); assert(!!interBlock, `Inter ${weight} normal declaration exists`); } // === 6. JetBrains Mono declaration (evidence voice) === console.log('\n6. JetBrains Mono declaration (evidence voice)'); const jbBlock = fontFaceBlocks?.find( (b) => (b.includes("'JetBrains Mono'") || b.includes('"JetBrains Mono"')) && b.includes('font-weight: 400') && b.includes('font-style: normal') ); assert(!!jbBlock, 'JetBrains Mono 400 normal declaration exists'); // === 7. All declarations use format('woff2') === console.log("\n7. All use format('woff2')"); if (fontFaceBlocks) { for (const block of fontFaceBlocks) { assert(block.includes("format('woff2')"), `@font-face block uses format('woff2')`); } } // === 8. All declarations use font-display: swap === console.log('\n8. All use font-display: swap'); if (fontFaceBlocks) { for (const block of fontFaceBlocks) { assert(block.includes('font-display: swap'), '@font-face block uses font-display: swap'); } } // === 9. All src URLs use relative paths to ../fonts/ === console.log('\n9. src URLs use relative ../fonts/ paths'); if (fontFaceBlocks) { for (const block of fontFaceBlocks) { assert(block.includes("url('../fonts/"), "@font-face src uses url('../fonts/...')"); } } // === 10. download-fonts.py script exists === console.log('\n10. download-fonts.py script exists'); const scriptPath = join(root, 'scripts/download-fonts.py'); assert(existsSync(scriptPath), 'scripts/download-fonts.py exists'); if (existsSync(scriptPath)) { const script = readFileSync(scriptPath, 'utf8'); assert(script.includes('urllib.request'), 'script uses urllib.request'); assert(script.includes('User-Agent') || script.includes('user-agent'), 'script sets User-Agent header'); assert(script.includes('woff2'), 'script references woff2 format'); } // === Summary === console.log(`\n${'='.repeat(40)}`); console.log(`Results: ${passed} passed, ${failed} failed out of ${passed + failed}`); if (failed > 0) { process.exit(1); } else { console.log('All font verification tests passed!'); }