194 lines
6.1 KiB
TypeScript
194 lines
6.1 KiB
TypeScript
import { describe, it, expect } from 'bun:test';
|
|
import { World } from '@common/rpg/core/world';
|
|
import { Stat, Health } from '@common/rpg/components/stat';
|
|
|
|
function world() { return new World(); }
|
|
|
|
describe('Stat — value / base / modifiers', () => {
|
|
it('value equals base when no modifiers', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10 }));
|
|
const s = e.get(Stat, 's')!;
|
|
expect(s.value).toBe(10);
|
|
expect(s.base).toBe(10);
|
|
});
|
|
|
|
it('set() changes base and value', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 5 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.set(20);
|
|
expect(s.base).toBe(20);
|
|
expect(s.value).toBe(20);
|
|
});
|
|
|
|
it('update() adds to base', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.update(5);
|
|
expect(s.value).toBe(15);
|
|
});
|
|
|
|
it('applyModifier shifts value without changing base', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(5);
|
|
expect(s.base).toBe(10);
|
|
expect(s.value).toBe(15);
|
|
});
|
|
|
|
it('removeModifier reverts applyModifier', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(5);
|
|
s.removeModifier(5);
|
|
expect(s.value).toBe(10);
|
|
});
|
|
|
|
it('multiple modifiers stack', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(3);
|
|
s.applyModifier(7);
|
|
expect(s.value).toBe(20);
|
|
});
|
|
|
|
it('modifier on max field shifts effective max', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10, max: 20 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(10, 'max');
|
|
expect(s.max).toBe(30);
|
|
});
|
|
|
|
it('modifier on min field shifts effective min', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10, min: 0 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(5, 'min');
|
|
expect(s.min).toBe(5);
|
|
});
|
|
|
|
it('value is clamped to [min, max]', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10, min: 0, max: 20 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.set(100);
|
|
expect(s.value).toBe(20);
|
|
s.set(-5);
|
|
expect(s.value).toBe(0);
|
|
});
|
|
|
|
it('large positibe modifier clamps to max', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10, max: 20 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(100);
|
|
expect(s.value).toBe(20);
|
|
});
|
|
|
|
it('large negative modifier clamps to min', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10, min: 0 }));
|
|
const s = e.get(Stat, 's')!;
|
|
s.applyModifier(-100);
|
|
expect(s.value).toBe(0);
|
|
});
|
|
|
|
it("set() emits 'set' event with prev and value", () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10 }));
|
|
const s = e.get(Stat, 's')!;
|
|
const events: unknown[] = [];
|
|
e.on('Stat(s).set', ({ data }) => events.push(data));
|
|
s.set(20);
|
|
expect(events).toEqual([{ prev: 10, value: 20 }]);
|
|
});
|
|
|
|
it("set() does not emit 'set' when value unchanged", () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('s', new Stat({ value: 10, min: 0 }));
|
|
const s = e.get(Stat, 's')!;
|
|
const events: unknown[] = [];
|
|
e.on('Stat(s).set', ({ data }) => events.push(data));
|
|
s.set(-100); // clamped to 0, still changes
|
|
s.set(-200); // still 0, no change
|
|
expect(events.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('Health', () => {
|
|
it('update reduces health', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('health', new Health({ value: 100, min: 0 }));
|
|
const h = e.get(Health)!;
|
|
h.update(-30);
|
|
expect(h.value).toBe(70);
|
|
});
|
|
|
|
it('update to zero triggers kill()', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('health', new Health({ value: 10, min: 0 }));
|
|
const h = e.get(Health)!;
|
|
const killed: unknown[] = [];
|
|
e.on('Health(health).killed', () => killed.push(true));
|
|
h.update(-10);
|
|
expect(killed.length).toBe(1);
|
|
expect(h.value).toBe(0);
|
|
});
|
|
|
|
it('kill() emits killed and sets value to 0', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('health', new Health({ value: 50, min: 0 }));
|
|
const h = e.get(Health)!;
|
|
const killed: unknown[] = [];
|
|
e.on('Health(health).killed', () => killed.push(true));
|
|
h.kill();
|
|
expect(killed.length).toBe(1);
|
|
expect(h.value).toBe(0);
|
|
});
|
|
|
|
it('kill() does not emit killed twice for overkill', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('health', new Health({ value: 10, min: 0 }));
|
|
const h = e.get(Health)!;
|
|
const killed: unknown[] = [];
|
|
e.on('Health(health).killed', () => killed.push(true));
|
|
h.update(-999);
|
|
expect(killed.length).toBe(1);
|
|
});
|
|
|
|
it('kill() does not emit killed twice for already killed entity', () => {
|
|
const w = world();
|
|
const e = w.createEntity();
|
|
e.add('health', new Health({ value: 10, min: 0 }));
|
|
const h = e.get(Health)!;
|
|
const killed: unknown[] = [];
|
|
e.on('Health(health).killed', () => killed.push(true));
|
|
h.update(-999);
|
|
h.update(-999);
|
|
expect(killed.length).toBe(1);
|
|
});
|
|
});
|