1664e1b3d5
Extend /explore (ExploreHub) into the 365-day never-dark hub. Two new self-hiding sections feed REAL always-available data into the offseason: - NewsWire (components/vyndr/NewsWire.tsx): real ESPN headlines from /api/news/:sport (newest-first, mono timestamps, type chips, player/team links) + real injuries from /api/schedule/:sport/injuries (OUT/GTD chips, token colors). Reuses the retired TerminalTemplates INJURY_WIRE layout but never routes its sample constants. Self-hides when both feeds are empty. - FuturesBoard (components/vyndr/FuturesBoard.tsx): real futures from /api/futures/:sport — championship/win-total/award markets, mono tabular prices + movement colored by the contract (shortening=green / drifting=amber / flat=dim, NEVER red; move shown ONLY when the backend supplies one). Carries the honest "TRACKED · NOT GRADED" label — no fabricated grades on futures. Self-hides when markets:[]. - ExploreHub is offseason-aware (via emptyState OFF_SEASON month check): the hub LEADS with futures + wire when the board is dark, COMPLEMENTS the live board in-season. Sport selector kept; each section self-hides independently. - Testable pure helpers: lib/futuresMove.js (move→color, never red) + lib/newsFormat.js (timeAgo mono-stamp, ESPN type labels). Contracts consumed (Wave 2A owns the proxy/service files); code self-hides on fetch failure if a proxy isn't present yet. Tests: tests/unit/newsWire.test.js + futuresBoard.test.js (26 new). Full suite 259 suites / 3144 green; web build exit 0. vyndrParityQA stays green (mono data, no glitch on data surfaces). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
3.2 KiB
JavaScript
86 lines
3.2 KiB
JavaScript
// 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/);
|
|
});
|
|
});
|