import { describe, it, expect } from 'bun:test'; import { World } from '@common/rpg/core/world'; import { Cooldown } from '@common/rpg/components/cooldown'; import { CooldownSystem } from '@common/rpg/systems/cooldown'; function world() { const w = new World(); w.addSystem(new CooldownSystem()); return w; } describe('Cooldown — initial state', () => { it('starts not ready', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(5)); expect(e.get(Cooldown, 'cd')!.ready).toBeFalse(); }); it('remaining equals duration on creation', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(3)); const cd = e.get(Cooldown, 'cd')!; expect(cd.state.remaining).toBe(3); expect(cd.state.duration).toBe(3); }); }); describe('Cooldown — clear()', () => { it('marks as ready immediately', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(10)); const cd = e.get(Cooldown, 'cd')!; cd.clear(); expect(cd.ready).toBeTrue(); }); it("emits 'ready' event", () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(10)); const events: unknown[] = []; e.on('cd.ready', () => events.push(true)); e.get(Cooldown, 'cd')!.clear(); expect(events.length).toBe(1); }); it('is no-op when already ready', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(1)); const cd = e.get(Cooldown, 'cd')!; cd.clear(); const events: unknown[] = []; e.on('cd.ready', () => events.push(true)); cd.clear(); expect(events.length).toBe(0); }); }); describe('Cooldown — reset()', () => { it('resets remaining to duration', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(5)); const cd = e.get(Cooldown, 'cd')!; cd.clear(); cd.reset(); expect(cd.ready).toBeFalse(); expect(cd.state.remaining).toBe(5); }); it('reset(duration) changes duration and remaining', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(5)); const cd = e.get(Cooldown, 'cd')!; cd.reset(10); expect(cd.state.duration).toBe(10); expect(cd.state.remaining).toBe(10); }); }); describe('Cooldown — update(dt)', () => { it('counts down remaining', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(5)); const cd = e.get(Cooldown, 'cd')!; cd.update(2); expect(cd.state.remaining).toBe(3); expect(cd.ready).toBeFalse(); }); it('becomes ready when remaining reaches zero', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(3)); const cd = e.get(Cooldown, 'cd')!; cd.update(3); expect(cd.ready).toBeTrue(); }); it("emits 'ready' when countdown completes", () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(2)); const events: unknown[] = []; e.on('cd.ready', () => events.push(true)); e.get(Cooldown, 'cd')!.update(2); expect(events.length).toBe(1); }); it('does not go below zero', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(1)); const cd = e.get(Cooldown, 'cd')!; cd.update(100); expect(cd.state.remaining).toBe(0); }); it('is no-op when already ready', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(1)); const cd = e.get(Cooldown, 'cd')!; cd.clear(); const events: unknown[] = []; e.on('cd.ready', () => events.push(true)); cd.update(1); expect(events.length).toBe(0); }); }); describe('CooldownSystem', () => { it('drives all cooldowns each tick', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(2)); w.update(1); expect(e.get(Cooldown, 'cd')!.state.remaining).toBe(1); }); it('marks cooldown ready after enough ticks', () => { const w = world(); const e = w.createEntity(); e.add('cd', new Cooldown(2)); const events: unknown[] = []; e.on('cd.ready', () => events.push(true)); w.update(1); w.update(1); expect(events.length).toBe(1); expect(e.get(Cooldown, 'cd')!.ready).toBeTrue(); }); it('handles multiple cooldowns on different entities', () => { const w = world(); const a = w.createEntity('a'); const b = w.createEntity('b'); a.add('cd', new Cooldown(1)); b.add('cd', new Cooldown(3)); w.update(2); expect(a.get(Cooldown, 'cd')!.ready).toBeTrue(); expect(b.get(Cooldown, 'cd')!.ready).toBeFalse(); }); });