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

136 lines
4.2 KiB
TypeScript

import { describe, it, expect } from 'bun:test';
import { World } from '@common/rpg/core/world';
import { Experience } from '@common/rpg/components/experience';
function withXp(spec: ConstructorParameters<typeof Experience>[0]) {
const w = new World();
const e = w.createEntity();
e.add('xp', new Experience(spec));
return { e, xp: e.get(Experience, 'xp')! };
}
describe('Experience — initial state', () => {
it('starts at level 1, xp 0', () => {
const { xp } = withXp([100, 200, 400]);
expect(xp.level).toBe(1);
expect(xp.xp).toBe(0);
expect(xp.xpInLevel).toBe(0);
});
it('progress is 0 at start', () => {
const { xp } = withXp([100]);
expect(xp.progress).toBe(0);
});
it('xpToNext equals first threshold', () => {
const { xp } = withXp([100, 300]);
expect(xp.xpToNext).toBe(100);
});
});
describe('Experience — award XP (array spec)', () => {
it('accumulates xp without leveling up', () => {
const { xp } = withXp([100]);
xp.award(50);
expect(xp.xp).toBe(50);
expect(xp.level).toBe(1);
expect(xp.xpInLevel).toBe(50);
});
it('levels up when xp meets threshold', () => {
const { xp } = withXp([100]);
xp.award(100);
expect(xp.level).toBe(2);
expect(xp.xpInLevel).toBe(0);
});
it("emits 'levelup' with prev and new level", () => {
const { e, xp } = withXp([100]);
const events: unknown[] = [];
e.on('Experience(xp).levelup', ({ data }) => events.push(data));
xp.award(100);
expect(events).toEqual([{ level: 2, prev: 1 }]);
});
it('handles multiple level-ups in a single award', () => {
const { xp } = withXp([100, 200, 400]);
xp.award(350); // 100 (L1→2) + 200 (L2→3) + 50 leftover
expect(xp.level).toBe(3);
expect(xp.xpInLevel).toBe(50);
});
it("emits 'levelup' for each level gained", () => {
const { e, xp } = withXp([100, 200]);
const events: unknown[] = [];
e.on('Experience(xp).levelup', ({ data }) => events.push(data));
xp.award(300); // gains 2 levels
expect(events.length).toBe(2);
expect((events[0] as any).level).toBe(2);
expect((events[1] as any).level).toBe(3);
});
it('stops leveling at max level (array spec)', () => {
const { xp } = withXp([100]); // max level = 2
xp.award(9999);
expect(xp.level).toBe(2);
});
it('xpToNext is null at max level', () => {
const { xp } = withXp([100]);
xp.award(9999);
expect(xp.xpToNext).toBeNull();
});
it('progress is 1 at max level', () => {
const { xp } = withXp([100]);
xp.award(9999);
expect(xp.progress).toBe(1);
});
it('xp.total never resets across levels', () => {
const { xp } = withXp([100, 200]);
xp.award(400);
expect(xp.xp).toBe(400);
});
});
describe('Experience — award XP (geometric spec)', () => {
it('levels up using geometric thresholds', () => {
// base=100, factor=2 → L1→L2: 100xp, L2→L3: 200xp
const { xp } = withXp({ base: 100, factor: 2 });
xp.award(100);
expect(xp.level).toBe(2);
xp.award(200);
expect(xp.level).toBe(3);
});
it('has no max level with geometric spec', () => {
const { xp } = withXp({ base: 10, factor: 1 }); // 10xp per level, forever
xp.award(10000);
expect(xp.level).toBeGreaterThan(100);
expect(xp.xpToNext).not.toBeNull();
});
it('progress tracks correctly within a level', () => {
const { xp } = withXp({ base: 100, factor: 2 }); // 100xp for L1→2
xp.award(40);
expect(xp.progress).toBeCloseTo(0.4);
});
});
describe('Experience — xpInLevel / xpToNext', () => {
it('xpInLevel resets after level-up', () => {
const { xp } = withXp([100, 200]);
xp.award(150); // levels up at 100, 50 left
expect(xp.xpInLevel).toBe(50);
expect(xp.xpToNext).toBe(150); // 200 - 50
});
it('xpToNext decreases as xp accumulates', () => {
const { xp } = withXp([100]);
expect(xp.xpToNext).toBe(100);
xp.award(30);
expect(xp.xpToNext).toBe(70);
});
});