Updates to validation.js and it's test - all are passing

This commit is contained in:
Jonathan Bobel
2025-02-28 11:31:47 -05:00
parent a5146fc58a
commit f4ca597964
2 changed files with 147 additions and 39 deletions

View File

@@ -1,47 +1,93 @@
// document.addEventListener("DOMContentLoaded", function () {
// const form = document.querySelector(".send-one-off-form");
// const phoneInput = document.getElementById("phone-number");
function showError(input, errorElement, message) {
errorElement.textContent = ""; // Clear existing message
errorElement.style.display = "block";
// // Try to get the error element
// let phoneError = document.getElementById("phone-number-error");
// Small delay to ensure screen readers pick up the change
setTimeout(() => {
errorElement.textContent = message;
}, 10);
// // If not found, create it
// if (!phoneError) {
// phoneError = document.createElement("span");
// phoneError.id = "phone-number-error";
// phoneError.classList.add("usa-error-message");
// phoneError.style.display = "none"; // Keep it hidden initially
// phoneInput.insertAdjacentElement("afterend", phoneError);
// }
input.classList.add("usa-input--error");
input.setAttribute("aria-describedby", errorElement.id);
}
// form.addEventListener("submit", function (event) {
// let isValid = true;
function hideError(input, errorElement) {
errorElement.style.display = "none";
input.classList.remove("usa-input--error");
input.removeAttribute("aria-describedby");
}
// if (phoneInput.value.trim() === "") {
// showError(phoneInput, phoneError, "Phone number cannot be empty.");
// isValid = false;
// }
function getFieldLabel(input) {
const label = document.querySelector(`label[for="${input.id}"]`);
return label ? label.textContent.trim() : "This field";
}
// if (!isValid) event.preventDefault();
// });
// Attach validation logic to forms
function attachValidation() {
const forms = document.querySelectorAll("form");
forms.forEach((form) => {
const inputs = form.querySelectorAll("input, textarea, select");
// // Remove error when user starts typing
// phoneInput.addEventListener("input", function () {
// if (phoneInput.value.trim() !== "") {
// hideError(phoneInput, phoneError);
// }
// });
form.addEventListener("submit", function (event) {
let isValid = true;
let firstInvalidInput = null;
// function showError(input, errorElement, message) {
// errorElement.textContent = message;
// errorElement.style.display = "block";
// input.classList.add("usa-input--error");
// input.setAttribute("aria-describedby", errorElement.id);
// }
inputs.forEach((input) => {
const errorId = input.id ? `${input.id}-error` : `${input.name}-error`;
let errorElement = document.getElementById(errorId);
// function hideError(input, errorElement) {
// errorElement.style.display = "none";
// input.classList.remove("usa-input--error");
// input.removeAttribute("aria-describedby");
// }
// });
if (!errorElement) {
errorElement = document.createElement("span");
errorElement.id = errorId;
errorElement.classList.add("usa-error-message");
errorElement.setAttribute("aria-live", "polite");
input.insertAdjacentElement("afterend", errorElement);
}
if (input.type === "radio") {
// Find all radio buttons with the same name
const radioGroup = document.querySelectorAll(`input[name="${input.name}"]`);
const isChecked = Array.from(radioGroup).some(radio => radio.checked);
if (!isChecked) {
showError(input, errorElement, `Error: ${getFieldLabel(input)} must be selected.`);
isValid = false;
if (!firstInvalidInput) {
firstInvalidInput = input;
}
}
} else if (input.value.trim() === "") {
showError(input, errorElement, `Error: ${getFieldLabel(input)} is required.`);
isValid = false;
if (!firstInvalidInput) {
firstInvalidInput = input;
}
}
});
if (!isValid) {
event.preventDefault();
if (firstInvalidInput) firstInvalidInput.focus();
}
});
inputs.forEach((input) => {
input.addEventListener("input", function () {
const errorElement = document.getElementById(`${input.id}-error`);
if (input.value.trim() !== "" && errorElement) {
hideError(input, errorElement);
}
});
});
});
}
// Automatically attach validation only in the browser
if (typeof window !== "undefined") {
document.addEventListener("DOMContentLoaded", attachValidation);
}
// ✅ Check if we're in a Node.js environment (for Jest) before using `module.exports`
if (typeof module !== "undefined" && typeof module.exports !== "undefined") {
module.exports = { showError, hideError, getFieldLabel, attachValidation };
}

View File

@@ -0,0 +1,62 @@
const { showError, hideError, getFieldLabel, attachValidation } = require("../../app/assets/javascripts/validation.js");
describe("Form Validation", () => {
let form, input, submitButton;
beforeEach(() => {
document.body.innerHTML = `
<form class="test-form">
<label for="test-input">Test Input</label>
<input id="test-input" name="testInput" type="text" />
<button type="submit">Submit</button>
</form>
`;
form = document.querySelector(".test-form");
input = document.getElementById("test-input");
submitButton = form.querySelector("button");
// Manually attach validation logic for Jest
attachValidation();
});
afterEach(() => {
document.body.innerHTML = ""; // Clean up DOM after each test
});
test("Displays an error message when input is empty", async () => {
form.dispatchEvent(new Event("submit", { bubbles: true }));
// Wait for the timeout to complete
await new Promise(resolve => setTimeout(resolve, 20));
const errorMessage = document.getElementById("test-input-error");
expect(errorMessage).not.toBeNull();
expect(errorMessage.textContent).toBe("Error: Test Input is required.");
expect(input.classList.contains("usa-input--error")).toBe(true);
});
test("Removes error message when input is filled", () => {
// Trigger validation first
form.dispatchEvent(new Event("submit", { bubbles: true }));
// Simulate user typing to remove the error
input.value = "Some text";
input.dispatchEvent(new Event("input", { bubbles: true }));
const errorMessage = document.getElementById("test-input-error");
expect(errorMessage).not.toBeNull();
expect(errorMessage.style.display).toBe("none");
expect(input.classList.contains("usa-input--error")).toBe(false);
});
test("Focus moves to first invalid input", async () => {
const spy = jest.spyOn(input, "focus");
form.dispatchEvent(new Event("submit", { bubbles: true }));
await new Promise((resolve) => setTimeout(resolve, 10)); // Allow DOM updates
expect(spy).toHaveBeenCalled();
});
});