import { describe, expect, it } from "vitest"; import type { InventoryItem } from "./lootTypes"; import { PLAYER_EQUIPMENT_OWNER_ID, classCanEquipItem, equipInventoryItem, equipmentItemLevel, equipmentStats, normalizeEquipment, unequipInventorySlot, } from "./equipment"; import { EMPTY_ITEM_STATS, type ItemStats } from "./itemStats"; import { deriveRomNativeStats } from "./romNativeStats"; function stats(overrides: Partial): ItemStats { return { ...EMPTY_ITEM_STATS, ...overrides }; } function item( instanceId: string, inventorySlot: string, overrides: Partial = {}, ): InventoryItem { return { instanceId, itemId: 1, itemKey: instanceId, name: instanceId, quality: "uncommon", inventorySlot, armorType: null, weaponType: null, itemLevel: 12, sourceItemLevel: 20, stats: stats({ spellPower: 3, stamina: 2 }), quantity: 1, sourceBossKey: "kresh", sourceBossName: "Kresh", acquiredAt: 1, ...overrides, }; } describe("equipment assignments", () => { it("moves a unique item between the player and companions", () => { const cloak = item("cloak", "back"); const player = equipInventoryItem({}, [cloak], PLAYER_EQUIPMENT_OWNER_ID, "priest", cloak.instanceId); expect(player.ok).toBe(true); expect(player.assignments.player?.back).toBe("cloak"); const companion = equipInventoryItem(player.assignments, [cloak], "party-damage-1-mage", "mage", cloak.instanceId); expect(companion.ok).toBe(true); expect(companion.assignments.player).toBeUndefined(); expect(companion.assignments["party-damage-1-mage"]?.back).toBe("cloak"); }); it("enforces armor, shield, and weapon compatibility", () => { const leather = item("leather", "chest", { armorType: "leather" }); const shield = item("shield", "off-hand", { armorType: "shield" }); const bow = item("bow", "ranged", { weaponType: "bow" }); expect(classCanEquipItem("priest", leather)).toBe(false); expect(classCanEquipItem("warrior", leather)).toBe(true); expect(classCanEquipItem("mage", shield)).toBe(false); expect(classCanEquipItem("shaman", shield)).toBe(true); expect(classCanEquipItem("hunter", bow)).toBe(true); expect(classCanEquipItem("warlock", bow)).toBe(false); }); it("handles two-handed conflicts, unequipping, normalization, and item level", () => { const staff = item("staff", "two-hand", { weaponType: "staff", itemLevel: 18 }); const shield = item("shield", "off-hand", { armorType: "shield", itemLevel: 14 }); const main = equipInventoryItem({}, [staff, shield], "player", "warrior", staff.instanceId); const offhand = equipInventoryItem(main.assignments, [staff, shield], "player", "warrior", shield.instanceId); expect(offhand.assignments.player?.["main-hand"]).toBeUndefined(); expect(offhand.assignments.player?.["off-hand"]).toBe("shield"); expect(equipmentItemLevel(offhand.assignments, [staff, shield], "player")).toBe(14); const unequipped = unequipInventorySlot(offhand.assignments, "player", "off-hand"); expect(unequipped.player).toBeUndefined(); expect(normalizeEquipment({ player: { back: "missing", "off-hand": "shield" } }, [shield])) .toEqual({ player: { "off-hand": "shield" } }); }); it("totals only the items worn by the selected owner", () => { const cloak = item("cloak", "back", { stats: stats({ spellPower: 3, stamina: 4, armor: 9 }) }); const staff = item("staff", "two-hand", { weaponType: "staff", stats: stats({ strength: 8, stamina: 2 }), }); const assignments = { player: { back: cloak.instanceId }, companion: { "main-hand": staff.instanceId }, }; expect(equipmentStats(assignments, [cloak, staff], "player")) .toMatchObject({ spellPower: 3, stamina: 4, armor: 9 }); expect(equipmentStats(assignments, [cloak, staff], "companion")) .toMatchObject({ strength: 8, stamina: 2, armor: 0 }); }); it("bridges foreign direct stats per item while native payloads take precedence", () => { const sourceNative = item("native", "head", { stats: stats({ strength: 40, health: 99, mana: 99, romNative: { strength: 3, health: 5, mana: 5 } }), }); const foreign = item("foreign", "back", { stats: stats({ strength: 2, health: 10, mana: 10, spirit: 700, attackPower: 500, armor: 100, spellPower: 600 }), }); const inventory = [sourceNative, foreign]; const assignments = { player: { head: sourceNative.instanceId, back: foreign.instanceId } }; const equipped = equipmentStats(assignments, inventory, "player"); expect(equipped).toMatchObject({ strength: 42, health: 109, mana: 109 }); expect(equipped.romNative).toEqual({ strength: 5, health: 15, mana: 15 }); const withoutGear = deriveRomNativeStats({ classId: "rom-warrior", level: 1 }); const withGear = deriveRomNativeStats({ classId: "rom-warrior", level: 1, gear: equipped.romNative }); expect(withGear.attributes.strength - withoutGear.attributes.strength).toBe(5); expect(withGear.physicalAttack - withoutGear.physicalAttack).toBe(10); expect(withGear.health.max - withoutGear.health.max).toBeCloseTo(16); expect(withGear.attributes.wisdom).toBe(withoutGear.attributes.wisdom); const mageWithoutGear = deriveRomNativeStats({ classId: "rom-mage", level: 1 }); const mageWithGear = deriveRomNativeStats({ classId: "rom-mage", level: 1, gear: equipped.romNative }); expect(mageWithGear.mana.max - mageWithoutGear.mana.max).toBe(15); const unequipped = equipmentStats({}, inventory, "player"); expect(unequipped.romNative).toBeUndefined(); expect(deriveRomNativeStats({ classId: "rom-warrior", level: 1, gear: unequipped.romNative }).health.max) .toBe(withoutGear.health.max); }); });