Условные правила на основе других полей

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

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


Базовый принцип зависимости полей

В Vest нет отдельного DSL для условных правил. Вместо этого используется обычная логика JavaScript внутри тестов:

  • чтение всех значений формы через входные данные suite
  • выполнение условий if
  • выборочная активация тестов
  • пропуск проверки при несоответствии контекста

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

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

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

  test('phone', 'Телефон обязателен при выборе SMS', () => {
    if (data.contactMethod === 'sms') {
      enforce(data.phone).isNotEmpty();
    }
  });
});

Здесь второе правило зависит от значения contactMethod. Если выбран email-канал, тест фактически становится неактивным.


Использование входных данных как источника истины

Все условные зависимости в Vest строятся вокруг объекта данных, передаваемого в suite. Он играет роль единого источника состояния формы.

Типичная структура:

  • data.fieldName — текущее значение поля
  • дополнительные вычисляемые признаки (например, режим формы)
  • вложенные структуры (адрес, платежные данные)
test('state', 'Штат обязателен для США', () => {
  if (data.country === 'US') {
    enforce(data.state).isNotEmpty();
  }
});

Такой подход позволяет не размазывать логику по UI-слою: правила остаются централизованными.


Отложенная активация тестов

Условные проверки в Vest не обязаны выполняться всегда. Логика может полностью исключить тест из фактической валидации.

test('companyName', 'Название компании обязательно для бизнеса', () => {
  if (data.accountType !== 'business') return;

  enforce(data.companyName).isNotEmpty();
});

Если условие не выполняется, тест завершится без ошибок и не повлияет на результат suite.

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


Валидация взаимосвязанных полей

Частый сценарий — взаимозависимость нескольких полей одновременно. Например, пароль и подтверждение пароля.

test('passwordConfirmation', 'Пароли должны совпадать', () => {
  if (!data.password || !data.passwordConfirmation) return;

  enforce(data.passwordConfirmation).equals(data.password);
});

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


Условная обязательность полей

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

test('vatNumber', 'Номер НДС обязателен для компаний в ЕС', () => {
  const isEUCountry = ['DE', 'FR', 'IT', 'ES'].includes(data.country);

  if (isEUCountry && data.accountType === 'business') {
    enforce(data.vatNumber).isNotEmpty();
  }
});

Здесь сразу две зависимости:

  • географическая
  • тип аккаунта

Такая композиция легко расширяется без изменения архитектуры suite.


Использование ранних выходов для упрощения логики

Вместо вложенных условий часто применяется ранний выход. Это делает код теста более читаемым.

test('zipCode', 'Почтовый индекс обязателен', () => {
  if (data.country !== 'US') return;

  enforce(data.zipCode).isNotEmpty();
});

При большом количестве условий такой стиль уменьшает вложенность и упрощает поддержку.


Комплексные зависимости между группами полей

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

test('deliveryAddress', 'Адрес доставки обязателен', () => {
  const needsDeliveryAddress =
    data.deliveryType === 'courier' ||
    (data.deliveryType === 'pickup' && data.insurance === true);

  if (!needsDeliveryAddress) return;

  enforce(data.deliveryAddress).isNotEmpty();
});

Здесь логика выходит за рамки одного поля и становится бизнес-правилом, описанным прямо внутри теста.


Использование вычисляемых флагов

При усложнении логики часто выделяют промежуточные переменные, чтобы не перегружать тесты условиями.

test('discountCode', 'Промокод доступен только для премиум', () => {
  const isPremium = data.plan === 'premium';
  const isPromoRequired = data.campaign === 'summer_sale';

  if (!(isPremium && isPromoRequired)) return;

  enforce(data.discountCode).isNotEmpty();
});

Такой подход позволяет описывать правила декларативно, сохраняя при этом JavaScript-выразительность.


Взаимосвязь с асинхронной логикой

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

test('username', 'Имя пользователя занято', async () => {
  if (!data.username) return;

  const isTaken = await api.checkUsername(data.username);

  enforce(isTaken).equals(false);
});

Здесь условие не отключает тест, но защищает от лишнего запроса при пустом значении.


Сложные сценарии с множественными режимами формы

Формы часто имеют режимы: создание, редактирование, просмотр. Условные правила позволяют адаптировать поведение без дублирования suite.

test('email', 'Email нельзя изменить после создания', () => {
  if (data.mode !== 'edit') return;
  if (!data.originalEmail) return;

  enforce(data.email).equals(data.originalEmail);
});

Таким образом, одна и та же форма обслуживает разные бизнес-сценарии.


Условные группы логики

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

const isBillingRequired = data.hasSubscription;
const isShippingRequired = data.isPhysicalProduct;

test('billingAddress', 'Требуется адрес оплаты', () => {
  if (!isBillingRequired) return;
  enforce(data.billingAddress).isNotEmpty();
});

test('shippingAddress', 'Требуется адрес доставки', () => {
  if (!isShippingRequired) return;
  enforce(data.shippingAddress).isNotEmpty();
});

Такой подход делает правила более читаемыми и управляемыми.


Минимизация побочных эффектов в условной логике

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

Корректный подход:

  • чтение данных
  • вычисление условий
  • выполнение enforce

Некорректный подход:

  • изменение data внутри теста
  • скрытые внешние зависимости
  • побочные вызовы, влияющие на другие тесты

Композиция условий через переиспользуемые функции

При повторяющихся паттернах условную логику выносят в функции.

const isBusinessAccount = (data) => data.accountType === 'business';

test('companyId', 'Требуется идентификатор компании', () => {
  if (!isBusinessAccount(data)) return;

  enforce(data.companyId).isNotEmpty();
});

Это снижает дублирование и упрощает масштабирование правил.


Контекстная адаптация правил

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

test('promoCode', 'Промокод недоступен в текущем регионе', () => {
  const isPromoAllowed = data.region === 'EU' && data.platform !== 'mobile';

  if (!isPromoAllowed) return;

  enforce(data.promoCode).isNotEmpty();
});

Такая модель делает правила ближе к предметной области, чем к UI-реализации.


Условные правила как слой бизнес-логики

При грамотной организации suite условные тесты начинают выполнять роль бизнес-логики формы. Они описывают не только “валидно/невалидно”, но и “когда применимо”.

Это позволяет:

  • централизовать правила
  • уменьшить зависимость от интерфейса
  • упростить сопровождение сложных форм
  • переиспользовать логику между приложениями

При этом сохраняется простота: всё выражается через обычный JavaScript и тестовые конструкции Vest.