import { describe, it, expect } from 'vitest'; import { computeGaugeLive, enclosGlobalState, calcLevelEtaLive } from '@presentation/helpers/gauge-live'; import { xpForLevel } from '@domain/value-objects/XpTable'; import { timeToGain } from '@domain/services/GaugeCalculator'; import { createDragodinde } from '@domain/entities/Dragodinde'; import type { Enclos } from '@domain/entities/Enclos'; function makeEnclos(overrides: Partial = {}): Enclos { return { id: 1, name: 'Test', activeGauges: ['mangeoire'], gaugeLevels: { baffeur: 0, caresseur: 0, foudroyeur: 0, abreuvoir: 0, dragofesse: 0, mangeoire: 50000 }, dragodindes: [createDragodinde(1)], nextDdId: 2, timer: { running: false, startTime: null, pausedAt: null, pausedMs: 0, snapGauges: {}, snapStats: {}, gaugeRecharges: {} }, alerted: {}, ...overrides, } as Enclos; } describe('Regression: timer prend en compte levelTarget', () => { it('computeGaugeLive utilise levelTarget pour le countdown mangeoire', () => { const enc = makeEnclos(); const dd = { ...enc.dragodindes[0]!, levelTarget: 10 }; const r = computeGaugeLive(enc, dd, 'mangeoire', 0, false); // Avec levelTarget=10, xpNeeded = xpForLevel(10) - xpForLevel(1) = 809 const xpNeeded = xpForLevel(10) - xpForLevel(1); expect(xpNeeded).toBe(809); // Le countdown devrait être timeToGain(50000, 809) const expectedSec = timeToGain(50000, 809); expect(r.cntDown).toBe(expectedSec); expect(r.done).toBe(false); }); it('computeGaugeLive sans levelTarget utilise 200 par défaut', () => { const enc = makeEnclos(); const dd = { ...enc.dragodindes[0]!, levelTarget: null }; const r = computeGaugeLive(enc, dd, 'mangeoire', 0, false); // Avec levelTarget=null → 200, xpNeeded >> gauge level // cntDown = timeToGain(50000, 50000) = drain time const drainTime = timeToGain(50000, 50000); expect(r.cntDown).toBe(drainTime); }); it('levelTarget=10 donne un countdown beaucoup plus court que levelTarget=null', () => { const enc = makeEnclos(); const ddWithTarget = { ...enc.dragodindes[0]!, levelTarget: 10 }; const ddWithoutTarget = { ...enc.dragodindes[0]!, levelTarget: null }; const rWith = computeGaugeLive(enc, ddWithTarget, 'mangeoire', 0, false); const rWithout = computeGaugeLive(enc, ddWithoutTarget, 'mangeoire', 0, false); // Avec target 10 : ~410 sec. Sans target (200) : ~45000 sec. expect(rWith.cntDown).toBeLessThan(1000); // < ~16 minutes expect(rWithout.cntDown).toBeGreaterThan(40000); // > ~11 heures }); it('enclosGlobalState reflète le levelTarget dans globalMax', () => { const enc = makeEnclos(); enc.dragodindes[0]!.levelTarget = 10; const state = enclosGlobalState(enc); // Avant démarrage, started=false → globalMax devrait refléter le target 10 expect(state.globalMax).toBeLessThan(1000); }); it('enclosGlobalState sans levelTarget a un globalMax plus élevé', () => { const enc = makeEnclos(); enc.dragodindes[0]!.levelTarget = null; const state = enclosGlobalState(enc); expect(state.globalMax).toBeGreaterThan(40000); }); it('calcLevelEtaLive utilise levelTarget', () => { const enc = makeEnclos(); const dd = { ...enc.dragodindes[0]!, levelTarget: 5 }; const eta = calcLevelEtaLive(enc, dd, 0, false); // Level 1→5 needs 161 XP, timeToGain(50000, 161) = 90 sec expect(eta).toContain('1m'); }); it('done=true quand estLevel >= levelTarget', () => { const enc = makeEnclos(); // DD déjà au level 50, target 50 const dd = { ...enc.dragodindes[0]!, stats: { ...enc.dragodindes[0]!.stats, xp: 50 }, levelTarget: 50 }; const r = computeGaugeLive(enc, dd, 'mangeoire', 0, false); expect(r.done).toBe(true); expect(r.cntDown).toBe(0); }); it('done=true quand estLevel > levelTarget', () => { const enc = makeEnclos(); const dd = { ...enc.dragodindes[0]!, stats: { ...enc.dragodindes[0]!.stats, xp: 100 }, levelTarget: 50 }; const r = computeGaugeLive(enc, dd, 'mangeoire', 0, false); expect(r.done).toBe(true); }); it('timer en cours avec levelTarget=10 : countdown reflète le target', () => { const now = Date.now(); const enc = makeEnclos({ timer: { running: true, startTime: now - 100_000, // 100 sec écoulées pausedAt: null, pausedMs: 0, snapGauges: { mangeoire: 50000 }, snapStats: { '1': { serenite: 0, endurance: 0, maturite: 0, amour: 0, xp: 1 } }, gaugeRecharges: {}, }, }); const ddWithTarget = { ...enc.dragodindes[0]!, levelTarget: 10 }; const ddNoTarget = { ...enc.dragodindes[0]!, levelTarget: null }; const rWith = computeGaugeLive(enc, ddWithTarget, 'mangeoire', 100, true); const rNo = computeGaugeLive(enc, ddNoTarget, 'mangeoire', 100, true); // Avec target 10 : cntDown devrait être < 1000 (beaucoup moins que sans target) // Sans target (200) : cntDown devrait être > 30000 expect(rWith.cntDown).toBeLessThan(rNo.cntDown); // Plus spécifiquement, avec target 10 après 100sec, le target devrait presque être atteint // xpNeeded = 809, et en 100sec (10 ticks) on gagne 10*20 = 200xp (tier 2) // xpRestante = 809 - 200 = 609 expect(rWith.cntDown).toBeLessThan(500); // < 500 sec restantes }); it('changer levelTarget pendant le timer met à jour le globalMax', () => { const now = Date.now(); const enc = makeEnclos({ timer: { running: true, startTime: now - 50_000, // 50 sec écoulées pausedAt: null, pausedMs: 0, snapGauges: { mangeoire: 50000 }, snapStats: { '1': { serenite: 0, endurance: 0, maturite: 0, amour: 0, xp: 1 } }, gaugeRecharges: {}, }, }); // D'abord sans target (level 200 par défaut) enc.dragodindes[0]!.levelTarget = null; const state1 = enclosGlobalState(enc); // Puis avec target 10 enc.dragodindes[0]!.levelTarget = 10; const state2 = enclosGlobalState(enc); // Le globalMax devrait diminuer drastiquement expect(state2.globalMax).toBeLessThan(state1.globalMax); expect(state2.globalMax).toBeLessThan(1000); }); });