// Module 2: The action queue, depth-first. Each test matches a heading in scenarios.md. import { expect, test } from "vitest"; import { newGame, type GameEvent, type Id } from "../src/harness"; const fillers = (n: number): string[] => Array(n).fill("Test Filler"); /** Sources of the `Triggered` events, in log order. */ const triggeredSources = (log: GameEvent[]): Id[] => log.flatMap((e) => (e.type === "Triggered" ? [e.source] : [])); test("test_02_aoe_damage_then_triggers", () => { const g = newGame({ decks: [fillers(5), fillers(5)] }); const s = g.summon("Armorsmith", 0); const a = g.summon("Acolyte of Pain", 0); g.clearLog(); g.cast("Whirlwind", 0); const log = g.log(); expect(log.filter((e) => e.type === "Damaged")).toEqual([ { type: "Damaged", target: s, amount: 1 }, { type: "Damaged", target: a, amount: 1 }, ]); const types = log.map((e) => e.type); const lastDamaged = types.lastIndexOf("Damaged"); const firstTriggered = types.indexOf("Triggered"); expect(firstTriggered).toBeGreaterThanOrEqual(0); expect(lastDamaged).toBeLessThan(firstTriggered); const sources = triggeredSources(log); expect(sources.filter((id) => id === s)).toHaveLength(2); expect(sources.filter((id) => id === a)).toHaveLength(1); expect(sources).toHaveLength(3); expect(g.armor(0)).toBe(2); expect(g.hand(0)).toHaveLength(1); }); test("test_02_mortally_wounded_still_triggers", () => { const g = newGame({ decks: [fillers(5), []] }); const a = g.summon("Acolyte of Pain", 0); g.dealDamage(a, 3); const n = g.hand(0).length; g.clearLog(); g.cast("Whirlwind", 0); const log = g.log(); expect(log).toContainEqual({ type: "Triggered", source: a, trigger: "damaged" }); expect(log.some((e) => e.type === "Drew" && e.player === 0)).toBe(true); expect(g.hand(0)).toHaveLength(n + 1); expect(g.health(a)).toBe(0); }); test("test_02_depth_first_nesting", () => { const g = newGame(); const e = g.summon("Test Echo", 0); const s = g.summon("Armorsmith", 0); const r = g.summon("Test Reactor", 0); g.clearLog(); g.dealDamage(s, 1); const log = g.log(); expect(triggeredSources(log)).toEqual([e, r, s]); const reactorGrew = log.findIndex((ev) => ev.type === "StatsChanged" && ev.id === r); const armorGained = log.findIndex((ev) => ev.type === "ArmorGained"); expect(reactorGrew).toBeGreaterThanOrEqual(0); expect(reactorGrew).toBeLessThan(armorGained); expect(g.attack(r)).toBe(2); expect(g.armor(0)).toBe(1); expect(g.health(g.hero(1))).toBe(29); }); test("test_02_empty_deck_draw_is_fatigue", () => { const g = newGame(); const a = g.summon("Acolyte of Pain", 0); g.clearLog(); g.dealDamage(a, 1); expect(g.log()).toEqual([ { type: "Damaged", target: a, amount: 1 }, { type: "Triggered", source: a, trigger: "damaged" }, { type: "Fatigue", player: 0 }, ]); expect(g.hand(0)).toEqual([]); expect(g.health(g.hero(0))).toBe(30); });