import { describe, expect, it } from "vitest"; import { sampleContentPack } from "@/data/sampleContentPack"; import { createStartingAdventurer } from "./character"; import { applyLevelProgression, getLevelForXp, getNextLevelXpThreshold, getXpThresholdForLevel, } from "./progression"; function createAdventurer() { return createStartingAdventurer(sampleContentPack, { name: "Aster", weaponId: "weapon.short-sword", armourId: "armour.leather-vest", scrollId: "scroll.lesser-heal", }); } describe("level progression rules", () => { it("uses linear xp thresholds for the current MVP ruleset", () => { expect(getXpThresholdForLevel(1)).toBe(0); expect(getXpThresholdForLevel(2)).toBe(8); expect(getXpThresholdForLevel(3)).toBe(16); expect(getNextLevelXpThreshold(1)).toBe(8); expect(getLevelForXp(0)).toBe(1); expect(getLevelForXp(8)).toBe(2); expect(getLevelForXp(16)).toBe(3); }); it("levels up immediately once xp crosses a threshold", () => { const adventurer = createAdventurer(); adventurer.xp = 8; adventurer.hp.current = 7; const result = applyLevelProgression({ content: sampleContentPack, adventurer, at: "2026-03-18T10:00:00.000Z", }); expect(result.adventurer.level).toBe(2); expect(result.adventurer.hp.max).toBe(12); expect(result.adventurer.hp.current).toBe(9); expect(result.adventurer.manoeuvreIds).toContain("manoeuvre.sweeping-cut"); expect(result.levelUps).toEqual([ expect.objectContaining({ previousLevel: 1, newLevel: 2, hpGained: 2, unlockedManoeuvreIds: ["manoeuvre.sweeping-cut"], }), ]); }); it("leaves the adventurer unchanged when no threshold is crossed", () => { const adventurer = createAdventurer(); adventurer.xp = 7; const result = applyLevelProgression({ content: sampleContentPack, adventurer, }); expect(result.adventurer.level).toBe(1); expect(result.adventurer.hp.max).toBe(10); expect(result.levelUps).toEqual([]); }); });