// Unit: hot-list engine (Session 23). Pure function. const { computeHotList } = require('../../src/services/hotListService'); describe('hotListService', () => { test('"hot" means above personal average, not just high raw numbers', () => { const players = [ // 20-PPG player erupting for 28/31/25 → HOT { name: 'Riser', games: [{ points: 28 }, { points: 31 }, { points: 25 }, { points: 20 }, { points: 19 }, { points: 21 }, { points: 20 }, { points: 18 }, { points: 22 }, { points: 20 }] }, // 30-PPG star who dropped 28 recently → NOT hot (below own baseline) { name: 'Star', games: [{ points: 28 }, { points: 27 }, { points: 26 }, { points: 31 }, { points: 33 }, { points: 30 }, { points: 32 }, { points: 31 }, { points: 30 }, { points: 33 }] }, ]; const list = computeHotList(players, 'nba', { stat: 'points', window: 3 }); expect(list.map((p) => p.name)).toContain('Riser'); expect(list.map((p) => p.name)).not.toContain('Star'); }); test('returns a ranked list with rank field', () => { const players = [ { name: 'A', games: [{ points: 40 }, { points: 40 }, { points: 10 }, { points: 10 }] }, { name: 'B', games: [{ points: 22 }, { points: 22 }, { points: 18 }, { points: 18 }] }, ]; const list = computeHotList(players, 'nba', { stat: 'points', window: 2 }); expect(list[0].rank).toBe(1); expect(list[0].name).toBe('A'); // biggest jump above baseline expect(list[0].delta).toBeGreaterThan(list[1].delta); }); test('uses explicit seasonAvg as baseline when present', () => { const players = [ { name: 'C', seasonAvg: { points: 15 }, games: [{ points: 25 }, { points: 25 }] }, ]; const list = computeHotList(players, 'nba', { stat: 'points', window: 2 }); expect(list[0].baseline).toBe(15); expect(list[0].delta).toBe(10); }); test('7-day window filters by date when dates + now supplied', () => { const now = new Date('2026-06-12T12:00:00Z').getTime(); const day = 86_400_000; const players = [ { name: 'D', games: [ { date: '2026-06-11', hits: 3 }, // in window { date: '2026-06-10', hits: 2 }, // in window { date: '2026-06-01', hits: 0 }, // outside 7d → baseline { date: '2026-05-30', hits: 0 }, ] }, ]; const list = computeHotList(players, 'mlb', { stat: 'hits', windowDays: 7, now }); expect(list).toHaveLength(1); expect(list[0].window).toBe(2); // only the 2 recent games expect(list[0].recentAvg).toBe(2.5); }); test('player with no baseline to trend against is excluded', () => { const players = [ { name: 'E', games: [{ points: 30 }, { points: 30 }] }, // window 7 → no rest, no seasonAvg ]; const list = computeHotList(players, 'nba', { stat: 'points' }); expect(list).toHaveLength(0); }); test('tie on delta broken by raw recent average', () => { const players = [ { name: 'Low', games: [{ points: 15 }, { points: 15 }, { points: 10 }, { points: 10 }] }, // +5, recent 15 { name: 'High', games: [{ points: 25 }, { points: 25 }, { points: 20 }, { points: 20 }] }, // +5, recent 25 ]; const list = computeHotList(players, 'nba', { stat: 'points', window: 2 }); expect(list[0].name).toBe('High'); }); test('empty input → empty list', () => { expect(computeHotList([], 'nba')).toEqual([]); expect(computeHotList(null, 'nba')).toEqual([]); }); test('all/default stat resolves to the sport headline stat', () => { const players = [{ name: 'F', games: [{ hits: 3 }, { hits: 3 }, { hits: 0 }, { hits: 0 }] }]; const list = computeHotList(players, 'mlb', { stat: 'all', window: 2 }); expect(list[0].stat).toBe('hits'); }); });