// VYNDR 2.0 — Phase D core screens (Session 35). Adapter LOGIC is exercised // directly via the CommonJS gradeAdapter; the .tsx screens/components are // asserted against source text (the plain-JS Jest config has no TS transform). 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 adapter = require('../../web/src/lib/gradeAdapter'); describe('Phase D — grade adapter (engine → §7 contract)', () => { const base = { player: 'Nikola Jokic', sport: 'NBA', stat: 'home_runs', line: 26.5, direction: 'over', grade: 'A', projection: 29.4, confidence: 67, sample_size: 34, factors: { matchup: 'soft interior', trend: 'hot', usage: 'up', rest: 'rested', pace: 'fast' }, kill_conditions: [{ code: 'BLOWOUT', reason: 'Blowout risk caps minutes' }], alt_lines: [{ line: 24.5, grade: 'A+' }, { line: 28.5, grade: 'B' }], }; it('maps direction → side and humanizes the stat', () => { const out = adapter.mapScanToGradeResult({ ...base, tier: 'desk' }); expect(out.side).toBe('Over'); expect(out.stat).toBe('Home Runs'); expect(out.player).toBe('Nikola Jokic'); }); it('computes a signed percentage edge from projection vs line', () => { expect(adapter.computeEdge(29.4, 26.5, 'over')).toBeCloseTo(10.9, 1); expect(adapter.computeEdge(24, 26.5, 'over')).toBeLessThan(0); expect(adapter.computeEdge(24, 26.5, 'under')).toBeGreaterThan(0); }); it('flags phosphor confirmed only for A/A+ with strong support', () => { expect(adapter.isPhosphorConfirmed('A+', 80, 40)).toBe(true); expect(adapter.isPhosphorConfirmed('A', 50, 35)).toBe(true); // sample carries it expect(adapter.isPhosphorConfirmed('B', 90, 90)).toBe(false); expect(adapter.isPhosphorConfirmed('A', 40, 5)).toBe(false); }); it('FREE tier teases 3 signals, hides kill conditions and the alt ladder', () => { const out = adapter.mapScanToGradeResult({ ...base, tier: 'free' }); expect(out.signals.length).toBe(3); expect(out.killConditions).toEqual([]); expect(out.altLadder).toEqual([]); }); it('ANALYST tier shows kill conditions + all signals, but no alt ladder', () => { const out = adapter.mapScanToGradeResult({ ...base, tier: 'analyst' }); expect(out.signals.length).toBe(5); expect(out.killConditions).toEqual(['Blowout risk caps minutes']); expect(out.altLadder).toEqual([]); }); it('DESK tier unlocks the alt line ladder', () => { const out = adapter.mapScanToGradeResult({ ...base, tier: 'desk' }); expect(out.altLadder).toEqual([{ line: 24.5, grade: 'A+' }, { line: 28.5, grade: 'B' }]); }); it('defaults missing fields safely', () => { const out = adapter.mapScanToGradeResult({}); expect(out.grade).toBe('—'); expect(out.side).toBe('Over'); expect(out.signals).toEqual([]); expect(out.books).toEqual([]); }); }); describe('Phase D — GradeResultCard (the core moment)', () => { const src = read('components/vyndr/GradeResultCard.tsx'); it('renders the grade letter at hero size (92–116px)', () => { expect(src).toContain('116'); expect(src).toContain('92'); }); it('uses the intel-surface for the grade hero zone + grade-reveal', () => { expect(src).toContain('intel-surface'); expect(src).toContain('grade-reveal'); }); it('highlights the best book with green tint + green left border', () => { expect(src).toContain('rgba(0,212,160,.13)'); expect(src).toContain("borderLeft: b.best ? '2px solid var(--g-a)'"); }); it('renders kill conditions with amber border, gated on non-empty', () => { expect(src).toContain('hasKill'); expect(src).toContain('rgba(255,179,71,.4)'); expect(src).toContain('KILL CONDITIONS'); }); it('self-hides books / alt ladder when empty', () => { expect(src).toContain('hasBooks'); expect(src).toContain('hasAlt'); }); }); describe('Phase D — ProcessingGrade', () => { const src = read('components/vyndr/ProcessingGrade.tsx'); it('plays the factor-ignite + proc-scan sequence then reveals the card', () => { expect(src).toContain('factor-ignite'); expect(src).toContain('proc-scan'); expect(src).toContain(' { const src = read('components/vyndr/GameCard.tsx'); it('renders team abbreviations', () => { expect(src).toContain('g.away.abbr'); expect(src).toContain('g.home.abbr'); }); it('best line = green tint + green border; worst = subtle red', () => { expect(src).toContain('rgba(0,212,160,.13)'); expect(src).toContain('rgba(255,82,82,.07)'); }); it('shows a live-dot for live games and grades props with GradeBadge', () => { expect(src).toContain('live-dot'); expect(src).toContain(' { const src = read('components/vyndr/ClaimMeter.tsx'); it('renders the amber founder-seat scarcity bar', () => { expect(src).toContain('CLAIMED'); expect(src).toContain('var(--amber)'); }); }); describe('Phase D — Terminal page (real intelligence, not a stub)', () => { const src = read('app/terminal/page.tsx'); it('is no longer a RouteStub', () => { expect(src).not.toContain('RouteStub'); }); it('uses the intel-surface and renders the VVI / cascade / leaders sections', () => { expect(src).toContain('intel-surface'); expect(src).toContain('VOLATILITY INDEX'); expect(src).toContain('INJURY WIRE'); expect(src).toContain('GRADEABLE LEADERS'); }); }); describe('Phase D — scan + landing wiring', () => { it('scan renders the new ProcessingGrade card via the adapter (legacy GradeCard removed)', () => { const src = read('app/scan/page.tsx'); expect(src).toContain('ProcessingGrade'); expect(src).toContain('mapScanToGradeResult'); expect(src).not.toContain("from '@/components/GradeCard'"); }); it('scan keeps sportsbook deep-links safe (noopener noreferrer)', () => { expect(read('app/scan/page.tsx')).toContain('noopener noreferrer'); }); it('landing mounts the ClaimMeter', () => { expect(read('app/page.tsx')).toContain('ClaimMeter'); }); });