// Tests for `apps/daemon/src/design-system-preview.ts`. This module exports // a single public function — `renderDesignSystemPreview` — but its behavior is // driven by several internal extractors (color/font parsers, neutral filter, // hint-based color picker). We exercise those extractors through the public // API by crafting DESIGN.md fixtures that pin each branch. // // Approach: feed a permissive DESIGN.md-shaped string into the renderer and // assert on the produced HTML. Each test isolates one extractor concern so a // regression points at the right helper. import { describe, expect, it } from 'vitest'; import { renderDesignSystemPreview } from '../src/design-systems/preview.js'; // Pull every `background:;` chip style out of the palette block. Order is // preserved, which lets us check the extractColors output ordering. function paletteHexes(html: string): string[] { return Array.from(html.matchAll(/
/g)).map( (m) => (m[1] ?? '').toLowerCase(), ); } // Pull every swatch's visible name (the .name span content) for ordering checks. function paletteNames(html: string): string[] { return Array.from(html.matchAll(/([^<]+)<\/span>/g)).map((m) => m[1] ?? ''); } // The accent color drives `--accent` in the inline :root block. function cssVar(html: string, name: string): string | null { const re = new RegExp(`--${name}:\\s*([^;]+);`); const m = re.exec(html); return m ? (m[1] ?? '').trim() : null; } describe('renderDesignSystemPreview — extractColors (color section parsing)', () => { it('parses Form A `- **Name**: `#hex`` entries and preserves order', () => { // Form A regex: bold around the name, colon outside the bold. const md = [ '# Acme Brand', '', '## Color', '', '- **Background**: `#FAFAFA`', '- **Heading**: `#0B0B0F`', '- **Primary**: `#FF5701`', ].join('\n'); const html = renderDesignSystemPreview('acme', md); const hexes = paletteHexes(html); const names = paletteNames(html); expect(hexes).toEqual(['#fafafa', '#0b0b0f', '#ff5701']); expect(names).toEqual(['Background', 'Heading', 'Primary']); }); it('parses Form B `**Name** (`#hex`)` entries common in awesome-design-md', () => { const md = [ '# Stripe', '', '## Palette', '', '- **Stripe Purple** (`#533afd`) — primary brand color.', '- **Slate Ink** (`#0a2540`) — headings.', ].join('\n'); const html = renderDesignSystemPreview('stripe', md); const hexes = paletteHexes(html); expect(hexes).toContain('#533afd'); expect(hexes).toContain('#0a2540'); }); it('normalizes shorthand 3-digit hex to 6-digit form', () => { const md = ['# Tiny', '', '## Color', '', '- **Accent**: `#abc`'].join('\n'); const html = renderDesignSystemPreview('tiny', md); // #abc → #aabbcc per CSS hex shorthand expansion. expect(paletteHexes(html)).toEqual(['#aabbcc']); }); it('deduplicates entries with the same name+value', () => { const md = [ '# Dup', '', '## Color', '', '- **Primary**: `#ff0000`', '- **Primary**: `#ff0000`', ].join('\n'); const html = renderDesignSystemPreview('dup', md); expect(paletteHexes(html)).toEqual(['#ff0000']); }); it('returns an empty palette when no color section is present', () => { const md = ['# Bare', '', 'No colors here, just prose.'].join('\n'); const html = renderDesignSystemPreview('bare', md); // No swatch chips rendered, but the palette section still exists. expect(paletteHexes(html)).toEqual([]); expect(html).toContain('class="palette"'); }); }); describe('renderDesignSystemPreview — pickColor (hint-based selection)', () => { it('picks the brand/primary color into --accent', () => { const md = [ '# Brand', '', '## Color', '', '- **Background**: `#ffffff`', '- **Primary**: `#7d2ae8`', ].join('\n'); const html = renderDesignSystemPreview('brand', md); expect(cssVar(html, 'accent')).toBe('#7d2ae8'); }); it('picks the background hint into --bg', () => { const md = [ '# Bg', '', '## Color', '', '- **Page Background**: `#101014`', '- **Text**: `#fefefe`', ].join('\n'); const html = renderDesignSystemPreview('bg', md); expect(cssVar(html, 'bg')).toBe('#101014'); }); it('falls back to default --accent when no colors are declared', () => { const md = '# Empty\n\nNo color section at all.'; const html = renderDesignSystemPreview('empty', md); // The renderer hard-coded fallback for accent is #2f6feb. expect(cssVar(html, 'accent')).toBe('#2f6feb'); expect(cssVar(html, 'bg')).toBe('#ffffff'); expect(cssVar(html, 'fg')).toBe('#111111'); }); }); describe('renderDesignSystemPreview — firstNonNeutral (saturation fallback)', () => { it('skips low-saturation neutrals and picks the first vivid color for --accent', () => { // No brand/primary names → pickColor returns null, firstNonNeutral runs. // Greys/whites/blacks should be skipped; the vivid red should win. const md = [ '# Neutral', '', '## Color', '', '- **Paper**: `#ffffff`', '- **Ink**: `#111111`', '- **Mute**: `#888888`', '- **Pop**: `#e63946`', ].join('\n'); const html = renderDesignSystemPreview('neutral', md); expect(cssVar(html, 'accent')).toBe('#e63946'); }); it('falls back to the default accent when every color is neutral', () => { const md = [ '# All Grey', '', '## Color', '', '- **Paper**: `#ffffff`', '- **Stone**: `#cccccc`', '- **Ink**: `#222222`', ].join('\n'); const html = renderDesignSystemPreview('grey', md); expect(cssVar(html, 'accent')).toBe('#2f6feb'); }); }); describe('renderDesignSystemPreview — extractFonts (typography parsing)', () => { it('parses display/body/mono font declarations into the :root block', () => { const md = [ '# Fonts', '', '## Typography', '', "- **Display**: `'GT Sectra', serif`", "- **Body**: `'Inter', system-ui, sans-serif`", "- **Mono**: `'JetBrains Mono', monospace`", ].join('\n'); const html = renderDesignSystemPreview('fonts', md); expect(cssVar(html, 'display')).toBe("'GT Sectra', serif"); expect(cssVar(html, 'body')).toBe("'Inter', system-ui, sans-serif"); expect(cssVar(html, 'mono')).toBe("'JetBrains Mono', monospace"); }); it('falls back to system stacks when no font declarations are present', () => { const md = '# Bare\n\nNo typography section here.'; const html = renderDesignSystemPreview('bare', md); // Default display stack defined in the source. expect(cssVar(html, 'display')).toContain('system-ui'); expect(cssVar(html, 'mono')).toContain('ui-monospace'); }); it('uses heading label as a display alias when "display" is absent', () => { const md = [ '# Headings', '', '## Typography', '', "- **Heading**: `'Playfair Display', serif`", ].join('\n'); const html = renderDesignSystemPreview('headings', md); // Heading label maps onto display via the heading regex branch. expect(cssVar(html, 'display')).toBe("'Playfair Display', serif"); }); }); describe('renderDesignSystemPreview — end-to-end render', () => { it('produces a complete HTML document with all major sections', () => { const md = [ '# Example', '', 'A short subtitle describing the system.', '', '## Color', '', '- **Primary**: `#2f6feb`', '', '## Typography', '', "- **Display**: `'Inter', sans-serif`", ].join('\n'); const html = renderDesignSystemPreview('example', md); expect(html.startsWith('')).toBe(true); expect(html).toContain('Example — design system preview'); expect(html).toContain('class="palette"'); expect(html).toContain('class="typo-row"'); expect(html).toContain('class="components"'); // The "Design system preview · " badge surfaces the id. expect(html).toContain('Design system preview · example'); }); it('strips the "Design System Inspired by" title prefix', () => { const md = '# Design System Inspired by Linear\n\nDesc.'; const html = renderDesignSystemPreview('linear', md); // cleanTitle should drop the "Design System Inspired by" prefix. expect(html).toContain('Linear — design system preview'); expect(html).toContain('

Linear

'); }); it('escapes HTML in title, id, and subtitle to prevent injection', () => { // Use angle brackets in the title and subtitle to confirm escapeHtml runs // on every user-content position before interpolation. const md = [ '# EvilBrand', '', 'Subtitle with .', ].join('\n'); const html = renderDesignSystemPreview('id">danger', md); // Raw should never leak into the title; only the escaped form. expect(html).not.toContain('<b>Evil</b>'); expect(html).toContain('<b>Evil</b>Brand'); // The id is rendered inside the badge with escapeHtml — the closing // sequence `">` must be encoded so an attacker cannot break out. expect(html).toContain('id">danger'); // Subtitle escapes too. expect(html).toContain('<img tag>'); }); it('caps the palette at 12 swatches even when more colors are extracted', () => { const lines = ['# Many', '', '## Color', '']; // Generate 15 distinct colors so we hit the .slice(0, 12) cap. for (let i = 0; i < 15; i++) { const hex = `#${i.toString(16).padStart(2, '0')}${i.toString(16).padStart(2, '0')}${i.toString(16).padStart(2, '0')}`; lines.push(`- **Color${i}**: \`${hex}\``); } const html = renderDesignSystemPreview('many', lines.join('\n')); // Only the first 12 entries should make it into the palette grid. expect(paletteHexes(html).length).toBe(12); }); it('uses the id as the title when no top-level heading is present', () => { const md = 'No heading at all, just body prose.'; const html = renderDesignSystemPreview('fallback-id', md); expect(html).toContain('<title>fallback-id — design system preview'); expect(html).toContain('

fallback-id

'); }); });