describe("SeaDeal", () => { let testDiv, overlay, stage, stageCard, statBlock; const CARD = { id: 99, name: "Queen of Crowns", arcana: "MIDDLE", suit: "CROWNS", number: 13, corner_rank: "Q", suit_icon: "fa-crown", name_group: "", name_title: "Queen of Crowns", levity_qualifier: "Elevated", gravity_qualifier: "Graven", reversal: "Vacant", keywords_upright: ["nurturing", "practical", "abundance"], keywords_reversed: ["financial dependence", "smothering"], energies: [{ type: "LIBIDO", effect: "Energy entry." }], operations: [{ type: "COVER", effect: "Operation entry." }], }; function makeFixture({ polarity = "levity" } = {}) { testDiv = document.createElement("div"); testDiv.innerHTML = `
`; document.body.appendChild(testDiv); overlay = testDiv.querySelector("#id_sea_overlay"); stage = testDiv.querySelector("#id_sea_stage"); stageCard = testDiv.querySelector(".sea-stage-card"); statBlock = testDiv.querySelector(".sea-stat-block"); SeaDeal._testInit(); } afterEach(() => { if (testDiv) testDiv.remove(); // Purge any stale overlays left by tests that called makeFixture() twice document.querySelectorAll('#id_sea_overlay').forEach(el => el.remove()); }); // ── openStage ────────────────────────────────────────────────────────── // describe("openStage()", () => { beforeEach(() => makeFixture()); it("makes #id_sea_stage visible", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); expect(stage.style.display).not.toBe("none"); }); it("populates corner rank on stage card", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); const rank = stageCard.querySelector(".fan-card-corner--tl .fan-corner-rank"); expect(rank.textContent).toBe("Q"); }); it("shows suit icon on stage card", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); const icon = stageCard.querySelector(".fan-card-corner--tl .stage-suit-icon"); expect(icon.style.display).not.toBe("none"); expect(icon.classList.contains("fa-crown")).toBe(true); }); it("populates upright card name for non-major", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); expect(stageCard.querySelector(".fan-card-name").textContent).toBe("Queen of Crowns"); }); it("puts levity qualifier in qualifier-above for non-major", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); expect(stageCard.querySelector(".sig-qualifier-above").textContent).toBe("Elevated"); }); it("puts gravity qualifier in qualifier-above for non-major gravity", () => { // Re-init with gravity polarity (no double-append; beforeEach already ran) overlay.dataset.seaUserPolarity = "gravity"; SeaDeal._testInit(); stageCard = testDiv.querySelector(".sea-stage-card"); SeaDeal.openStage(CARD, ".sea-pos-cover", false); expect(stageCard.querySelector(".sig-qualifier-above").textContent).toBe("Graven"); }); it("puts reversal word in reversal-qualifier slot", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); expect(stageCard.querySelector(".fan-card-reversal-qualifier").textContent).toBe("Vacant"); }); it("populates upright keywords in stat block", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); const items = testDiv.querySelectorAll("#id_sea_stat_upright li"); expect(items.length).toBe(3); expect(items[0].textContent).toBe("nurturing"); }); it("populates reversed keywords in stat block", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); const items = testDiv.querySelectorAll("#id_sea_stat_reversed li"); expect(items.length).toBe(2); }); it("fills the target position slot", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); const slot = testDiv.querySelector(".sea-pos-cover .sea-card-slot"); expect(slot.classList.contains("sea-card-slot--filled")).toBe(true); }); it("resets SPIN to upright when opening a new card", () => { SeaDeal.openStage(CARD, ".sea-pos-cover", true); stageCard.classList.add("stage-card--reversed"); statBlock.classList.add("is-reversed"); SeaDeal.openStage(CARD, ".sea-pos-cross", true); expect(stageCard.classList.contains("stage-card--reversed")).toBe(false); expect(statBlock.classList.contains("is-reversed")).toBe(false); }); }); // ── SPIN in sea stage ─────────────────────────────────────────────────── // describe("SPIN btn in sea stage", () => { beforeEach(() => { makeFixture(); SeaDeal.openStage(CARD, ".sea-pos-cover", true); }); it("toggles is-reversed on stat block", () => { testDiv.querySelector(".sea-spin-btn").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(statBlock.classList.contains("is-reversed")).toBe(true); }); it("toggles stage-card--reversed on stage card", () => { testDiv.querySelector(".sea-spin-btn").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(stageCard.classList.contains("stage-card--reversed")).toBe(true); }); it("second SPIN click restores upright", () => { const btn = testDiv.querySelector(".sea-spin-btn"); btn.dispatchEvent(new MouseEvent("click", { bubbles: true })); btn.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(statBlock.classList.contains("is-reversed")).toBe(false); }); }); // ── FYI in sea stage ──────────────────────────────────────────────────── // describe("FYI btn in sea stage", () => { beforeEach(() => { makeFixture(); SeaDeal.openStage(CARD, ".sea-pos-cover", true); }); it("FYI click shows the info panel", () => { testDiv.querySelector(".sea-fyi-btn").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(testDiv.querySelector("#id_sea_fyi_panel").style.display).not.toBe("none"); }); it("shows first energy entry title as 'Energy'", () => { testDiv.querySelector(".sea-fyi-btn").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(testDiv.querySelector(".sig-info-title").textContent).toBe("Energy"); }); it("shows first entry type", () => { testDiv.querySelector(".sea-fyi-btn").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(testDiv.querySelector(".sig-info-type").textContent).toBe("LIBIDO"); }); it("NXT advances to operation entry", () => { testDiv.querySelector(".sea-fyi-btn").dispatchEvent(new MouseEvent("click", { bubbles: true })); testDiv.querySelector(".sea-fyi-next").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(testDiv.querySelector(".sig-info-title").textContent).toBe("Operation"); expect(testDiv.querySelector(".sig-info-type").textContent).toBe("COVER"); }); }); // ── Backdrop dismiss ──────────────────────────────────────────────────── // describe("stage backdrop click", () => { beforeEach(() => { makeFixture(); SeaDeal.openStage(CARD, ".sea-pos-cover", true); }); it("hides the sea stage", () => { testDiv.querySelector(".sea-stage-backdrop").dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(stage.style.display).toBe("none"); }); it("leaves the slot filled after dismiss", () => { testDiv.querySelector(".sea-stage-backdrop").dispatchEvent(new MouseEvent("click", { bubbles: true })); const slot = testDiv.querySelector(".sea-pos-cover .sea-card-slot"); expect(slot.classList.contains("sea-card-slot--filled")).toBe(true); }); }); // ── Re-open from deposited slot (two-step tap) ────────────────────────── // describe("clicking a deposited slot", () => { let slot; beforeEach(() => { makeFixture(); SeaDeal.openStage(CARD, ".sea-pos-cover", true); // Dismiss first — adds --visible to slot testDiv.querySelector(".sea-stage-backdrop").dispatchEvent(new MouseEvent("click", { bubbles: true })); slot = testDiv.querySelector(".sea-pos-cover .sea-card-slot"); }); it("first click focuses the slot (does not yet open stage)", () => { slot.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(slot.classList.contains("sea-card-slot--focused")).toBeTrue(); expect(stage.style.display).toBe("none"); }); it("re-opens the sea stage on second click", () => { slot.dispatchEvent(new MouseEvent("click", { bubbles: true })); // focus slot.dispatchEvent(new MouseEvent("click", { bubbles: true })); // open expect(stage.style.display).not.toBe("none"); }); it("re-populates stage with the same card rank on second click", () => { slot.dispatchEvent(new MouseEvent("click", { bubbles: true })); // focus slot.dispatchEvent(new MouseEvent("click", { bubbles: true })); // open const rank = stageCard.querySelector(".fan-card-corner--tl .fan-corner-rank"); expect(rank.textContent).toBe("Q"); }); }); });