Files
vyndr/tests/unit/gradeShift.test.js
builtbykev 911cae4992 Wave 4B: Parlay Lab page + Live Grade-Shift timeline
TASK 1 — Parlay Lab (/parlay): a dedicated Correlation Builder with a
leg source INDEPENDENT of the live slate. Browses tonight's pre-graded
props from /api/snapshot/:sport (resolves players via /api/players/search),
adds legs through useParlay().addLeg (deduped by legKey), and renders the
PARLAY SLIP — combined grade, correlation, and payout read straight off
ParlayContext. Surfaces parlayService's correlation warning as the
CAUTION · CORRELATION FLAG, honors the tier leg-cap (free 2 / analyst 4 /
desk 6) and blurs the payout for free tier with the __goPaywall upsell.
Added /parlay to OPEN_ROUTES (free funnel, like scan/dashboard). Retired
the cleanly-dead ParlayTray.tsx (unmounted since Session 50). No new
proxies — reuses existing snapshot/search/parlay-grade endpoints.

TASK 2 — Live Grade-Shift timeline: web/src/lib/gradeShift.js (pure,
testable) builds a line/grade-movement timeline from already-emitted data
(intraday {t,line} history + revised_from_grade). Color law mirrors
ROW-GRAMMAR / StatStrip.LineSparkline: green = toward the graded side,
amber = against, dim = flat (never red). GradeShift.tsx renders it, shows
the original grade struck-through on a revision, and self-hides below 3
real points. Mounted in GradeResultCard (self-hides on the scan path,
which carries no captured history — honest, never fabricated).

Tests: tests/unit/gradeShift.test.js (15) + tests/unit/parlayLab.test.js
(13). Full suite 245 suites / 2989 tests green (baseline 243/2961).
Next build EXIT=0.

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

124 lines
5.2 KiB
JavaScript

// Wave 4B — LIVE GRADE-SHIFT timeline helper + component (source-asserted).
// Locks the color law (green toward / amber against / dim flat), the revision
// strike-through, and the <3-point self-hide.
const fs = require('fs');
const path = require('path');
const {
buildGradeTimeline,
classifyMove,
cleanHistory,
TOWARD_COLOR,
AGAINST_COLOR,
FLAT_COLOR,
MIN_POINTS,
} = require('../../web/src/lib/gradeShift');
const read = (rel) => fs.readFileSync(path.join(__dirname, '..', '..', 'web', 'src', rel), 'utf8');
describe('gradeShift helper — color law', () => {
it('color tokens are green / amber / dim (never red)', () => {
expect(TOWARD_COLOR).toBe('var(--g-a)');
expect(AGAINST_COLOR).toBe('var(--amber)');
expect(FLAT_COLOR).toBe('var(--text-1)');
for (const c of [TOWARD_COLOR, AGAINST_COLOR, FLAT_COLOR]) {
expect(c).not.toBe('var(--miss)');
}
});
it('OVER: line up = TOWARD (green), line down = AGAINST (amber), no move = FLAT (dim)', () => {
expect(classifyMove(0.5, 'Over')).toEqual({ dir: 'toward', color: TOWARD_COLOR });
expect(classifyMove(-0.5, 'Over')).toEqual({ dir: 'against', color: AGAINST_COLOR });
expect(classifyMove(0, 'Over')).toEqual({ dir: 'flat', color: FLAT_COLOR });
});
it('UNDER inverts the sign: line down = TOWARD (green), line up = AGAINST (amber)', () => {
expect(classifyMove(-0.5, 'Under')).toEqual({ dir: 'toward', color: TOWARD_COLOR });
expect(classifyMove(0.5, 'Under')).toEqual({ dir: 'against', color: AGAINST_COLOR });
});
});
describe('gradeShift helper — timeline assembly', () => {
it('self-hides below 3 real captured points', () => {
expect(buildGradeTimeline({ history: null }).show).toBe(false);
expect(buildGradeTimeline({ history: [{ t: '1', line: 5.5 }] }).show).toBe(false);
expect(buildGradeTimeline({ history: [{ t: '1', line: 5.5 }, { t: '2', line: 5.5 }] }).show).toBe(false);
expect(buildGradeTimeline({ history: [{ t: '1', line: 5.5 }, { t: '2', line: 6 }, { t: '3', line: 6.5 }] }).show).toBe(true);
});
it('drops non-numeric points (Number(null)===0 guard)', () => {
const tl = buildGradeTimeline({ history: [{ t: '1', line: 5.5 }, { t: '2', line: null }, { t: '3', line: 6.5 }] });
expect(tl.points.length).toBe(2); // the null point is dropped, not coerced to 0
expect(cleanHistory([{ line: null }, { line: 3 }])).toHaveLength(1);
});
it('net move on an OVER that rose reads TOWARD (green)', () => {
const tl = buildGradeTimeline({ history: [{ t: '1', line: 5.5 }, { t: '2', line: 6 }, { t: '3', line: 6.5 }], side: 'Over' });
expect(tl.net.dir).toBe('toward');
expect(tl.net.color).toBe(TOWARD_COLOR);
expect(tl.firstLine).toBe(5.5);
expect(tl.lastLine).toBe(6.5);
});
it('net move on an OVER that fell reads AGAINST (amber)', () => {
const tl = buildGradeTimeline({ history: [{ t: '1', line: 6.5 }, { t: '2', line: 6 }, { t: '3', line: 5.5 }], side: 'Over' });
expect(tl.net.dir).toBe('against');
expect(tl.net.color).toBe(AGAINST_COLOR);
});
it('a revision surfaces the ORIGINAL grade struck (from) alongside the new (to)', () => {
const tl = buildGradeTimeline({
history: [{ t: '1', line: 5.5 }, { t: '2', line: 6 }, { t: '3', line: 6.5 }],
grade: 'B',
revisedFrom: 'A',
});
expect(tl.revision).toEqual({ from: 'A', to: 'B' });
});
it('no revision when there was no prior grade or it is unchanged', () => {
const base = [{ t: '1', line: 5.5 }, { t: '2', line: 6 }, { t: '3', line: 6.5 }];
expect(buildGradeTimeline({ history: base, grade: 'A' }).revision).toBeNull();
expect(buildGradeTimeline({ history: base, grade: 'A', revisedFrom: 'A' }).revision).toBeNull();
});
it('accepts the snapshot field name revised_from_grade too', () => {
const tl = buildGradeTimeline({
history: [{ t: '1', line: 5.5 }, { t: '2', line: 6 }, { t: '3', line: 6.5 }],
grade: 'C',
revised_from_grade: 'B',
});
expect(tl.revision).toEqual({ from: 'B', to: 'C' });
});
it('MIN_POINTS matches the LineSparkline floor of 3', () => {
expect(MIN_POINTS).toBe(3);
});
});
describe('GradeShift component', () => {
const src = read('components/vyndr/GradeShift.tsx');
it('self-hides on !tl.show (returns null)', () => {
expect(src).toContain('if (!tl.show) return null');
});
it('renders the original grade struck-through when revised', () => {
expect(src).toContain('tl.revision');
expect(src).toContain('line-through');
expect(src).toContain('tl.revision.from');
expect(src).toContain('tl.revision.to');
});
it('is a data surface — mono, no glitch classes', () => {
expect(src).toContain('mono');
expect(src).not.toMatch(/wm-tear|glitch-shift|head-tear|glitch-hover/);
});
});
describe('GradeResultCard mounts the timeline', () => {
const src = read('components/vyndr/GradeResultCard.tsx');
it('imports and renders GradeShift with the optional history fields', () => {
expect(src).toContain("import GradeShift from '@/components/vyndr/GradeShift'");
expect(src).toContain('<GradeShift');
expect(src).toContain('history?: Array<{ t: string; line: number }> | null');
expect(src).toContain('revisedFrom');
});
});