1
0
Fork 0
tsgames/test/common/rpg/stat.test.ts

194 lines
6.0 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(new Stat({ value: 10 }), 's');
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(new Stat({ value: 5 }), 's');
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(new Stat({ value: 10 }), 's');
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(new Stat({ value: 10 }), 's');
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(new Stat({ value: 10 }), 's');
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(new Stat({ value: 10 }), 's');
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(new Stat({ value: 10, max: 20 }), 's');
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(new Stat({ value: 10, min: 0 }), 's');
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(new Stat({ value: 10, min: 0, max: 20 }), 's');
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(new Stat({ value: 10, max: 20 }), 's');
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(new Stat({ value: 10, min: 0 }), 's');
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(new Stat({ value: 10 }), 's');
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(new Stat({ value: 10, min: 0 }), 's');
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(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(new Health({ value: 10, min: 0 }));
const h = e.get(Health)!;
const killed: unknown[] = [];
e.on('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(new Health({ value: 50, min: 0 }));
const h = e.get(Health)!;
const killed: unknown[] = [];
e.on('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(new Health({ value: 10, min: 0 }));
const h = e.get(Health)!;
const killed: unknown[] = [];
e.on('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(new Health({ value: 10, min: 0 }));
const h = e.get(Health)!;
const killed: unknown[] = [];
e.on('Health.killed', () => killed.push(true));
h.update(-999);
h.update(-999);
expect(killed.length).toBe(1);
});
});