Files
vyndr/tests/unit/combatFightCard.test.js
T
builtbykev 54fa5853f5 Wave 6: Combat Intelligence Layer (honest free v1)
Net-new MMA/UFC vertical — fight-card discovery, tale-of-the-tape,
style-blend archetypes, ML + round-total odds, and a style-edge VERDICT
(a MODEL read, explicitly NOT a settled grade). Built to
specs/combat-intelligence.md.

Backend:
- combatAdapter: ESPN MMA scoreboard (date-pinned, free JSON) -> fight
  cards + tale-of-tape (record/weight class/rounds/ESPN athlete id);
  defensive parse (null on unknown shape, never throws); injectable
  fetchImpl + cache; pure normalizeCombatOdds (odds-api h2h/totals ->
  ML + round total, allow-listed books, best price). Number(null) guard.
- archetypeService: 6 pinned combat styles in a SEPARATE COMBAT_ARCHETYPES
  registry (FINISHER collides with soccer + its green trips the signal-
  green gate); classify('mma') blends range/tempo/outcome, honest-empty on
  thin data (no forced fallback); styleMatchup() honest verdict.
- oddsService: SPORT_KEYS.mma + MMA_MARKETS=['h2h','totals'] + SPORT_MARKETS
  (no spreads suffix). oddsNormalizer MARKET_MAP h2h/totals.
- config/sports.js + web mirror: mma.active=true (collectData stays false;
  NOT in the graded-props pipeline SPORT_CONFIG or snapshot/settle loop).
- routes/combat.js: GET /api/combat/:date + GET /api/fight/:id (public,
  cached, honest empty off-card) + Next proxies.

Frontend:
- FightCard: two-fighter tale-of-the-tape (initials monogram — no photos),
  GRAPPLER/STRIKER blend bars, discipline pedigree tags, shared
  ArchetypeBadge (sport="mma", unicode glyphs), CENTER VERDICT, ML +
  round-total real; method/round/KO = honest "data-limited", never
  fabricated. Self-hides on a non-two-fighter bout.
- /fight/[id] page (server wrapper + client), EmptyState off-season.
- MMA SportBadge token (#D4AF37); archetypes.js sport-aware resolution.

DEFERRED (per spec, NOT built): matchup-GRADE engine, method/round/props
board, combat settlement, ufcstats scraping.

Tests: +3 suites (31 tests) — combat archetype cross-file color/glyph
match, classify blends, styleMatchup honesty, adapter defensive parse +
odds normalize, FightCard honesty grep; extended oddsNormalizer +
sportMarkets. Full suite 253/253 green.

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

51 lines
2.0 KiB
JavaScript

// Wave 6 — FightCard honesty locks (source-grep, same discipline as
// colorContract.test.js). The card must: self-hide on a non-two-fighter bout,
// render tale-of-the-tape as MONO data, label the verdict a MODEL read, and
// show method/round/KO as honest "data-limited" — never a fabricated grade.
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('FightCard.tsx — honest v1 tale-of-the-tape', () => {
const src = read('components/vyndr/FightCard.tsx');
it('self-hides (returns null) when there arent two named fighters', () => {
expect(src).toMatch(/fighters\.length < 2/);
expect(src).toMatch(/return null/);
});
it('data rows are MONO, and no glitch CLASS is applied to any element (data never glitches)', () => {
expect(src).toContain('className="mono"');
// No glitch animation class on any element (comments about "never glitches" are fine).
expect(src).not.toMatch(/className=["'`][^"'`]*glitch/);
});
it('method / round / KO cells are shown as honest "data-limited", not fabricated', () => {
expect(src).toMatch(/dataLimited/);
expect(src).toContain('data-limited');
expect(src).toMatch(/METHOD/);
});
it('the verdict is explicitly labeled a MODEL read, not a settled grade', () => {
expect(src).toContain('MODEL READ');
expect(src).toContain('INSUFFICIENT READ');
});
it('uses a monogram (no fighter photo / likeness)', () => {
expect(src).toContain('Monogram');
expect(src).not.toMatch(/headshot|espncdn.*headshots|<img/i);
});
it('renders the archetype chip through the shared ArchetypeBadge with sport="mma"', () => {
expect(src).toContain('ArchetypeBadge');
expect(src).toMatch(/sport="mma"/);
});
it('absent odds render as a dash, never a fabricated number', () => {
expect(src).toMatch(/const DASH = '—'/);
expect(src).toMatch(/Number\.isFinite/);
});
});