// ── BurgerSpec.js ──────────────────────────────────────────────────────────── // // Unit specs for burger-btn.js — the room.html corner-menu btn + its 5-btn // fanning arc (sky, earth, sea, voice, text). // // DOM contract assumed by the module: // #id_burger_btn — the burger btn // #id_burger_fan — the fan container (sibling) // .burger-fan-btn — each sub-btn (5 of them) // #id_kit_btn — kit btn (optional) // #id_kit_bag_dialog — kit dialog (optional) // #id_bud_btn — bud btn (optional) // // Public API under test (window.bindBurger): // var ac = bindBurger(); // returns an AbortController for cleanup // // Behaviours: // • Click toggles .active class on #id_burger_btn. // • Escape closes when open. // • Click outside closes when open. // • Opening burger closes the kit dialog + the bud panel if either is open. // // ───────────────────────────────────────────────────────────────────────────── describe("Burger", () => { let burgerBtn, fan, ac; beforeEach(() => { burgerBtn = document.createElement("button"); burgerBtn.id = "id_burger_btn"; document.body.appendChild(burgerBtn); fan = document.createElement("div"); fan.id = "id_burger_fan"; document.body.appendChild(fan); ac = bindBurger(); }); afterEach(() => { if (ac) ac.abort(); burgerBtn.remove(); fan.remove(); // Clean up any test-added kit/bud elements + classes. ["id_kit_btn", "id_kit_bag_dialog", "id_bud_btn"].forEach(id => { const el = document.getElementById(id); if (el) el.remove(); }); document.documentElement.classList.remove("bud-open"); }); describe("bindBurger()", () => { it("returns an AbortController when DOM is present", () => { expect(ac).not.toBeNull(); expect(ac.abort).toBeDefined(); }); it("returns null when #id_burger_btn is absent", () => { burgerBtn.remove(); const ac2 = bindBurger(); expect(ac2).toBeNull(); }); it("returns null when #id_burger_fan is absent", () => { fan.remove(); const ac2 = bindBurger(); expect(ac2).toBeNull(); }); }); describe("click toggle", () => { it("first click adds .active to #id_burger_btn", () => { burgerBtn.click(); expect(burgerBtn.classList.contains("active")).toBe(true); }); it("first click sets aria-expanded='true'", () => { burgerBtn.click(); expect(burgerBtn.getAttribute("aria-expanded")).toBe("true"); }); it("first click sets fan aria-hidden='false'", () => { burgerBtn.click(); expect(fan.getAttribute("aria-hidden")).toBe("false"); }); it("second click removes .active (closes)", () => { burgerBtn.click(); burgerBtn.click(); expect(burgerBtn.classList.contains("active")).toBe(false); }); it("second click resets aria-expanded='false'", () => { burgerBtn.click(); burgerBtn.click(); expect(burgerBtn.getAttribute("aria-expanded")).toBe("false"); }); }); describe("Escape key", () => { it("closes burger when open", () => { burgerBtn.click(); const evt = new KeyboardEvent("keydown", { key: "Escape" }); document.dispatchEvent(evt); expect(burgerBtn.classList.contains("active")).toBe(false); }); it("no-op when burger already closed", () => { const evt = new KeyboardEvent("keydown", { key: "Escape" }); document.dispatchEvent(evt); expect(burgerBtn.classList.contains("active")).toBe(false); }); }); describe("click-outside", () => { it("closes burger on click outside btn + fan", () => { burgerBtn.click(); const outsider = document.createElement("div"); document.body.appendChild(outsider); outsider.click(); expect(burgerBtn.classList.contains("active")).toBe(false); outsider.remove(); }); it("does NOT close on click inside fan", () => { burgerBtn.click(); const subBtn = document.createElement("button"); subBtn.className = "burger-fan-btn"; fan.appendChild(subBtn); subBtn.click(); expect(burgerBtn.classList.contains("active")).toBe(true); }); }); describe("opening burger closes other corner panels", () => { it("clicks #id_kit_btn when kit dialog is open", () => { const kitBtn = document.createElement("button"); kitBtn.id = "id_kit_btn"; const kitClickSpy = jasmine.createSpy("kitClick"); kitBtn.addEventListener("click", kitClickSpy); document.body.appendChild(kitBtn); const dialog = document.createElement("dialog"); dialog.id = "id_kit_bag_dialog"; dialog.setAttribute("open", ""); document.body.appendChild(dialog); burgerBtn.click(); expect(kitClickSpy).toHaveBeenCalled(); }); it("does NOT click #id_kit_btn when kit dialog is closed", () => { const kitBtn = document.createElement("button"); kitBtn.id = "id_kit_btn"; const kitClickSpy = jasmine.createSpy("kitClick"); kitBtn.addEventListener("click", kitClickSpy); document.body.appendChild(kitBtn); const dialog = document.createElement("dialog"); dialog.id = "id_kit_bag_dialog"; // no open attribute document.body.appendChild(dialog); burgerBtn.click(); expect(kitClickSpy).not.toHaveBeenCalled(); }); it("clicks #id_bud_btn when html.bud-open is set", () => { const budBtn = document.createElement("button"); budBtn.id = "id_bud_btn"; const budClickSpy = jasmine.createSpy("budClick"); budBtn.addEventListener("click", budClickSpy); document.body.appendChild(budBtn); document.documentElement.classList.add("bud-open"); burgerBtn.click(); expect(budClickSpy).toHaveBeenCalled(); }); it("does NOT click #id_bud_btn when html.bud-open is absent", () => { const budBtn = document.createElement("button"); budBtn.id = "id_bud_btn"; const budClickSpy = jasmine.createSpy("budClick"); budBtn.addEventListener("click", budClickSpy); document.body.appendChild(budBtn); burgerBtn.click(); expect(budClickSpy).not.toHaveBeenCalled(); }); it("ignores missing kit/bud btns w.o. error", () => { // No kit, no bud — opening should still succeed. expect(() => burgerBtn.click()).not.toThrow(); expect(burgerBtn.classList.contains("active")).toBe(true); }); }); describe("inactive sub-btn flash", () => { let subBtn; beforeEach(() => { subBtn = document.createElement("button"); subBtn.className = "burger-fan-btn"; fan.appendChild(subBtn); jasmine.clock().install(); }); afterEach(() => { jasmine.clock().uninstall(); }); it("adds .flash-inactive to inactive sub-btn on click", () => { subBtn.click(); expect(subBtn.classList.contains("flash-inactive")).toBe(true); }); it("removes .flash-inactive after the ON window (~180ms)", () => { subBtn.click(); jasmine.clock().tick(200); expect(subBtn.classList.contains("flash-inactive")).toBe(false); }); it("pulses twice — second ON window arrives after off+on cycle (~480ms)", () => { subBtn.click(); // First pulse cycle: 180ms ON + 120ms OFF = 300ms. Second pulse ON starts. jasmine.clock().tick(310); expect(subBtn.classList.contains("flash-inactive")).toBe(true); }); it("settles back to default after 2 pulses (~700ms total)", () => { subBtn.click(); jasmine.clock().tick(800); expect(subBtn.classList.contains("flash-inactive")).toBe(false); }); it("does NOT flash when sub-btn carries .active class", () => { subBtn.classList.add("active"); subBtn.click(); expect(subBtn.classList.contains("flash-inactive")).toBe(false); }); }); describe("AbortController teardown", () => { it("detaches click handler when ac.abort() is called", () => { ac.abort(); burgerBtn.click(); expect(burgerBtn.classList.contains("active")).toBe(false); }); }); });