Тестирование взаимодействий

Особенности тестирования интерактивных картографических сценариев

Взаимодействия в OpenLayers основаны на событийной модели и тесно связаны с DOM, canvas-рендерингом и внутренним состоянием карты. Это создаёт специфические требования к тестированию: необходимо проверять не только чистую логику, но и поведение при пользовательских событиях, корректность обновления слоёв, а также синхронизацию состояния объектов карты.

Ключевая сложность заключается в том, что взаимодействия (interaction) работают поверх Map-инстанса и зависят от:

  • событий мыши и клавиатуры;
  • текущего состояния представления (View);
  • слоёв и источников данных (Source);
  • геометрии объектов;
  • внутренних механизмов обработки пиксельных координат.

Поэтому тестирование делится на три уровня:

  • модульное (unit);
  • интеграционное;
  • e2e (end-to-end).

Подготовка тестовой среды

Тестирование взаимодействий требует имитации DOM-окружения. Чаще всего используются:

  • Jest + jsdom
  • Vitest + jsdom
  • Mocha + jsdom
  • Playwright / Cypress (для e2e)

jsdom не реализует canvas полностью, поэтому OpenLayers часто тестируется с частичной или полной мок-реализацией canvas API.

Минимальная настройка тестовой среды включает:

  • создание виртуального DOM;
  • мока HTMLCanvasElement;
  • стабилизацию requestAnimationFrame;
  • фиксацию размеров контейнера карты.

Инициализация карты в тестах

Создание карты в тестовой среде требует детерминированного контейнера:

  • фиксированные размеры width/height;
  • ручное управление рендерингом;
  • отключение анимаций.

Типовой подход:

  • создаётся div контейнер;
  • задаются размеры через style;
  • инициализируется Map;
  • добавляется View с фиксированным центром и zoom.

Важный аспект — синхронный рендеринг:

  • используется map.renderSync() для принудительного обновления;
  • события ожидания заменяются на прямой вызов обновления.

Тестирование базовых взаимодействий

Select interaction

Взаимодействие выбора объектов (Select) зависит от попадания курсора в feature.

Основные проверяемые аспекты:

  • добавление feature в коллекцию selected;
  • корректная обработка клика;
  • снятие выделения при повторном клике;
  • работа фильтров condition.

При тестировании имитируются события:

  • singleclick;
  • pointerdown;
  • pointerup.

Важно учитывать преобразование координат:

  • экранные координаты → координаты карты;
  • учитывается pixelRatio.

Draw interaction

Интеракция рисования (Draw) одна из наиболее сложных для тестирования.

Проверяются сценарии:

  • начало рисования (drawstart);
  • добавление вершин;
  • завершение (drawend);
  • формирование геометрии.

Типовой сценарий теста:

  • создаётся VectorSource;
  • активируется Draw с типом геометрии;
  • эмулируются клики по карте;
  • проверяется итоговая геометрия.

Особое внимание:

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

Modify interaction

Modify изменяет существующие геометрии.

Ключевые аспекты тестирования:

  • корректность выбора вершины;
  • обновление геометрии при drag;
  • сохранение структуры feature;
  • корректная работа insertVertexCondition.

В тестах проверяются:

  • изменение координат массива geometry;
  • события modifystart и modifyend;
  • отсутствие побочных изменений других features.

Snap interaction

Snap-интеракция автоматически “прилипает” к ближайшим вершинам.

Тестирование включает:

  • корректность поиска ближайшей точки;
  • применение snap при добавлении координат;
  • приоритет snap над свободным вводом.

Типовые проверки:

  • расстояние до ближайшей вершины;
  • изменение координат при pointermove;
  • взаимодействие с Draw и Modify одновременно.

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

OpenLayers активно использует систему событий:

  • on('click')
  • on('pointermove')
  • on('change')
  • on('addfeature')

При тестировании важно проверять:

  • порядок вызова обработчиков;
  • корректную передачу event объекта;
  • отмену событий (preventDefault / stopPropagation);
  • подписку и отписку от событий.

Особое внимание уделяется:

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

Интеграционные тесты взаимодействий

Интеграционные тесты проверяют связку:

Map + Layer + Source + Interaction

Типовые сценарии:

  • добавление feature через Draw → проверка появления на VectorLayer;
  • изменение геометрии через Modify → проверка обновления источника;
  • Select + Style изменение → проверка визуального состояния;
  • Snap + Draw → проверка корректности координат.

Интеграционные тесты часто требуют:

  • синхронного рендера карты;
  • фиксации таймеров (fake timers);
  • контроля состояния источников данных.

Мокирование внутренних компонентов OpenLayers

Для ускорения unit-тестов часто мокируются:

  • ol/Map
  • ol/View
  • ol/render/canvas
  • requestAnimationFrame

Цели мокирования:

  • исключить canvas-рендеринг;
  • контролировать события вручную;
  • ускорить выполнение тестов.

Пример подхода:

  • Map заменяется заглушкой с событиями;
  • getPixelFromCoordinate возвращает фиксированное значение;
  • forEachFeatureAtPixel эмулирует попадание по feature.

Тестирование пользовательских сценариев взаимодействий

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

  • Draw + Snap + Modify;
  • Select + Translate;
  • Rotate + Modify geometry;
  • DragPan конфликт с Select.

Проверяется:

  • приоритет взаимодействий (active состояния);
  • корректное отключение конфликтующих interaction;
  • стабильность состояния карты при переключении режимов.

Особенно важно тестировать:

  • переключение режимов редактирования;
  • очистку interaction stack;
  • предотвращение двойной обработки событий.

E2E тестирование взаимодействий

End-to-end тесты выполняются в реальном браузере (Playwright, Cypress).

Проверяются:

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

Сценарии:

  • рисование полигона мышью;
  • перетаскивание вершины;
  • выделение объектов;
  • масштабирование и панорамирование.

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

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

Тестирование производительности взаимодействий

Взаимодействия могут вызывать нагрузку:

  • частые pointermove события;
  • перерасчёт геометрии;
  • rerender canvas.

Метрики:

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

Проверяется:

  • debounce/throttle поведения;
  • отсутствие лишних перерисовок;
  • оптимизация spatial index.

Ошибки и нестабильности тестирования

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

  • нестабильные координаты из-за pixel ratio;
  • асинхронные события render cycle;
  • различия jsdom и реального браузера;
  • некорректная эмуляция pointer events;
  • race conditions между interaction.

Причины нестабильности:

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

Стратегии повышения надёжности тестов

Используются следующие подходы:

  • фиксация размеров карты;
  • отключение анимаций View;
  • использование fake timers;
  • синхронный render;
  • минимизация DOM-зависимости;
  • изоляция interaction в отдельных тестах.

Дополнительно:

  • разделение unit и integration тестов;
  • использование тестовых фикстур геометрий;
  • повторяемые сценарии pointer events.

Проверка корректности геометрических изменений

При тестировании взаимодействий с геометрией важно проверять:

  • сохранение топологии полигона;
  • отсутствие самопересечений (если не допускаются);
  • корректность порядка координат;
  • обновление bounding extent.

Особенно критично для:

  • Modify interaction;
  • Draw interaction;
  • Translate interaction.

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

Кастомные взаимодействия часто реализуются через наследование ol/interaction/Interaction.

Проверяются:

  • подписка на handleEvent;
  • корректная обработка mapBrowserEvent;
  • изменение состояния interaction;
  • взаимодействие с другими слоями.

Типовые сценарии:

  • обработка pointerdown → старт режима;
  • pointermove → изменение состояния;
  • pointerup → завершение действия.