/** * Tests for design-parser.mjs — frontmatter + body extraction. * Run with: node --test tests/design-parser.test.mjs */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { assessCoverage, parseDesignMd } from '../skill/scripts/lib/design-parser.mjs'; describe('parseDesignMd frontmatter branch', () => { it('returns null frontmatter when the file has no YAML header', () => { const md = `# Design System: Demo ## 1. Overview Some prose. `; const model = parseDesignMd(md); assert.equal(model.schemaVersion, 2); assert.equal(model.frontmatter, null); assert.equal(model.title, 'Design System: Demo'); }); it('parses a Stitch-shaped frontmatter and strips it from the body', () => { const md = `--- name: Demo System description: A quiet editorial look. colors: primary: "#b8422e" neutral-bg: "#faf7f2" typography: display: fontFamily: "Cormorant Garamond, Georgia, serif" fontWeight: 300 lineHeight: 1 body: fontFamily: "Inter, sans-serif" rounded: sm: "4px" md: "8px" components: button-primary: backgroundColor: "{colors.primary}" textColor: "{colors.neutral-bg}" rounded: "{rounded.sm}" --- # Design System: Demo ## 1. Overview Opening prose. `; const model = parseDesignMd(md); assert.equal(model.schemaVersion, 2); assert.equal(model.title, 'Design System: Demo'); assert.ok(model.frontmatter); assert.equal(model.frontmatter.name, 'Demo System'); assert.equal(model.frontmatter.description, 'A quiet editorial look.'); assert.equal(model.frontmatter.colors.primary, '#b8422e'); assert.equal(model.frontmatter.colors['neutral-bg'], '#faf7f2'); assert.equal(model.frontmatter.typography.display.fontFamily, 'Cormorant Garamond, Georgia, serif'); assert.equal(model.frontmatter.typography.display.fontWeight, 300); assert.equal(model.frontmatter.typography.display.lineHeight, 1); assert.equal(model.frontmatter.rounded.md, '8px'); assert.equal(model.frontmatter.components['button-primary'].backgroundColor, '{colors.primary}'); }); it('recovers gracefully when frontmatter has no closing marker', () => { // No `---` terminator: the whole file is treated as body, not partial // frontmatter. The H1 title still resolves from the body. const md = `--- this is not valid yaml : : : no closing marker # Design System: Broken ## 1. Overview Prose. `; const model = parseDesignMd(md); assert.equal(model.frontmatter, null); assert.equal(model.title, 'Design System: Broken'); }); it('ignores line-only comments but preserves unquoted hex values', () => { const md = `--- # Top-level comment colors: primary: #b8422e # mid-block comment accent: "#ec4899" --- # Design System: Commented ## 1. Overview Prose. `; const model = parseDesignMd(md); assert.equal(model.frontmatter.colors.primary, '#b8422e'); assert.equal(model.frontmatter.colors.accent, '#ec4899'); }); it('strips inline comments after quoted OKLCH values', () => { const md = `--- colors: kinpaku-gold: "oklch(84% 0.19 80.46)" # primary accent gold-hairline: "oklch(58% 0.065 82 / 0.32)" # default rule --- # Design System: Kinpaku ## 1. Overview Prose. `; const model = parseDesignMd(md); assert.equal(model.frontmatter.colors['kinpaku-gold'], 'oklch(84% 0.19 80.46)'); assert.equal(model.frontmatter.colors['gold-hairline'], 'oklch(58% 0.065 82 / 0.32)'); }); it('normalizes quoted YAML keys in token maps', () => { const md = `--- rounded: "2xl": "80px" '3xl': "96px" colors: "brand-gold": "#d9a531" --- # Design System: Quoted Keys ## 1. Overview Prose. `; const model = parseDesignMd(md); assert.equal(model.frontmatter.rounded['2xl'], '80px'); assert.equal(model.frontmatter.rounded['3xl'], '96px'); assert.equal(model.frontmatter.colors['brand-gold'], '#d9a531'); assert.equal(model.frontmatter.rounded['"2xl"'], undefined); }); it('unescapes quote escapes inside quoted scalars (issue #428)', () => { const md = `--- typography: body: fontFamily: "\\"IBM Plex Sans\\", system-ui, sans-serif" name: 'It''s quiet' empty: " --- # Design System: Escaped ## 1. Overview Prose. `; const model = parseDesignMd(md); // YAML double-quoted scalars process backslash escapes. assert.equal(model.frontmatter.typography.body.fontFamily, '"IBM Plex Sans", system-ui, sans-serif'); // Single-quoted scalars escape the quote by doubling it. assert.equal(model.frontmatter.name, "It's quiet"); // A lone quote satisfies startsWith and endsWith at once; keep it literal // instead of slicing it into an empty string. assert.equal(model.frontmatter.empty, '"'); }); it('decodes hex, Unicode, and whitespace escapes in double-quoted scalars', () => { const md = `--- colors: accent: "\\x23b8422e" typography: accent: fontFamily: "S\\u00f6hne, sans-serif" label: fontFamily: "IBM\\ Plex\\ Serif, serif" mono: fontFamily: "Space\\_Grotesk, sans-serif" emoji: "\\U0001F44D" bad-hex: "\\xZZ nope" bad-range: "\\UFFFFFFFF nope" --- # Design System: Hex Escapes ## 1. Overview Prose. `; const model = parseDesignMd(md); assert.equal(model.frontmatter.colors.accent, '#b8422e'); assert.equal(model.frontmatter.typography.accent.fontFamily, 'Söhne, sans-serif'); // \ (escaped space) and \_ (non-breaking space) are valid YAML escapes. assert.equal(model.frontmatter.typography.label.fontFamily, 'IBM Plex Serif, serif'); assert.equal(model.frontmatter.typography.mono.fontFamily, 'Space\u00a0Grotesk, sans-serif'); assert.equal(model.frontmatter.emoji, '\u{1F44D}'); // Malformed or out-of-range sequences stay literal. assert.equal(model.frontmatter['bad-hex'], '\\xZZ nope'); assert.equal(model.frontmatter['bad-range'], '\\UFFFFFFFF nope'); }); }); describe('parseDesignMd overview branch', () => { it('joins wrapped Key Characteristics bullets without leaking continuations into philosophy', () => { const md = `# Design System: Example ## Overview **Creative North Star: "Structured clarity"** **Key Characteristics:** - Status remains understandable without relying on color alone. - Navigation controls remain visible when the viewport becomes narrow. `; const overview = parseDesignMd(md).overview; assert.deepEqual(overview.keyCharacteristics, [ 'Status remains understandable without relying on color alone.', 'Navigation controls remain visible when the viewport becomes narrow.', ]); assert.deepEqual(overview.philosophy, []); }); }); describe('parseDesignMd named rules', () => { it('preserves format precedence while deduplicating later definitions', () => { const md = `# Design System: Rules ## Layout ### The "Rhythm" Rule Ignore this duplicate heading definition. ### The Fallback Principle Use the heading definition. ### Named Rules - **The Fallback Principle:** Ignore this duplicate bullet definition. - **The Layering Principle:** Use the bullet definition. **The Rhythm Rule.** Keep the first definition. `; assert.deepEqual(parseDesignMd(md).layout.rules, [ { name: 'The Rhythm Rule', body: 'Keep the first definition.' }, { name: 'The Fallback Principle', body: 'Use the heading definition.' }, { name: 'The Layering Principle', body: 'Use the bullet definition.' }, ]); }); }); describe('parseDesignMd canonical sections', () => { it('preserves content and named rules from all eight canonical sections', () => { const md = `# Design System: Complete ## Overview **Creative North Star: "Structured clarity"** ## Colors ### Primary - **Ink** (#111111): Primary text. ## Typography **Body Font:** Inter (with sans-serif) ## Layout: Responsive rhythm Primary regions use a twelve-column grid that collapses to one column on narrow screens. ### Named Rules **The Spatial Hierarchy Rule.** Primary content must remain visually dominant. ## Elevation & Depth Surfaces use tonal layering instead of shadows. ## Shapes Selected objects use a double outline and clipped corners. ### The "Recognizable Silhouette" Rule Repeated geometry must remain recognizable without color. ## Components ### Button - **Primary:** Uses the accent color. ## Do's and Don'ts ### Do - Preserve the spatial hierarchy. ### Don't - Flatten every surface. `; const model = parseDesignMd(md); assert.equal(model.layout.subtitle, 'Responsive rhythm'); assert.equal( model.layout.description, 'Primary regions use a twelve-column grid that collapses to one column on narrow screens.', ); assert.deepEqual(model.layout.rules, [{ name: 'The Spatial Hierarchy Rule', body: 'Primary content must remain visually dominant.', }]); assert.equal(model.shapes.description, 'Selected objects use a double outline and clipped corners.'); assert.deepEqual(model.shapes.rules, [{ name: 'The Recognizable Silhouette Rule', body: 'Repeated geometry must remain recognizable without color.', }]); const coverage = assessCoverage(model); assert.deepEqual( Object.entries(coverage).filter(([, v]) => v === 'missing').map(([k]) => k), [], 'a section present in the markdown must not be reported as missing', ); assert.deepEqual(Object.keys(coverage), [ 'overview', 'colors', 'typography', 'layout', 'elevation', 'shapes', 'components', 'dosDonts', ]); assert.deepEqual(coverage.layout, { description: true, rules: 1 }); assert.deepEqual(coverage.shapes, { description: true, rules: 1 }); }); });