Инспекция состояния

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

Инспекция особенно важна в следующих сценариях:

  • отображение ошибок в UI;
  • проверка успешности формы перед отправкой;
  • отображение состояния загрузки;
  • асинхронная валидация;
  • условный рендеринг сообщений;
  • построение сложной логики интерфейса;
  • интеграция с React, Vue, Angular и другими фреймворками.

Результат выполнения suite

После запуска suite Vest возвращает объект состояния:

const result = validate(data);

Этот объект содержит API инспекции.

Пример:

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

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

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

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

Теперь объект result содержит информацию обо всех проверках.


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

hasErrors()

Метод hasErrors() проверяет наличие ошибок.

result.hasErrors();

Результат:

true

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

false

hasWarnings()

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

result.hasWarnings();

Пример:

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

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

isValid()

Проверяет общую валидность suite.

result.isValid();

Эквивалентно:

!result.hasErrors()

isValidByGroup()

Если используются группы:

result.isValidByGroup('shipping');

Пример:

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

const validate = create(data => {
  group('shipping', () => {
    test('city', 'Введите город', () => {
      enforce(data.city).isNotBlank();
    });
  });
});

Инспекция конкретных полей

hasErrors(fieldName)

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

result.hasErrors('email');

hasWarnings(fieldName)

result.hasWarnings('password');

isValid(fieldName)

result.isValid('email');

Получение сообщений об ошибках

getErrors()

Возвращает массив ошибок.

result.getErrors();

Результат:

[
  'Email обязателен',
  'Минимум 8 символов'
]

getErrors(fieldName)

result.getErrors('email');

Результат:

[
  'Email обязателен'
]

getWarnings()

result.getWarnings();

getWarnings(fieldName)

result.getWarnings('password');

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

Vest поддерживает асинхронную валидацию. Поэтому состояние выполнения тестов играет важную роль.


isPending()

Проверяет наличие незавершённых проверок.

result.isPending();

isPending(fieldName)

result.isPending('username');

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

import { create, test } from 'vest';

const validate = create(data => {
  test('username', 'Имя уже занято', async () => {
    const response = await fetch('/check-username');
    const json = await response.json();

    if (!json.available) {
      throw new Error();
    }
  });
});

Во время выполнения:

result.isPending('username');

вернёт:

true

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

isTested()

Позволяет определить, выполнялась ли проверка.

result.isTested('email');

Это особенно важно при пошаговых формах.


Пример

const result = validate({
  email: ''
});

result.isTested('email');

Результат:

true

А для поля, которое не проверялось:

result.isTested('password');

Результат:

false

Работа с предупреждениями

Vest разделяет ошибки и предупреждения.

Ошибки блокируют отправку формы, предупреждения — нет.


warn()

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

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

Проверка предупреждений

result.hasWarnings('password');

Получение текста:

result.getWarnings('password');

Группы состояний

Группы позволяют логически разделять форму.


Создание группы

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

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

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

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

Проверка группы

result.hasErrorsByGroup('profile');

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

result.getErrorsByGroup('profile');

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

result.isValidByGroup('security');

Полная инспекция suite

get()

Метод get() возвращает внутреннее состояние suite.

const state = result.get();

Структура содержит:

  • тесты;
  • предупреждения;
  • ошибки;
  • pending-состояния;
  • группы;
  • метаданные.

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

{
  tests: {
    email: {...},
    password: {...}
  },

  errorCount: 2,

  valid: false
}

Точная структура зависит от версии Vest.


Проверка количества ошибок

errorCount

const state = result.get();

console.log(state.errorCount);

warningCount

console.log(state.warningCount);

Частичная валидация и инспекция

Vest позволяет запускать только часть проверок.


only()

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

const validate = create(data => {
  only('email');

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

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

Инспекция после partial validation

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

Исключение полей из проверки

skip()

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

const validate = create(data => {
  skip('password');

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

Результат инспекции

result.isTested('password');

Результат:

false

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

Инспекция состояния особенно полезна в React-приложениях.


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

function EmailField({ result }) {
  const errors = result.getErrors('email');

  return (
    <div>
      {errors.map(error => (
        <div key={error}>
          {error}
        </div>
      ))}
    </div>
  );
}

Динамический UI на основе состояния

Отображение индикатора загрузки

{
  result.isPending('username') && (
    <Spinner />
  )
}

Блокировка кнопки отправки

<button disabled={!result.isValid()}>
  Отправить
</button>

Отображение предупреждений

{
  result.hasWarnings('password') && (
    <PasswordWarning />
  )
}

Проверка touched-состояния

Vest напрямую не хранит touched, однако состояние можно моделировать через isTested().


Пример

const showError =
  result.isTested('email') &&
  result.hasErrors('email');

Инспекция асинхронных сценариев

Асинхронные проверки требуют контроля состояния выполнения.


Комбинация pending и errors

if (result.isPending('email')) {
  console.log('Проверка...');
}

if (result.hasErrors('email')) {
  console.log(result.getErrors('email'));
}

Отладка состояния

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

console.log(result.get());

Анализ тестов

const state = result.get();

console.log(state.tests);

Паттерн централизованной инспекции

Крупные приложения часто создают слой абстракции.


Пример helper-функции

function fieldState(result, fieldName) {
  return {
    errors: result.getErrors(fieldName),
    warnings: result.getWarnings(fieldName),
    pending: result.isPending(fieldName),
    valid: result.isValid(fieldName),
    tested: result.isTested(fieldName)
  };
}

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

const emailState = fieldState(result, 'email');

Инспекция нескольких полей

Проверка группы полей

const fields = ['email', 'password'];

const hasErrors = fields.some(field =>
  result.hasErrors(field)
);

Построение универсального FormState

Пример агрегированного состояния

function createFormState(result) {
  return {
    valid: result.isValid(),
    pending: result.isPending(),
    errors: result.getErrors(),
    warnings: result.getWarnings()
  };
}

Инспекция в многошаговых формах

Проверка текущего шага

const step1Valid =
  result.isValid('email') &&
  result.isValid('password');

Проверка группы шага

result.isValidByGroup('step1');

Оптимизация инспекции

При больших формах частые вызовы методов инспекции могут создавать лишние вычисления.


Кэширование результатов

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

Вместо:

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

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

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

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

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

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

console.log(result.isValid());

console.log(result.hasErrors());

console.log(result.getErrors());

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

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

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

Архитектура инспекции в Vest

Система инспекции Vest строится вокруг нескольких ключевых принципов:

  • immutable state;
  • декларативная модель проверок;
  • независимость тестов;
  • разделение ошибок и предупреждений;
  • поддержка асинхронности;
  • группировка проверок;
  • ленивое вычисление состояния.

Это делает библиотеку пригодной для сложных enterprise-форм с большим количеством зависимых проверок и динамической логикой интерфейса.