Первый тест валидации

Библиотека Vest строится вокруг концепции декларативных сценариев валидации. Вместо ручного управления массивами ошибок и множеством if-проверок используется единый набор тестов, объединённых в так называемый suite — набор правил проверки формы.

Установка через npm:

npm install vest

Или через yarn:

yarn add vest

Минимальный пример структуры:

import { create, test, enforce } from 'vest';

const suite = create((data = {}) => {
  test('username', 'Имя пользователя обязательно', () => {
    enforce(data.username).isNotBlank();
  });
});

Основные элементы:

Элемент Назначение
create Создаёт набор тестов
test Описывает отдельное правило
enforce Выполняет проверку значения
suite Функция запуска валидации

Создание первого теста

Простейшая проверка поля:

import { create, test, enforce } from 'vest';

const suite = create((data = {}) => {
  test('email', 'Поле email обязательно', () => {
    enforce(data.email).isNotBlank();
  });
});

Запуск проверки:

const result = suite({
  email: '',
});

Проверка наличия ошибок:

console.log(result.hasErrors()); // true

Получение ошибок поля:

console.log(result.getErrors('email'));

Результат:

['Поле email обязательно']

Как работает test

Функция test принимает три аргумента:

test(fieldName, errorMessage, callback);

Имя поля

test('password', ...);

Используется как идентификатор.

Сообщение ошибки

'Пароль слишком короткий'

Отображается при провале проверки.

Callback-функция

Содержит саму логику проверки:

() => {
  enforce(value).longerThan(5);
}

Проверка обязательного поля

Самая распространённая валидация:

test('login', 'Введите логин', () => {
  enforce(data.login).isNotBlank();
});

Проверка не пропускает:

  • пустую строку;
  • null;
  • undefined;
  • строку из пробелов.

Пример:

suite({
  login: '   ',
});

Результат:

result.hasErrors(); // true

Проверка длины строки

Проверка минимальной длины:

test('password', 'Минимум 8 символов', () => {
  enforce(data.password).longerThanOrEquals(8);
});

Проверка максимальной длины:

test('nickname', 'Максимум 20 символов', () => {
  enforce(data.nickname).shorterThanOrEquals(20);
});

Комбинация нескольких ограничений:

test('username', 'Логин слишком короткий', () => {
  enforce(data.username).longerThanOrEquals(3);
});

test('username', 'Логин слишком длинный', () => {
  enforce(data.username).shorterThanOrEquals(15);
});

Несколько тестов для одного поля

Vest поддерживает множественные проверки:

import { create, test, enforce } from 'vest';

const suite = create((data = {}) => {
  test('email', 'Email обязателен', () => {
    enforce(data.email).isNotBlank();
  });

  test('email', 'Некорректный email', () => {
    enforce(data.email).matches(/^\S+@\S+\.\S+$/);
  });
});

Результаты:

const result = suite({
  email: '',
});

console.log(result.getErrors('email'));

Вывод:

[
  'Email обязателен',
  'Некорректный email'
]

Проверка регулярным выражением

Метод matches позволяет использовать регулярные выражения.

Проверка телефона:

test('phone', 'Некорректный номер', () => {
  enforce(data.phone).matches(/^\+7\d{10}$/);
});

Проверка пароля:

test('password', 'Пароль должен содержать цифру', () => {
  enforce(data.password).matches(/\d/);
});

Проверка числовых значений

Vest умеет валидировать числа:

test('age', 'Возраст должен быть больше 18', () => {
  enforce(data.age).greaterThan(18);
});

Проверка диапазона:

test('rating', 'Рейтинг должен быть от 1 до 5', () => {
  enforce(data.rating).insideRange(1, 5);
});

Использование обычного JavaScript внутри теста

Внутри callback допускается любая логика:

test('password', 'Пароли не совпадают', () => {
  if (data.password !== data.confirmPassword) {
    throw new Error();
  }
});

Однако предпочтительнее использовать enforce, поскольку ошибки обрабатываются более предсказуемо.

Эквивалент через enforce:

test('password', 'Пароли не совпадают', () => {
  enforce(data.password).equals(data.confirmPassword);
});

Объект результата проверки

После запуска suite возвращается объект result.

Пример:

const result = suite(formData);

Основные методы:

Есть ли ошибки

result.hasErrors();

Ошибки конкретного поля

result.getErrors('email');

Есть ли ошибки у поля

result.hasErrors('email');

Все ошибки

result.getErrors();

Полный пример первой валидации

import { create, test, enforce } from 'vest';

const suite = create((data = {}) => {

  test('email', 'Введите email', () => {
    enforce(data.email).isNotBlank();
  });

  test('email', 'Некорректный email', () => {
    enforce(data.email).matches(/^\S+@\S+\.\S+$/);
  });

  test('password', 'Введите пароль', () => {
    enforce(data.password).isNotBlank();
  });

  test('password', 'Минимум 8 символов', () => {
    enforce(data.password).longerThanOrEquals(8);
  });

});

const result = suite({
  email: 'wrong_email',
  password: '123',
});

console.log(result.getErrors());

Результат:

{
  email: ['Некорректный email'],
  password: ['Минимум 8 символов']
}

Поведение при успешной проверке

Если ошибок нет:

const result = suite({
  email: 'admin@test.com',
  password: '12345678',
});

Тогда:

result.hasErrors(); // false

Ошибки поля:

result.getErrors('email');

Результат:

[]

Проверка нескольких полей одновременно

Vest запускает все тесты последовательно:

const suite = create((data = {}) => {

  test('firstName', 'Введите имя', () => {
    enforce(data.firstName).isNotBlank();
  });

  test('lastName', 'Введите фамилию', () => {
    enforce(data.lastName).isNotBlank();
  });

  test('age', 'Возраст должен быть больше 18', () => {
    enforce(data.age).greaterThan(18);
  });

});

Запуск:

const result = suite({
  firstName: '',
  lastName: '',
  age: 16,
});

Ошибки:

{
  firstName: ['Введите имя'],
  lastName: ['Введите фамилию'],
  age: ['Возраст должен быть больше 18']
}

Использование enforce как fluent API

Методы enforce можно объединять:

test('username', 'Некорректный логин', () => {
  enforce(data.username)
    .isNotBlank()
    .longerThanOrEquals(3)
    .shorterThanOrEquals(20);
});

Такой подход делает правила компактнее и легче для чтения.


Отличие Vest от классической валидации

Традиционный подход:

if (!email) {
  errors.email = 'Введите email';
}

Подход Vest:

test('email', 'Введите email', () => {
  enforce(data.email).isNotBlank();
});

Преимущества:

  • декларативное описание правил;
  • группировка тестов;
  • переиспользование suite;
  • удобная интеграция с формами;
  • независимость от UI-фреймворков.

Типичный сценарий использования

Наиболее распространённая схема:

const result = suite(formData);

if (result.hasErrors()) {
  // показать ошибки
} else {
  // отправить форму
}

Получение ошибок конкретного поля:

const emailErrors = result.getErrors('email');

Проверка используется в:

  • React;
  • Vue;
  • Angular;
  • Node.js;
  • обычном JavaScript.

Частые ошибки при первом тесте

Отсутствие запуска suite

Неправильно:

suite;

Правильно:

suite(data);

Ошибка в названии поля

Неправильно:

test('emial', ...);

Правильно:

test('email', ...);

Отсутствие enforce

Неправильно:

data.email.isNotBlank();

Правильно:

enforce(data.email).isNotBlank();

Проверка undefined

Если поле отсутствует:

suite({});

Тогда:

enforce(data.email).isNotBlank();

корректно обработает отсутствие значения и вернёт ошибку.


Минимальная рабочая схема

import { create, test, enforce } from 'vest';

const suite = create((data = {}) => {

  test('email', 'Введите email', () => {
    enforce(data.email).isNotBlank();
  });

});

const result = suite({
  email: '',
});

console.log(result.hasErrors());
console.log(result.getErrors());

Такая структура является базой для любой дальнейшей валидации в Vest: сложных форм, асинхронных проверок, условной логики и интеграции с интерфейсами приложений.