Files
vyndr/tests/unit/vyndrAppShell.test.js
T
builtbykev 612f5e0b72 Session 36: Design system Phase E — remaining screens + dashboard Bloomberg lines (1853 tests)
VYNDR 2.0 conversion, Phase E. Frontend-only; zero backend changes.

- lib/slateAdapter.js: parseAmericanOdds, detectBestLines, mapScheduleToGameCards
  (best/worst line detection — the Bloomberg pattern).
- Reskinned the LEGACY GameCard's game-lines grid with best/worst highlighting +
  SportBadge, keeping inline grading intact (a wholesale swap to the display-only
  vyndr/GameCard would have deleted the slate's grading interaction).
- compare/invite/help/about: RouteStubs -> real design-system pages.
- login reskinned (scanlines, system voice, new Wordmark); pricing + ClaimMeter.

Honest scope: the full GameCard swap needs inline grading ported into the new
component first; profile/settings/blog/game-detail reskins are light/deferred.

18 new tests. Backend 1839 -> 1853, 144 suites, zero regressions. Web build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 01:04:37 -04:00

143 lines
5.5 KiB
JavaScript

// 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('<Ticker');
});
it('styles nav links in JetBrains Mono with grade-green active state', () => {
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('<AuthGate>');
expect(src).toContain('<Footer />');
expect(src).toContain('<HashRedirect />');
});
});
describe('Phase C — route stubs for not-yet-built screens', () => {
// /terminal became real in Session 35; /compare /invite /help /about became
// real in Session 36 (Phase E). Only /notifications remains a stub.
const stubs = ['notifications'];
it.each(stubs)('/%s has a page that uses the design-system RouteStub', (name) => {
expect(exists(`app/${name}/page.tsx`)).toBe(true);
expect(read(`app/${name}/page.tsx`)).toContain('RouteStub');
});
it('does NOT stub over existing real pages (ledger/tracker/blog/game)', () => {
// these predate Session 34 with real content — must not be RouteStubs
['app/ledger/page.tsx', 'app/tracker/page.tsx', 'app/blog/page.tsx'].forEach((p) => {
expect(read(p)).not.toContain('RouteStub');
});
});
it('RouteStub speaks the system language', () => {
const src = read('components/vyndr/RouteStub.tsx');
expect(src).toContain('ROUTE UNDER CONSTRUCTION');
expect(src).toContain('SectionHead');
});
});