import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import { AbilityTooltipContent, AbilityTooltipTarget, abilityTooltipFacts, abilityTooltipPosition, type AbilityTooltipData, } from "./AbilityTooltip"; const ability: AbilityTooltipData = { name: "Healing Wave", description: "Heals a friendly target for 120 health.", resourceCost: 15, resourceLabel: "mana", cooldownSeconds: 6, castSeconds: 1.5, range: 40, target: "friendly", }; describe("ability tooltips", () => { it("shows the ability's description, binding, and gameplay facts", () => { const markup = renderToStaticMarkup(createElement(AbilityTooltipContent, { ability, bindingLabel: "Shift+1" })); for (const text of [ability.name, ability.description!, "Shift+1", "15 mana", "1.5 sec cast", "6 sec cooldown", "40 m range", "Selected ally or self"]) { expect(markup).toContain(text); } }); it("explains free, instant abilities and distinguishes no cooldown from missing data", () => { expect(abilityTooltipFacts({ name: "Free", resourceCost: 0, castSeconds: 0, cooldownSeconds: 0 })) .toEqual(["No cost", "Instant", "No cooldown"]); expect(abilityTooltipFacts({ name: "Unknown" })).toEqual([]); }); it("shows unlock and talent requirements without hiding the description", () => { const markup = renderToStaticMarkup(createElement(AbilityTooltipContent, { ability: { ...ability, lockedUntilLevel: 20, talentLocked: true }, })); expect(markup).toContain(ability.description); expect(markup).toContain("Unlocks at level 20"); expect(markup).toContain("Learn in talents"); }); it("does not present active cast facts for passive abilities", () => { expect(abilityTooltipFacts({ ...ability, passive: true, rank: 2 })).toEqual(["Passive", "Rank 2"]); }); it("preserves inspectable unavailable controls and creates no layout wrapper", () => { const markup = renderToStaticMarkup(createElement(AbilityTooltipTarget, { ability, children: createElement("button", { "aria-disabled": true, title: "Old browser tooltip" }, "Healing Wave"), })); expect(markup).toBe(''); }); it("keeps the empty-slot browser hint without creating an ability tooltip", () => { const markup = renderToStaticMarkup(createElement(AbilityTooltipTarget, { ability: null, children: createElement("button", { title: "Empty binding" }, "Empty"), })); expect(markup).toBe(''); }); it("places a bottom-bar tooltip above its control", () => { expect(abilityTooltipPosition({ left: 400, right: 440, top: 700, bottom: 740 }, { width: 300, height: 180 }, { width: 1000, height: 800 })) .toEqual({ left: 270, top: 512 }); }); it("flips below top-edge controls and clamps to either horizontal edge", () => { expect(abilityTooltipPosition({ left: 0, right: 40, top: 20, bottom: 60 }, { width: 300, height: 180 }, { width: 1000, height: 800 })) .toEqual({ left: 12, top: 68 }); expect(abilityTooltipPosition({ left: 960, right: 1000, top: 20, bottom: 60 }, { width: 300, height: 180 }, { width: 1000, height: 800 })) .toEqual({ left: 688, top: 68 }); }); it("keeps a tall tooltip inside a narrow viewport", () => { expect(abilityTooltipPosition({ left: 200, right: 240, top: 120, bottom: 160 }, { width: 296, height: 400 }, { width: 320, height: 480 })) .toEqual({ left: 12, top: 68 }); }); });