Files
vyndr/tests/unit/vyndrParityQA.test.js
T
builtbykev c8790fde55 Session 57: Phase 0 — Kill the Lies (2309 tests)
Work-order Phase 0 (Jul 10 live audit): every fabricated UI element deleted
or rewired to real data. Deletion sprint — no new product features.

0.1 Fake NBA game: root cause was scheduleService fetching the ESPN
    scoreboard with no ?dates= param or date filter — off-season ESPN
    returns the NEAREST slate (Jun 13 NYK@SA Finals rendered as tonight).
    Now pinned to the requested ET date + defensive filter; undated events
    dropped. Honest month-aware per-sport empty states (lib/emptyState.js).
0.2 Fake header counters: liveTick stripped to a bare 1s pulse (the
    auto-incrementing "247 graded", sin-driven brain-%, aPlus/cascades are
    dead). New GET /api/snapshot/summary (cache-only, before /:sport) +
    Next proxy; HeartbeatBar shows the real graded count and SYNC =
    elapsed since the last pipeline run (amber past 5 min).
0.3 Ticker: hardcoded fallback items deleted (real snapshot exhaust only);
    MOVE kept — computeLineDeltas is real movement. <4 real items → no
    ticker; bar publishes --ticker-h so the fixed header collapses cleanly.
0.4 /terminal retired: route redirects to /dashboard; VVI/injury-wire/
    leaders layouts preserved unrouted as §12 content-engine templates.
    Nav PRIMARY = Slate/Scan/Ledger; BottomTabBar Terminal→Explore; PWA
    shortcut Terminal→Ledger; #terminal alias → /dashboard.
0.5 ›Query nav pill deleted (duplicate /scan link).

Backend 2289 → 2309 tests (199 suites), web build exit 0.
Spec: specs/phase-0-kill-the-lies.md. Next: work-order Phase 1 (ledger
persistence + settlement).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 20:17:49 -04:00

94 lines
3.4 KiB
JavaScript

// VYNDR 2.0 — Phase H parity QA (Session 39). Locks the automated §13
// checklist invariants so the design conversion can't silently regress.
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('QA.6 — glitch discipline (data NEVER glitches)', () => {
const GLITCH = /wm-tear|glitch-shift|head-tear|glitch-hover/;
it.each([
'components/vyndr/GradeResultCard.tsx',
'components/vyndr/GameCard.tsx',
'components/GameCard.tsx',
'components/vyndr/ProcessingGrade.tsx',
])('%s contains no glitch classes', (rel) => {
expect(GLITCH.test(read(rel))).toBe(false);
});
});
describe('QA.1 — token resolution (no literals that duplicate a token)', () => {
it('ProcessingGrade uses the A+ token, not the raw #00ffb8 literal', () => {
const src = read('components/vyndr/ProcessingGrade.tsx');
expect(src).not.toContain('#00ffb8');
expect(src).toContain('var(--g-ap)');
});
it('game detail uses sport tokens, not duplicated sport hex', () => {
const src = read('app/game/[id]/page.tsx');
expect(src).toContain('var(--s-nba)');
expect(src).not.toMatch(/#E94B3C/);
});
});
describe('QA.4 — Bloomberg best/worst line pattern', () => {
it('legacy GameCard (live slate) tints best green / worst red', () => {
const src = read('components/GameCard.tsx');
expect(src).toContain('rgba(0,212,160,.13)');
expect(src).toContain('rgba(255,82,82,.07)');
expect(src).toMatch(/bestAway|bestHome/);
});
});
describe('QA.5 — wordmark everywhere', () => {
it.each([
'components/Nav.tsx',
'components/Footer.tsx',
'app/login/page.tsx',
'app/about/page.tsx',
'app/not-found.tsx',
])('%s renders the Wordmark', (rel) => {
expect(read(rel)).toContain('Wordmark');
});
});
describe('QA.11 — mobile parity (5-tab bar)', () => {
// Session 57 (Phase 0) — Terminal retired; Explore holds its slot.
it('BottomTabBar declares Slate/Explore/Scan/Ledger/More', () => {
const src = read('components/BottomTabBar.tsx');
['Slate', 'Explore', 'Scan', 'Ledger', 'More'].forEach((t) => expect(src).toContain(`'${t}'`));
});
});
describe('QA.16 — no dead buttons', () => {
it.each(['components', 'app'])('no empty onClick handlers under web/src/%s', (dir) => {
const root = path.join(WEB, dir);
const offenders = [];
const walk = (d) => {
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
const fp = path.join(d, e.name);
if (e.isDirectory()) walk(fp);
else if (e.name.endsWith('.tsx') && /onClick=\{\}/.test(fs.readFileSync(fp, 'utf8'))) offenders.push(fp);
}
};
walk(root);
expect(offenders).toEqual([]);
});
});
describe('QA.18 — auth gate integration', () => {
it('AuthGate gates via lib/routes and bounces to /login?next=', () => {
const src = read('components/AuthGate.tsx');
expect(src).toContain('isGatedRoute');
expect(src).toContain('/login?next=');
});
it('gated route list covers the personal surfaces', () => {
const routes = require('../../web/src/lib/routes');
['/ledger', '/tracker', '/account', '/notifications'].forEach((r) =>
expect(routes.isGatedRoute(r)).toBe(true),
);
expect(routes.isGatedRoute('/dashboard')).toBe(false); // free funnel stays open
});
});