import { describe, expect, it } from 'vitest'; import { SCORING } from '../../app/static/js/config.js'; import { multiplierForStreak, ScoreKeeper, waveBonus } from '../../app/static/js/logic/scoring.js'; describe('multiplierForStreak', () => { it('steigt alle stepKills Abschüsse um stepValue', () => { expect(multiplierForStreak(0)).toBe(1); expect(multiplierForStreak(1)).toBe(1); expect(multiplierForStreak(SCORING.stepKills - 1)).toBe(1); expect(multiplierForStreak(SCORING.stepKills)).toBe(1 + SCORING.stepValue); expect(multiplierForStreak(SCORING.stepKills * 2)).toBe(1 + 2 * SCORING.stepValue); }); it('ist nach oben gedeckelt', () => { expect(multiplierForStreak(10_000)).toBe(SCORING.maxMultiplier); }); }); describe('waveBonus', () => { it('wächst linear mit der Welle', () => { expect(waveBonus(1)).toBe(SCORING.waveBonusPerWave); expect(waveBonus(7)).toBe(7 * SCORING.waveBonusPerWave); }); }); describe('ScoreKeeper', () => { const cfg = { ...SCORING, streakWindowMs: 1000, stepKills: 2, stepValue: 1, maxMultiplier: 3 }; it('zählt Abschüsse ohne Serie mit Faktor 1', () => { const keeper = new ScoreKeeper(cfg); expect(keeper.registerKill(10, 0)).toEqual({ awarded: 10, multiplier: 1, streak: 1 }); expect(keeper.registerKill(10, 5000)).toEqual({ awarded: 10, multiplier: 1, streak: 1 }); expect(keeper.score).toBe(20); expect(keeper.kills).toBe(2); }); it('baut innerhalb des Fensters eine Serie mit Multiplikator auf', () => { const keeper = new ScoreKeeper(cfg); const results = [0, 500, 900, 1400, 1800].map((t) => keeper.registerKill(10, t)); expect(results.map((r) => r.multiplier)).toEqual([1, 2, 2, 3, 3]); expect(keeper.score).toBe(10 + 20 + 20 + 30 + 30); expect(keeper.bestStreak).toBe(5); }); it('lässt die Serie nach Ablauf des Fensters verfallen', () => { const keeper = new ScoreKeeper(cfg); keeper.registerKill(10, 0); keeper.registerKill(10, 100); expect(keeper.multiplierAt(500)).toBe(2); expect(keeper.multiplierAt(1200)).toBe(1); expect(keeper.registerKill(10, 1200).streak).toBe(1); expect(keeper.bestStreak).toBe(2); }); it('beendet die Serie bei Treffern am Präsidenten', () => { const keeper = new ScoreKeeper(cfg); keeper.registerKill(10, 0); keeper.registerKill(10, 100); keeper.breakStreak(); expect(keeper.multiplierAt(200)).toBe(1); expect(keeper.registerKill(10, 200)).toEqual({ awarded: 10, multiplier: 1, streak: 1 }); }); it('vergibt Bonuspunkte ohne Multiplikator und ignoriert negative Werte', () => { const keeper = new ScoreKeeper(cfg); keeper.registerKill(10, 0); keeper.registerKill(10, 10); expect(keeper.addBonus(100)).toBe(100); expect(keeper.addBonus(-50)).toBe(0); expect(keeper.score).toBe(10 + 20 + 100); }); it('liefert eine Momentaufnahme mit Restzeit der Serie', () => { const keeper = new ScoreKeeper(cfg); keeper.registerKill(10, 0); const snap = keeper.snapshot(250); expect(snap).toMatchObject({ score: 10, kills: 1, streak: 1, multiplier: 1, bestStreak: 1 }); expect(snap.streakTimeLeft).toBeCloseTo(0.75); expect(keeper.snapshot(5000)).toMatchObject({ streak: 0, streakTimeLeft: 0 }); }); it('rundet Punkte bei gebrochenen Multiplikatoren', () => { const keeper = new ScoreKeeper({ ...cfg, stepValue: 0.5 }); keeper.registerKill(7, 0); expect(keeper.registerKill(7, 10).awarded).toBe(Math.round(7 * 1.5)); }); });