Библиотека MapLibre GL JS работает поверх WebGL и активно взаимодействует с браузерным окружением: DOM, графическим контекстом, сетевыми запросами к тайлам и стилям, системой событий и асинхронной загрузкой ресурсов. Это создаёт сложность при тестировании и выполнении кода вне реального браузера.
Основные источники нестабильности:
canvas.getContext('webgl'))load, render,
idle)requestAnimationFrameМокирование устраняет эти зависимости, заменяя их предсказуемыми заглушками.
Конструктор Map создаёт сложную цепочку внутренних
процессов:
В тестовой среде эти шаги заменяются упрощённой моделью:
class MockMap {
constructor(options) {
this.options = options;
this.style = options.style || {};
this._listeners = {};
this.loaded = false;
}
on(event, handler) {
this._listeners[event] = this._listeners[event] || [];
this._listeners[event].push(handler);
}
_emit(event, payload) {
(this._listeners[event] || []).forEach(fn => fn(payload));
}
load() {
this.loaded = true;
this._emit('load');
}
}
WebGL в jsdom отсутствует, поэтому критическая точка —
getContext.
Подходы:
HTMLCanvasElement.prototype.getContext = () => ({
canvas: {},
getExtension: () => null,
createShader: () => ({}),
shaderSource: () => {},
compileShader: () => {},
createProgram: () => ({}),
linkProgram: () => {},
useProgram: () => {},
});
Такой подход подходит для поверхностных тестов, где рендер не проверяется.
Использование gl:
import createGL from 'gl';
const gl = createGL(256, 256, { preserveDrawingBuffer: true });
Преимущество — частичная совместимость с реальными вызовами WebGL.
Недостаток — сложность настройки и нестабильность в CI.
MapLibre позволяет перехватывать цикл рендера:
const map = new MockMap({
renderWorldCopies: false,
interactive: false
});
map._render = () => {};
Используется в unit-тестах бизнес-логики поверх карты.
MapLibre загружает:
style.json.pbf).png, .jpg).json, .png)global.fetch = jest.fn((url) => {
if (url.includes('style.json')) {
return Promise.resolve({
json: () => Promise.resolve({
version: 8,
sources: {},
layers: []
})
});
}
return Promise.resolve({
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8))
});
});
Более структурированный подход:
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/style.json', (req, res, ctx) => {
return res(ctx.json({
version: 8,
sources: {},
layers: []
}));
}),
rest.get('/tiles/:z/:x/:y.pbf', (req, res, ctx) => {
return res(ctx.body(new ArrayBuffer(10)));
})
);
MSW обеспечивает реалистичное поведение HTTP без изменения кода приложения.
MapLibre активно использует события:
loadrenderidleerrordataДля тестирования важно контролировать их вручную.
const map = new MockMap({});
let loaded = false;
map.on('load', () => {
loaded = true;
});
map.load();
expect(loaded).toBe(true);
Часто требуется проверка вызовов API:
setCentersetZoomflyTosetStyleaddSourceaddLayerconst map = {
setCenter: jest.fn(),
setZoom: jest.fn(),
flyTo: jest.fn(),
addSource: jest.fn(),
addLayer: jest.fn()
};
Применение:
map.setCenter([30, 50]);
expect(map.setCenter).toHaveBeenCalledWith([30, 50]);
Стиль в MapLibre — ключевой источник сложности. Его структура включает:
Упрощённая модель для тестов:
const mockStyle = {
version: 8,
sources: {
cities: {
type: 'geojson',
data: { type: 'FeatureCollection', features: [] }
}
},
layers: [
{
id: 'cities-layer',
type: 'circle',
source: 'cities'
}
]
};
Tile-система является асинхронной и потоковой.
Упрощённая заглушка:
class MockTile {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
this.loaded = false;
}
load() {
this.loaded = true;
}
}
Используется для тестирования логики кеширования и запросов.
MapLibre рендерит карту через RAF. В тестах он заменяется:
global.requestAnimationFrame = (cb) => {
return setTimeout(cb, 0);
};
global.cancelAnimationFrame = (id) => {
clearTimeout(id);
};
Это делает рендер детерминированным.
Для архитектурной изоляции применяется инъекция зависимостей:
export function createMap(MapImpl, options) {
return new MapImpl(options);
}
В тестах:
const map = createMap(MockMap, {});
Существуют специализированные решения:
mapbox-gl-js-mock (совместим с MapLibre API)__mocks__ директория)Пример Jest mock:
__mocks__/maplibre-gl.js
export const Map = jest.fn().mockImplementation(() => ({
on: jest.fn(),
addSource: jest.fn(),
addLayer: jest.fn(),
setStyle: jest.fn()
}));
При тестировании React/Vue компонентов с картой используется частичная эмуляция:
jest.mock('maplibre-gl', () => ({
Map: jest.fn(() => ({
on: jest.fn(),
remove: jest.fn(),
addControl: jest.fn()
}))
}));
Ключевые состояния:
initializingloadingloadederrorМок-модель:
const state = {
initialized: true,
styleLoaded: true,
sourcesLoaded: true,
rendered: true
};
Используется для проверки бизнес-логики поверх карты.
Тестирование устойчивости требует имитации ошибок WebGL:
const errorMap = {
on: (event, cb) => {
if (event === 'error') {
cb(new Error('WebGL context lost'));
}
}
};
Полная имитация MapLibre невозможна без значительного упрощения:
Поэтому мокирование используется только для: