function flushPromises() { return new Promise(resolve => setTimeout(resolve, 0)); } describe("NotePage", () => { let testDiv, item1, item2, don1, doff1, don2, doff2; function makeItem(slug, isEquipped) { const li = document.createElement("li"); li.className = "note-item"; li.dataset.slug = slug; li.dataset.donUrl = `/billboard/note/${slug}/don`; li.dataset.doffUrl = `/billboard/note/${slug}/doff`; li.innerHTML = `

${slug}

`; return li; } beforeEach(() => { NotePage._testReset(); testDiv = document.createElement("div"); testDiv.innerHTML = `Welcome,Earthman`; item1 = makeItem("super-schizo", false); item2 = makeItem("super-nomad", false); testDiv.appendChild(item1); testDiv.appendChild(item2); document.body.appendChild(testDiv); window.fetch = jasmine.createSpy("fetch").and.returnValue( Promise.resolve({ ok: true, json: () => Promise.resolve({ title: "Schizoid Man", greeting: "21st Century" }) }) ); don1 = item1.querySelector(".note-don-btn"); doff1 = item1.querySelector(".note-doff-btn"); don2 = item2.querySelector(".note-don-btn"); doff2 = item2.querySelector(".note-doff-btn"); NotePage._init(); }); afterEach(() => { testDiv.remove(); document.body.classList.remove("notes-locked"); delete window.fetch; }); // ── Lock / unlock ───────────────────────────────────────────────────────── describe("click-lock behaviour", () => { it("clicking a note adds note-item--locked", () => { item1.click(); expect(item1.classList.contains("note-item--locked")).toBe(true); }); it("clicking a note adds notes-locked to body", () => { item1.click(); expect(document.body.classList.contains("notes-locked")).toBe(true); }); it("clicking the same note again removes lock", () => { item1.click(); item1.click(); expect(item1.classList.contains("note-item--locked")).toBe(false); expect(document.body.classList.contains("notes-locked")).toBe(false); }); it("clicking a different note moves lock to that note", () => { item1.click(); item2.click(); expect(item1.classList.contains("note-item--locked")).toBe(false); expect(item2.classList.contains("note-item--locked")).toBe(true); }); it("body click clears all locks", () => { item1.click(); document.body.click(); expect(item1.classList.contains("note-item--locked")).toBe(false); expect(document.body.classList.contains("notes-locked")).toBe(false); }); }); // ── DON/DOFF state ──────────────────────────────────────────────────────── describe("DON button", () => { it("clicking DON sends POST to don URL", async () => { don1.click(); await flushPromises(); expect(window.fetch).toHaveBeenCalledWith( "/billboard/note/super-schizo/don", jasmine.objectContaining({ method: "POST" }) ); }); it("after DON, note gains note-item--donned", async () => { don1.click(); await flushPromises(); expect(item1.classList.contains("note-item--donned")).toBe(true); }); it("after DON, lock is cleared", async () => { item1.click(); // lock first don1.click(); await flushPromises(); expect(item1.classList.contains("note-item--locked")).toBe(false); expect(document.body.classList.contains("notes-locked")).toBe(false); }); it("after DON, greeting prefix and name are updated", async () => { don1.click(); await flushPromises(); expect(document.getElementById("id_greeting_prefix").innerHTML).toContain("21st Century"); expect(document.getElementById("id_greeting_name").textContent).toBe("Schizoid Man"); }); it("DONning item1 auto-removes donned state from previously DONned item2", async () => { item2.classList.add("note-item--donned"); don2.classList.add("btn-disabled"); don2.textContent = "×"; doff2.classList.remove("btn-disabled"); doff2.textContent = "DOFF"; NotePage._donnedItem = item2; don1.click(); await flushPromises(); expect(item2.classList.contains("note-item--donned")).toBe(false); expect(don2.classList.contains("btn-disabled")).toBe(false); expect(don2.textContent).toBe("DON"); expect(doff2.classList.contains("btn-disabled")).toBe(true); }); it("DONning one does not POST a doff for the previous", async () => { item2.classList.add("note-item--donned"); NotePage._donnedItem = item2; don1.click(); await flushPromises(); // Only one fetch call (the DON), no DOFF call for item2 expect(window.fetch.calls.count()).toBe(1); }); }); describe("DOFF button", () => { it("clicking DOFF sends POST to doff URL", async () => { window.fetch = jasmine.createSpy("fetch").and.returnValue( Promise.resolve({ ok: true, json: () => Promise.resolve({ greeting: "Welcome,", title: "Earthman" }) }) ); item1.classList.add("note-item--donned"); doff1.classList.remove("btn-disabled"); doff1.textContent = "DOFF"; don1.classList.add("btn-disabled"); don1.textContent = "×"; NotePage._donnedItem = item1; doff1.click(); await flushPromises(); expect(window.fetch).toHaveBeenCalledWith( "/billboard/note/super-schizo/doff", jasmine.objectContaining({ method: "POST" }) ); }); it("after DOFF, note loses note-item--donned", async () => { window.fetch = jasmine.createSpy("fetch").and.returnValue( Promise.resolve({ ok: true, json: () => Promise.resolve({ greeting: "Welcome,", title: "Earthman" }) }) ); item1.classList.add("note-item--donned"); doff1.classList.remove("btn-disabled"); doff1.textContent = "DOFF"; NotePage._donnedItem = item1; doff1.click(); await flushPromises(); expect(item1.classList.contains("note-item--donned")).toBe(false); }); it("after DOFF, greeting reverts to Welcome, / Earthman", async () => { window.fetch = jasmine.createSpy("fetch").and.returnValue( Promise.resolve({ ok: true, json: () => Promise.resolve({ greeting: "Welcome,", title: "Earthman" }) }) ); document.getElementById("id_greeting_prefix").textContent = "21st Century"; document.getElementById("id_greeting_name").textContent = "Schizoid Man"; item1.classList.add("note-item--donned"); doff1.classList.remove("btn-disabled"); doff1.textContent = "DOFF"; NotePage._donnedItem = item1; doff1.click(); await flushPromises(); expect(document.getElementById("id_greeting_prefix").textContent).toBe("Welcome,"); expect(document.getElementById("id_greeting_name").textContent).toBe("Earthman"); }); }); // ── Initial donned state ────────────────────────────────────────────────── describe("initial load with an already-donned note", () => { it("note whose DON is btn-disabled gets note-item--donned on init", () => { const equippedItem = makeItem("stargazer", true); testDiv.appendChild(equippedItem); NotePage._testReset(); NotePage._init(); expect(equippedItem.classList.contains("note-item--donned")).toBe(true); }); }); });