// Session 50 — Parlay Lab UI: context behaviors + components (source-asserted, // matching the repo's frontend test pattern) + legKey logic. 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'); describe('ParlayContext (Session 50 extensions)', () => { const src = read('contexts/ParlayContext.tsx'); it('legs carry team/game/archetype for the correlation model', () => { expect(src).toContain('team?: string;'); expect(src).toContain('game?: string;'); expect(src).toContain('archetype?: string;'); }); it('addLeg dedupes by player|stat|line|direction and caps at maxLegs', () => { expect(src).toContain('legKey'); expect(src).toContain('prev.length >= maxLegsRef.current'); }); it('auto-grades via /api/parlay/grade when legs >= 2', () => { expect(src).toContain("fetch('/api/parlay/grade'"); expect(src).toContain('if (legs.length < 2)'); expect(src).toContain('setCombined'); }); it('exposes combined/correlation/payout + tier cap', () => { for (const k of ['combined', 'correlation', 'payout', 'maxLegs', 'setMaxLegs', 'atCap', 'hasLeg']) { expect(src).toContain(k); } }); it('removeLeg + clear reset state', () => { expect(src).toContain('const removeLeg'); expect(src).toContain('const clear = useCallback(() => setLegs([])'); }); }); describe('StatStrip "+" button', () => { const src = read('components/vyndr/StatStrip.tsx'); it('renders an add/remove parlay button on graded props', () => { expect(src).toContain('onAddLeg'); expect(src).toContain('isLegActive'); expect(src).toContain(''); expect(src).toContain("active ? '✓' : '+'"); }); }); describe('GameCard wires the "+" to the parlay', () => { const src = read('components/vyndr/GameCard.tsx'); it('uses useParlay and builds a leg with team + game', () => { expect(src).toContain('useParlay'); expect(src).toContain('stripHandlers'); expect(src).toContain('game: gameId'); }); it('toggles the leg (add if absent, remove if present)', () => { expect(src).toContain('if (existing) removeLeg(existing.id); else addLeg(leg);'); }); }); describe('GradeResultCard add-to-parlay (scan page)', () => { it('GradeResultCard has the Add to Parlay action', () => { expect(read('components/vyndr/GradeResultCard.tsx')).toContain('Add to Parlay'); }); it('scan page wires onAddToParlay → addLeg', () => { const src = read('app/scan/page.tsx'); expect(src).toContain('onAddToParlay'); expect(src).toContain('addLeg('); }); }); describe('ParlayPanel', () => { const src = read('components/vyndr/ParlayPanel.tsx'); it('renders legs (player + grade), combined grade, correlation warning, payout', () => { expect(src).toContain('PARLAY LAB'); expect(src).toContain('COMBINED GRADE'); expect(src).toContain('correlation?.warning'); expect(src).toContain('EST. PAYOUT'); }); it('floating badge with leg count when closed', () => { expect(src).toContain('Open Parlay Lab'); expect(src).toContain('{legs.length}'); }); it('tier-gates: free blurs payout + caps at 2, desk/analyst full', () => { expect(src).toContain('TIER_MAX = { free: 2, analyst: 4, desk: 6 }'); expect(src).toContain("filter: 'blur(6px)'"); expect(src).toContain('__goPaywall'); expect(src).toContain('setMaxLegs(tierMaxLegs'); }); it('CLEAR ALL resets the slip', () => { expect(src).toContain('CLEAR ALL'); expect(src).toContain('onClick={clear}'); }); it('is mounted globally in the layout', () => { const layout = read('app/layout.tsx'); expect(layout).toContain(''); expect(layout).not.toContain(''); }); });