// VYNDR 2.0 design system — Phase A (tokens/CSS/fonts/glitch) + Phase B // (shared components). The .tsx components can't run under the plain-JS Jest // config (no Babel/TS transform), so component contracts are asserted against // their source text; the helper LOGIC is exercised directly via the CommonJS // vyndrTokens module that the components actually import. const fs = require('fs'); const path = require('path'); const WEB = path.join(__dirname, '..', '..', 'web', 'src'); const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8'); const css = read('app/globals.css'); const layout = read('app/layout.tsx'); const tokens = require('../../web/src/lib/vyndrTokens'); describe('Phase A — design tokens (§2)', () => { const REQUIRED_TOKENS = [ '--bg-0', '--bg-1', '--bg-2', '--bg-3', '--border', '--border-hi', '--text-0', '--text-1', '--text-2', '--g-ap', '--g-a', '--g-b', '--g-c', '--g-d', '--s-nba', '--s-mlb', '--s-wnba', '--s-soccer', '--acc-0', '--acc-1', '--amber', '--amber-glow', '--live', '--hit', '--miss', '--glitch', '--scan-op', '--grade-hero', '--sans', '--mono', ]; it.each(REQUIRED_TOKENS)('defines %s in :root', (token) => { expect(css).toMatch(new RegExp(`${token.replace(/[-]/g, '\\-')}\\s*:`)); }); it('uses the exact §2 grade hex values', () => { expect(css).toContain('--g-ap: #00ffb8'); expect(css).toContain('--g-a: #00d4a0'); expect(css).toContain('--g-b: #4a9eff'); expect(css).toContain('--g-c: #ffb347'); expect(css).toContain('--g-d: #ff5252'); }); it('sets --sans to Inter and --mono to JetBrains Mono', () => { expect(css).toMatch(/--sans:\s*'Inter'/); expect(css).toMatch(/--mono:\s*'JetBrains Mono'/); }); it('sets glitch intensity to the §2 baseline of 1 and scan-op to 0.04', () => { expect(css).toMatch(/--glitch:\s*1;/); expect(css).toMatch(/--scan-op:\s*0\.04;/); }); }); describe('Phase A — fonts (§2)', () => { it('loads Inter (400–900) and JetBrains Mono (400–800) from Google Fonts', () => { expect(layout).toContain('Inter:wght@400;500;600;700;800;900'); expect(layout).toContain('JetBrains+Mono:wght@400;500;600;700;800'); }); }); describe('Phase A — glitch + animation keyframes (§4, ported verbatim)', () => { const KEYFRAMES = [ 'wm-tear', 'glitch-shift-r', 'glitch-shift-b', 'wm-caret', 'head-tear', 'crt-sweep', 'crt-sweep-local', 'phosphor-pulse', 'grade-reveal', 'ticker-scroll', 'live-pulse', 'flash-up', 'flash-down', 'ekg-scroll', 'node-pulse', 'synapse-travel', 'count-tick', 'factor-ignite', 'toast-in', 'sheet-up', 'cmd-in', 'fade-in', 'scan-drift', ]; it.each(KEYFRAMES)('defines @keyframes %s', (name) => { expect(css).toContain(`@keyframes ${name}`); }); it('floors fade-in at the visible state (opacity .6, never 0)', () => { expect(css).toMatch(/@keyframes fade-in\s*\{\s*from\s*\{\s*opacity:\s*0\.6;/); }); }); describe('Phase A — scanline texture + reduced motion (§4, §10)', () => { it('has the .scanlines::after CRT texture class', () => { expect(css).toContain('.scanlines::after'); expect(css).toMatch(/repeating-linear-gradient\(0deg, transparent 50%, rgba\(0,0,0,1\) 50%\)/); expect(css).toMatch(/opacity: var\(--scan-op\)/); }); it('respects prefers-reduced-motion and the in-app data-motion toggle', () => { expect(css).toContain('@media (prefers-reduced-motion: reduce)'); expect(css).toContain('html[data-motion="reduced"]'); }); it('ships the a11y preference layer (contrast / text / colorblind / readable font)', () => { expect(css).toContain('html[data-contrast="high"]'); expect(css).toContain('html[data-text="xl"]'); expect(css).toContain('html[data-cb="1"]'); expect(css).toContain('html[data-font="readable"]'); }); }); describe('Phase B — token helpers (vyndrTokens)', () => { it('gradeColor returns the correct CSS variable for each grade tier', () => { expect(tokens.gradeColor('A+')).toBe('var(--g-ap)'); expect(tokens.gradeColor('A')).toBe('var(--g-a)'); expect(tokens.gradeColor('A-')).toBe('var(--g-a)'); expect(tokens.gradeColor('B+')).toBe('var(--g-b)'); expect(tokens.gradeColor('B')).toBe('var(--g-b)'); expect(tokens.gradeColor('C')).toBe('var(--g-c)'); expect(tokens.gradeColor('D')).toBe('var(--g-d)'); }); it('gradeColor falls back to primary text for unknown grades', () => { expect(tokens.gradeColor('Z')).toBe('var(--text-0)'); }); it('gradeHex returns the exact §2 hex for each grade', () => { expect(tokens.gradeHex('A+')).toBe('#00ffb8'); expect(tokens.gradeHex('A')).toBe('#00d4a0'); expect(tokens.gradeHex('B')).toBe('#4a9eff'); expect(tokens.gradeHex('C')).toBe('#ffb347'); expect(tokens.gradeHex('D')).toBe('#ff5252'); }); it('SPORT map carries every sport with its token color and label', () => { expect(tokens.SPORT.nba).toEqual({ label: 'NBA', color: 'var(--s-nba)', hex: '#e94b3c' }); expect(tokens.SPORT.mlb.hex).toBe('#1e90ff'); expect(tokens.SPORT.wnba.hex).toBe('#f7944a'); expect(tokens.SPORT.soccer.hex).toBe('#3ddc84'); }); it('gradeBadgeSize maps variants and passes raw numbers through; hero is 80–120px', () => { expect(tokens.gradeBadgeSize('sm')).toBe(16); expect(tokens.gradeBadgeSize('lg')).toBe(48); expect(tokens.gradeBadgeSize(72)).toBe(72); const hero = tokens.gradeBadgeSize('hero'); expect(hero).toBeGreaterThanOrEqual(80); expect(hero).toBeLessThanOrEqual(120); }); }); describe('Phase B — shared component contracts (§5)', () => { const comp = (name) => read(path.join('components', 'vyndr', name)); it('Wordmark renders the green R, caret, VYNDR data-text and BETA pill', () => { const src = comp('Wordmark.tsx'); expect(src).toContain('data-text="VYNDR"'); expect(src).toContain('className="wm-r"'); expect(src).toContain('wm-cursor'); expect(src).toContain('wm-beta'); }); it('GradeBadge colors by grade token and supports the hero size', () => { const src = comp('GradeBadge.tsx'); expect(src).toContain('gradeColor(grade)'); expect(src).toContain('gradeBadgeSize(size)'); expect(src).toMatch(/'hero'/); }); it('SportBadge derives its color from the SPORT token map', () => { expect(comp('SportBadge.tsx')).toContain('SPORT[sport'); }); it('VBtn defines all five variants', () => { const src = comp('VBtn.tsx'); ['primary', 'outline', 'ghost', 'amber', 'danger'].forEach((v) => { expect(src).toContain(`${v}:`); }); expect(src).toContain("background: 'var(--g-a)'"); // primary = grade-green filled }); it('Card is a Level-1 (bg-1) surface with the scanline skin', () => { const src = comp('Card.tsx'); expect(src).toContain("background: 'var(--bg-1)'"); expect(src).toContain('scanlines'); }); it('TerminalInput uses a phosphor cursor while empty', () => { expect(comp('TerminalInput.tsx')).toContain('phosphor-cursor'); }); it('Sparkline renders an SVG path from its data points', () => { const src = comp('Sparkline.tsx'); expect(src).toContain(' { const src = comp('Ticker.tsx'); expect(src).toContain('ticker-track'); expect(src).toMatch(/\{content\}\s*\{content\}/); }); });