Vest — библиотека декларативной валидации данных для JavaScript и
TypeScript, ориентированная на создание сложных сценариев проверки форм.
Основная идея Vest заключается в построении тестовых наборов
(suite), напоминающих структуру модульных тестов. Вместо
описания правил через конфигурационные объекты используется программный
подход с условиями, группировкой и управлением потоком выполнения.
Vest особенно удобна в следующих сценариях:
npm install vest
yarn add vest
pnpm add vest
npm list vest
yarn list vest
const vest = require('vest');
Современный вариант подключения:
import { create, test, enforce } from 'vest';
Наиболее часто используются:
| Функция | Назначение |
|---|---|
create |
создание набора проверок |
test |
объявление отдельного теста |
enforce |
встроенные правила проверки |
only |
запуск конкретного поля |
skip |
пропуск проверок |
omitWhen |
условное исключение |
warn |
предупреждения вместо ошибок |
project/
├── src/
│ ├── validation/
│ │ └── userValidation.js
│ ├── forms/
│ └── app.js
├── package.json
└── node_modules/
import { create, test, enforce } from 'vest';
export const userSuite = create((data = {}) => {
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
createФункция create формирует набор тестов — validation
suite.
const suite = create((data) => {
});
Аргументом выступает callback, внутри которого размещаются проверки.
При вызове:
suite(formData);
Vest:
const result = userSuite({
email: 'admin@mail.com',
password: '12345678'
});
result.hasErrors();
result.getErrors('email');
result.getErrors();
{
email: [
'Некорректный email'
],
password: [
'Минимум 8 символов'
]
}
testКаждая проверка описывается через test.
Сигнатура:
test(fieldName, message, callback);
test('username', 'Имя обязательно', () => {
enforce(data.username).isNotBlank();
});
| Параметр | Описание |
|---|---|
fieldName |
имя поля |
message |
текст ошибки |
callback |
логика проверки |
enforceenforce — встроенный механизм assertions.
enforce(value).longerThan(3);
enforce(age).greaterThanOrEquals(18);
enforce(name).isNotBlank();
enforce(items).isArray();
enforce(value).isString();
| Метод | Назначение |
|---|---|
isNotBlank() |
строка не пустая |
isString() |
строка |
isNumber() |
число |
isArray() |
массив |
isBoolean() |
boolean |
longerThan() |
длина больше |
shorterThan() |
длина меньше |
equals() |
равенство |
inside() |
наличие в массиве |
matches() |
регулярное выражение |
import { create, test, enforce } from 'vest';
export const registrationSuite = create((data = {}) => {
test('login', 'Логин обязателен', () => {
enforce(data.login).isNotBlank();
});
test('login', 'Минимум 4 символа', () => {
enforce(data.login).longerThanOrEquals(4);
});
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
Vest позволяет назначать несколько проверок одному полю.
test('password', 'Введите пароль', () => {
enforce(data.password).isNotBlank();
});
test('password', 'Минимум 8 символов', () => {
enforce(data.password).longerThanOrEquals(8);
});
test('password', 'Нужна цифра', () => {
enforce(data.password).matches(/\d/);
});
test('confirmPassword', 'Пароли не совпадают', () => {
enforce(data.confirmPassword).equals(data.password);
});
test('companyName', 'Введите название компании', () => {
if (!data.isBusiness) {
return;
}
enforce(data.companyName).isNotBlank();
});
test('middleName', 'Слишком короткое отчество', () => {
if (!data.middleName) {
return;
}
enforce(data.middleName).longerThan(2);
});
onlyonly запускает проверки только указанного поля.
import { only } from 'vest';
const suite = create((data, fieldName) => {
only(fieldName);
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
test('password', 'Пароль слишком короткий', () => {
enforce(data.password).longerThanOrEquals(8);
});
});
suite(formData, 'email');
Выполнятся только проверки email.
skipskip временно отключает проверки.
import { skip } from 'vest';
skip(
data.isAdmin,
() => {
test('role', 'Роль обязательна', () => {
enforce(data.role).isNotBlank();
});
}
);
omitWhenomitWhen полностью исключает проверки из выполнения.
import { omitWhen } from 'vest';
omitWhen(data.isGuest, () => {
test('phone', 'Телефон обязателен', () => {
enforce(data.phone).isNotBlank();
});
});
warnИногда требуется выводить предупреждения вместо ошибок.
import { warn } from 'vest';
test('password', 'Слабый пароль', () => {
warn();
enforce(data.password).matches(/[A-Z]/);
});
result.hasWarnings();
result.getWarnings();
Vest поддерживает async/await.
test(
'email',
'Email уже используется',
async () => {
const response = await fetch('/api/check-email');
const result = await response.json();
enforce(result.available).isTruthy();
}
);
const result = await registrationSuite(data);
test(
'username',
'Имя уже занято',
async () => {
try {
const response = await fetch('/api/check-username');
const result = await response.json();
enforce(result.available).isTruthy();
} catch (e) {
throw new Error('Ошибка проверки');
}
}
);
npm install vest vest-utils
import { useState } from 'react';
import { create, test, enforce, only } from 'vest';
const suite = create((data, fieldName) => {
only(fieldName);
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
});
function App() {
const [form, setForm] = useState({
email: ''
});
const [errors, setErrors] = useState({});
const validate = (fieldName) => {
const result = suite(form, fieldName);
setErrors(result.getErrors());
};
return (
<input
value={form.email}
onCha nge={(e) => {
setForm({
...form,
email: e.target.value
});
validate('email');
}}
/>
);
}
Vest содержит встроенную поддержку TypeScript.
Дополнительные пакеты обычно не требуются.
interface RegistrationForm {
email: string;
password: string;
}
import { create, test, enforce } from 'vest';
const suite = create((data: RegistrationForm) => {
test('email', 'Некорректный email', () => {
enforce(data.email).isString();
});
});
Крупные проекты обычно разделяют проверки по модулям.
validation/
├── auth/
│ ├── loginSuite.js
│ └── registrationSuite.js
├── profile/
│ └── profileSuite.js
└── shared/
└── rules.js
export const emailRule = (value) => {
enforce(value).matches(/^\S+@\S+\.\S+$/);
};
Использование:
import { emailRule } from './rules';
test('email', 'Некорректный email', () => {
emailRule(data.email);
});
Хорошая практика — хранить:
Нежелательно смешивать разные стили:
'Поле обязательно'
'Введите значение'
'Error'
Лучше придерживаться единого формата:
'Введите email'
'Введите пароль'
'Минимум 8 символов'
testПлохо:
test('email', 'Ошибка', () => {
if (
data.email &&
data.email.length > 5 &&
/^\S+@\S+\.\S+$/.test(data.email)
) {
}
});
Лучше:
test('email', 'Некорректный email', () => {
enforce(data.email).matches(/^\S+@\S+\.\S+$/);
});
onlyБез only при каждом изменении поля может запускаться вся
форма.
only(fieldName);
Нежелательно:
test('email', 'Ошибка', () => {
setState();
enforce(data.email).isNotBlank();
});
Vest должен отвечать только за проверки.
Плохо:
validation.js
на 2000 строк.
Лучше:
validation/
auth/
profile/
settings/
const result = suite(data);
console.log(result.getErrors());
console.log(result.getErrors('email'));
Для production-проектов обычно добавляют:
Vest хорошо сочетается с:
test('email validation', () => {
const result = suite({
email: ''
});
expect(result.hasErrors('email')).toBe(true);
});