describe("RoleSelect", () => { let testDiv; beforeEach(() => { testDiv = document.createElement("div"); testDiv.innerHTML = `
`; document.body.appendChild(testDiv); window.fetch = jasmine.createSpy("fetch").and.returnValue( Promise.resolve({ ok: true }) ); }); afterEach(() => { RoleSelect.closeFan(); testDiv.remove(); }); // ------------------------------------------------------------------ // // openFan() // // ------------------------------------------------------------------ // describe("openFan()", () => { it("creates .role-select-backdrop in the DOM", () => { RoleSelect.openFan(); expect(document.querySelector(".role-select-backdrop")).not.toBeNull(); }); it("creates #id_role_select inside the backdrop", () => { RoleSelect.openFan(); expect(document.getElementById("id_role_select")).not.toBeNull(); }); it("renders exactly 6 .card elements", () => { RoleSelect.openFan(); const cards = document.querySelectorAll("#id_role_select .card"); expect(cards.length).toBe(6); }); it("does not open a second backdrop if already open", () => { RoleSelect.openFan(); RoleSelect.openFan(); expect(document.querySelectorAll(".role-select-backdrop").length).toBe(1); }); }); // ------------------------------------------------------------------ // // closeFan() // // ------------------------------------------------------------------ // describe("closeFan()", () => { it("removes .role-select-backdrop from the DOM", () => { RoleSelect.openFan(); RoleSelect.closeFan(); expect(document.querySelector(".role-select-backdrop")).toBeNull(); }); it("removes #id_role_select from the DOM", () => { RoleSelect.openFan(); RoleSelect.closeFan(); expect(document.getElementById("id_role_select")).toBeNull(); }); it("does not throw if no fan is open", () => { expect(() => RoleSelect.closeFan()).not.toThrow(); }); }); // ------------------------------------------------------------------ // // Card interactions // // ------------------------------------------------------------------ // describe("card interactions", () => { beforeEach(() => { RoleSelect.openFan(); }); it("mouseenter adds .flipped to the card", () => { const card = document.querySelector("#id_role_select .card"); card.dispatchEvent(new MouseEvent("mouseenter")); expect(card.classList.contains("flipped")).toBe(true); }); it("mouseleave removes .flipped from the card", () => { const card = document.querySelector("#id_role_select .card"); card.dispatchEvent(new MouseEvent("mouseenter")); card.dispatchEvent(new MouseEvent("mouseleave")); expect(card.classList.contains("flipped")).toBe(false); }); it("clicking a card closes the fan", () => { const card = document.querySelector("#id_role_select .card"); card.click(); expect(document.getElementById("id_role_select")).toBeNull(); }); it("clicking a card appends a .card to #id_inv_role_card", () => { const card = document.querySelector("#id_role_select .card"); card.click(); expect(document.querySelector("#id_inv_role_card .card")).not.toBeNull(); }); it("clicking a card POSTs to the select_role URL", () => { const card = document.querySelector("#id_role_select .card"); card.click(); expect(window.fetch).toHaveBeenCalledWith( "/epic/room/test-uuid/select-role", jasmine.objectContaining({ method: "POST" }) ); }); it("clicking a card results in exactly one card in inventory", () => { const card = document.querySelector("#id_role_select .card"); card.click(); expect(document.querySelectorAll("#id_inv_role_card .card").length).toBe(1); }); }); // ------------------------------------------------------------------ // // Backdrop click // // ------------------------------------------------------------------ // describe("backdrop click", () => { it("closes the fan", () => { RoleSelect.openFan(); document.querySelector(".role-select-backdrop").click(); expect(document.getElementById("id_role_select")).toBeNull(); }); it("does not add a card to inventory", () => { RoleSelect.openFan(); document.querySelector(".role-select-backdrop").click(); expect(document.querySelector("#id_inv_role_card .card")).toBeNull(); }); }); // ------------------------------------------------------------------ // // room:roles_revealed event // // ------------------------------------------------------------------ // describe("room:roles_revealed event", () => { let reloadCalled; beforeEach(() => { reloadCalled = false; RoleSelect.setReload(() => { reloadCalled = true; }); }); afterEach(() => { RoleSelect.setReload(() => { window.location.reload(); }); }); it("triggers a page reload", () => { window.dispatchEvent(new CustomEvent("room:roles_revealed", { detail: {} })); expect(reloadCalled).toBe(true); }); }); // ------------------------------------------------------------------ // // room:turn_changed event // // ------------------------------------------------------------------ // describe("room:turn_changed event", () => { let stack; beforeEach(() => { // Six table seats, slot 1 starts active for (let i = 1; i <= 6; i++) { const seat = document.createElement("div"); seat.className = "table-seat" + (i === 1 ? " active" : ""); seat.dataset.slot = String(i); seat.innerHTML = '
'; testDiv.appendChild(seat); } stack = document.createElement("div"); stack.className = "card-stack"; stack.dataset.state = "ineligible"; stack.dataset.userSlots = "1"; stack.dataset.starterRoles = ""; testDiv.appendChild(stack); }); it("moves .active to the newly active seat", () => { window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 2 } })); expect( testDiv.querySelector(".table-seat.active").dataset.slot ).toBe("2"); }); it("removes .active from the previously active seat", () => { window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 2 } })); expect( testDiv.querySelector(".table-seat[data-slot='1']").classList.contains("active") ).toBe(false); }); it("sets data-state to eligible when active_slot matches user slot", () => { window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 1 } })); expect(stack.dataset.state).toBe("eligible"); }); it("sets data-state to ineligible when active_slot does not match", () => { stack.dataset.state = "eligible"; window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 2 } })); expect(stack.dataset.state).toBe("ineligible"); }); it("clicking stack opens fan when newly eligible", () => { window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 1 } })); stack.click(); expect(document.querySelector(".role-select-backdrop")).not.toBeNull(); }); it("clicking stack does not open fan when ineligible", () => { // Make eligible first (adds listener), then flip back to ineligible window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 1 } })); window.dispatchEvent(new CustomEvent("room:turn_changed", { detail: { active_slot: 2 } })); stack.click(); expect(document.querySelector(".role-select-backdrop")).toBeNull(); }); }); });