import { describe, expect, it } from "vitest"; import { abilityById } from "./abilityCatalog"; import { DEFAULT_COMBAT_PRESENTATION_SETTINGS, combatEffectManifestEntry, combatEffectStyle, combatPresentationSessionId, emitCombatPresentation, normalizeCombatPresentationSettings, originalCombatPhaseAssets, presentationDeliveryForAbility, presentationSchoolForAbility, resetCombatPresentationSession, subscribeCombatPresentation, type CombatPresentationMessage, } from "./combatPresentation"; describe("combat presentation", () => { it("normalizes owner settings without leaking partial or invalid values", () => { expect(normalizeCombatPresentationSettings({ vfxQuality: "low", vfxEnabled: { player: false }, audioEnabled: { enemy: false }, audioVolume: { player: 2, party: -1, enemy: 0.35 }, })).toEqual({ originalMediaEnabled: false, vfxMode: "generated", vfxQuality: "low", vfxEnabled: { player: false, party: true, enemy: true }, audioEnabled: { player: true, party: true, enemy: false }, audioVolume: { player: 1, party: 0, enemy: 0.35 }, }); expect(normalizeCombatPresentationSettings({ ...DEFAULT_COMBAT_PRESENTATION_SETTINGS, originalMediaEnabled: true, vfxMode: "original", }).vfxMode).toBe("original"); expect(normalizeCombatPresentationSettings({ ...DEFAULT_COMBAT_PRESENTATION_SETTINGS, vfxMode: "original", }).vfxMode).toBe("generated"); expect(normalizeCombatPresentationSettings({ ...DEFAULT_COMBAT_PRESENTATION_SETTINGS, vfxMode: "hybrid", }).vfxMode).toBe("generated"); expect(normalizeCombatPresentationSettings(null)).toEqual(DEFAULT_COMBAT_PRESENTATION_SETTINGS); }); it("delivers immutable, exactly-once events and a session reset", () => { const messages: CombatPresentationMessage[] = []; const unsubscribe = subscribeCombatPresentation((message) => messages.push(message)); const origin = [1, 2, 3] as [number, number, number]; const event = emitCombatPresentation({ occurredAt: 1_000, phase: "release", actorGroup: "player", sourceActorId: "player", targetActorId: "mob", abilityId: "fixture", sourceSpellId: 116, name: "Frostbolt", source: "wow335", school: "frost", delivery: "projectile", origin, targetPosition: [8, 0, 0], }); origin[0] = 99; const nextSession = resetCombatPresentationSession(); unsubscribe(); expect(messages).toHaveLength(2); expect(messages[0]).toEqual({ kind: "event", event }); expect(event.origin).toEqual([1, 2, 3]); expect(messages[1]).toEqual({ kind: "reset", sessionId: nextSession }); expect(combatPresentationSessionId()).toBe(nextSession); }); it("uses authentic source descriptors and school/delivery fallbacks", () => { const frostbolt = abilityById("mage-frostbolt"); expect(frostbolt).not.toBeNull(); expect(presentationSchoolForAbility(frostbolt!)).toBe("frost"); expect(presentationDeliveryForAbility(frostbolt!)).toBe("projectile"); expect(combatEffectManifestEntry(999_999, "wow335")).toBeNull(); const style = combatEffectStyle({ id: 1, sessionId: 1, occurredAt: 1, phase: "release", actorGroup: "player", sourceActorId: "player", abilityId: "mage-frostbolt", sourceSpellId: 999_999, name: "Frostbolt", source: "wow335", school: "frost", delivery: "projectile", origin: [0, 0, 0], }); expect(style.authentic).toBe(false); expect(style.sourceModelNames).toEqual([]); }); it("exposes only packaged original phase graphs", () => { const entry = { key: "wow335:2061", source: "wow335" as const, spellId: 2061, phases: { impact: [ { model: "Spells/FlashHeal_Base.mdx" }, { model: "Spells/FlashHeal_Base.mdx", effectKind: "wow-m2-particles" as const, effectGraphUrl: "/assets/game/combat-effects/original/wow335/priest/graphs/flash-heal.json", }, { effectKind: "wow-m2-particles" as const, effectGraphUrl: "https://example.invalid/not-a-packaged-effect.json", }, { model: "Spells/HolyDivineShield_State_Base.mdx", effectKind: "wow-m2-static-mesh" as const, effectGraphUrl: "/assets/game/combat-effects/original/wow335/priest/graphs/shield.json", }, ], }, }; expect(originalCombatPhaseAssets(entry, "impact")).toEqual([ entry.phases.impact[1], entry.phases.impact[3], ]); expect(originalCombatPhaseAssets(entry, "release")).toEqual([]); }); });