Тестирование валидации

Библиотека Cleave.js предназначена для форматирования пользовательского ввода в режиме реального времени. Несмотря на то что основной задачей библиотеки является визуальное представление данных, а не полноценная проверка значений, корректное тестирование валидации становится критически важным этапом разработки.

Форматирование и валидация — разные процессы:

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

Пример:

new Cleave('#card', {
    creditCard: true
});

Поле автоматически разбивает номер карты на блоки:

4111 1111 1111 1111

Однако визуально корректный номер не означает:

  • существование карты;
  • прохождение алгоритма Луна;
  • принадлежность поддерживаемой платежной системе;
  • допустимость BIN;
  • отсутствие лишних символов.

Именно поэтому тестирование валидации должно охватывать:

  • форматирование;
  • очистку значения;
  • обработку пользовательского ввода;
  • синхронизацию rawValue;
  • взаимодействие с backend-валидацией;
  • edge-case сценарии.

Проверка formatted value и raw value

Одной из ключевых особенностей Cleave.js является наличие двух представлений значения:

Тип Описание
formatted value значение в поле
rawValue очищенное значение

Пример:

const cleave = new Cleave('#phone', {
    phone: true,
    phoneRegionCode: 'US'
});

Пользователь вводит:

(213) 373-4253

Внутреннее значение:

event.target.rawValue

содержит:

2133734253

Тестирование должно проверять оба значения одновременно.


Проверка formatted значения

test('formatted phone value', () => {
    input.value = '2133734253';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toBe('(213) 373-4253');
});

Проверка rawValue

test('raw phone value', () => {
    const event = {
        target: {
            rawValue: '2133734253'
        }
    };

    expect(event.target.rawValue).toBe('2133734253');
});

Тестирование кредитных карт

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

test('credit card formatting', () => {
    const cleave = new Cleave(input, {
        creditCard: true
    });

    input.value = '4111111111111111';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toBe('4111 1111 1111 1111');
});

Проверка удаления пробелов

test('raw card value', () => {
    const cleave = new Cleave(input, {
        creditCard: true
    });

    input.value = '4111 1111 1111 1111';

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue()).toBe('4111111111111111');
});

Проверка ограничения длины

test('max card length', () => {
    const cleave = new Cleave(input, {
        creditCard: true
    });

    input.value = '41111111111111112222';

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue().length).toBeLessThanOrEqual(19);
});

Тестирование телефонных номеров

Телефонные номера особенно сложны из-за:

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

Проверка регионального форматирования

test('kazakhstan phone format', () => {
    const cleave = new Cleave(input, {
        phone: true,
        phoneRegionCode: 'KZ'
    });

    input.value = '7771234567';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toContain('777');
});

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

test('phone raw value contains digits only', () => {
    const cleave = new Cleave(input, {
        phone: true,
        phoneRegionCode: 'US'
    });

    input.value = '(213) 373-4253';

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue()).toMatch(/^\d+$/);
});

Тестирование дат

Проверка автоматического разделения

test('date formatting', () => {
    const cleave = new Cleave(input, {
        date: true,
        datePattern: ['d', 'm', 'Y']
    });

    input.value = '25052026';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toBe('25/05/2026');
});

Проверка невозможной даты

Cleave.js не выполняет полноценную календарную валидацию.

Это означает, что строка:

39/19/2026

может быть успешно отформатирована.

Поэтому необходимы дополнительные тесты.

function isValidDate(value) {
    const [day, month, year] = value.split('/').map(Number);

    return (
        day >= 1 &&
        day <= 31 &&
        month >= 1 &&
        month <= 12
    );
}

test('invalid date validation', () => {
    expect(isValidDate('39/19/2026')).toBe(false);
});

Тестирование числового форматирования

Проверка разделителей тысяч

test('numeral formatting', () => {
    const cleave = new Cleave(input, {
        numeral: true,
        numeralThousandsGroupStyle: 'thousand'
    });

    input.value = '1000000';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toBe('1,000,000');
});

Проверка decimal mark

test('decimal mark formatting', () => {
    const cleave = new Cleave(input, {
        numeral: true,
        numeralDecimalMark: ','
    });

    input.value = '1234,56';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toContain(',');
});

Проверка отрицательных чисел

test('negative numbers support', () => {
    const cleave = new Cleave(input, {
        numeral: true,
        numeralPositiveOnly: false
    });

    input.value = '-1000';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toContain('-');
});

Проверка numeralPositiveOnly

test('positive only restriction', () => {
    const cleave = new Cleave(input, {
        numeral: true,
        numeralPositiveOnly: true
    });

    input.value = '-1000';

    input.dispatchEvent(new Event('input'));

    expect(input.value).not.toContain('-');
});

Тестирование кастомных блоков

Проверка блоков

test('custom blocks formatting', () => {
    const cleave = new Cleave(input, {
        blocks: [4, 4, 4],
        delimiter: '-'
    });

    input.value = '123456789012';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toBe('1234-5678-9012');
});

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

test('block overflow', () => {
    const cleave = new Cleave(input, {
        blocks: [2, 2]
    });

    input.value = '123456';

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue().length).toBeLessThanOrEqual(4);
});

Проверка paste-сценариев

Вставка текста часто ломает форматирование сильнее обычного ввода.


Проверка вставки карты

test('paste credit card', () => {
    const cleave = new Cleave(input, {
        creditCard: true
    });

    input.value = '4111111111111111';

    input.dispatchEvent(new Event('paste'));

    expect(input.value).toBe('4111 1111 1111 1111');
});

Проверка вставки мусорных символов

test('paste invalid symbols', () => {
    const cleave = new Cleave(input, {
        numeral: true
    });

    input.value = '12abc34';

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue()).toBe('1234');
});

Проверка удаления символов

Тестирование Backspace

test('backspace handling', () => {
    const cleave = new Cleave(input, {
        creditCard: true
    });

    input.value = '4111 1111';

    input.dispatchEvent(new KeyboardEvent('keydown', {
        key: 'Backspace'
    }));

    expect(input.value.length).toBeLessThanOrEqual(9);
});

Проверка cursor position

Некорректная работа курсора — одна из самых частых проблем масок ввода.


Проверка позиции курсора после delimiter

test('cursor position after delimiter', () => {
    const cleave = new Cleave(input, {
        blocks: [4, 4],
        delimiter: ' '
    });

    input.value = '12345';

    input.dispatchEvent(new Event('input'));

    expect(input.selectionStart).toBeGreaterThan(4);
});

Тестирование событий

Проверка onValueChanged

test('onValueChanged callback', () => {
    const handler = jest.fn();

    const cleave = new Cleave(input, {
        numeral: true,
        onValueChanged: handler
    });

    input.value = '1000';

    input.dispatchEvent(new Event('input'));

    expect(handler).toHaveBeenCalled();
});

Проверка rawValue внутри callback

test('rawValue in callback', () => {
    let raw = '';

    const cleave = new Cleave(input, {
        numeral: true,
        onValueChanged: (e) => {
            raw = e.target.rawValue;
        }
    });

    input.value = '1000';

    input.dispatchEvent(new Event('input'));

    expect(raw).toBe('1000');
});

Тестирование совместимости с form validation

Cleave.js часто используется вместе с:

  • HTML5 validation;
  • React Hook Form;
  • Formik;
  • Yup;
  • Zod;
  • VeeValidate.

Проверка required

test('required field validation', () => {
    input.required = true;

    input.value = '';

    expect(input.checkValidity()).toBe(false);
});

Проверка pattern

test('pattern validation', () => {
    input.pattern = '\\d{4}';

    input.value = '123';

    expect(input.checkValidity()).toBe(false);
});

Тестирование React-интеграции

Проверка controlled input

test('react controlled input', () => {
    render(<CardInput />);

    const input = screen.getByRole('textbox');

    fireEvent.change(input, {
        target: {
            value: '4111111111111111'
        }
    });

    expect(input.value).toContain(' ');
});

Проверка edge cases

Пустой ввод

test('empty input', () => {
    const cleave = new Cleave(input, {
        numeral: true
    });

    input.value = '';

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue()).toBe('');
});

Null-значения

test('null value handling', () => {
    expect(() => {
        input.value = null;
    }).not.toThrow();
});

Очень длинные строки

test('very long input', () => {
    const cleave = new Cleave(input, {
        numeral: true
    });

    input.value = '1'.repeat(10000);

    input.dispatchEvent(new Event('input'));

    expect(cleave.getRawValue().length)
        .toBeLessThanOrEqual(10000);
});

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

Большое количество форматирований может вызывать:

  • лаги ввода;
  • задержки курсора;
  • блокировки UI;
  • лишние re-render;
  • проблемы mobile-устройств.

Пример performance-теста

test('formatting performance', () => {
    const start = performance.now();

    for (let i = 0; i < 1000; i++) {
        input.value = '4111111111111111';

        input.dispatchEvent(new Event('input'));
    }

    const end = performance.now();

    expect(end - start).toBeLessThan(100);
});

Проверка memory leaks

Неправильное уничтожение экземпляров Cleave.js способно вызывать утечки памяти.


Проверка destroy

test('destroy instance', () => {
    const cleave = new Cleave(input, {
        numeral: true
    });

    cleave.destroy();

    expect(cleave.element).toBeNull();
});

Тестирование асинхронной валидации

Некоторые сценарии требуют проверки значения через API.

Пример:

  • проверка BIN карты;
  • проверка номера телефона;
  • проверка существования IBAN;
  • серверная антифрод-валидация.

Пример async validation

async function validateCard(card) {
    const response = await fetch('/validate-card', {
        method: 'POST',
        body: JSON.stringify({ card })
    });

    return response.json();
}

test('async card validation', async () => {
    const result = await validateCard(
        '4111111111111111'
    );

    expect(result.valid).toBe(true);
});

Snapshot-тестирование

Snapshot-тесты помогают фиксировать состояние поля после форматирования.

test('snapshot formatted input', () => {
    const cleave = new Cleave(input, {
        creditCard: true
    });

    input.value = '4111111111111111';

    input.dispatchEvent(new Event('input'));

    expect(input.value).toMatchSnapshot();
});

Стратегии тестирования

Unit-тесты

Проверяют:

  • форматирование;
  • очистку;
  • callbacks;
  • delimiters;
  • rawValue.

Integration-тесты

Проверяют:

  • формы;
  • API;
  • state management;
  • взаимодействие с UI.

E2E-тесты

Проверяют:

  • реальный ввод;
  • мобильные устройства;
  • вставку;
  • курсор;
  • автозаполнение браузера.

Типичные ошибки тестирования

Проверка только formatted value

Ошибка:

expect(input.value).toBe('4111 1111 1111 1111');

Без проверки:

cleave.getRawValue()

Тест становится неполным.


Игнорирование paste-событий

Paste-сценарии часто работают иначе, чем обычный input.


Отсутствие тестов курсора

Форматирование может быть корректным, но UX — полностью сломанным.


Игнорирование мобильных устройств

Мобильные клавиатуры:

  • иначе обрабатывают input;
  • могут отправлять composition events;
  • меняют поведение курсора;
  • влияют на selectionStart.

Проверка IME и composition events

Особенно важно для:

  • японского;
  • китайского;
  • корейского ввода.

Пример composition test

test('composition input', () => {
    input.dispatchEvent(
        new CompositionEvent('compositionstart')
    );

    input.value = '1234';

    input.dispatchEvent(
        new CompositionEvent('compositionend')
    );

    expect(input.value).toContain('1234');
});

Изоляция логики форматирования

Хорошей практикой является вынесение валидации отдельно от Cleave.js.


Плохой подход

if (input.value.length === 19)

Хороший подход

if (cleave.getRawValue().length === 16)

Mocking Cleave.js

Иногда библиотеку необходимо мокировать.

jest.mock('cleave.js', () => {
    return jest.fn().mockImplementation(() => ({
        getRawValue: () => '4111111111111111',
        destroy: jest.fn()
    }));
});

Проверка debounce и throttling

Форматирование часто комбинируется с debounce-валидацией.

test('debounced validation', () => {
    jest.useFakeTimers();

    const validate = jest.fn();

    input.addEventListener(
        'input',
        debounce(validate, 300)
    );

    input.dispatchEvent(new Event('input'));

    jest.advanceTimersByTime(300);

    expect(validate).toHaveBeenCalled();
});