/** * D1-A — design self-contained implementation (specs/design-vs-build-gap-audit.md). * Built to the EXACT package spec in specs/design-reference/HANDOFF.md. */ const fs = require('fs'); const path = require('path'); const read = (p) => fs.readFileSync(path.join(__dirname, '../../', p), 'utf8'); const arche = require('../../web/src/lib/archetypes'); const rx = require('../../web/src/lib/reactions'); describe('glyphs — wired ONLY where a classifier can assign the archetype', () => { const COMBAT = ['striker', 'grappler', 'pressure', 'counter', 'grinder', 'finisher']; test('the six classifier-backed combat marks are wired from the package SVGs', () => { const src = read('web/src/lib/archetypes.js'); const m = src.match(/const GLYPHS = \{([\s\S]*?)\n\};/); const keys = [...m[1].matchAll(/^\s*'([a-z0-9 _-]+)':/gm)].map((x) => x[1]); for (const c of COMBAT) expect(keys).toContain(c); }); test('each wired mark is real SVG inner-markup on currentColor (not an emoji)', () => { const src = read('web/src/lib/archetypes.js'); for (const c of COMBAT) { const row = src.match(new RegExp(`'${c}': '([^']*)'`)); expect(row).toBeTruthy(); expect(row[1]).toMatch(/<(path|circle|rect|g|line|polygon)/); expect(row[1]).toMatch(/currentColor/); } }); test('glyphs whose archetype has NO classifier are NOT wired dead (held for D1-B)', () => { // The package ships 83 SVGs; the registry backs far fewer. Wiring a mark the // classifier can never assign would render nothing, so the count must stay // well under the package total. const src = read('web/src/lib/archetypes.js'); const m = src.match(/const GLYPHS = \{([\s\S]*?)\n\};/); const keys = [...m[1].matchAll(/^\s*'([a-z0-9 _-]+)':/gm)].map((x) => x[1]); const pkg = fs.readdirSync(path.join(__dirname, '../../specs/design-reference/assets/glyphs')) .filter((f) => f.endsWith('.svg')); expect(pkg.length).toBe(83); expect(keys.length).toBeLessThan(pkg.length); // deliberately not all 83 expect(keys.length).toBe(44); }); }); describe('boundary channel — one meaning, EVERY boundary state', () => { test('the blue tokens exist as a semantic set', () => { const css = read('web/src/app/globals.css'); expect(css).toMatch(/--priced-out:\s*#8fb2de/i); expect(css).toMatch(/--priced-out-tint-2:\s*rgba\(106, ?147, ?200, ?0?\.12\)/); expect(css).toMatch(/--priced-out-border-hi:\s*rgba\(106, ?147, ?200, ?0?\.34\)/); }); test('NO_MODEL (line not priced) now renders in the boundary channel — the last uncovered state', () => { const src = read('web/src/components/vyndr/PriceTriplet.tsx'); expect(src).toMatch(/function modelAbsentColor/); expect(src).toMatch(/STATES\.NO_MODEL \? 'var\(--priced-out\)'/); }); test('the other two boundary states already use it (all of them, or none)', () => { expect(read('web/src/components/vyndr/NoMarketState.tsx')).toMatch(/priced-out-tint-2/); expect(read('web/src/app/scan/page.tsx')).toMatch(/priced-out-border-hi/); }); test('boundary blue is NOT the refusal red nor the quarantine amber', () => { const css = read('web/src/app/globals.css'); expect(css).not.toMatch(/--priced-out:\s*#FF4757/i); expect(css).not.toMatch(/--priced-out:\s*#FFB347/i); }); }); describe('reaction primitives — exact HANDOFF timings, data events only', () => { test('timings match the handoff verbatim', () => { expect(rx.FLASH_MS).toBe(750); // .75s ease-out expect(rx.BOOT_STEP_MS).toBe(60); // 60ms steps expect(rx.REACTION_GATE_MS).toBe(1500); // ~1.5s expect(rx.WIRE_HOLD_MS).toBe(6000); // ~6s }); test('boot stagger steps at 60ms and the hero (index 0) has no delay', () => { expect(rx.bootDelayMs(0)).toBe(0); expect(rx.bootDelayMs(1)).toBe(60); expect(rx.bootDelayMs(5)).toBe(300); }); test('reactions are GATED until boot settles — no flash during the entrance', () => { expect(rx.reactionsReady(0)).toBe(false); expect(rx.reactionsReady(1499)).toBe(false); expect(rx.reactionsReady(1500)).toBe(true); }); test('nudge REFUSES a no-op — no direction means no flash (never an idle loop)', () => { expect(rx.nudge({}, null)).toBeNull(); expect(rx.nudge({}, undefined)).toBeNull(); expect(rx.nudge(null, true)).toBeNull(); }); test('a real change flashes by SIGN', () => { expect(rx.nudge({}, true)).toBe('rx-up'); expect(rx.nudge({}, false)).toBe('rx-down'); }); test('the keyframes exist and honour reduced motion', () => { const css = read('web/src/app/globals.css'); expect(css).toMatch(/@keyframes rx-up/); expect(css).toMatch(/@keyframes vy-rowin/); expect(css).toMatch(/@keyframes vy-arrive/); expect(css).toMatch(/animation: rx-up\s+\.75s ease-out/); expect(css).toMatch(/prefers-reduced-motion: reduce/); }); }); describe('READ-FAB — exact package geometry', () => { const src = read('web/src/components/BottomTabBar.tsx'); test('50px circle, translateY(-14px), 6px void ring', () => { expect(src).toMatch(/width: 50/); expect(src).toMatch(/height: 50/); expect(src).toMatch(/translateY\(-14px\)/); expect(src).toMatch(/border: '6px solid var\(--bg-0\)'/); }); test('the pre-spec geometry is gone', () => { expect(src).not.toMatch(/width: 46,\s*\n\s*height: 46/); expect(src).not.toMatch(/marginTop: -16/); }); }); describe('card token', () => { test('#0E0E14 is the tokenised card surface (void -> card -> elevated)', () => { const css = read('web/src/app/globals.css'); expect(css).toMatch(/--bg-1:\s*#0E0E14/i); expect(css).toMatch(/--card:\s*var\(--bg-1\)/); }); });