Взаимодействия в Deck.gl строятся вокруг системы picking — механизма определения объектов под курсором и генерации событий уровня слоя. В отличие от DOM-событий, здесь ключевую роль играет рендер в WebGL-контексте и вычисление попаданий через offscreen-pass. Это формирует специфический класс тестов, где проверяется не только логика обработчиков, но и корректность работы слоя в условиях графического рендера.
Основные точки взаимодействия:
onClick — обработка клика по объектам слояonHover — реакции на наведениеonDragStart / onDrag / onDragEnd — события
перетаскиванияpickObject и pickMultipleObjects —
программный вызов механизма pickingEventManagerТестирование этих механизмов требует разделения логики на уровни: слой, контейнер (Deck / DeckGL) и интеграция с картографическим движком (например, Mapbox GL).
Picking в Deck.gl реализуется через дополнительный рендеринг в буфер, где каждый объект кодируется уникальным идентификатором. При тестировании возникает несколько проблем:
Поэтому тесты делятся на:
На уровне слоя проверяется только корректность формирования
pickingInfo и реакций на входные события.
Пример теста логики onHover:
import {ScatterplotLayer} from '@deck.gl/layers';
test('onHover вызывается с корректным pickingInfo', () => {
const mock = jest.fn();
const layer = new ScatterplotLayer({
id: 'points',
data: [{position: [0, 0]}],
getPosition: d => d.position,
onHover: mock
});
const pickingInfo = {
object: {position: [0, 0]},
x: 100,
y: 200,
coordinate: [0, 0],
layer
};
layer.props.onHover(pickingInfo);
expect(mock).toHaveBeenCalledWith(pickingInfo);
});
Здесь проверяется не WebGL, а контракт слоя: корректность передачи данных и отсутствие побочных эффектов.
@deck.gl/test-utilsБиблиотека @deck.gl/test-utils предоставляет инструменты
для создания mock-дека и симуляции рендера.
Ключевые возможности:
pickObjectПример использования:
import {createDeckInstance} from '@deck.gl/test-utils';
import {ScatterplotLayer} from '@deck.gl/layers';
test('pickObject возвращает объект слоя', () => {
const deck = createDeckInstance({
layers: [
new ScatterplotLayer({
id: 'points',
data: [{position: [10, 10]}],
getPosition: d => d.position
})
]
});
const info = deck.pickObject({x: 0, y: 0});
expect(info).toBeDefined();
expect(info.object).toBeDefined();
});
Этот подход позволяет тестировать логику picking без полноценного браузера.
Интеграционные тесты проверяют реальную связку:
Чаще всего используется headless WebGL через headless-gl
или puppeteer.
Пример через Puppeteer:
await page.mouse.move(100, 100);
await page.mouse.click(100, 100);
const result = await page.evaluate(() => window.__LAST_PICK_INFO__);
expect(result.object).toBeTruthy();
В приложении при этом сохраняется последний
pickingInfo:
const deck = new Deck({
layers: [...],
onClick: info => {
window.__LAST_PICK_INFO__ = info;
}
});
onHover может вызываться при каждом движении мыши, что
создаёт поток событий. В тестах важно избегать избыточных ассертов и
контролировать частоту вызовов.
Используются техники:
Пример:
jest.useFakeTimers();
const hoverMock = jest.fn();
layer.props.onHover({x: 10, y: 10});
layer.props.onHover({x: 11, y: 11});
layer.props.onHover({x: 12, y: 12});
jest.runAllTimers();
expect(hoverMock).toHaveBeenCalledTimes(3);
Drag-логика в Deck.gl опирается на последовательность событий:
pointerdownpointermovepointerupВ тестах важно сохранять состояние взаимодействия между вызовами.
Пример:
const layer = new ScatterplotLayer({
id: 'drag',
data: [{position: [0, 0]}],
pickable: true,
onDragStart: jest.fn(),
onDrag: jest.fn(),
onDragEnd: jest.fn()
});
layer.props.onDragStart({x: 0, y: 0, object: {position: [0, 0]}});
layer.props.onDrag({x: 10, y: 10, delta: [10, 10]});
layer.props.onDragEnd({x: 10, y: 10});
Тестируется:
При использовании @deck.gl/react добавляется слой
React-событий, который требует отдельного тестирования через React
Testing Library или Enzyme.
Пример:
import {render, fireEvent} from '@testing-library/react';
test('click вызывает onClick слой', () => {
const onCl ick = jest.fn();
render(
<DeckGL
initialViewState={{longitude: 0, latitude: 0, zoom: 1}}
controller={true}
layers={[
new ScatterplotLayer({
id: 'points',
data: [{position: [0, 0]}],
onClick
})
]}
/>
);
fireEvent.click(document.querySelector('canvas'));
expect(onClick).toHaveBeenCalled();
});
Основная сложность заключается в том, что React не управляет WebGL напрямую, поэтому тест проверяет только связку событий, а не сам rendering pipeline.
Многие ошибки взаимодействий связаны с неверным состоянием камеры:
В тестах фиксируется viewport:
const viewState = {
longitude: 0,
latitude: 0,
zoom: 10,
pitch: 0,
bearing: 0
};
И проверяется стабильность результата picking:
const info1 = deck.pickObject({x: 50, y: 50});
const info2 = deck.pickObject({x: 50, y: 50});
expect(info1.object).toEqual(info2.object);
Deck.gl позволяет расширять систему событий через
EventManager. Это требует отдельного тестирования
пользовательских событий.
Пример:
const eventManager = new EventManager(document.body, {
events: ['click', 'pointermove']
});
eventManager.on({
click: e => {
result = e;
}
});
document.body.dispatchEvent(new MouseEvent('click'));
expect(result.type).toBe('click');
Полное тестирование взаимодействий в Deck.gl обычно строится по слоям:
Критически важные проверки:
pickingInfoТакие ошибки приводят к нестабильным тестам, которые ломаются при изменении таймингов или GPU-поведения, а не логики приложения.