Files
vyndr/tests/unit/clvBadge.test.js
builtbykev da8bfdf1db Render directional-CLV badge — Analyst+Desk, server-gated, receipt-bearing
CARD BADGE ONLY. Ticker-CLV explicitly DEFERRED (named, not lost).

PHASE 1 — SERVER-SIDE GATE AT THE DATA LAYER. A Free request never
RECEIVES dclv data: the CLV columns are appended to the SELECT only behind
canAccess(tier,'clv_badge') (new capability, analyst+desk), and responses
are ALSO stripped as defence in depth so a future SELECT change cannot
quietly leak. No CSS/client gate — data that reaches the browser has left
the building. dclv_fair_lock/fair_close are de-vig internals and are never
sent at all.

SURFACE AUDIT, all six channels, each test-locked to contain no CLV:
public profile (share link), snapshot/card feed, ticker feed, share
card/OG, embeddable widget, newsletter. A test also asserts no
ledger_entries read uses select('*') — a star would auto-leak every new
column, which is exactly how a gate becomes theatre.

PHASE 2 — IMMUTABLE ONCE COMPUTED. A settle can re-run (stat correction,
protested game) and a badge that flips positive->negative AFTER a user saw
or screenshotted it is a credibility failure. First computation wins: dclv
is only computed when dclv_computed_at is null, so a re-settle can never
rewrite a shown badge. Same discipline as the locked grade.

PHASE 3 — RENDER, test-first, ABSENCE IS HONEST. unknown / flat / null /
missing-receipt all render NOTHING — no element, no placeholder, no
"pending". Proven on an ALL-NULL board (today: 0 badges) and a MIXED board
(tomorrow: 1 of 4 badged, badge-less cards clean). Binary states only:
  positive -> MOVED TOWARD US  "graded -110 · closed -145"
  negative -> MOVED AWAY       "graded -110 · closed +120"
The RECEIPT is the persuasive part, so a badge with no numbers is
suppressed rather than shown as a bare claim. Negative is neutral context
and NEVER touches the locked grade — no back-door re-grading.

NO aggregate, count or rollup exists by construction: the module exports
exactly {clvBadge, fmtPrice} and a badge payload carries exactly
{tone,label,receipt} — asserted by test, because an on-screen tally would
be the held aggregate claim through the side door.

Build gotcha hit and fixed: clvBadge is CommonJS (allowJs) with no TS
types, so the .tsx needed an explicit cast at the call site — the build
worker exits 1 on type errors even though compilation "succeeds".

Suite 288/3473 green, build exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmNjJAwEnqHPtXbvSZR8kA
2026-07-20 13:37:52 -04:00

77 lines
3.0 KiB
JavaScript

/**
* Session 64 — CLV badge rendering model. Absence must be a NON-ELEMENT.
*/
const { clvBadge } = require('../../web/src/lib/clvBadge');
const read = (o = {}) => ({ dclv_state: 'positive', locked_odds: '-110', closing_odds: '-145', ...o });
describe('ABSENCE IS HONEST — renders NOTHING', () => {
test('unknown → null (no element, not "pending")', () => {
expect(clvBadge(read({ dclv_state: 'unknown' }))).toBeNull();
});
test('flat → null (a held line is not news)', () => {
expect(clvBadge(read({ dclv_state: 'flat' }))).toBeNull();
});
test('missing state → null', () => {
expect(clvBadge(read({ dclv_state: null }))).toBeNull();
expect(clvBadge({})).toBeNull();
expect(clvBadge(null)).toBeNull();
});
test('a Free response (CLV stripped server-side) renders nothing', () => {
const free = { player_name: 'X', grade: 'B', locked_odds: '-110' }; // no dclv_*
expect(clvBadge(free)).toBeNull();
});
test('no receipt numbers → NO badge (a label without evidence is an assertion)', () => {
expect(clvBadge(read({ closing_odds: null }))).toBeNull();
expect(clvBadge(read({ locked_odds: null }))).toBeNull();
});
});
describe('THE RECEIPT — numbers, not just a label', () => {
test('positive carries tone, label and the concrete prices', () => {
const b = clvBadge(read());
expect(b.tone).toBe('confirm');
expect(b.label).toBe('MOVED TOWARD US');
expect(b.receipt).toBe('graded -110 · closed -145');
});
test('negative is CAUTION and also carries the receipt', () => {
const b = clvBadge(read({ dclv_state: 'negative', closing_odds: '+120' }));
expect(b.tone).toBe('caution');
expect(b.receipt).toBe('graded -110 · closed +120');
});
test('positive prices render with an explicit +', () => {
expect(clvBadge(read({ locked_odds: 105, closing_odds: 130 })).receipt)
.toBe('graded +105 · closed +130');
});
});
describe('BOARD SHAPES — all-null and mixed both render clean', () => {
const board = [
read({ dclv_state: 'positive' }),
read({ dclv_state: 'unknown' }),
read({ dclv_state: 'flat' }),
{ player_name: 'free-view', grade: 'B' },
];
test('a MIXED board badges only the real signals', () => {
const badges = board.map(clvBadge);
expect(badges.filter(Boolean)).toHaveLength(1);
expect(badges[1]).toBeNull();
expect(badges[2]).toBeNull();
});
test('an ALL-NULL board (today) produces zero badges and no errors', () => {
const allNull = [read({ dclv_state: 'unknown' }), read({ dclv_state: null }), {}];
expect(allNull.map(clvBadge).filter(Boolean)).toHaveLength(0);
});
});
describe('NO AGGREGATE anywhere', () => {
test('the module exposes no count/rollup helper', () => {
const mod = require('../../web/src/lib/clvBadge');
expect(Object.keys(mod).sort()).toEqual(['clvBadge', 'fmtPrice']);
});
test('a badge payload carries no totals', () => {
const b = clvBadge(read());
expect(Object.keys(b).sort()).toEqual(['label', 'receipt', 'tone']);
});
});