feat: support custom required validation messages

This commit is contained in:
2026-07-13 20:34:36 +05:30
parent b273e30c2f
commit 7a1c6c0041
58 changed files with 169 additions and 93 deletions
+50 -1
View File
@@ -1,5 +1,6 @@
import { test, expect } from "bun:test";
import { v, invalid, parseBody, checkField, parseEnv } from "../src/index.ts";
import { Window } from "happy-dom";
import { v, invalid, parseBody, checkField, parseEnv, VALIDATE_RUNTIME } from "../src/index.ts";
const login = v.object({
email: v.string().email(),
@@ -33,6 +34,51 @@ test("required (non-optional) empty fields fail", () => {
expect(r.errors.password).toBe("Required");
});
test("required(message) customizes empty-field errors and overrides optional", () => {
const schema = v.object({
email: v.string().required("Enter your email or username"),
password: v.string().optional().required("Enter your password"),
accepted: v.boolean().required("Accept the terms to continue"),
});
const result = schema.parse({ accepted: false });
expect(result.errors).toEqual({
email: "Enter your email or username",
password: "Enter your password",
accepted: "Accept the terms to continue",
});
expect(schema.describe().fields.email.requiredMessage).toBe("Enter your email or username");
});
test("browser validation displays the serialized custom required message", () => {
const schema = v.object({
email: v.string().required("Enter your email or username"),
});
const win = new Window() as unknown as Window & Record<string, unknown>;
win.document.body.innerHTML = `
<form data-schema="login">
<input name="email">
<span data-error="email"></span>
</form>`;
win.__wireSchemas = { login: schema.describe() };
(globalThis as Record<string, unknown>).window = win;
(globalThis as Record<string, unknown>).document = win.document;
try {
(0, eval)(VALIDATE_RUNTIME);
const runtime = win.__wireValidate as { init(root: Document): void };
runtime.init(win.document as unknown as Document);
win.document.querySelector("input")!.dispatchEvent(new win.Event("blur", { bubbles: true }));
expect(win.document.querySelector("[data-error=email]")!.textContent).toBe(
"Enter your email or username",
);
} finally {
delete (globalThis as Record<string, unknown>).window;
delete (globalThis as Record<string, unknown>).document;
}
});
test("describe emits a JSON descriptor", () => {
const d = login.describe();
expect(d.type).toBe("object");
@@ -46,6 +92,9 @@ test("checkField coerces and applies rules", () => {
);
expect(checkField({ type: "number", rules: [{ kind: "min", n: 18 }] }, "20").value).toBe(20);
expect(checkField({ type: "string", optional: true, rules: [] }, "").error).toBeNull();
expect(
checkField({ type: "string", requiredMessage: "Email is required", rules: [] }, "").error,
).toBe("Email is required");
});
test("invalid() returns a 400 with errors", async () => {