mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
AI-assisted by OpenAI Codex under maintainer pbakaus's scheduled-refactor authorization.
343 lines
9.5 KiB
JavaScript
343 lines
9.5 KiB
JavaScript
/**
|
|
* 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 });
|
|
});
|
|
});
|