// Wave 4B — Parlay Lab page (/parlay). Source-asserts the dedicated builder: // an independent (non-slate) leg source, useParlay/legKey wiring, the // correlation-flag caution, and the tier-aware leg cap. 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('Parlay Lab page exists at /parlay', () => { it('the route file is present', () => { expect(fs.existsSync(path.join(WEB, 'app', 'parlay', 'page.tsx'))).toBe(true); }); }); describe('/parlay — independent leg source (not the live slate)', () => { const src = read('app/parlay/page.tsx'); it('browses pre-graded props from /api/snapshot/:sport', () => { expect(src).toContain('/api/snapshot/'); }); it('resolves players via /api/players/search (per sport)', () => { expect(src).toContain('/api/players/search'); }); it('does NOT depend on the live Slate component', () => { expect(src).not.toContain("from '@/components/Slate'"); expect(src).not.toContain('slateAdapter'); }); it('offers the three parlay sports', () => { for (const s of ['MLB', 'NBA', 'WNBA']) expect(src).toContain(s); }); }); describe('/parlay — assembles a slip via useParlay + legKey', () => { const src = read('app/parlay/page.tsx'); it('imports useParlay and legKey', () => { expect(src).toContain('useParlay'); expect(src).toContain('legKey'); }); it('adds legs through addLeg and dedupes/toggles by leg key', () => { expect(src).toContain('addLeg('); expect(src).toContain('hasLeg('); expect(src).toContain('removeLeg('); }); it('renders the PARLAY SLIP with combined grade + correlation + payout from context', () => { expect(src).toContain('PARLAY SLIP'); expect(src).toContain('combined'); expect(src).toContain('correlation'); expect(src).toContain('payout'); }); }); describe('/parlay — correlation-flag caution', () => { const src = read('app/parlay/page.tsx'); it('surfaces parlayService correlation warning as a caution flag', () => { expect(src).toContain('correlation?.warning'); expect(src).toContain('CORRELATION FLAG'); }); }); describe('/parlay — tier-aware leg cap + free upsell', () => { const src = read('app/parlay/page.tsx'); it('drives the leg cap from the user tier (free 2 / analyst 4 / desk 6)', () => { expect(src).toContain('useAuth'); expect(src).toContain('setMaxLegs'); expect(src).toContain('free: 2'); expect(src).toContain('desk: 6'); }); it('honors atCap and blurs the payout for free tier with the paywall upsell', () => { expect(src).toContain('atCap'); expect(src).toContain('__goPaywall'); expect(src).toContain("blur("); }); }); describe('/parlay is reachable (open route, not gated)', () => { const routes = require('../../web/src/lib/routes.js'); it('is an OPEN route (free funnel, like scan/dashboard)', () => { expect(routes.OPEN_ROUTES).toContain('/parlay'); expect(routes.isGatedRoute('/parlay')).toBe(false); }); }); describe('legacy ParlayTray is retired', () => { it('the dead component file is gone', () => { expect(fs.existsSync(path.join(WEB, 'components', 'ParlayTray.tsx'))).toBe(false); }); });