Результаты валидации

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

Каждый запуск suite возвращает объект результата (SuiteResult), через который происходит доступ ко всем сведениям о валидации.

Базовый пример:

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

const suite = vest.create((data = {}) => {
  test('email', 'Некорректный email', () => {
    enforce(data.email).isEmail();
  });

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

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

console.log(result);

Объект result содержит внутреннее состояние проверки и предоставляет API для анализа результатов.


Получение ошибок

Метод getErrors

Наиболее распространённый способ получения ошибок — метод getErrors.

const errors = result.getErrors();

console.log(errors);

Результат:

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

Каждое поле содержит массив сообщений.


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

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

console.log(emailErrors);

Результат:

['Некорректный email']

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

[]

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

Метод hasErrors

result.hasErrors();

Результат:

true

Проверка конкретного поля:

result.hasErrors('email');

Практический сценарий

if (result.hasErrors()) {
  console.log('Форма содержит ошибки');
}

Для поля:

if (result.hasErrors('password')) {
  showPasswordError();
}

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

Метод isValid

isValid противоположен hasErrors.

result.isValid();

Если все проверки прошли успешно:

true

Проверка отдельного поля:

result.isValid('email');

Сравнение hasErrors и isValid

Метод Возвращает true, если
hasErrors() Есть ошибки
isValid() Ошибок нет

Пример:

if (result.isValid()) {
  submitForm();
}

Предупреждения

Vest поддерживает предупреждения (warnings) отдельно от ошибок.


Создание warning

import vest, { test, warn } from 'vest';

const suite = vest.create((data = {}) => {
  test('password', 'Слишком простой пароль', () => {
    warn();

    if (data.password === '12345678') {
      throw new Error();
    }
  });
});

Получение предупреждений

const result = suite({
  password: '12345678'
});

console.log(result.getWarnings());

Результат:

{
  password: ['Слишком простой пароль']
}

Проверка warning

result.hasWarnings();

Для конкретного поля:

result.hasWarnings('password');

Проверка прохождения тестов

Метод getTestCount

Позволяет узнать количество тестов.

result.getTestCount();

Пример результата:

4

Количество ошибок

result.errorCount;

Пример:

console.log(result.errorCount);

Количество warning

result.warnCount;

Проверка протестированных полей

Метод tested

Vest отслеживает поля, участвовавшие в проверке.

result.tested('email');

Результат:

true

Практическое применение

Особенно полезно при динамической валидации форм.

if (result.tested('email')) {
  renderEmailState();
}

Проверка незавершённых тестов

Асинхронная валидация

Vest умеет отслеживать состояние асинхронных тестов.

import vest, { test } from 'vest';

const suite = vest.create((data = {}) => {
  test('email', 'Email уже используется', async () => {
    const exists = await checkEmail(data.email);

    if (exists) {
      throw new Error();
    }
  });
});

Метод isPending

Проверка наличия активных асинхронных тестов:

result.isPending();

Для конкретного поля:

result.isPending('email');

Сценарий использования

if (result.isPending()) {
  showLoader();
}

Получение статуса поля

Vest предоставляет агрегированное состояние поля.


Метод get

const state = result.get();

console.log(state);

Пример структуры:

{
  valid: false,
  pending: false,
  tested: true,
  errors: [...],
  warnings: [...]
}

Состояние конкретного поля

const emailState = result.get('email');

Пример:

{
  valid: false,
  pending: false,
  tested: true,
  errors: ['Некорректный email'],
  warnings: []
}

Глобальное состояние формы

Метод done

Показывает, завершены ли все асинхронные проверки.

result.done();

Если всё завершено:

true

Отличие done от isPending

Метод Назначение
isPending() Есть незавершённые тесты
done() Все проверки завершены

Группировка результатов

Vest позволяет организовывать тесты по группам.


Использование group

import vest, { test, group } from 'vest';

const suite = vest.create(data => {
  group('auth', () => {
    test('email', 'Некорректный email', () => {});
    test('password', 'Слабый пароль', () => {});
  });
});

Получение результатов группы

result.getErrorsByGroup('auth');

Результат:

{
  email: ['Некорректный email'],
  password: ['Слабый пароль']
}

Работа с omit и skip

Vest может пропускать тесты.


Пропуск теста через skip

import vest, { test, skip } from 'vest';

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

Как отражается в результатах

Пропущенные тесты:

  • не создают ошибок;
  • не считаются pending;
  • не входят в статистику.

Частичная валидация и результаты

Валидация одного поля

suite(data, 'email');

Теперь будут выполняться только тесты, связанные с email.


Поведение результатов

result.tested('email'); // true
result.tested('password'); // false

Сериализация результатов

В крупных приложениях состояние валидации иногда сохраняется.


Метод toJSON

const json = result.toJSON();

Результат:

{
  valid: false,
  errors: {
    email: ['Некорректный email']
  }
}

Формирование UI на основе результатов

Отображение ошибок

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

errors.forEach(error => {
  renderError(error);
});

CSS-классы состояния

const className = result.hasErrors('email')
  ? 'input-error'
  : 'input-success';

Индикатор загрузки

const loading = result.isPending('email');

Работа с несколькими сообщениями

Одно поле может содержать несколько ошибок.

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

test('password', 'Требуется спецсимвол', () => {
  enforce(data.password).matches(/[!@#$%^&*]/);
});

Результат

result.getErrors('password');

Вернёт:

[
  'Минимум 8 символов',
  'Требуется спецсимвол'
]

Поведение при исключениях

Vest интерпретирует выброшенное исключение как провал теста.

test('username', 'Имя занято', async () => {
  const exists = await apiCheck();

  if (exists) {
    throw new Error();
  }
});

Проверка успешных тестов

Метод hasErrors как индикатор успеха

const success = !result.hasErrors('email');

Получение полностью валидного состояния

const formIsReady =
  result.isValid() &&
  result.done();

Интеграция с React

Формирование состояния компонента

const validation = suite(formData);

setState({
  errors: validation.getErrors(),
  valid: validation.isValid(),
  pending: validation.isPending()
});

Условный рендеринг

{
  validation.hasErrors('email') && (
    <div>
      {validation.getErrors('email')[0]}
    </div>
  )
}

Интеграция с Redux

Результаты Vest хорошо сериализуются и подходят для хранения в глобальном состоянии.

dispatch({
  type: 'SET_VALIDATION',
  payload: result.toJSON()
});

Производительность результатов

Vest использует:

  • кеширование;
  • инкрементальную обработку;
  • повторное использование состояния;
  • изоляцию тестов.

Результаты формируются без полного пересчёта всех тестов при каждом изменении данных.


Отладка результатов

Вывод полного состояния

console.log(result.get());

Анализ конкретного поля

console.log(result.get('password'));

Частые ошибки при работе с результатами

Проверка getErrors вместо hasErrors

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

if (result.getErrors('email')) {
}

Поскольку массив всегда truthy.

Правильно:

if (result.hasErrors('email')) {
}

Игнорирование pending-состояния

Ошибка:

if (result.isValid()) {
  submit();
}

При асинхронных тестах форма может отправиться раньше завершения проверки.

Правильно:

if (result.isValid() && result.done()) {
  submit();
}

Полный пример анализа результатов

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

const suite = vest.create(data => {
  test('email', 'Некорректный email', () => {
    enforce(data.email).isEmail();
  });

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

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

console.log(result.isValid());

console.log(result.getErrors());

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

console.log(result.hasErrors('password'));

Результат:

false

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

{
  valid: false,
  pending: false,
  tested: true,
  errors: ['Некорректный email'],
  warnings: []
}

true