Тестирование приложений на базе MapLibre GL JS строится вокруг ключевой сложности: работа WebGL-рендеринга, асинхронной загрузки тайлов и сложного состояния карты (камера, стиль, источники данных). Это делает классические подходы unit-тестирования недостаточными без дополнительной изоляции графического слоя.
Тестовая стратегия обычно разделяется на четыре уровня:
Каждый уровень закрывает свой класс проблем:
На этом уровне MapLibre GL JS вообще не подключается. Проверяется код, который формирует:
Пример теста генерации style expression:
export function createHeatmapLayer(intensity) {
return {
id: 'heat',
type: 'heatmap',
paint: {
'heatmap-intensity': intensity,
'heatmap-color': [
'interpolate',
['linear'],
['heatmap-density'],
0, 'blue',
1, 'red'
]
}
};
}
import { createHeatmapLayer } from './layers';
test('creates heatmap layer with intensity', () => {
const layer = createHeatmapLayer(2);
expect(layer.paint['heatmap-intensity']).toBe(2);
});
Основная проблема тестирования карт — отсутствие WebGL в Node.js окружении.
Для решения используют:
Популярные подходы:
Создаётся заглушка, имитирующая API:
export class MockMap {
constructor() {
this.layers = [];
this.sources = {};
}
addLayer(layer) {
this.layers.push(layer);
}
addSource(id, source) {
this.sources[id] = source;
}
getLayer(id) {
return this.layers.find(l => l.id === id);
}
}
Это позволяет тестировать логику добавления слоёв без рендера.
Для частичного WebGL окружения используется headless-gl,
который эмулирует WebGL context.
В связке с Jest часто применяется мок canvas:
import 'jest-canvas-mock';
Это позволяет избежать ошибок типа:
Интеграционные тесты запускаются уже в браузерной среде и проверяют:
Пример с Playwright:
import { test, expect } from '@playwright/test';
test('map renders and loads style', async ({ page }) => {
await page.goto('http://localhost:3000');
await page.waitForSelector('.maplibre-map');
const canvas = await page.locator('canvas');
await expect(canvas).toBeVisible();
});
Cypress часто используют для проверки пользовательских сценариев:
describe('Map interaction', () => {
it('zooms in on click', () => {
cy.visit('/map');
cy.get('canvas').click(200, 200);
cy.get('.popup').should('be.visible');
});
});
Наиболее важный уровень для MapLibre GL JS — проверка визуального результата.
Подход:
Пример с Playwright:
test('visual regression - map baseline', async ({ page }) => {
await page.goto('http://localhost:3000/map');
await page.waitForTimeout(2000);
const image = await page.screenshot();
expect(image).toMatchSnapshot('map-baseline.png');
});
Ключевой момент — стабильность рендера:
Карты сложно тестировать из-за недетерминированности:
Для стабилизации применяются:
map.jumpTo({
center: [0, 0],
zoom: 5,
bearing: 0,
pitch: 0
});
map.setRenderWorldCopies(false);
map.stop();
Используются локальные tile server или фикстуры:
При тестировании важно изолировать внешние API.
Используется:
MSW
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/tiles/:z/:x/:y.pbf', (req, res, ctx) => {
return res(ctx.body(new Uint8Array([0, 1, 2])));
})
);
Это позволяет контролировать:
MapLibre использует Style Spec (аналог Mapbox Style Spec). Тестируют:
import style from './style.json';
test('style has sources', () => {
expect(style.sources).toBeDefined();
});
test('has base layer', () => {
expect(style.layers.find(l => l.id === 'background')).toBeTruthy();
});
Важно проверять события:
Пример:
map.on('load', () => {
map.addLayer({
id: 'points',
type: 'circle',
source: 'geo'
});
});
Тест проверяет, что layer действительно добавлен после load.
Источники могут быть:
Тестируются сценарии:
map.getSource('points').setData({
type: 'FeatureCollection',
features: []
});
Карты чувствительны к:
Метрики:
Пример теста:
const start = performance.now();
for (let i = 0; i < 100; i++) {
map.addLayer({ id: `layer-${i}`, type: 'circle', source: 'geo' });
}
const duration = performance.now() - start;
expect(duration).toBeLessThan(200);
В CI важно учитывать:
Рекомендуемая схема:
Эти проблемы решаются через: