another attempt to unclog pipeline; this time a slight sleep timeout used to accomodate headless browser resize flush
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
@@ -1,385 +0,0 @@
|
||||
describe("RoleSelect", () => {
|
||||
let testDiv;
|
||||
|
||||
beforeEach(() => {
|
||||
testDiv = document.createElement("div");
|
||||
testDiv.innerHTML = `
|
||||
<div class="room-page"
|
||||
data-select-role-url="/epic/room/test-uuid/select-role">
|
||||
</div>
|
||||
<div id="id_inv_role_card"></div>
|
||||
`;
|
||||
document.body.appendChild(testDiv);
|
||||
window.fetch = jasmine.createSpy("fetch").and.returnValue(
|
||||
Promise.resolve({ ok: true })
|
||||
);
|
||||
// Default stub: auto-confirm so existing card-click tests pass unchanged.
|
||||
// The click-guard integration describe overrides this with a capturing spy.
|
||||
window.showGuard = (_anchor, _msg, onConfirm) => onConfirm && onConfirm();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
RoleSelect.closeFan();
|
||||
testDiv.remove();
|
||||
delete window.showGuard;
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// 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 = '<div class="seat-card-arc"></div>';
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------------ //
|
||||
// click-guard integration //
|
||||
// ------------------------------------------------------------------ //
|
||||
// NOTE: cascade prevention (outside-click on backdrop not closing the //
|
||||
// fan while the guard is active) relies on the guard portal's capture- //
|
||||
// phase stopPropagation, which lives in base.html and requires //
|
||||
// integration testing. The callback contract is fully covered below. //
|
||||
// ------------------------------------------------------------------ //
|
||||
|
||||
describe("click-guard integration", () => {
|
||||
let guardAnchor, guardMessage, guardConfirm, guardDismiss;
|
||||
|
||||
beforeEach(() => {
|
||||
window.showGuard = jasmine.createSpy("showGuard").and.callFake(
|
||||
(anchor, message, onConfirm, onDismiss) => {
|
||||
guardAnchor = anchor;
|
||||
guardMessage = message;
|
||||
guardConfirm = onConfirm;
|
||||
guardDismiss = onDismiss;
|
||||
}
|
||||
);
|
||||
RoleSelect.openFan();
|
||||
});
|
||||
|
||||
describe("clicking a card", () => {
|
||||
let card;
|
||||
|
||||
beforeEach(() => {
|
||||
card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
});
|
||||
|
||||
it("calls window.showGuard", () => {
|
||||
expect(window.showGuard).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes the card element as the anchor", () => {
|
||||
expect(guardAnchor).toBe(card);
|
||||
});
|
||||
|
||||
it("message contains the role name", () => {
|
||||
const roleName = card.querySelector(".card-role-name").textContent.trim();
|
||||
expect(guardMessage).toContain(roleName);
|
||||
});
|
||||
|
||||
it("message contains the role code", () => {
|
||||
expect(guardMessage).toContain(card.dataset.role);
|
||||
});
|
||||
|
||||
it("message contains a <br>", () => {
|
||||
expect(guardMessage).toContain("<br>");
|
||||
});
|
||||
|
||||
it("does not immediately close the fan", () => {
|
||||
expect(document.querySelector(".role-select-backdrop")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not immediately POST to the select_role URL", () => {
|
||||
expect(window.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds .flipped to the card", () => {
|
||||
expect(card.classList.contains("flipped")).toBe(true);
|
||||
});
|
||||
|
||||
it("adds .guard-active to the card", () => {
|
||||
expect(card.classList.contains("guard-active")).toBe(true);
|
||||
});
|
||||
|
||||
it("mouseleave does not remove .flipped while guard is active", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseleave"));
|
||||
expect(card.classList.contains("flipped")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("confirming the guard (OK)", () => {
|
||||
let card;
|
||||
|
||||
beforeEach(() => {
|
||||
card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
guardConfirm();
|
||||
});
|
||||
|
||||
it("removes .guard-active from the card", () => {
|
||||
expect(card.classList.contains("guard-active")).toBe(false);
|
||||
});
|
||||
|
||||
it("closes the fan", () => {
|
||||
expect(document.querySelector(".role-select-backdrop")).toBeNull();
|
||||
});
|
||||
|
||||
it("POSTs to the select_role URL", () => {
|
||||
expect(window.fetch).toHaveBeenCalledWith(
|
||||
"/epic/room/test-uuid/select-role",
|
||||
jasmine.objectContaining({ method: "POST" })
|
||||
);
|
||||
});
|
||||
|
||||
it("appends a .card to #id_inv_role_card", () => {
|
||||
expect(document.querySelector("#id_inv_role_card .card")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("dismissing the guard (NVM or outside click)", () => {
|
||||
let card;
|
||||
|
||||
beforeEach(() => {
|
||||
card = document.querySelector("#id_role_select .card");
|
||||
card.click();
|
||||
guardDismiss();
|
||||
});
|
||||
|
||||
it("removes .guard-active from the card", () => {
|
||||
expect(card.classList.contains("guard-active")).toBe(false);
|
||||
});
|
||||
|
||||
it("removes .flipped from the card", () => {
|
||||
expect(card.classList.contains("flipped")).toBe(false);
|
||||
});
|
||||
|
||||
it("leaves the fan open", () => {
|
||||
expect(document.querySelector(".role-select-backdrop")).not.toBeNull();
|
||||
});
|
||||
|
||||
it("does not POST to the select_role URL", () => {
|
||||
expect(window.fetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not add a card to inventory", () => {
|
||||
expect(document.querySelector("#id_inv_role_card .card")).toBeNull();
|
||||
});
|
||||
|
||||
it("restores normal mouseleave behaviour on the card", () => {
|
||||
card.dispatchEvent(new MouseEvent("mouseenter"));
|
||||
card.dispatchEvent(new MouseEvent("mouseleave"));
|
||||
expect(card.classList.contains("flipped")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user