From 7ec726aad1874cfe59baa75609f9936c3ccb2670 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:42:07 +0000 Subject: [PATCH] Add tests for staticModalAccessibility.js - Create `tests/static-modal-accessibility.test.mjs` to test `prepareStaticModal`, `openStaticModal`, and `closeStaticModal`. - Tests cover element resolution, DOM state updates (classes, attributes), and edge cases (missing elements, null targets). - Use `jsdom` to mock the browser environment. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- tests/static-modal-accessibility.test.mjs | 276 ++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 tests/static-modal-accessibility.test.mjs diff --git a/tests/static-modal-accessibility.test.mjs b/tests/static-modal-accessibility.test.mjs new file mode 100644 index 00000000..04571f7f --- /dev/null +++ b/tests/static-modal-accessibility.test.mjs @@ -0,0 +1,276 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { JSDOM } from "jsdom"; +import { + prepareStaticModal, + openStaticModal, + closeStaticModal, +} from "../js/ui/components/staticModalAccessibility.js"; + +function installDom(html) { + const dom = new JSDOM(html, { url: "https://example.com" }); + const { window } = dom; + globalThis.window = window; + globalThis.document = window.document; + globalThis.HTMLElement = window.HTMLElement; + globalThis.Element = window.Element; + globalThis.EventTarget = window.EventTarget; + globalThis.CustomEvent = window.CustomEvent; + globalThis.Event = window.Event; + globalThis.Node = window.Node; + globalThis.DOMParser = window.DOMParser; + Object.defineProperty(globalThis, "navigator", { + value: window.navigator, + writable: true, + configurable: true, + }); + globalThis.location = window.location; + globalThis.KeyboardEvent = window.KeyboardEvent; + globalThis.MouseEvent = window.MouseEvent; + if (typeof window.requestAnimationFrame !== "function") { + window.requestAnimationFrame = (callback) => + setTimeout(() => callback(Date.now()), 0); + } + if (typeof window.cancelAnimationFrame !== "function") { + window.cancelAnimationFrame = (id) => clearTimeout(id); + } + globalThis.requestAnimationFrame = window.requestAnimationFrame.bind(window); + globalThis.cancelAnimationFrame = window.cancelAnimationFrame.bind(window); + return () => { + delete globalThis.window; + delete globalThis.document; + delete globalThis.HTMLElement; + delete globalThis.Element; + delete globalThis.EventTarget; + delete globalThis.CustomEvent; + delete globalThis.Event; + delete globalThis.Node; + delete globalThis.DOMParser; + delete globalThis.navigator; + delete globalThis.location; + delete globalThis.KeyboardEvent; + delete globalThis.MouseEvent; + delete globalThis.requestAnimationFrame; + delete globalThis.cancelAnimationFrame; + }; +} + +async function flushMicrotasks() { + await Promise.resolve(); + await Promise.resolve(); +} + +test("prepareStaticModal finds element by ID and initializes data-open", async (t) => { + const cleanup = installDom(` + + + + + + + `); + + const modal = prepareStaticModal({ id: "testModal" }); + assert.ok(modal, "Modal should be found and returned"); + assert.strictEqual(modal.id, "testModal", "Returned element should match ID"); + assert.strictEqual(modal.getAttribute("data-open"), "false", "data-open should be initialized to false for hidden modal"); + + cleanup(); +}); + +test("prepareStaticModal accepts direct element reference", async (t) => { + const cleanup = installDom(` + + + + + + + `); + + const element = document.getElementById("testModal"); + const modal = prepareStaticModal({ root: element }); + assert.ok(modal, "Modal should be returned when passed directly"); + assert.strictEqual(modal, element, "Returned element should be the same instance"); + assert.strictEqual(modal.getAttribute("data-open"), "false", "data-open should be initialized"); + + cleanup(); +}); + +test("prepareStaticModal returns null for missing element", async (t) => { + const cleanup = installDom(``); + + const modal = prepareStaticModal({ id: "nonExistent" }); + assert.strictEqual(modal, null, "Should return null if element not found"); + + cleanup(); +}); + +test("prepareStaticModal initializes data-open to true if modal is visible", async (t) => { + const cleanup = installDom(` + + + +
+
+
+
+ + + `); + + const modal = prepareStaticModal({ id: "testModal" }); + assert.strictEqual(modal.getAttribute("data-open"), "true", "data-open should be true for visible modal"); + + cleanup(); +}); + +test("openStaticModal shows modal and updates state", async (t) => { + const cleanup = installDom(` + + + + + + + + `); + + const modal = document.getElementById("testModal"); + const trigger = document.getElementById("trigger"); + + // Ensure prepared first + prepareStaticModal({ root: modal }); + + const result = openStaticModal(modal, { triggerElement: trigger }); + await flushMicrotasks(); + + assert.strictEqual(result, true, "openStaticModal should return true on success"); + assert.strictEqual(modal.classList.contains("hidden"), false, "hidden class should be removed"); + assert.strictEqual(modal.getAttribute("data-open"), "true", "data-open attribute should be true"); + assert.ok(document.documentElement.classList.contains("modal-open"), "document should have modal-open class"); + + cleanup(); +}); + +test("openStaticModal returns false for invalid target", async (t) => { + const cleanup = installDom(``); + + const result = openStaticModal(null); + assert.strictEqual(result, false, "Should return false for null target"); + + cleanup(); +}); + +test("closeStaticModal hides modal and updates state", async (t) => { + const cleanup = installDom(` + + + + +
+
+
+
+ + + `); + + const modal = document.getElementById("testModal"); + // Ensure prepared + prepareStaticModal({ root: modal }); + + // Set initial state manually just to be sure + document.documentElement.classList.add("modal-open"); + + const result = closeStaticModal(modal); + await flushMicrotasks(); + + assert.strictEqual(result, true, "closeStaticModal should return true on success"); + assert.strictEqual(modal.classList.contains("hidden"), true, "hidden class should be added"); + assert.strictEqual(modal.getAttribute("data-open"), "false", "data-open attribute should be false"); + assert.strictEqual(document.documentElement.classList.contains("modal-open"), false, "modal-open class should be removed from document"); + + cleanup(); +}); + +test("closeStaticModal returns false for invalid target", async (t) => { + const cleanup = installDom(``); + + const result = closeStaticModal(null); + assert.strictEqual(result, false, "Should return false for null target"); + + cleanup(); +}); + +test("modal state synchronization handles multiple open modals", async (t) => { + const cleanup = installDom(` + + + + + + + + `); + + const modal1 = document.getElementById("modal1"); + const modal2 = document.getElementById("modal2"); + + prepareStaticModal({ root: modal1 }); + prepareStaticModal({ root: modal2 }); + + openStaticModal(modal1); + await flushMicrotasks(); + assert.ok(document.documentElement.classList.contains("modal-open"), "modal-open set after opening first modal"); + + openStaticModal(modal2); + await flushMicrotasks(); + assert.ok(document.documentElement.classList.contains("modal-open"), "modal-open remains set after opening second modal"); + + closeStaticModal(modal1); + await flushMicrotasks(); + assert.ok(document.documentElement.classList.contains("modal-open"), "modal-open remains set while one modal is still open"); + + closeStaticModal(modal2); + await flushMicrotasks(); + assert.strictEqual(document.documentElement.classList.contains("modal-open"), false, "modal-open removed after closing last modal"); + + cleanup(); +}); + +test("prepareStaticModal handles missing document gracefully", async (t) => { + // This is hard to test with JSDOM as we rely on global document, + // but we can try passing a null document explicitly if the API supports it. + // The API signature is prepareStaticModal({ id, root, document: providedDocument } = {}) + + const result = prepareStaticModal({ id: "test", document: null }); + // If no global document (which exists due to JSDOM import in other tests context or node environment), it might use global. + // But existing code: const doc = resolveDocument(providedDocument); -> returns global document if provided is null. + // So we can't easily simulate "no document" unless we don't install DOM first. + + // Let's rely on the fact that if we don't call installDom, global.document is undefined. + // But JSDOM might be polluting global scope from other tests running in the same process? + // Node test runner runs files in isolation usually, or we can assume clean slate. + + // However, since we are using JSDOM in this file, let's just test passing a dummy object as document that doesn't have getElementById + + const mockDoc = { getElementById: () => null }; + const result2 = prepareStaticModal({ id: "test", document: mockDoc }); + assert.strictEqual(result2, null); +});