83 lines
3.7 KiB
JavaScript
83 lines
3.7 KiB
JavaScript
// 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');
|
|
});
|
|
});
|