170 lines
5.1 KiB
TypeScript
170 lines
5.1 KiB
TypeScript
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();
|
|
});
|
|
});
|