// S6 (A1 board) — ROW-GRAMMAR source locks (specs/ROW-GRAMMAR.md). // Density is unlimited when grammar is fixed: every element is DATA with ONE // meaning in the SAME slot on every row of its type. These tests fail if a // slot moves, a color changes meaning, or truncation sneaks onto a name. 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 strip = read('components/vyndr/StatStrip.tsx'); const spec = fs.readFileSync(path.join(__dirname, '..', '..', 'specs', 'ROW-GRAMMAR.md'), 'utf8'); /** Assert `markers` appear in this exact order in `src`, starting at `from`. */ function assertOrder(src, markers, from = 0) { let cursor = from; for (const m of markers) { const i = src.indexOf(m, cursor); expect({ marker: m, found: i >= 0 }).toEqual({ marker: m, found: true }); cursor = i + m.length; } return cursor; } describe('ROW-GRAMMAR §2 — canonical prop-row slot order (snapshot mode)', () => { test('stat+line → market context (dot, movement) → model output (revision, grade) → outcome → actions → provenance', () => { // Anchor on the graded row's stat+side+line span (unique to it). const base = strip.indexOf("{p.stat} {p.side}{p.line}"); expect(base).toBeGreaterThan(-1); assertOrder(strip, [ '', // slot 4a — best available price '', // slot 4b — market movement 'p.revisedFrom', // slot 5a — revision strikethrough '', // slot 6 — settled result '', // slot 7a — action '', // slot 7b — action 'Graded {p.gradedAt.ago}', // slot 8 — provenance, always last ], base); }); test('sub-line order: last-10 dots → sparkline → current-line delta', () => { const base = strip.indexOf('ROW-GRAMMAR sub-line'); expect(base).toBeGreaterThan(-1); assertOrder(strip, [ '', '', 'Current {p.delta.currentLine}', ], base); }); test('strip header: identity run (name → team → archetype) then viability chips', () => { const base = strip.indexOf('// compact'); expect(base).toBeGreaterThan(-1); assertOrder(strip, [ '', '', ], base); }); test('settled/dead rows suppress actions (the bet is over)', () => { expect(strip).toContain('{!p.outcome && !p.dead && }'); expect(strip).toContain('{!p.outcome && !p.dead && }'); }); }); describe('ROW-GRAMMAR §3 — one meaning per color', () => { const section = (start, end) => { const a = strip.indexOf(start); const b = end ? strip.indexOf(end, a) : strip.length; expect(a).toBeGreaterThan(-1); return strip.slice(a, b === -1 ? strip.length : b); }; test('sparkline is green/amber/dim — never red (nothing has settled)', () => { const src = section('export function LineSparkline', 'export function ViabilityChips'); expect(src).toContain("'var(--g-a)'"); expect(src).toContain("'var(--amber)'"); expect(src).not.toContain('--miss'); }); test('dot strip: filled = signal green, hollow = dim (never red/amber)', () => { const src = section('export function DotStrip', 'export function LineSparkline'); expect(src).toContain('var(--g-a'); expect(src).toContain('var(--text-2'); expect(src).not.toContain('--miss'); expect(src).not.toContain('--amber'); }); test('movement chips keep STEAM amber / VALUE green (QA.20 family)', () => { const src = section('function MovementChip', 'export interface StripArchetype'); expect(src).toContain("steam ? 'var(--amber"); expect(src).not.toContain('--miss'); }); }); describe('ROW-GRAMMAR §1.4 — no truncation of names/times/pitchers', () => { test('StatStrip never ellipsizes', () => { expect(strip).not.toMatch(/textOverflow|text-overflow|ellipsis/); }); test('SearchModal result names wrap, never truncate', () => { const sm = read('components/vyndr/SearchModal.tsx'); expect(sm).not.toMatch(/textOverflow|ellipsis/); }); }); describe('ROW-GRAMMAR — the law is written down and wired', () => { test('spec exists with the canonical slot order and color law', () => { expect(spec).toContain('CANONICAL PROP-ROW SLOT ORDER'); expect(spec).toContain('one meaning per color'); expect(spec).toContain('never'); expect(spec).not.toMatch(/!/); // VOICE — no exclamation points }); test('data marks are mono', () => { // DotStrip + sub-line render with the mono class (data never sans). const dot = strip.slice(strip.indexOf('export function DotStrip'), strip.indexOf('export function LineSparkline')); expect(dot).toContain('className="mono"'); }); });