mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-05-03 07:31:28 -04:00
Updates to validation.js and it's test - all are passing
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
|
||||
62
tests/javascripts/validation.test.js
Normal file
62
tests/javascripts/validation.test.js
Normal 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user