Merge branch 'main' of https://github.com/GSA/notifications-admin into 2568-css-cleanup

This commit is contained in:
Jonathan Bobel
2025-05-15 12:28:17 -04:00
24 changed files with 545 additions and 181 deletions

View File

@@ -237,11 +237,11 @@ def test_should_show_job_with_sending_limit_exceeded_status(
job_id=fake_uuid,
)
assert normalize_spaces(page.select("main p")[2].text) == (
assert normalize_spaces(page.select("main p")[3].text) == (
"Notify cannot send these messages because you have reached a limit. "
"You can only send 1,000 messages per day and 250,000 messages in total."
)
assert normalize_spaces(page.select("main p")[3].text) == (
assert normalize_spaces(page.select("main p")[4].text) == (
"Upload this spreadsheet again tomorrow or contact the Notify.gov team to raise the limit."
)

View File

@@ -1451,7 +1451,7 @@ def test_send_one_off_offers_link_to_upload(
assert back_link.text.strip() in {
"Back to all templates",
"Back to confirm your template"
"Back to confirm your template",
}
assert link.text.strip() == "Upload a list of phone numbers"
@@ -2288,7 +2288,9 @@ def test_check_messages_back_link(
actual_href = page.find_all("a", {"class": "usa-back-link"})[0]["href"]
expected_href = expected_url(service_id=SERVICE_ONE_ID, template_id=fake_uuid)
assert actual_href != "#", "Back link href fell back to '#' — missing correct back_link in view"
assert (
actual_href != "#"
), "Back link href fell back to '#' — missing correct back_link in view"
assert actual_href == expected_href

View File

@@ -25,7 +25,7 @@ def test_sets_metadata(client_request, mocker):
def test_removes_blank_lines():
filedata = {
"data": "phone number\r\n15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
"data": "variable,phone number\r\ntest,+15555555555\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
}
file_data = remove_blank_lines(filedata)
assert file_data == {"data": "phone number\n15555555555"}
assert file_data == {"data": "variable,phone number\r\ntest,+15555555555"}

View File

@@ -0,0 +1,161 @@
/**
* @jest-environment jsdom
*/
const { openModal, closeModal, attachModalTriggers } = require("../../app/assets/javascripts/notifyModal.js"); // adjust path if needed
describe("Modal functionality", () => {
let modalWrapper, modalElement, openBtn, closeBtn, anotherFocusable;
beforeEach(() => {
document.body.innerHTML = `
<button data-open-modal="myModal">Open Modal</button>
<div id="myModal" class="is-hidden">
<div class="usa-modal">
<div class="usa-modal-overlay">
<div class="usa-modal-content">
<button data-close-modal>Close</button>
<a href="#">Focusable Link</a>
<input type="text" />
</div>
</div>
</div>
</div>
`;
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);
});
});
describe("Modal trigger buttons", () => {
beforeEach(() => {
document.body.innerHTML = `
<button data-open-modal="myModal">Open Modal</button>
<div id="myModal" class="is-hidden">
<div class="usa-modal">
<div class="usa-modal-content">
<button data-close-modal>Close</button>
</div>
</div>
</div>
`;
});
afterEach(() => {
document.body.innerHTML = "";
});
test("Clicking [data-open-modal] opens the modal", () => {
attachModalTriggers();
const openButton = document.querySelector('[data-open-modal]');
openButton.click();
const modalWrapper = document.getElementById("myModal");
expect(modalWrapper.classList.contains("is-hidden")).toBe(false);
});
test("Clicking [data-close-modal] closes the modal", () => {
const modalWrapper = document.getElementById("myModal");
modalWrapper.classList.remove("is-hidden");
attachModalTriggers();
const closeButton = document.querySelector('[data-close-modal]');
closeModal(); // ensure modal is open to begin with
openModal("myModal");
closeButton.click();
expect(modalWrapper.classList.contains("is-hidden")).toBe(true);
});
});

View File

@@ -5,7 +5,7 @@ beforeAll(() => {
<dialog class="usa-modal" id="sessionTimer" aria-labelledby="sessionTimerHeading" aria-describedby="timeLeft">
<div class="usa-modal__content">
<div class="usa-modal__main">
<h2 class="usa-modal__heading" id="sessionTimerHeading">
<h2 class="usa-modal__heading font-body-lg" id="sessionTimerHeading">
Your session will end soon.
<span class="usa-sr-only">Please choose to extend your session or sign out. Your session will expire in 5 minutes or less.</span>
</h2>