import { describe, expect, it, vi } from "vitest"; import { collectBossCardDrop, emptyBossCardCollection, normalizeBossCardCollection, rollBossCards, type BossCardDrop } from "./bossCards"; import { BOSS_CARD_CATALOG, bossCardForSource, buildBossCardCatalog, groupBossCards } from "./bossCardCatalog"; import { DUNGEON_DEFINITIONS } from "./dungeonRegistry"; import { useCombatStore } from "./combatStore"; import { useGameStore } from "./store"; import { allowedLootDrops } from "./lootPolicy"; import { EMPTY_ITEM_STATS } from "./itemStats"; describe("boss card rewards", () => { it("guarantees Normal and rolls each bonus independently at its exact boundary", () => { const random = vi.fn().mockReturnValueOnce(0.099).mockReturnValueOnce(0.02).mockReturnValueOnce(0.009); expect(rollBossCards(random)).toEqual(["normal", "rare", "legendary"]); expect(random).toHaveBeenCalledTimes(3); expect(rollBossCards(() => 0)).toEqual(["normal", "rare", "epic", "legendary"]); expect(rollBossCards(() => 0.1)).toEqual(["normal"]); expect(rollBossCards(() => NaN)).toEqual(["normal"]); }); it("counts copies and makes repeated world rewards idempotent after save normalization", () => { const drop: BossCardDrop = { bossKey: "wow:lady anacondra", spawnId: "boss-spawn", claimId: "kill:1", rarities: ["normal", "rare"] }; const first = collectBossCardDrop(emptyBossCardCollection(), drop); expect(collectBossCardDrop(first, drop)).toBe(first); const restored = normalizeBossCardCollection(JSON.parse(JSON.stringify(first))); expect(collectBossCardDrop(restored, drop)).toBe(restored); expect(collectBossCardDrop(restored, { ...drop, claimId: "kill:2" }).counts[drop.bossKey]).toEqual({ normal: 2, rare: 2 }); const later = collectBossCardDrop(restored, { ...drop, claimId: "5000:2:boss-spawn" }); expect(collectBossCardDrop(later, { ...drop, claimId: "4000:1:boss-spawn" })).toBe(later); }); it("migrates old profiles and rejects corrupt counts", () => { expect(normalizeBossCardCollection(undefined)).toEqual(emptyBossCardCollection()); expect(normalizeBossCardCollection({ counts: { boss: { normal: 3.9, rare: -1, epic: Infinity, legendary: "5" } } }).counts).toEqual({ boss: { normal: 3 } }); }); it("grants cards from a boss without an equipment loot table exactly once", () => { const dungeon = DUNGEON_DEFINITIONS.find((entry) => entry.id === "ragefire-chasm")!; const boss = Object.values(dungeon.entities).find((entry) => entry.kind === "boss")!; useGameStore.getState().activateDungeon(dungeon.id); useCombatStore.getState().initializeCharacter({ classId: "priest", level: 79 }); useCombatStore.getState().registerMob("no-loot-boss", { name: boss.name, boss: true, lootSourceId: boss.id, maxHealth: 1, xpReward: 0 }); useCombatStore.getState().damageMob("no-loot-boss", 1, 4000); const key = bossCardForSource(boss.id, dungeon.id)!.key; expect(useCombatStore.getState().bossCards.counts[key]?.normal).toBe(1); expect(useCombatStore.getState().inventory).toEqual([]); expect(useCombatStore.getState().lootMob("no-loot-boss", 4001)).toBe(false); expect(useCombatStore.getState().bossCards.counts[key]?.normal).toBe(1); useCombatStore.getState().initializeCharacter({ classId: "priest", level: 79 }); expect(useCombatStore.getState().bossCards).toEqual(emptyBossCardCollection()); useGameStore.getState().activateDungeon("wailing-caverns"); }); it.each([1, 40, 79, 80])("preserves non-equipment while allowing equipment only at 80 (level %i)", (level) => { const item = { instanceId: "i", itemId: 1, itemKey: "item", name: "Item", quality: "rare" as const, inventorySlot: "waist", armorType: "cloth", weaponType: null, itemLevel: level, sourceItemLevel: 1, stats: EMPTY_ITEM_STATS, quantity: 1, sourceBossKey: "boss", sourceBossName: "Boss", acquiredAt: 1 }; const misc = { ...item, instanceId: "m", inventorySlot: "misc" }; expect(allowedLootDrops([item, misc], level)).toEqual(level === 80 ? [item, misc] : [misc]); }); }); describe("boss card catalogue and filters", () => { it("covers every registered boss with its actual model and dungeon", () => { expect(BOSS_CARD_CATALOG.length).toBeGreaterThan(100); for (const dungeon of DUNGEON_DEFINITIONS) for (const boss of Object.values(dungeon.entities)) { if (boss.kind !== "boss") continue; const card = bossCardForSource(boss.id, dungeon.id)!; expect(card, `${dungeon.id}:${boss.id}`).not.toBeNull(); expect(card.dungeons).toContainEqual({ id: dungeon.id, title: dungeon.title }); if (boss.visual.model) expect(card.model).toBeDefined(); } }); it("merges the same boss across dungeons and groups without changing earned copies", () => { const wc = DUNGEON_DEFINITIONS.find((entry) => entry.id === "wailing-caverns")!; const boss = wc.entities["lady-anacondra"]; const catalog = buildBossCardCatalog([ { ...wc, entities: { [boss.id]: boss } }, { ...wc, id: "second-dungeon", title: "Second Dungeon", entities: { [boss.id]: boss } }, ]); expect(catalog).toHaveLength(1); expect(catalog[0].dungeons).toHaveLength(2); const collection = { counts: { [catalog[0].key]: { normal: 5, legendary: 1 } }, lastClaims: {} }; expect(groupBossCards(collection, { earnedOnly: false, grouping: "all" }, catalog)[0].rows).toHaveLength(4); expect(groupBossCards(collection, { earnedOnly: true, grouping: "all" }, catalog)[0].rows.map((row) => row.copies)).toEqual([5, 1]); const dungeons = groupBossCards(collection, { earnedOnly: true, grouping: "dungeon" }, catalog); expect(dungeons).toHaveLength(2); expect(dungeons.every((group) => group.rows[0].copies === 5)).toBe(true); expect(groupBossCards(collection, { earnedOnly: true, grouping: "rarity" }, catalog).map((group) => group.key)).toEqual(["normal", "legendary"]); expect(groupBossCards(collection, { earnedOnly: false, grouping: "all", search: "second dungeon" }, catalog)[0].rows).toHaveLength(4); expect(groupBossCards(collection, { earnedOnly: true, grouping: "all", category: "rom" }, catalog)).toEqual([]); }); });