Тестирование пользовательского ввода

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

Ключевой объект тестирования — HTMLInputElement, к которому применена маска через Inputmask().mask(element). Любое изменение value проходит через цепочку обработки, включающую нормализацию, проверку по паттерну и перерасчёт отображаемого значения.


Архитектура тестирования маски

Тестирование Inputmask условно разделяется на три уровня:

  1. Модульное тестирование логики маски

    • проверка преобразования входных строк
    • валидация шаблонов
    • работа alias-конфигураций
  2. Интеграционное тестирование DOM

    • работа с реальными input-событиями
    • реакция на paste, keydown, input
    • корректность отображения masked value
  3. E2E тестирование пользовательского сценария

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

Тестирование базовой установки маски

Простейший сценарий проверяет, что маска корректно инициализируется и форматирует ввод.

import Inputmask from "inputmask";

test("инициализация маски для телефона", () => {
  document.body.innerHTML = `<input id="phone" />`;

  const input = document.querySelector("#phone");

  Inputmask("+7 (999) 999-99-99").mask(input);

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

  expect(input.value).toBe("+7 (912) 345-67-89");
});

Основной аспект проверки — реакция на событие input, поскольку именно оно запускает перерасчёт маски.


Симуляция клавиатурного ввода

Inputmask активно использует keydown и keypress для контроля допустимых символов. Тестирование требует ручной генерации событий:

function triggerKeyDown(el, key) {
  const event = new KeyboardEvent("keydown", {
    key,
    bubbles: true
  });
  el.dispatchEvent(event);
}

test("разрешение только цифр", () => {
  document.body.innerHTML = `<input id="num" />`;

  const input = document.querySelector("#num");
  Inputmask("9999").mask(input);

  triggerKeyDown(input, "A");
  input.value = "A";

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

  expect(input.value).toBe("");
});

Здесь проверяется фильтрация недопустимых символов до их попадания в буфер маски.


Проверка вставки (paste events)

Вставка данных является отдельным каналом ввода, который часто обходит клавиатурную фильтрацию. Inputmask обрабатывает paste через перехват clipboardData.

test("обработка вставки номера", () => {
  document.body.innerHTML = `<input id="phone" />`;

  const input = document.querySelector("#phone");
  Inputmask("(999) 999-9999").mask(input);

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

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

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

  expect(input.value).toBe("(800) 555-1234");
});

Особенность тестирования paste заключается в необходимости эмуляции ClipboardEvent, который в разных тестовых окружениях может иметь ограничения.


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

Маска должна корректно реагировать на удаление символов, сохраняя структуру.

test("удаление символов сохраняет формат", () => {
  document.body.innerHTML = `<input id="date" />`;

  const input = document.querySelector("#date");
  Inputmask("99.99.9999").mask(input);

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

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

  expect(input.value).toBe("12.03.202");
});

В реальной среде также учитывается позиция каретки, которая влияет на поведение удаления.


Тестирование позиции каретки (caret position)

Одним из наиболее сложных аспектов является контроль caret position после каждого ввода.

test("позиция каретки после ввода", () => {
  document.body.innerHTML = `<input id="phone" />`;

  const input = document.querySelector("#phone");
  Inputmask("999-999").mask(input);

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

  expect(input.selectionStart).toBeGreaterThanOrEqual(3);
});

Caret тестирование требует учёта браузерных различий, особенно в Chromium и WebKit.


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

В unit-тестах чаще всего используется связка Jest + JSDOM. Однако JSDOM не полностью эмулирует поведение input-событий, поэтому часть тестов требует дополнительных утилит.

Типичная конфигурация тестового окружения:

import "jest-environment-jsdom";
import Inputmask from "inputmask";

Проблемные зоны JSDOM:

  • неполная поддержка InputEvent
  • ограниченная работа selectionStart
  • упрощённая модель событий клавиатуры

Для обхода используются ручные триггеры событий и фикстуры DOM.


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

Inputmask позволяет менять конфигурацию на лету, что требует проверки реактивности.

test("смена маски динамически", () => {
  document.body.innerHTML = `<input id="field" />`;

  const input = document.querySelector("#field");

  const im = Inputmask("9999");
  im.mask(input);

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

  im.setOptions({ mask: "999-9999" });

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

  expect(input.value).toBe("123-4567");
});

Ключевой момент — корректная переинициализация внутренних буферов без разрушения DOM-состояния.


Проверка alias-конфигураций

Alias-режимы (phone, email, numeric) требуют отдельного слоя тестирования, так как включают предустановленные правила.

test("alias numeric", () => {
  document.body.innerHTML = `<input id="num" />`;

  const input = document.querySelector("#num");

  Inputmask({
    alias: "numeric",
    rightAlign: false
  }).mask(input);

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

  expect(input.value).toBe("123456");
});

Здесь проверяется фильтрация нечисловых символов и нормализация строки.


E2E тестирование с Cypress

Для полного контроля пользовательского сценария используется Cypress, который корректно эмулирует реальные браузерные события.

describe("Inputmask E2E", () => {
  it("ввод телефона", () => {
    cy.visit("/form");

    cy.get("input#phone")
      .type("9123456789")
      .should("have.value", "+7 (912) 345-67-89");
  });
});

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


Тестирование edge cases

Особое внимание требуется нестандартным ситуациям:

  • вставка частично валидных строк
  • ввод Unicode-символов
  • быстрое чередование input/backspace
  • потеря фокуса во время ввода

Пример:

test("unicode ввод игнорируется", () => {
  document.body.innerHTML = `<input id="field" />`;

  const input = document.querySelector("#field");
  Inputmask("9999").mask(input);

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

  expect(input.value).toBe("1234");
});

Диагностика тестовых провалов

При нестабильных тестах анализируется:

  • последовательность DOM-событий
  • состояние internal buffer Inputmask
  • значение isComplete
  • позиция каретки до и после обработки

Дополнительно используются console-hooks внутри Inputmask для логирования промежуточных состояний.


Стратегия стабильных тестов

Стабильность достигается через:

  • фиксированные события вместо реального набора текста
  • контроль значения через value setter, а не user typing simulation
  • изоляцию каждого теста с пересозданием DOM
  • отключение анимаций и debounce-логики при тестировании

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