// Session 49 — onboarding flow: page, redirect, personalization, settings, book // highlight. Page logic is asserted as source (the journey is structural); // book-matching logic runs directly. 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 books = require('../../web/src/lib/books'); describe('isPreferredBook (book matching)', () => { it('matches a book across id / code / name forms', () => { expect(books.isPreferredBook('DK', ['draftkings'])).toBe(true); expect(books.isPreferredBook('DraftKings', ['draftkings'])).toBe(true); expect(books.isPreferredBook('draftkings', ['DK'])).toBe(true); expect(books.isPreferredBook('FD', ['draftkings'])).toBe(false); }); it('returns false for empty/missing preferences', () => { expect(books.isPreferredBook('DK', [])).toBe(false); expect(books.isPreferredBook('DK', undefined)).toBe(false); }); }); describe('Onboarding page', () => { const src = read('app/onboarding/page.tsx'); it('renders 3 steps + a completion step', () => { expect(src).toContain('data-step="1"'); expect(src).toContain('data-step="2"'); expect(src).toContain('data-step="3"'); expect(src).toContain('SIGNAL ACTIVE'); expect(src).toContain("You're locked in"); }); it('step 1 requires at least one sport', () => { expect(src).toContain('disabled={sports.length === 0}'); }); it('saves preferences via POST with onboarding_complete:true → /dashboard', () => { expect(src).toContain("fetch('/api/preferences'"); expect(src).toContain('onboarding_complete: true'); expect(src).toContain("router.replace('/dashboard')"); }); it('redirects to login when not signed in (never to onboarding)', () => { expect(src).toContain("router.replace('/login?next=/onboarding')"); }); }); describe('Dashboard onboarding redirect + personalization', () => { const src = read('app/dashboard/page.tsx'); it('fetches prefs and redirects new incomplete users to /onboarding', () => { expect(src).toContain("fetch('/api/preferences'"); expect(src).toContain("router.replace('/onboarding')"); expect(src).toContain('onboarding_complete !== true'); }); it('exempts existing users via created_at cutoff', () => { expect(src).toContain('ONBOARDING_CUTOFF'); expect(src).toContain('isNewUser'); }); it('does not redirect while auth is loading', () => { expect(src).toContain('if (authLoading || !user || !session?.access_token) return;'); }); it('passes primary sport tab + preferred books to the Slate', () => { expect(src).toContain('initialTab={primaryTab}'); expect(src).toContain('preferredBooks={prefBooks}'); }); }); describe('Slate + GameCard thread preferred books', () => { it('Slate accepts + forwards preferredBooks', () => { const src = read('components/Slate.tsx'); expect(src).toContain('preferredBooks'); expect(src).toContain('preferredBooks={preferredBooks}'); }); it('GameCard highlights the preferred book row', () => { const src = read('components/vyndr/GameCard.tsx'); expect(src).toContain('isPreferredBook(ln.book, preferredBooks)'); }); }); describe('Settings preferences section', () => { const src = read('app/settings/page.tsx'); it('renders a PREFERENCES section that loads + saves prefs', () => { expect(src).toContain('label="PREFERENCES"'); expect(src).toContain("fetch('/api/preferences'"); expect(src).toContain('savePrefs'); }); }); describe('Preferences Next proxy', () => { it('forwards GET + POST with auth', () => { const src = read('app/api/preferences/route.ts'); expect(src).toContain('export async function GET'); expect(src).toContain('export async function POST'); expect(src).toContain('Authorization'); }); });