Юнит-тестирование масок ввода в контексте Inputmask требует понимания того, что сама маска представляет собой слой трансформации между «сырыми» пользовательскими данными и нормализованным значением, которое хранится в модели данных. В отличие от классической логики функций, маска затрагивает события DOM, форматирование строки, позиционирование курсора и частично поведение браузера, поэтому тестирование должно учитывать не только результат, но и промежуточные состояния.
Основная сложность тестирования Inputmask заключается в том, что маска работает на нескольких уровнях:
Юнит-тесты должны разделять эти уровни, иначе тесты становятся нестабильными и зависят от DOM-реализации.
Ключевой принцип: тестировать не библиотеку целиком, а поведение маски как чистую функцию, когда это возможно.
Для тестирования удобно выделять «чистую» часть логики Inputmask — преобразование входной строки в форматированную строку:
import Inputmask from "inputmask";
const mask = new Inputmask("999-999");
function format(value) {
const el = document.createElement("input");
mask.mask(el);
el.value = value;
el.dispatchEvent(new Event("input"));
return el.value;
}
Такой подход позволяет тестировать преобразование без сложной симуляции событий.
Юнит-тестирование масок обычно покрывает следующие категории поведения:
Каждый сценарий должен проверять как итоговое значение, так и промежуточное состояние при необходимости.
Простейший случай — числовая маска фиксированной длины:
describe("Inputmask 999-999", () => {
it("форматирует число с дефисом", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999");
mask.mask(input);
input.value = "123456";
input.dispatchEvent(new Event("input"));
expect(input.value).toBe("123-456");
});
});
В таких тестах важно учитывать, что Inputmask может реагировать
только на события, а прямое присвоение value без
dispatchEvent часто не вызывает обработчики.
Маска должна блокировать символы, не соответствующие шаблону:
it("игнорирует буквы в числовой маске", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999");
mask.mask(input);
input.value = "12a34b";
input.dispatchEvent(new Event("input"));
expect(input.value).toBe("123-4");
});
Здесь важно учитывать, что поведение может различаться в зависимости
от конфигурации greedy, clearIncomplete,
skipOptionalPartCharacter.
Вставка — один из самых нестабильных сценариев, поскольку Inputmask обрабатывает вставленные данные как отдельный поток:
it("корректно форматирует вставку", () => {
const input = document.createElement("input");
const mask = new Inputmask("(999) 999-9999");
mask.mask(input);
const pasteEvent = new Event("paste");
Object.assign(pasteEvent, {
clipboardData: {
getData: () => "77011234567"
}
});
input.dispatchEvent(pasteEvent);
expect(input.value).toBe("(770) 112-3456");
});
В реальной среде часто требуется мокировать
clipboardData, так как JSDOM не предоставляет полноценную
реализацию.
Каретка является наиболее сложным аспектом тестирования, так как зависит от реализации браузера. Однако можно проверять логические ожидания:
it("ставит каретку после вставленного блока", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999");
mask.mask(input);
input.value = "123";
input.setSelectionRange(3, 3);
input.dispatchEvent(new Event("input"));
const pos = input.selectionStart;
expect(pos).toBeGreaterThanOrEqual(3);
});
Вместо точного значения часто проверяется диапазон допустимых позиций.
Наиболее распространённая связка для тестирования Inputmask:
Типичная конфигурация:
module.exports = {
testEnvironment: "jsdom",
setupFilesAfterEnv: ["./jest.setup.js"]
};
В jest.setup.js часто добавляются polyfill-ы
событий:
import "jest-canvas-mock";
global.InputEvent = window.InputEvent;
Некоторые события требуют ручного создания:
function createInputEvent(value) {
const event = new Event("input", { bubbles: true });
event.simulated = true;
return event;
}
Также часто требуется расширение Event для корректной
работы старых окружений.
Inputmask поддерживает множество опций, каждая из которых должна тестироваться отдельно.
Пример тестирования clearIncomplete:
it("очищает незавершенное значение", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999", {
clearIncomplete: true
});
mask.mask(input);
input.value = "12";
input.dispatchEvent(new Event("blur"));
expect(input.value).toBe("");
});
Для проверки множества масок удобно использовать таблицы сценариев:
const cases = [
["999-999", "123456", "123-456"],
["(999) 999", "111222", "(111) 222"],
["99/99", "1224", "12/24"]
];
describe("маски форматирования", () => {
test.each(cases)(
"маска %s для %s",
(maskPattern, inputValue, expected) => {
const input = document.createElement("input");
const mask = new Inputmask(maskPattern);
mask.mask(input);
input.value = inputValue;
input.dispatchEvent(new Event("input"));
expect(input.value).toBe(expected);
}
);
});
Особое внимание требуется следующим случаям:
Пример переполнения:
it("обрезает лишние символы", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999");
mask.mask(input);
input.value = "123456789";
input.dispatchEvent(new Event("input"));
expect(input.value).toBe("123-456");
});
Inputmask позволяет менять маску во время выполнения, что требует отдельной проверки:
it("переключает маску без потери состояния", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999");
mask.mask(input);
input.value = "123456";
input.dispatchEvent(new Event("input"));
mask.option({ mask: "(999) 999" });
expect(input.value.length).toBeGreaterThan(0);
});
Хотя производительность чаще относится к интеграционным тестам, в юнит-тестах можно измерять базовую деградацию:
it("обрабатывает 1000 итераций без значимой задержки", () => {
const input = document.createElement("input");
const mask = new Inputmask("999-999");
mask.mask(input);
const start = performance.now();
for (let i = 0; i < 1000; i++) {
input.value = "123456";
input.dispatchEvent(new Event("input"));
}
const end = performance.now();
expect(end - start).toBeLessThan(200);
});
Некоторые реализации Inputmask могут зависеть от глобальных объектов браузера. Для стабильных тестов требуется:
document.activeElementrequestAnimationFrameDate.now при необходимостиbeforeAll(() => {
jest.spyOn(Date, "now").mockReturnValue(0);
});
bubbles: true в событияхОптимальная стратегия тестирования Inputmask строится на слоях:
Разделение слоев снижает хрупкость тестов и упрощает поддержку при обновлениях библиотеки.