Write a class FormValidator that checks a form's values and says what's wrong with each field. The values are a plain object from field name to value, like { email: "ada@example.com", age: 36 }.
new FormValidator(rules) takes an object that maps field names to arrays of rules. A rule is an object { test, message }. test(value, values) gets the field's value and the object of all the values. It returns true when the value is fine and false when it isn't.
message is the text to show when the rule's test fails. It can also be a function. Then it's called only when the test fails, with the same value and values, and it returns the text.
validate(values) checks every field that has rules. It returns an object with an entry for each failing field: the field's name, mapped to its message. Fields that pass aren't in it, so when everything passes, it returns an empty object.
A field's rules run in order and stop at the first one that fails. That rule's message is the field's message, and the rules after it don't run.
A field that has rules but is missing from values is checked with the value undefined. Values for fields that have no rules are ignored.
validateField(name, values) checks one field in the same way and returns its message, or null when it passes. It gets all the values, so rules that compare fields still work. A field with no rules passes.
isValid(values) returns true when every field passes, and false otherwise.
const signup = new FormValidator({
email: [
{ test: (value) => Boolean(value), message: "Enter your email" },
{ test: (value) => value.includes("@"), message: "Enter a valid email" },
],
password: [
{ test: (value) => value.length >= 8, message: (value) => "Add " + (8 - value.length) + " more characters" },
],
confirm: [
{ test: (value, values) => value === values.password, message: "The passwords don't match" },
],
});
signup.validate({ email: "", password: "secret", confirm: "secrets" });
// { email: "Enter your email", password: "Add 2 more characters", confirm: "The passwords don't match" }
signup.validateField("email", { email: "ada" }); // "Enter a valid email"
signup.validateField("email", { email: "ada@example.com" }); // null
signup.isValid({ email: "ada@example.com", password: "correct horse", confirm: "correct horse" }); // true
const form = new FormValidator({
name: [{ test: (value) => value !== "", message: "Enter your name" }],
age: [{ test: (value) => value >= 18, message: "You must be 18 or older" }],
});
expect(form.validate({ name: "Ada", age: 36 })).toEqual({});
const form = new FormValidator({
name: [{ test: (value) => value !== "", message: "Enter your name" }],
age: [{ test: (value) => value >= 18, message: "You must be 18 or older" }],
city: [{ test: (value) => value !== "", message: "Enter your city" }],
});
expect(form.validate({ name: "", age: 12, city: "Paris" })).toEqual({
name: "Enter your name",
age: "You must be 18 or older",
});
const later = spy(() => false);
const form = new FormValidator({
password: [
{ test: (value) => value.length >= 8, message: "Too short" },
{ test: (value) => /[0-9]/.test(value), message: "Add a digit" },
{ test: later, message: "Never shown" },
],
});
expect(form.validate({ password: "abc" })).toEqual({ password: "Too short" });
expect(form.validate({ password: "abcdefgh" })).toEqual({ password: "Add a digit" });
expect(later).not.toHaveBeenCalled();
const test = spy((value, values) => value === values.password);
const form = new FormValidator({ confirm: [{ test, message: "The passwords don't match" }] });
const values = { password: "hunter22", confirm: "hunter2" };
expect(form.validate(values)).toEqual({ confirm: "The passwords don't match" });
expect(test).toHaveBeenCalledWith("hunter2", values);
expect(form.validate({ password: "hunter22", confirm: "hunter22" })).toEqual({});
const message = spy((value) => "Add " + (8 - value.length) + " more characters");
const form = new FormValidator({ password: [{ test: (value) => value.length >= 8, message }] });
const values = { password: "secret" };
expect(form.validate(values)).toEqual({ password: "Add 2 more characters" });
expect(message).toHaveBeenCalledWith("secret", values);
form.validate({ password: "long enough" });
expect(message).toHaveBeenCalledTimes(1);
const test = spy((value) => value !== undefined && value !== "");
const form = new FormValidator({ email: [{ test, message: "Enter your email" }] });
expect(form.validate({ nickname: "" })).toEqual({ email: "Enter your email" });
expect(test).toHaveBeenCalledWith(undefined, { nickname: "" });
const other = spy(() => true);
const form = new FormValidator({
password: [{ test: (value) => value.length >= 8, message: "Too short" }],
confirm: [{ test: (value, values) => value === values.password, message: "No match" }],
email: [{ test: other, message: "Enter your email" }],
});
expect(form.validateField("password", { password: "abc", confirm: "abc" })).toBe("Too short");
expect(form.validateField("confirm", { password: "abc", confirm: "abc" })).toBeNull();
expect(form.validateField("confirm", { password: "abc", confirm: "abd" })).toBe("No match");
expect(form.validateField("nickname", { nickname: "" })).toBeNull();
expect(other).not.toHaveBeenCalled();
const form = new FormValidator({
age: [{ test: (value) => value >= 18, message: "Too young" }],
name: [{ test: (value) => value !== "", message: "Enter your name" }],
});
expect(form.isValid({ age: 30, name: "Ada" })).toBe(true);
expect(form.isValid({ age: 12, name: "Ada" })).toBe(false);
expect(form.isValid({ age: 30, name: "" })).toBe(false);
Run tests runs every test on your code, here in your browser. The problem is solved when they all pass.
Ctrl + Enter runs the tests (⌘ + Enter on a Mac).
I'm here to help with Form validator. I give hints, never the solution, so you get to solve it yourself. Ask for a hint, select code to ask about it, or press Ask Tuto why on a failing test.
Your first 3 answers need no account. for free to keep asking.