Условная валидация

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

Условная валидация строится на идее, что правило не обязано выполняться всегда. Оно может:

  • активироваться только при выполнении определённого условия;
  • зависеть от значений других полей;
  • изменять свою строгость в зависимости от контекста;
  • полностью пропускаться, если данные не требуют проверки.

В Vest нет отдельного «магического» оператора для условий — вместо этого используется обычный JavaScript, что делает систему предсказуемой и расширяемой.

Использование значений формы как источника условий

Основной сценарий условной валидации связан с доступом к текущим данным формы внутри тестов.

import { create, test } from 'vest';

const validation = create((data = {}) => {
  test('email', 'Email обязателен', () => {
    if (!data.emailRequired) return;

    expect(data.email).toBeTruthy();
  });

  test('email', 'Некорректный email', () => {
    if (!data.email) return;

    expect(data.email).toMatch(/.+@.+\..+/);
  });
});

В этом примере логика проверки зависит от флага emailRequired. Если он выключен, правило полностью пропускается.

Ключевой момент заключается в том, что пропуск теста — это не ошибка, а штатное поведение, позволяющее строить динамические схемы.

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

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

const validation = create((data = {}) => {
  test('address', 'Адрес обязателен при доставке курьером', () => {
    if (data.deliveryType !== 'courier') return;

    expect(data.address).toBeTruthy();
  });
});

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

Динамическое включение и исключение полей

Условная валидация часто используется для полного исключения поля из проверки.

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

  expect(data.companyName).toBeTruthy();
});

Если пользователь выбирает «частное лицо», поле companyName перестаёт участвовать в валидации.

Важный аспект: исключение поля через return внутри теста означает, что поле не влияет на общий статус ошибки.

Зависимые поля и каскадные условия

Сложные формы часто содержат цепочки зависимостей:

  • поле A влияет на поле B;
  • поле B влияет на поле C.
test('vatNumber', 'Номер НДС обязателен для компаний из ЕС', () => {
  if (!data.isCompany) return;
  if (data.country !== 'EU') return;

  expect(data.vatNumber).toBeTruthy();
});

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

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

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

const validation = create((data = {}, ctx = {}) => {
  test('password', 'Слабый пароль', () => {
    if (!ctx.strictMode) return;

    expect(data.password).toMatch(/^(?=.*[A-Z])(?=.*\d).{8,}$/);
  });
});

Контекст используется для режимов:

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

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

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

const validation = create((data = {}) => {
  if (data.paymentMethod === 'card') {
    test('cardNumber', 'Некорректный номер карты', () => {
      expect(data.cardNumber).toMatch(/^\d{16}$/);
    });

    test('cvv', 'Некорректный CVV', () => {
      expect(data.cvv).toMatch(/^\d{3}$/);
    });
  }
});

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

Условная валидация через ранний выход

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

test('discountCode', 'Промокод недействителен', () => {
  if (!data.discountEnabled) return;

  expect(isValidCode(data.discountCode)).toBe(true);
});

Этот стиль делает логику линейной и легко читаемой, особенно при большом количестве условий.

Асинхронные условия

Условная логика также применяется в асинхронных тестах, например при проверке уникальности через API.

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

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

  expect(exists).toBe(false);
});

Здесь условие предотвращает лишний запрос, если поле пустое.

Переиспользуемые условия

При росте формы повторяющиеся условия можно выносить в функции:

const isCompanyUser = (data) => data.type === 'company';

test('companyId', 'ID компании обязателен', () => {
  if (!isCompanyUser(data)) return;

  expect(data.companyId).toBeTruthy();
});

Такой подход снижает дублирование и упрощает сопровождение.

Типичные ошибки при условной валидации

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

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

Логическая композиция условий

Условия могут комбинироваться для построения более сложных сценариев:

test('shippingDate', 'Некорректная дата доставки', () => {
  if (!data.deliveryType) return;
  if (data.deliveryType !== 'scheduled') return;
  if (!data.shippingDate) return;

  expect(new Date(data.shippingDate).getTime()).toBeGreaterThan(Date.now());
});

Такой подход позволяет постепенно «сужать» область применения проверки.

Управление сложностью через условия

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

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

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