Files
vyndr/tests/unit/tierRecord.test.js
T
builtbykev 9bafc76092 Wave 3: Record by grade tier — shared TierRecord across dashboard, /u, ledger
Addition 2 (non-negotiable): the model's record must show PER GRADE TIER
(A+ went X-Y, A X-Y, …) everywhere the record appears. A blended % hides the
proof that higher grades win more — the tier calibration IS the credibility.

The backend already computed `by_tier` in getModelAggregate; this is display
propagation via ONE shared component (the class fix, not five one-offs).

- web/src/lib/tierRecord.js — testable CommonJS row-builder. W-L counts ALWAYS
  (honest at any n); hit-% only when the upstream n≥20 gate passed (hit_pct !=
  null), else "RECORD BUILDING · N settled". Order A+ A B C D F. A-tier is the
  only edge (green) tier — no glow below A, red reserved for outcomes.
- web/src/components/vyndr/TierRecord.tsx — the ONE shared table. Presentational
  (byTier) for /u + ledger; self-fetch (/api/ledger/model, sport-scoped) for the
  dashboard. Fully self-hides until a tier has a settled read.
- Ledger swaps its inline TierCalibration for the shared component (single
  source). /u PublicProfile renders it below the blended hero (by_tier added to
  the aggregate type; it already flows through the route + proxy untouched).
  Dashboard gains a compact per-tier surface.
- Endpoint/proxy audit: profiles.js + ledger.js return the full aggregate
  (by_tier included); both Next proxies pass the body through — no threading
  needed. No change to the gate or math in getModelAggregate.

Tests: tests/unit/tierRecord.test.js (row logic + tier order + edge contract +
source-assert all three surfaces render the shared component). ledgerService
test gains an A+-stands-alone bucketing case. Full suite green (243 suites /
2961 tests); web next build exits 0.

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

131 lines
5.5 KiB
JavaScript

// Wave 3 (Addition 2) — RECORD BY GRADE TIER.
// The tier calibration IS the credibility: A+ wins more than A, A more than
// B. This suite locks (a) the shared row-building logic, (b) the tier order,
// and (c) that the ONE shared TierRecord component is imported + rendered on
// all three record surfaces (dashboard, /u, ledger). Plain-JS Jest — the .tsx
// is asserted against source (same pattern as publicProfilePage/vyndrAppShell).
const fs = require('fs');
const path = require('path');
const ROOT = path.join(__dirname, '..', '..');
const WEB = path.join(ROOT, 'web', 'src');
const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8');
const exists = (rel) => fs.existsSync(path.join(WEB, rel));
const { buildTierRows, TIER_ORDER, anyTierReady, isEdgeTier } = require('../../web/src/lib/tierRecord');
describe('buildTierRows — honesty rules', () => {
// A ≥20 tier carries a real hit_pct (gate applied upstream); a small tier
// arrives with hit_pct null even though it has settles.
const byTier = {
'A+': { settled: 22, hits: 18, misses: 4, pushes: 0, hit_pct: 82 },
A: { settled: 25, hits: 17, misses: 8, pushes: 0, hit_pct: 68 },
B: { settled: 3, hits: 2, misses: 1, pushes: 0, hit_pct: null }, // small sample
};
test('shows each tier W-L record ALWAYS, even at small n', () => {
const rows = buildTierRows(byTier);
const b = rows.find((r) => r.tier === 'B');
expect(b).toBeTruthy();
expect(b.record).toBe('2-1'); // honest at n=3
const ap = rows.find((r) => r.tier === 'A+');
expect(ap.record).toBe('18-4');
});
test('shows the hit-% ONLY when hit_pct != null; else RECORD BUILDING', () => {
const rows = buildTierRows(byTier);
const ap = rows.find((r) => r.tier === 'A+');
expect(ap.ready).toBe(true);
expect(ap.hitPct).toBe(82);
// Small-sample tier: no percentage, building copy with the settled count.
const b = rows.find((r) => r.tier === 'B');
expect(b.ready).toBe(false);
expect(b.hitPct).toBeNull();
expect(b.buildingLabel).toBe('RECORD BUILDING · 3 settled');
});
test('never fabricates a 0% from a null hit_pct (Number(null)===0 guard)', () => {
const rows = buildTierRows({ C: { settled: 5, hits: 0, misses: 0, pushes: 0, hit_pct: null } });
expect(rows[0].hitPct).toBeNull();
expect(rows[0].ready).toBe(false);
});
test('omits tiers with zero settled reads (no fake empty rows)', () => {
const rows = buildTierRows({ A: { settled: 0, hits: 0, misses: 0, hit_pct: null }, B: { settled: 4, hits: 3, misses: 1, hit_pct: null } });
expect(rows.map((r) => r.tier)).toEqual(['B']);
});
test('empty / missing map → no rows (self-hides)', () => {
expect(buildTierRows(null)).toEqual([]);
expect(buildTierRows(undefined)).toEqual([]);
expect(buildTierRows({})).toEqual([]);
});
});
describe('tier order + edge color contract', () => {
test('renders A+ → F in canonical order', () => {
expect(TIER_ORDER).toEqual(['A+', 'A', 'B', 'C', 'D', 'F']);
});
test('buildTierRows preserves A+ → F order regardless of map insertion order', () => {
const rows = buildTierRows({
F: { settled: 2, hits: 0, misses: 2, hit_pct: null },
A: { settled: 3, hits: 2, misses: 1, hit_pct: null },
'A+': { settled: 4, hits: 4, misses: 0, hit_pct: null },
C: { settled: 5, hits: 2, misses: 3, hit_pct: null },
});
expect(rows.map((r) => r.tier)).toEqual(['A+', 'A', 'C', 'F']);
});
test('GLOW RESERVED FOR A-TIER — only A+/A are edge tiers', () => {
expect(isEdgeTier('A+')).toBe(true);
expect(isEdgeTier('A')).toBe(true);
expect(isEdgeTier('B')).toBe(false);
expect(isEdgeTier('C')).toBe(false);
expect(isEdgeTier('F')).toBe(false);
});
test('anyTierReady reflects the upstream n≥20 gate', () => {
expect(anyTierReady({ A: { settled: 5, hits: 3, misses: 2, hit_pct: null } })).toBe(false);
expect(anyTierReady({ A: { settled: 25, hits: 17, misses: 8, hit_pct: 68 } })).toBe(true);
});
});
describe('the ONE shared component reaches all three record surfaces', () => {
test('TierRecord.tsx exists and is exported from the vyndr barrel', () => {
expect(exists('components/vyndr/TierRecord.tsx')).toBe(true);
expect(read('components/vyndr/index.ts')).toContain("export { default as TierRecord } from './TierRecord'");
});
test('TierRecord renders W-L always + % only when ready, else BUILDING', () => {
const src = read('components/vyndr/TierRecord.tsx');
expect(src).toContain('buildTierRows');
expect(src).toContain('{r.record}'); // W-L always
expect(src).toContain('{r.hitPct}% HIT'); // % only in the ready branch
expect(src).toContain('RECORD BUILDING · {r.settled} settled');
});
test('/u PublicProfile imports AND renders TierRecord', () => {
const src = read('app/u/[handle]/PublicProfile.tsx');
expect(src).toMatch(/import TierRecord from '@\/components\/vyndr\/TierRecord'/);
expect(src).toContain('<TierRecord');
expect(src).toContain('by_tier'); // the aggregate carries the tier buckets
});
test('dashboard imports AND renders TierRecord', () => {
const src = read('app/dashboard/page.tsx');
expect(src).toMatch(/TierRecord/);
expect(src).toContain('<TierRecord');
});
test('ledger uses the shared TierRecord (single source, no inline table)', () => {
const src = read('app/ledger/page.tsx');
expect(src).toContain('TierRecord');
expect(src).toContain('<TierRecord');
// the old inline per-tier component is gone (folded into the shared one)
expect(src).not.toContain('function TierCalibration');
});
});