Имитация пользовательского ввода

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

Архитектурная модель обработки ввода

Cleave.js работает поверх стандартного HTMLInputElement и подписывается на события:

  • keydown
  • input
  • focus
  • blur
  • paste

Основной принцип — перехват «сырого» значения и преобразование его в форматированную строку через внутренний механизм токенизации и маскирования.

Ключевой момент: библиотека оперирует не только DOM-значением, но и внутренним состоянием экземпляра, которое включает:

  • rawValue — неформатированное значение
  • value — форматированное отображение
  • позицию каретки (caret position)
  • применённые правила форматирования (numeral, date, phone и т.д.)

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


Программное присвоение значения и его ограничения

Простейшая попытка:

input.value = "79991234567";

не гарантирует корректное форматирование, потому что:

  • Cleave не всегда реагирует на прямое изменение value
  • внутренний обработчик не обновляет rawValue
  • каретка остаётся некорректной

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


Корректная имитация через события

Наиболее близкий к реальному пользовательскому вводу подход:

input.value = "7";
input.dispatchEvent(new Event("input", { bubbles: true }));

Далее поэтапное расширение:

input.value = "79";
input.dispatchEvent(new Event("input", { bubbles: true }));

input.value = "799";
input.dispatchEvent(new Event("input", { bubbles: true }));

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


Использование экземпляра Cleave для синхронизации состояния

При инициализации:

const cleave = new Cleave(input, {
  phone: true,
  phoneRegionCode: "RU"
});

Имитация ввода должна учитывать API экземпляра:

cleave.setRawValue("79991234567");

Метод setRawValue является предпочтительным, так как:

  • обновляет внутренний rawValue
  • триггерит перерасчёт форматирования
  • синхронизирует отображение

Однако важно учитывать, что в некоторых конфигурациях обновление DOM может происходить асинхронно.


Имитация ввода через paste-событие

Часто реальные пользователи вставляют значения, и Cleave.js обрабатывает paste отдельно:

const pasteEvent = new ClipboardEvent("paste", {
  clipboardData: new DataTransfer()
});

pasteEvent.clipboardData.setData("text", "79991234567");

input.dispatchEvent(pasteEvent);
input.dispatchEvent(new Event("input", { bubbles: true }));

Особенность:

  • paste может быть обработан до input
  • форматирование запускается после цепочки событий
  • некоторые браузеры блокируют ClipboardEvent в тестовой среде

Симуляция по символам (typing emulation)

Для максимального приближения к реальному вводу используется поэтапная симуляция:

function typeLikeUser(el, value) {
  el.value = "";

  for (let i = 0; i < value.length; i++) {
    el.value += value[i];
    el.dispatchEvent(new Event("input", { bubbles: true }));
  }
}

Этот метод важен при тестировании:

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

Cleave.js в этом режиме корректно пересчитывает формат на каждом шаге, включая вставку разделителей (дефисы, пробелы, слэши).


Работа с кареткой при имитации ввода

Одной из сложностей является позиция курсора. Cleave управляет caret position автоматически, но при программной имитации возможны ошибки.

Типичные проблемы:

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

Решение — принудительное обновление selection:

input.setSelectionRange(input.value.length, input.value.length);

Однако при работе с Cleave.js это не всегда необходимо, так как библиотека может переопределять caret после обработки.


Интеграция с тестовыми фреймворками

Cypress

cy.get("input")
  .type("79991234567");

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

Jest + JSDOM

В JSDOM необходимо вручную вызывать события:

input.value = "7999";
input.dispatchEvent(new Event("input", { bubbles: true }));

При этом Cleave.js может вести себя иначе, чем в браузере, из-за неполной реализации layout и selection API.


Программная синхронизация в React и Vue

React controlled input

При использовании controlled компонента:

const [value, setValue] = useState("");

Имитация должна менять state:

setValue("79991234567");

Но Cleave-обёртки часто требуют:

  • синхронизации rawValue
  • вызова обновления через ref

Иначе возникает конфликт между React state и внутренним состоянием Cleave.js.


Сброс и повторная инициализация состояния

В сложных сценариях имитации требуется полный reset:

cleave.destroy();
input.value = "";

После чего повторная инициализация:

const cleave = new Cleave(input, config);

Это применяется при:

  • смене типа маски (телефон → карта)
  • переключении регионов
  • динамических формах

Имитация частичного редактирования строки

Реальный пользователь редко вводит значение линейно. Часто происходит:

  • вставка в середину
  • удаление отдельных символов
  • замена сегментов

Пример:

input.value = "79991234567";
input.dispatchEvent(new Event("input", { bubbles: true }));

input.setSelectionRange(4, 4);
document.execCommand("insertText", false, "55");

Cleave.js в таких сценариях перерасчитывает формат, но поведение зависит от браузера и типа маски.


Асинхронные эффекты и задержки обновления

Некоторые конфигурации Cleave используют внутренние setTimeout или batching обновлений. Поэтому после имитации ввода может потребоваться ожидание:

await new Promise(r => setTimeout(r, 50));

Это критично при:

  • автотестах
  • проверке formatted value
  • чтении DOM после изменений

Контроль согласованности rawValue и value

Основная проблема имитации заключается в рассинхронизации:

  • value может быть форматирован
  • rawValue может оставаться старым
  • DOM и instance state расходятся

Рекомендуемый подход:

cleave.setRawValue("79991234567");

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


Поведение при отключённых событиях input

Некоторые кастомные реализации блокируют input-событие. В таком случае применяется цепочка:

input.value = "7999";
input.dispatchEvent(new Event("change", { bubbles: true }));
input.dispatchEvent(new Event("input", { bubbles: true }));

Cleave.js обычно реагирует именно на input, поэтому порядок событий критичен.


Особенности в мобильных браузерах

Мобильные WebKit-движки добавляют дополнительные ограничения:

  • delayed input events
  • autocorrect вмешательство
  • изменение порядка событий paste/input

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


Итоговые паттерны имитации

На практике используются три устойчивых стратегии:

  • Событийная симуляция: input + постепенное изменение value
  • API-уровень: setRawValue через экземпляр Cleave
  • Тестовая эмуляция: Cypress .type() или аналогичные инструменты

Каждая стратегия применима в зависимости от того, доступен ли экземпляр Cleave.js и насколько важна точность воспроизведения пользовательского поведения.