// VYNDR 2.0 — Phase C app shell (Session 34): routing config + auth gate, // nav, footer, 404, route stubs. Routing LOGIC is exercised directly via the // CommonJS routes module; the .tsx shell is asserted against its source text // (the plain-JS Jest config has no TS/Babel 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 exists = (rel) => fs.existsSync(path.join(WEB, rel)); const routes = require('../../web/src/lib/routes'); describe('Phase C — routing config (lib/routes)', () => { it('gates the personal-data routes', () => { ['/ledger', '/tracker', '/account', '/profile', '/notifications', '/invite'].forEach((r) => { expect(routes.isGatedRoute(r)).toBe(true); }); }); it('gates nested paths under a gated route', () => { expect(routes.isGatedRoute('/ledger/2026-06')).toBe(true); expect(routes.isGatedRoute('/settings/security')).toBe(true); }); it('leaves the landing + free funnel open (dashboard, scan stay public)', () => { ['/', '/dashboard', '/scan', '/pricing', '/blog', '/terminal', '/login'].forEach((r) => { expect(routes.isGatedRoute(r)).toBe(false); }); }); it('handles empty/undefined pathnames without throwing', () => { expect(routes.isGatedRoute('')).toBe(false); expect(routes.isGatedRoute(undefined)).toBe(false); }); it('resolves hash deep-link aliases to real routes', () => { expect(routes.resolveHashAlias('#scan')).toBe('/scan'); expect(routes.resolveHashAlias('#terminal')).toBe('/terminal'); expect(routes.resolveHashAlias('#slate')).toBe('/dashboard'); expect(routes.resolveHashAlias('#nope')).toBeNull(); expect(routes.resolveHashAlias('')).toBeNull(); }); it('keeps GATED and OPEN route lists disjoint', () => { const overlap = routes.GATED_ROUTES.filter((r) => routes.OPEN_ROUTES.includes(r)); expect(overlap).toEqual([]); }); }); describe('Phase C — AuthGate (client-side gate, keeps Supabase auth)', () => { const src = read('components/AuthGate.tsx'); it('decides gating from lib/routes, not a server middleware', () => { expect(src).toContain("from '@/lib/routes'"); expect(src).toContain('isGatedRoute'); expect(src).toContain('useAuth'); }); it('redirects to /login carrying the intended path via ?next=', () => { expect(src).toContain('/login?next='); }); it('waits for auth to finish loading before redirecting', () => { expect(src).toMatch(/if \(loading\) return/); }); }); describe('Phase C — Nav conversion', () => { const src = read('components/Nav.tsx'); it('uses the new VYNDR 2.0 Wordmark (.wm), not the legacy one', () => { expect(src).toContain("from '@/components/vyndr'"); expect(src).not.toContain("from '@/components/Wordmark'"); }); it('renders the Ticker under the bar', () => { expect(src).toContain(' { expect(src).toContain("fontFamily: 'var(--mono)'"); expect(src).toContain("'var(--g-a)'"); }); it('exposes the primary Slate/Terminal/Scan/Ledger routes', () => { ['/dashboard', '/terminal', '/scan', '/ledger'].forEach((href) => { expect(src).toContain(href); }); }); }); describe('Phase C — Footer (system voice)', () => { const src = read('components/Footer.tsx'); it('uses the new Wordmark and mono type', () => { expect(src).toContain("from '@/components/vyndr'"); expect(src).toContain('var(--mono)'); }); it('carries the Detroit signature + legal/21+ system language', () => { expect(src).toContain('DETROIT'); expect(src).toContain('21+'); expect(src).toContain('not a sportsbook'); expect(src).toContain('Not financial advice'); }); }); describe('Phase C — 404 north star', () => { const src = read('app/not-found.tsx'); it('renders the amber 404 with system language', () => { expect(src).toContain('TRANSMISSION INTERRUPTED'); expect(src).toContain('404'); expect(src).toContain('var(--amber)'); }); it('uses the scanline texture and a CRT sweep on load', () => { expect(src).toContain('scanlines'); expect(src).toContain('crt-sweep'); }); }); describe('Phase C — layout wiring', () => { const src = read('app/layout.tsx'); it('mounts AuthGate, global Footer, and the hash bridge', () => { expect(src).toContain(''); expect(src).toContain('