Система событий в MapLibre GL JS построена вокруг модели наблюдателя:
карта и её сущности (слои, источники данных, DOM-интеграции) генерируют
события, на которые подписываются обработчики через map.on,
map.once и снимаются через map.off.
События делятся на несколько уровней:
Каждое событие передаёт объект контекста, содержащий состояние карты, координаты, свойства фич и технические параметры рендера.
Ключевые события, определяющие готовность карты:
load — стиль и ресурсы загружены, карта готова к
работеstyle.load — загружен стильidle — отсутствуют активные операции рендерингаremove — карта уничтожаетсяОсобенность тестирования этих событий заключается в необходимости учитывать асинхронную загрузку ресурсов (тайлы, изображения, glyphs).
Основной класс событий взаимодействия:
clickdblclickmousemovemouseenter / mouseleavemousedown / mouseuptouchstart / touchendЭти события содержат:
point)lngLat)features)В тестах важно учитывать, что features формируются
только после завершения рендеринга и наличия данных в соответствующих
слоях.
data — изменение данных источникаsourcedata — прогресс загрузки источникаdataloading — начало загрузки данныхsourcedataloading — начало загрузки конкретного
источникаsource.load — источник полностью загруженОсобенность: эти события часто возникают несколько раз в одном сценарии, что требует фильтрации в тестах.
render — каждый кадр отрисовкиrendercomplete — завершение финального рендераЭти события критичны при проверке визуального состояния карты, особенно при асинхронной загрузке тайлов.
Тестирование событий в MapLibre GL JS обычно делится на три уровня:
Каждый уровень требует разных инструментов и допущений.
Юнит-тестирование применяется для проверки логики функций, привязанных к событиям карты:
function onMapClick(e) {
return e.features?.map(f => f.properties.id);
}
Тестирование проводится без реальной карты, через имитацию событийного объекта:
test('onMapClick extracts feature ids', () => {
const event = {
features: [
{ properties: { id: 1 } },
{ properties: { id: 2 } }
]
};
expect(onMapClick(event)).toEqual([1, 2]);
});
Такой подход исключает зависимость от WebGL и рендеринга.
Интеграционные тесты требуют создания экземпляра карты и проверки событий в реальной среде выполнения.
import maplibregl from 'maplibre-gl';
test('map fires load event', (done) => {
const map = new maplibregl.Map({
container: document.createElement('div'),
style: 'https://example.com/style.json'
});
map.on('load', () => {
done();
});
});
Ключевая сложность — асинхронная загрузка стиля и тайлов.
В Node.js отсутствует WebGL-контекст, поэтому тестирование требует эмуляции:
headless-gljsdom (частично)HTMLCanvasElementrequestAnimationFrameПример мокирования:
global.HTMLCanvasElement.prototype.getContext = () => {
return {
createShader: () => {},
shaderSource: () => {},
compileShader: () => {},
createProgram: () => ({}),
linkProgram: () => {},
useProgram: () => {}
};
};
Без этих заглушек инициализация карты завершается ошибкой.
MapLibre GL JS позволяет программно вызывать события через методы
fire или DOM-эмуляцию.
map.on('click', (e) => {
console.log(e.lngLat);
});
map.fire('click', {
point: { x: 100, y: 200 },
lngLat: { lng: 30, lat: 50 }
});
В тестах это используется для проверки реакции логики без реального DOM-взаимодействия.
В E2E тестах часто используется Playwright или Cypress:
await page.mouse.click(200, 150);
После чего проверяется вызов обработчика через UI-эффекты или состояние приложения.
Ключевая особенность тестирования — многослойная асинхронность:
События load, render, idle не
гарантируют мгновенного исполнения логики.
Типичный антишаблон:
map.on('load', () => {
expect(map.getSource('data')).toBeDefined();
});
Без ожидания idle источник может быть ещё не готов.
Для тестов используется паттерн ожидания:
function waitForIdle(map) {
return new Promise((resolve) => {
const check = () => {
if (map.isStyleLoaded() && map.loaded()) {
resolve();
} else {
setTimeout(check, 50);
}
};
check();
});
}
Это позволяет стабилизировать состояние перед проверками событий.
События взаимодействия часто включают features, которые
зависят от слоёв:
map.on('click', (e) => {
console.log(e.features);
});
В тестах важно:
map.addLayer)idleБез этого features будет пустым массивом.
Некоторые сценарии зависят от последовательности:
dataloading → data →
idlemousemove → mouseenter →
mouseleaveПример проверки порядка:
const events = [];
map.on('dataloading', () => events.push('loading'));
map.on('data', () => events.push('data'));
map.on('idle', () => events.push('idle'));
При работе с GeoJSON-источниками:
data срабатывают при обновлении данныхmap.on('click', 'clusters', (e) => {
console.log(e.features[0].properties.cluster_id);
});
Тестирование требует симуляции изменения масштаба:
map.setZoom(10);
В Jest часто применяются spy-функции:
const handler = jest.fn();
map.on('click', handler);
map.fire('click', {
point: { x: 0, y: 0 },
lngLat: { lng: 0, lat: 0 }
});
expect(handler).toHaveBeenCalled();
Это позволяет проверять факт вызова без анализа DOM.
События могут срабатывать до завершения GPU-процессов.
mousemove и render могут вызываться десятки
раз в секунду.
load не гарантирует доступность всех данных.
Без моков карта не инициализируется.
idle перед проверками состояния