Files
bibdle/tests/stats-segments.test.ts

69 lines
2.6 KiB
TypeScript

import { describe, test, expect } from 'bun:test';
import { deriveSegments, countSegments } from '$lib/server/stats/segments';
import { SETTINGS_DEFAULTS, type PlayerState, type StatsSettings } from '$lib/types/stats';
import { addDays } from '$lib/server/stats/dates';
const AS_OF = '2026-09-15';
function settings(overrides: Partial<StatsSettings> = {}): StatsSettings {
return { ...SETTINGS_DEFAULTS, ...overrides };
}
function player(id: string, overrides: Partial<PlayerState>): PlayerState {
return {
id,
firstDate: addDays(AS_OF, -100),
lastDate: AS_OF,
totalPlays: 10,
plays7: 2,
plays30: 10,
streak: 2,
...overrides
};
}
const players: PlayerState[] = [
player('new', { firstDate: addDays(AS_OF, -1), lastDate: AS_OF, plays7: 1 }),
player('core', { lastDate: AS_OF, plays7: 5 }),
player('resurrected', { lastDate: AS_OF, plays7: 2, plays30: 2 }),
player('returning', { lastDate: AS_OF, plays7: 2, plays30: 10 }),
player('atRisk', { lastDate: addDays(AS_OF, -10) }),
player('churned', { lastDate: addDays(AS_OF, -40) })
];
describe('deriveSegments', () => {
test('classifies each lifecycle segment', () => {
const sets = deriveSegments(players, AS_OF, settings());
expect(sets.new).toEqual(['new']);
expect(sets.core).toEqual(['core']);
expect(sets.resurrected).toEqual(['resurrected']);
expect(sets.returning).toEqual(['returning']);
expect(sets.atRisk).toEqual(['atRisk']);
expect(sets.churned).toEqual(['churned']);
expect(sets.active).toEqual(['new', 'core', 'resurrected', 'returning']);
});
test('changing the core threshold re-segments without new data', () => {
const strict = deriveSegments(players, AS_OF, settings({ core_plays_per_week: 6 }));
expect(strict.core).toEqual([]);
expect(strict.returning).toContain('core');
const lenient = deriveSegments(players, AS_OF, settings({ core_plays_per_week: 2 }));
expect(lenient.core).toContain('core');
expect(lenient.core).toContain('returning');
});
test('counts include every player exactly once across mutually exclusive sets', () => {
const counts = countSegments(deriveSegments(players, AS_OF, settings()));
const activeTotal = counts.new + counts.core + counts.returning + counts.resurrected;
expect(activeTotal).toBe(counts.active);
expect(counts.total).toBe(players.length);
});
test('at-risk and churned boundaries follow settings', () => {
const sets = deriveSegments(players, AS_OF, settings({ at_risk_days: 5, churn_days: 20 }));
expect(sets.atRisk).toContain('atRisk'); // inactive 10 >= 5, < 20
expect(sets.churned).toContain('churned'); // inactive 40 >= 20
});
});