Merge Wave 2B: never-dark offseason hub UI (NewsWire + FuturesBoard on /explore)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// Wave 2B — OFFSEASON HUB · FuturesBoard. Source + helper asserts. Futures are
|
||||
// TRACKED, NOT GRADED — the board carries that label, never renders a grade,
|
||||
// self-hides on an empty feed, and colors movement per the contract
|
||||
// (shortening→green / drifting→amber / flat→dim) — NEVER red for a drift.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
|
||||
const src = fs.readFileSync(path.join(WEB, 'components', 'vyndr', 'FuturesBoard.tsx'), 'utf8');
|
||||
const { moveColor, moveLabel, MOVE_COLOR } = require('../../web/src/lib/futuresMove');
|
||||
|
||||
describe('futuresMove — the color contract (never red)', () => {
|
||||
it('shortening → green (toward the selection)', () => {
|
||||
expect(moveColor('shortening')).toBe('var(--g-a)');
|
||||
});
|
||||
it('drifting → amber (caution, NOT a miss)', () => {
|
||||
expect(moveColor('drifting')).toBe('var(--amber)');
|
||||
});
|
||||
it('flat / absent / unknown → dim (say less)', () => {
|
||||
expect(moveColor('flat')).toBe('var(--text-2)');
|
||||
expect(moveColor(undefined)).toBe('var(--text-2)');
|
||||
expect(moveColor('sideways')).toBe('var(--text-2)');
|
||||
});
|
||||
it('NO move ever renders red (--miss reserved for settled-negative)', () => {
|
||||
Object.values(MOVE_COLOR).forEach((v) => expect(v).not.toMatch(/miss/));
|
||||
['shortening', 'drifting', 'flat', undefined, 'x'].forEach((m) =>
|
||||
expect(moveColor(m)).not.toMatch(/miss/)
|
||||
);
|
||||
});
|
||||
it('moveLabel is empty when there is no move (never fabricated)', () => {
|
||||
expect(moveLabel(undefined)).toBe('');
|
||||
expect(moveLabel('shortening')).toMatch(/SHORTENING/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('FuturesBoard.tsx source', () => {
|
||||
it('fetches the REAL futures endpoint', () => {
|
||||
expect(src).toMatch(/\/api\/futures\//);
|
||||
});
|
||||
|
||||
it('carries the honest "TRACKED · NOT GRADED" label', () => {
|
||||
expect(src).toContain('TRACKED · NOT GRADED');
|
||||
});
|
||||
|
||||
it('NEVER renders a grade on a future (no GradeBadge)', () => {
|
||||
expect(src).not.toContain('GradeBadge');
|
||||
expect(src).not.toMatch(/import.*GradeBadge/);
|
||||
});
|
||||
|
||||
it('SELF-HIDES when there are no markets (returns null)', () => {
|
||||
expect(src).toMatch(/return null/);
|
||||
expect(src).toMatch(/list\.length === 0/);
|
||||
});
|
||||
|
||||
it('renders real markets + selections with mono tabular prices', () => {
|
||||
expect(src).toMatch(/list\.map/);
|
||||
expect(src).toMatch(/selections/);
|
||||
expect(src).toContain('className="mono"');
|
||||
expect(src).toContain('tabular-nums');
|
||||
});
|
||||
|
||||
it('shows a move ONLY when the backend supplied one, via the contract color', () => {
|
||||
expect(src).toContain('moveColor');
|
||||
expect(src).toContain('moveLabel');
|
||||
// movement is gated on a truthy label (never fabricated)
|
||||
expect(src).toMatch(/label &&/);
|
||||
});
|
||||
|
||||
it('data surface carries NO glitch classes', () => {
|
||||
expect(src).not.toMatch(/wm-tear|glitch-shift|head-tear|glitch-hover/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExploreHub mounts FuturesBoard', () => {
|
||||
const hub = fs.readFileSync(path.join(WEB, 'components', 'ExploreHub.tsx'), 'utf8');
|
||||
it('imports + mounts <FuturesBoard sport=', () => {
|
||||
expect(hub).toMatch(/import FuturesBoard from '@\/components\/vyndr\/FuturesBoard'/);
|
||||
expect(hub).toMatch(/<FuturesBoard sport=\{sport\}/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
// Wave 2B — OFFSEASON HUB · NewsWire. Source + helper asserts. The wire is REAL
|
||||
// ESPN news + real injuries fed into the retired INJURY_WIRE layout; it self-
|
||||
// hides when both feeds are empty, timestamps are mono, and no sample data
|
||||
// from TerminalTemplates is imported.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const WEB = path.join(__dirname, '..', '..', 'web', 'src');
|
||||
const src = fs.readFileSync(path.join(WEB, 'components', 'vyndr', 'NewsWire.tsx'), 'utf8');
|
||||
const { timeAgo, typeLabel } = require('../../web/src/lib/newsFormat');
|
||||
|
||||
describe('newsFormat.timeAgo — mono-timestamp source', () => {
|
||||
const now = Date.parse('2026-07-13T12:00:00Z');
|
||||
it('absent / invalid → empty string (never a fabricated stamp)', () => {
|
||||
expect(timeAgo(null, now)).toBe('');
|
||||
expect(timeAgo('', now)).toBe('');
|
||||
expect(timeAgo('not-a-date', now)).toBe('');
|
||||
});
|
||||
it('minutes / hours / days ago', () => {
|
||||
expect(timeAgo(now - 5 * 60000, now)).toBe('5m ago');
|
||||
expect(timeAgo(now - 3 * 3600000, now)).toBe('3h ago');
|
||||
expect(timeAgo(now - 2 * 86400000, now)).toBe('2d ago');
|
||||
});
|
||||
it('sub-minute → now', () => {
|
||||
expect(timeAgo(now - 10000, now)).toBe('now');
|
||||
});
|
||||
});
|
||||
|
||||
describe('newsFormat.typeLabel', () => {
|
||||
it('maps known ESPN types', () => {
|
||||
expect(typeLabel('Recap')).toBe('RECAP');
|
||||
expect(typeLabel('HeadlineNews')).toBe('NEWS');
|
||||
});
|
||||
it('falls back to a title-cased split for unknown types (never dropped)', () => {
|
||||
expect(typeLabel('Story')).toBe('STORY');
|
||||
expect(typeLabel('')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NewsWire.tsx source', () => {
|
||||
it('fetches the REAL news + injuries endpoints (no sample data)', () => {
|
||||
expect(src).toMatch(/\/api\/news\//);
|
||||
expect(src).toMatch(/\/api\/schedule\/.*\/injuries/);
|
||||
expect(src).not.toMatch(/import[^\n]*TerminalTemplates/); // never routes sample data
|
||||
expect(src).not.toContain('Jamal Murray'); // no sample constants
|
||||
});
|
||||
|
||||
it('SELF-HIDES when both feeds are empty (returns null)', () => {
|
||||
expect(src).toMatch(/return null/);
|
||||
// the guard combines news + injuries emptiness
|
||||
expect(src).toMatch(/news\.length === 0 && inj\.length === 0/);
|
||||
});
|
||||
|
||||
it('renders real items newest-first and maps them', () => {
|
||||
expect(src).toMatch(/news\.map/);
|
||||
expect(src).toMatch(/\.sort\(/);
|
||||
expect(src).toContain('headline');
|
||||
});
|
||||
|
||||
it('timestamps + statuses render in mono (data-is-mono rule)', () => {
|
||||
expect(src).toContain('className="mono"');
|
||||
expect(src).toContain('timeAgo(');
|
||||
});
|
||||
|
||||
it('links player/team via the canonical helpers', () => {
|
||||
expect(src).toContain('playerHref');
|
||||
expect(src).toMatch(/\/team\//);
|
||||
});
|
||||
|
||||
it('data surface carries NO glitch classes', () => {
|
||||
expect(src).not.toMatch(/wm-tear|glitch-shift|head-tear|glitch-hover/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExploreHub mounts NewsWire', () => {
|
||||
const hub = fs.readFileSync(path.join(WEB, 'components', 'ExploreHub.tsx'), 'utf8');
|
||||
it('imports + mounts <NewsWire sport=', () => {
|
||||
expect(hub).toMatch(/import NewsWire from '@\/components\/vyndr\/NewsWire'/);
|
||||
expect(hub).toMatch(/<NewsWire sport=\{sport\}/);
|
||||
});
|
||||
it('is offseason-aware (leads with the hub when the board is dark)', () => {
|
||||
expect(hub).toContain('OFF_SEASON');
|
||||
expect(hub).toMatch(/isOffseason/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user