From be038daa0a38314d3509f414bcd105b1bfc285e3 Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Wed, 7 May 2025 11:16:00 -0400 Subject: [PATCH] Create notifyModal.test.js --- tests/javascripts/notifyModal.test.js | 120 ++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 tests/javascripts/notifyModal.test.js diff --git a/tests/javascripts/notifyModal.test.js b/tests/javascripts/notifyModal.test.js new file mode 100644 index 000000000..df1aba404 --- /dev/null +++ b/tests/javascripts/notifyModal.test.js @@ -0,0 +1,120 @@ +/** + * @jest-environment jsdom + */ + +const { openModal, closeModal } = require("../../app/assets/javascripts/modal"); // adjust path if needed + +describe("Modal functionality", () => { + let modalWrapper, modalElement, openBtn, closeBtn, anotherFocusable; + + beforeEach(() => { + document.body.innerHTML = ` + + + + `; + + modalWrapper = document.getElementById("myModal"); + modalElement = modalWrapper.querySelector(".usa-modal"); + openBtn = document.querySelector('[data-open-modal]'); + closeBtn = modalWrapper.querySelector('[data-close-modal]'); + anotherFocusable = modalWrapper.querySelector('a'); + }); + + afterEach(() => { + document.body.innerHTML = ""; + }); + + test("Opens the modal and sets focus to the first focusable element", () => { + document.activeElement.blur(); // ensure focus starts elsewhere + openModal("myModal"); + + expect(modalWrapper.classList.contains("is-hidden")).toBe(false); + expect(modalElement.hasAttribute("aria-hidden")).toBe(false); + expect(modalElement.hasAttribute("inert")).toBe(false); + expect(modalElement.hasAttribute("hidden")).toBe(false); + expect(document.body.classList.contains("modal-open")).toBe(true); + expect(document.activeElement).toBe(closeBtn); + }); + + test("Closes the modal and restores focus", () => { + openBtn.focus(); + openModal("myModal"); + closeModal(); + + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + expect(modalElement.getAttribute("aria-hidden")).toBe("true"); + expect(modalElement.hasAttribute("inert")).toBe(true); + expect(modalElement.hasAttribute("hidden")).toBe(true); + expect(document.body.classList.contains("modal-open")).toBe(false); + expect(document.activeElement).toBe(openBtn); + }); + + test("Closes the modal when pressing Escape", () => { + openModal("myModal"); + + const event = new KeyboardEvent("keydown", { key: "Escape" }); + document.dispatchEvent(event); + + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + }); + + test("Traps focus within the modal when Tab is pressed", () => { + openModal("myModal"); + + const focusableElements = modalElement.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])' + ); + + focusableElements[focusableElements.length - 1].focus(); // Last element + + const tabEvent = new KeyboardEvent("keydown", { + key: "Tab", + bubbles: true + }); + + modalElement.dispatchEvent(tabEvent); + expect(document.activeElement).toBe(focusableElements[0]); + }); + + test("Traps focus backwards when Shift+Tab is pressed from first element", () => { + openModal("myModal"); + + const focusableElements = modalElement.querySelectorAll( + 'a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), [tabindex]:not([tabindex="-1"])' + ); + + focusableElements[0].focus(); // First element + + const shiftTabEvent = new KeyboardEvent("keydown", { + key: "Tab", + shiftKey: true, + bubbles: true + }); + + modalElement.dispatchEvent(shiftTabEvent); + expect(document.activeElement).toBe(focusableElements[focusableElements.length - 1]); + }); + + test("Closes modal when clicking on overlay", () => { + openModal("myModal"); + const overlay = modalElement.querySelector(".usa-modal-overlay"); + + const clickEvent = new MouseEvent("click", { + bubbles: true + }); + + overlay.dispatchEvent(clickEvent); + expect(modalWrapper.classList.contains("is-hidden")).toBe(true); + }); +});