/** * WAVE 3 — /compare head-to-head + the resolution-tail state * (specs/wave3-compare-and-resolution-tail.md). */ const fs = require('fs'); const path = require('path'); const read = (p) => fs.readFileSync(path.join(__dirname, '../../', p), 'utf8'); describe('/compare is a real same-market head-to-head, not two cards', () => { const src = read('web/src/app/compare/page.tsx'); test('the in-development placeholder is GONE', () => { expect(src).not.toMatch(/IN DEVELOPMENT/); expect(src).not.toMatch(/Check back soon/); }); test('it fetches BOTH sides from the live player feed', () => { expect(src).toMatch(/\/api\/stats\/player\//); expect(src).toMatch(/Promise\.all\(\[loadPlayer\(nameA/); }); test('rows are ALIGNED on shared measures (what makes it a comparison)', () => { expect(src).toMatch(/function alignRows/); expect(src).toMatch(/shared keys/i); }); test('carries NO fabricated player and NO verdict', () => { expect(src).not.toMatch(/Jokic|Jokić|Wembanyama/); expect(src).toMatch(/NO VERDICT/); }); test('a missing side is honest-absent — dash, never zero, never invented', () => { expect(src).toMatch(/NO DATA/); expect(src).toMatch(/r\.a \?\? '—'/); expect(src).toMatch(/not a zero/); // and a totally unresolved pair refuses to compare expect(src).toMatch(/we will not invent a comparison/); }); }); describe('alignRows — the comparison contract', () => { // mirrors the page's pure helper const alignRows = (a, b) => { const keys = []; for (const r of a || []) if (r && r.k && !keys.includes(r.k)) keys.push(r.k); for (const r of b || []) if (r && r.k && !keys.includes(r.k)) keys.push(r.k); return keys.map((k) => ({ k, a: (a || []).find((r) => r.k === k)?.v ?? null, b: (b || []).find((r) => r.k === k)?.v ?? null, })); }; test('keys present on only one side yield null on the other, not 0', () => { const out = alignRows([{ k: 'HR', v: '17' }], [{ k: 'AVG', v: '.301' }]); expect(out).toEqual([ { k: 'HR', a: '17', b: null }, { k: 'AVG', a: null, b: '.301' }, ]); for (const r of out) { expect(r.a === 0 || r.b === 0).toBe(false); } }); test('shared keys line up on one row', () => { const out = alignRows([{ k: 'HR', v: '17' }], [{ k: 'HR', v: '9' }]); expect(out).toEqual([{ k: 'HR', a: '17', b: '9' }]); }); test('empty input compares nothing rather than inventing rows', () => { expect(alignRows(undefined, undefined)).toEqual([]); }); }); describe('resolution tail — nothing shipped as a dead shell', () => { test('ShareCard is still NOT wired to any generator (its pipeline step does not exist)', () => { const grading = read('src/routes/grading.js'); expect(grading).not.toMatch(/shareCard/i); // no generation step in the resolve fanout const importers = fs.readdirSync(path.join(__dirname, '../../web/src/components')) .filter((f) => f !== 'ShareCard.tsx') .filter((f) => f.endsWith('.tsx')) .filter((f) => read(`web/src/components/${f}`).includes('ShareCard')); expect(importers).toEqual([]); // still unrouted, deliberately }); test('/notifications remains a stub — no settings screen over a silent pipeline', () => { const src = read('web/src/app/notifications/page.tsx'); expect(src).toMatch(/RouteStub/); }); });