mapicgc-gl-js 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/dist/chunk-efA98nb6-Dfm7IivL.mjs +13 -0
- package/dist/html2canvas-DhrjCa5K-zEqRDH-g.mjs +4024 -0
- package/dist/index.es-D_FsJY9H-CO3QIBGM.mjs +5741 -0
- package/dist/mapicgc-gl-js.css +2 -0
- package/dist/mapicgc-gl.css +1948 -1
- package/dist/mapicgc-gl.js +224 -440
- package/dist/mapicgc-gl.mjs +88130 -5
- package/dist/purify.es-DVGU85zV-CJOZ7ABI.mjs +549 -0
- package/dist/typeof-CTd55yxz-BxqQpTq1.mjs +11 -0
- package/dist/webgl-device-CDzrheIu.mjs +2 -0
- package/dist/webgl-device-DPB4IspV.mjs +5573 -0
- package/package.json +19 -20
- package/src/map/Map.js +33 -3
- package/test/exemples/addMapStyle.html +7 -5
- package/test/vitest/Config.test.js +145 -0
- package/test/vitest/MapFunctions.test.js +971 -0
- package/vite.config.mjs +7 -2
- package/dist/html2canvas.esm-Dmi1NfiH-AQaq32X6.mjs +0 -4534
- package/dist/index-DdkbNQVU.mjs +0 -83555
- package/dist/index.es-CDV9zB2B-CB-dpJjG.mjs +0 -6731
- package/dist/mapicgc-gl.umd.js +0 -4064
- package/dist/purify.es-DHbHSKL1-2rarU4M1.mjs +0 -480
- package/test/vitest/Map.test.js +0 -215
- package/test/vitest/MapStyle.test.js +0 -129
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
import { vi, describe, expect, test, beforeEach } from 'vitest';
|
|
2
|
+
import { JSDOM } from 'jsdom';
|
|
3
|
+
import mapicgcConfig from '../../src/mapicgc-config.json';
|
|
4
|
+
|
|
5
|
+
// ─── Mock maplibre-gl ────────────────────────────────────────────────
|
|
6
|
+
const mockMapInstance = {
|
|
7
|
+
addControl: vi.fn(),
|
|
8
|
+
addLayer: vi.fn(),
|
|
9
|
+
addSource: vi.fn(),
|
|
10
|
+
addImage: vi.fn(),
|
|
11
|
+
addSprite: vi.fn(),
|
|
12
|
+
areTilesLoaded: vi.fn(() => true),
|
|
13
|
+
cameraForBounds: vi.fn(() => ({ center: [1.5, 41.8], zoom: 10, bearing: 0 })),
|
|
14
|
+
easeTo: vi.fn(),
|
|
15
|
+
fitBounds: vi.fn(),
|
|
16
|
+
fitScreenCoordinates: vi.fn(),
|
|
17
|
+
flyTo: vi.fn(),
|
|
18
|
+
getBearing: vi.fn(() => 45),
|
|
19
|
+
getBounds: vi.fn(() => ({ _sw: { lng: 0, lat: 40 }, _ne: { lng: 3, lat: 43 } })),
|
|
20
|
+
getCanvas: vi.fn(() => ({ width: 800, height: 600 })),
|
|
21
|
+
getCanvasContainer: vi.fn(() => ({})),
|
|
22
|
+
getCenter: vi.fn(() => ({ lng: 1.537786, lat: 41.837539 })),
|
|
23
|
+
getContainer: vi.fn(() => document.createElement('div')),
|
|
24
|
+
getFilter: vi.fn(),
|
|
25
|
+
getLayer: vi.fn(),
|
|
26
|
+
getLayoutProperty: vi.fn(),
|
|
27
|
+
getLight: vi.fn(),
|
|
28
|
+
getMaxBounds: vi.fn(),
|
|
29
|
+
getMaxPitch: vi.fn(() => 85),
|
|
30
|
+
getMaxZoom: vi.fn(() => 18),
|
|
31
|
+
getMinPitch: vi.fn(() => 0),
|
|
32
|
+
getMinZoom: vi.fn(() => 0),
|
|
33
|
+
getPadding: vi.fn(() => ({ top: 0, bottom: 0, left: 0, right: 0 })),
|
|
34
|
+
getPaintProperty: vi.fn(),
|
|
35
|
+
getPitch: vi.fn(() => 0),
|
|
36
|
+
getPixelRatio: vi.fn(() => 1),
|
|
37
|
+
getRenderWorldCopies: vi.fn(() => false),
|
|
38
|
+
getSource: vi.fn(),
|
|
39
|
+
getSprite: vi.fn(() => []),
|
|
40
|
+
getStyle: vi.fn(() => ({ name: 'ICGC mapa base gris', id: 'icgc_mapa_base_gris' })),
|
|
41
|
+
getZoom: vi.fn(() => 7.5),
|
|
42
|
+
hasControl: vi.fn(() => true),
|
|
43
|
+
hasImage: vi.fn(() => false),
|
|
44
|
+
isMoving: vi.fn(() => false),
|
|
45
|
+
isZooming: vi.fn(() => false),
|
|
46
|
+
isRotating: vi.fn(() => false),
|
|
47
|
+
loaded: vi.fn(() => true),
|
|
48
|
+
loadImage: vi.fn(),
|
|
49
|
+
on: vi.fn((event, callback) => {
|
|
50
|
+
if (event === 'load') callback();
|
|
51
|
+
}),
|
|
52
|
+
once: vi.fn(),
|
|
53
|
+
off: vi.fn(),
|
|
54
|
+
remove: vi.fn(),
|
|
55
|
+
removeControl: vi.fn(),
|
|
56
|
+
removeFeatureState: vi.fn(),
|
|
57
|
+
removeImage: vi.fn(),
|
|
58
|
+
removeLayer: vi.fn(),
|
|
59
|
+
removeSource: vi.fn(),
|
|
60
|
+
removeSprite: vi.fn(),
|
|
61
|
+
resize: vi.fn(),
|
|
62
|
+
setBearing: vi.fn(),
|
|
63
|
+
setCenter: vi.fn(),
|
|
64
|
+
setEventedParent: vi.fn(),
|
|
65
|
+
setFeatureState: vi.fn(),
|
|
66
|
+
setFilter: vi.fn(),
|
|
67
|
+
setLayoutProperty: vi.fn(),
|
|
68
|
+
setLight: vi.fn(),
|
|
69
|
+
setMaxBounds: vi.fn(),
|
|
70
|
+
setMaxPitch: vi.fn(),
|
|
71
|
+
setMaxZoom: vi.fn(),
|
|
72
|
+
setMinPitch: vi.fn(),
|
|
73
|
+
setMinZoom: vi.fn(),
|
|
74
|
+
setPadding: vi.fn(),
|
|
75
|
+
setPitch: vi.fn(),
|
|
76
|
+
setPixelRatio: vi.fn(),
|
|
77
|
+
setRenderWorldCopies: vi.fn(),
|
|
78
|
+
setSky: vi.fn(),
|
|
79
|
+
setStyle: vi.fn(),
|
|
80
|
+
setTerrain: vi.fn(),
|
|
81
|
+
setZoom: vi.fn(),
|
|
82
|
+
stop: vi.fn(),
|
|
83
|
+
triggerRepaint: vi.fn(),
|
|
84
|
+
style: {
|
|
85
|
+
stylesheet: { id: 'icgc_mapa_base_gris', name: 'ICGC mapa base gris' },
|
|
86
|
+
},
|
|
87
|
+
queryRenderedFeatures: vi.fn(() => []),
|
|
88
|
+
querySourceFeatures: vi.fn(() => []),
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
vi.mock('maplibre-gl', () => {
|
|
92
|
+
// Must use function() not arrow so `new` works
|
|
93
|
+
const MapCtor = vi.fn(function () {
|
|
94
|
+
Object.assign(this, mockMapInstance);
|
|
95
|
+
return mockMapInstance;
|
|
96
|
+
});
|
|
97
|
+
const MarkerCtor = vi.fn(function () {
|
|
98
|
+
this.setLngLat = vi.fn().mockReturnValue(this);
|
|
99
|
+
this.setPopup = vi.fn().mockReturnValue(this);
|
|
100
|
+
this.addTo = vi.fn().mockReturnValue(this);
|
|
101
|
+
});
|
|
102
|
+
const PopupCtor = vi.fn(function () {
|
|
103
|
+
this.setLngLat = vi.fn().mockReturnValue(this);
|
|
104
|
+
this.setHTML = vi.fn().mockReturnValue(this);
|
|
105
|
+
this.addTo = vi.fn().mockReturnValue(this);
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
default: {
|
|
109
|
+
Map: MapCtor,
|
|
110
|
+
Marker: MarkerCtor,
|
|
111
|
+
Popup: PopupCtor,
|
|
112
|
+
AttributionControl: vi.fn(function (opts) { this._options = opts; this.type = 'AttributionControl'; }),
|
|
113
|
+
NavigationControl: vi.fn(function (opts) { this._options = opts; this.type = 'NavigationControl'; }),
|
|
114
|
+
ScaleControl: vi.fn(function (opts) { this._options = opts; this.type = 'ScaleControl'; }),
|
|
115
|
+
GeolocateControl: vi.fn(function (opts) { this._options = opts; this.type = 'GeolocateControl'; }),
|
|
116
|
+
FullscreenControl: vi.fn(function (opts) { this._options = opts; this.type = 'FullscreenControl'; }),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ─── Mock other dependencies ─────────────────────────────────────────
|
|
122
|
+
vi.mock('../../public/mapicgc-gl.css', () => ({}));
|
|
123
|
+
vi.mock('@watergis/maplibre-gl-export/dist/maplibre-gl-export.css', () => ({}));
|
|
124
|
+
vi.mock('flatgeobuf/lib/mjs/geojson.js', () => ({ deserialize: vi.fn() }));
|
|
125
|
+
vi.mock('@deck.gl/mapbox', () => ({ MapboxOverlay: vi.fn() }));
|
|
126
|
+
vi.mock('@deck.gl/geo-layers', () => ({ Tile3DLayer: vi.fn() }));
|
|
127
|
+
vi.mock('@loaders.gl/3d-tiles', () => ({ Tiles3DLoader: vi.fn() }));
|
|
128
|
+
vi.mock('@deck.gl/core', () => ({ AmbientLight: vi.fn(), LightingEffect: vi.fn() }));
|
|
129
|
+
vi.mock('@watergis/maplibre-gl-export', () => ({
|
|
130
|
+
MaplibreExportControl: vi.fn(function (opts) { this._options = opts; this.type = 'ExportControl'; }),
|
|
131
|
+
Size: { A4: 'A4' },
|
|
132
|
+
PageOrientation: { Landscape: 'Landscape' },
|
|
133
|
+
Format: { PNG: 'PNG' },
|
|
134
|
+
DPI: { 300: 300 },
|
|
135
|
+
}));
|
|
136
|
+
vi.mock('@maplibre/maplibre-gl-geocoder', () => ({ default: vi.fn() }));
|
|
137
|
+
vi.mock('../../src/controls/LogoControl.js', () => ({ default: vi.fn() }));
|
|
138
|
+
vi.mock('../../src/controls/LegendControl.js', () => ({ default: vi.fn() }));
|
|
139
|
+
vi.mock('../../src/controls/MouseCoordinatesControl.js', () => ({ default: vi.fn() }));
|
|
140
|
+
vi.mock('../../src/controls/Toggle3DControl.js', () => ({ default: vi.fn() }));
|
|
141
|
+
vi.mock('../../src/constants/Legends.js', () => ({ default: {} }));
|
|
142
|
+
|
|
143
|
+
// Mock ConfigICGC to return local config immediately
|
|
144
|
+
vi.mock('../../src/constants/ConfigICGC.js', () => ({
|
|
145
|
+
default: {
|
|
146
|
+
getConfigICGC: vi.fn(() => Promise.resolve(mapicgcConfig)),
|
|
147
|
+
},
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
import maplibregl from 'maplibre-gl';
|
|
151
|
+
import Map from '../../src/map/Map.js';
|
|
152
|
+
|
|
153
|
+
// ─── Helper: create a Map instance and wait for config to load ──────
|
|
154
|
+
function createMapInstance(options) {
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
const dom = new JSDOM(`<!DOCTYPE html><div id="map"></div>`);
|
|
157
|
+
global.document = dom.window.document;
|
|
158
|
+
global.window = dom.window;
|
|
159
|
+
|
|
160
|
+
const mapInstance = new Map(options || { container: 'map' });
|
|
161
|
+
// Give time for async Config.getConfigICGC() and initTheMap to run
|
|
162
|
+
setTimeout(() => resolve(mapInstance), 50);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
167
|
+
// CONFIG GETTERS
|
|
168
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
169
|
+
describe('Config getter methods', () => {
|
|
170
|
+
let instance;
|
|
171
|
+
|
|
172
|
+
beforeEach(async () => {
|
|
173
|
+
vi.clearAllMocks();
|
|
174
|
+
instance = await createMapInstance();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('getConfigStyles returns undefined when Styles from local config is an object (not iterable)', () => {
|
|
178
|
+
// NOTE: The local mapicgc-config.json has Styles as an object {TOPO: url, ORTO: url,...}
|
|
179
|
+
// but getConfigStyles() uses for...of which requires an iterable (Array).
|
|
180
|
+
// The remote config may return Styles as an array. With the local fallback, this returns undefined.
|
|
181
|
+
const styles = instance.getConfigStyles();
|
|
182
|
+
expect(styles).toBeUndefined();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test('getConfigWMSLayers should return an array of WMS layer keys', () => {
|
|
186
|
+
const layers = instance.getConfigWMSLayers();
|
|
187
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
188
|
+
expect(layers.length).toBeGreaterThan(0);
|
|
189
|
+
layers.forEach((l) => expect(typeof l).toBe('string'));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('getConfigOrtoLayers should return an array of Orto layer keys', () => {
|
|
193
|
+
const layers = instance.getConfigOrtoLayers();
|
|
194
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
195
|
+
expect(layers.length).toBeGreaterThan(0);
|
|
196
|
+
layers.forEach((l) => expect(typeof l).toBe('string'));
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('getConfigVectorLayers should return an array of Vector layer keys', () => {
|
|
200
|
+
const layers = instance.getConfigVectorLayers();
|
|
201
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
202
|
+
layers.forEach((l) => expect(typeof l).toBe('string'));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('getConfigVectorAdminLayers should return an array of VectorAdmin layer keys', () => {
|
|
206
|
+
const layers = instance.getConfigVectorAdminLayers();
|
|
207
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
208
|
+
layers.forEach((l) => expect(typeof l).toBe('string'));
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('getConfigFGBAdminLayers should return an array of FGBAdmin layer keys', () => {
|
|
212
|
+
const layers = instance.getConfigFGBAdminLayers();
|
|
213
|
+
expect(Array.isArray(layers)).toBe(true);
|
|
214
|
+
layers.forEach((l) => expect(typeof l).toBe('string'));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('getConfigStyles returns undefined with local config (Styles is an object, not iterable array)', () => {
|
|
218
|
+
// The local config has Styles as an object but getConfigStyles uses for...of (needs array).
|
|
219
|
+
// Remote config likely provides an array. This documents the current local fallback behavior.
|
|
220
|
+
const styles = instance.getConfigStyles();
|
|
221
|
+
expect(styles).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('getConfigOrtoLayers should contain known Orto keys from config', () => {
|
|
225
|
+
const layers = instance.getConfigOrtoLayers();
|
|
226
|
+
const ortoKeys = Object.keys(mapicgcConfig.Layers.Orto);
|
|
227
|
+
ortoKeys.forEach((key) => {
|
|
228
|
+
expect(layers).toContain(key);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
234
|
+
// MAP INITIALIZATION (initTheMap)
|
|
235
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
236
|
+
describe('Map initialization', () => {
|
|
237
|
+
beforeEach(() => {
|
|
238
|
+
vi.clearAllMocks();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test('should create a maplibre-gl Map instance', async () => {
|
|
242
|
+
const instance = await createMapInstance();
|
|
243
|
+
expect(instance.map).toBeDefined();
|
|
244
|
+
expect(maplibregl.Map).toHaveBeenCalled();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('should pass default options when no options provided', async () => {
|
|
248
|
+
const instance = await createMapInstance();
|
|
249
|
+
expect(maplibregl.Map).toHaveBeenCalled();
|
|
250
|
+
const callArgs = maplibregl.Map.mock.calls[0][0];
|
|
251
|
+
expect(callArgs).toBeDefined();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('should merge user options with defaults', async () => {
|
|
255
|
+
const instance = await createMapInstance({
|
|
256
|
+
container: 'map',
|
|
257
|
+
zoom: 12,
|
|
258
|
+
});
|
|
259
|
+
const callArgs = maplibregl.Map.mock.calls[0][0];
|
|
260
|
+
expect(callArgs.zoom).toBe(12);
|
|
261
|
+
expect(callArgs.container).toBe('map');
|
|
262
|
+
// Default center should be applied from config
|
|
263
|
+
expect(callArgs.center).toBeDefined();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('should force maxPitch to 85', async () => {
|
|
267
|
+
const instance = await createMapInstance({ container: 'map', maxPitch: 60 });
|
|
268
|
+
const callArgs = maplibregl.Map.mock.calls[0][0];
|
|
269
|
+
expect(callArgs.maxPitch).toBe(85);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test('should force maplibreLogo to false', async () => {
|
|
273
|
+
const instance = await createMapInstance({ container: 'map' });
|
|
274
|
+
const callArgs = maplibregl.Map.mock.calls[0][0];
|
|
275
|
+
expect(callArgs.maplibreLogo).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('should force attributionControl to false', async () => {
|
|
279
|
+
const instance = await createMapInstance({ container: 'map' });
|
|
280
|
+
const callArgs = maplibregl.Map.mock.calls[0][0];
|
|
281
|
+
expect(callArgs.attributionControl).toBe(false);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('should call addAttributionControl on map load', async () => {
|
|
285
|
+
const instance = await createMapInstance();
|
|
286
|
+
// The on('load') handler calls addAttributionControl - verify AttributionControl was created
|
|
287
|
+
expect(maplibregl.AttributionControl).toHaveBeenCalled();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
292
|
+
// GETTER / SETTER DELEGATIONS
|
|
293
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
294
|
+
describe('Getter and setter delegations to maplibre-gl', () => {
|
|
295
|
+
let instance;
|
|
296
|
+
|
|
297
|
+
beforeEach(async () => {
|
|
298
|
+
vi.clearAllMocks();
|
|
299
|
+
instance = await createMapInstance();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('getZoom should delegate to map.getZoom', () => {
|
|
303
|
+
const zoom = instance.getZoom();
|
|
304
|
+
expect(mockMapInstance.getZoom).toHaveBeenCalled();
|
|
305
|
+
expect(zoom).toBe(7.5);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('getCenter should delegate to map.getCenter', () => {
|
|
309
|
+
const center = instance.getCenter();
|
|
310
|
+
expect(mockMapInstance.getCenter).toHaveBeenCalled();
|
|
311
|
+
expect(center).toEqual({ lng: 1.537786, lat: 41.837539 });
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test('getBearing should delegate to map.getBearing', () => {
|
|
315
|
+
const bearing = instance.getBearing();
|
|
316
|
+
expect(mockMapInstance.getBearing).toHaveBeenCalled();
|
|
317
|
+
expect(bearing).toBe(45);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('getBounds should delegate to map.getBounds', () => {
|
|
321
|
+
const bounds = instance.getBounds();
|
|
322
|
+
expect(mockMapInstance.getBounds).toHaveBeenCalled();
|
|
323
|
+
expect(bounds).toBeDefined();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test('getPitch should delegate to map.getPitch', () => {
|
|
327
|
+
const pitch = instance.getPitch();
|
|
328
|
+
expect(mockMapInstance.getPitch).toHaveBeenCalled();
|
|
329
|
+
expect(pitch).toBe(0);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('getMaxZoom should delegate to map.getMaxZoom', () => {
|
|
333
|
+
const maxZoom = instance.getMaxZoom();
|
|
334
|
+
expect(mockMapInstance.getMaxZoom).toHaveBeenCalled();
|
|
335
|
+
expect(maxZoom).toBe(18);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('getMinZoom should delegate to map.getMinZoom', () => {
|
|
339
|
+
const minZoom = instance.getMinZoom();
|
|
340
|
+
expect(mockMapInstance.getMinZoom).toHaveBeenCalled();
|
|
341
|
+
expect(minZoom).toBe(0);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test('getMaxPitch should delegate to map.getMaxPitch', () => {
|
|
345
|
+
expect(instance.getMaxPitch()).toBe(85);
|
|
346
|
+
expect(mockMapInstance.getMaxPitch).toHaveBeenCalled();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test('getContainer should delegate to map.getContainer', () => {
|
|
350
|
+
instance.getContainer();
|
|
351
|
+
expect(mockMapInstance.getContainer).toHaveBeenCalled();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('getStyle should delegate to map.getStyle', () => {
|
|
355
|
+
const style = instance.getStyle();
|
|
356
|
+
expect(mockMapInstance.getStyle).toHaveBeenCalled();
|
|
357
|
+
expect(style.name).toBe('ICGC mapa base gris');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('getSource should delegate to map.getSource', () => {
|
|
361
|
+
instance.getSource('my-source');
|
|
362
|
+
expect(mockMapInstance.getSource).toHaveBeenCalledWith('my-source');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test('getSprite should delegate to map.getSprite', () => {
|
|
366
|
+
instance.getSprite();
|
|
367
|
+
expect(mockMapInstance.getSprite).toHaveBeenCalled();
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test('getPadding should delegate to map.getPadding', () => {
|
|
371
|
+
instance.getPadding();
|
|
372
|
+
expect(mockMapInstance.getPadding).toHaveBeenCalled();
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test('getPixelRatio should delegate to map.getPixelRatio', () => {
|
|
376
|
+
expect(instance.getPixelRatio()).toBe(1);
|
|
377
|
+
expect(mockMapInstance.getPixelRatio).toHaveBeenCalled();
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test('areTilesLoaded should delegate to map.areTilesLoaded', () => {
|
|
381
|
+
expect(instance.areTilesLoaded()).toBe(true);
|
|
382
|
+
expect(mockMapInstance.areTilesLoaded).toHaveBeenCalled();
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('setBearing should delegate to map.setBearing', () => {
|
|
386
|
+
instance.setBearing(90, {});
|
|
387
|
+
expect(mockMapInstance.setBearing).toHaveBeenCalledWith(90, {});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test('setCenter should delegate to map.setCenter', () => {
|
|
391
|
+
instance.setCenter([2.0, 41.5], {});
|
|
392
|
+
expect(mockMapInstance.setCenter).toHaveBeenCalledWith([2.0, 41.5], {});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('setZoom should delegate to map.setZoom', () => {
|
|
396
|
+
instance.setZoom(14);
|
|
397
|
+
expect(mockMapInstance.setZoom).toHaveBeenCalled();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test('setPitch should delegate to map.setPitch', () => {
|
|
401
|
+
instance.setPitch(60);
|
|
402
|
+
expect(mockMapInstance.setPitch).toHaveBeenCalled();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test('setMaxBounds should delegate to map.setMaxBounds', () => {
|
|
406
|
+
const bounds = [[0, 40], [3, 43]];
|
|
407
|
+
instance.setMaxBounds(bounds);
|
|
408
|
+
expect(mockMapInstance.setMaxBounds).toHaveBeenCalledWith(bounds);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test('setMaxZoom should delegate to map.setMaxZoom', () => {
|
|
412
|
+
instance.setMaxZoom(20);
|
|
413
|
+
expect(mockMapInstance.setMaxZoom).toHaveBeenCalled();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test('setMinZoom should delegate to map.setMinZoom', () => {
|
|
417
|
+
instance.setMinZoom(5);
|
|
418
|
+
expect(mockMapInstance.setMinZoom).toHaveBeenCalled();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test('setTerrain should delegate to map.setTerrain', () => {
|
|
422
|
+
instance.setTerrain({ source: 'terrain' });
|
|
423
|
+
expect(mockMapInstance.setTerrain).toHaveBeenCalled();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('flyTo should delegate to map.flyTo', () => {
|
|
427
|
+
const options = { center: [2, 41], zoom: 15 };
|
|
428
|
+
instance.flyTo(options);
|
|
429
|
+
expect(mockMapInstance.flyTo).toHaveBeenCalledWith(options, undefined);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
test('easeTo should delegate to map.easeTo', () => {
|
|
433
|
+
const options = { center: [2, 41] };
|
|
434
|
+
instance.easeTo(options);
|
|
435
|
+
expect(mockMapInstance.easeTo).toHaveBeenCalledWith(options, undefined);
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test('fitBounds should delegate to map.fitBounds', () => {
|
|
439
|
+
const bounds = [[0, 40], [3, 43]];
|
|
440
|
+
instance.fitBounds(bounds);
|
|
441
|
+
expect(mockMapInstance.fitBounds).toHaveBeenCalled();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test('loaded should delegate to map.loaded', () => {
|
|
445
|
+
expect(instance.loaded()).toBe(true);
|
|
446
|
+
expect(mockMapInstance.loaded).toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
test('resize should delegate to map.resize', () => {
|
|
450
|
+
instance.resize();
|
|
451
|
+
expect(mockMapInstance.resize).toHaveBeenCalled();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('stop should delegate to map.stop', () => {
|
|
455
|
+
instance.stop();
|
|
456
|
+
expect(mockMapInstance.stop).toHaveBeenCalled();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
test('remove should delegate to map.remove', () => {
|
|
460
|
+
instance.remove();
|
|
461
|
+
expect(mockMapInstance.remove).toHaveBeenCalled();
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
466
|
+
// CONTROLS
|
|
467
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
468
|
+
describe('addControl', () => {
|
|
469
|
+
let instance;
|
|
470
|
+
|
|
471
|
+
beforeEach(async () => {
|
|
472
|
+
vi.clearAllMocks();
|
|
473
|
+
instance = await createMapInstance();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('should default position to "top-right" when not provided', () => {
|
|
477
|
+
const ctrl = { type: 'custom' };
|
|
478
|
+
instance.addControl(ctrl);
|
|
479
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(ctrl, 'top-right');
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test('should use the provided position', () => {
|
|
483
|
+
const ctrl = { type: 'custom' };
|
|
484
|
+
instance.addControl(ctrl, 'bottom-left');
|
|
485
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(ctrl, 'bottom-left');
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
describe('addAttributionControl', () => {
|
|
490
|
+
let instance;
|
|
491
|
+
|
|
492
|
+
beforeEach(async () => {
|
|
493
|
+
vi.clearAllMocks();
|
|
494
|
+
instance = await createMapInstance();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test('should always include "Fet amb MapICGC" when called without options', () => {
|
|
498
|
+
mockMapInstance.addControl.mockClear();
|
|
499
|
+
instance.addAttributionControl();
|
|
500
|
+
expect(maplibregl.AttributionControl).toHaveBeenCalled();
|
|
501
|
+
const lastCall = maplibregl.AttributionControl.mock.calls.at(-1)[0];
|
|
502
|
+
expect(lastCall.compact).toBe(true);
|
|
503
|
+
expect(lastCall.customAttribution).toContain('Fet amb MapICGC');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
test('should merge with a single custom attribution string', () => {
|
|
507
|
+
mockMapInstance.addControl.mockClear();
|
|
508
|
+
instance.addAttributionControl({ customAttribution: '© My Project' });
|
|
509
|
+
const lastCall = maplibregl.AttributionControl.mock.calls.at(-1)[0];
|
|
510
|
+
expect(Array.isArray(lastCall.customAttribution)).toBe(true);
|
|
511
|
+
expect(lastCall.customAttribution).toContain('© My Project');
|
|
512
|
+
// Must also contain MapICGC
|
|
513
|
+
expect(lastCall.customAttribution.some((a) => a.includes('Fet amb MapICGC'))).toBe(true);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test('should merge with an array of custom attributions', () => {
|
|
517
|
+
mockMapInstance.addControl.mockClear();
|
|
518
|
+
instance.addAttributionControl({ customAttribution: ['© A', '© B'] });
|
|
519
|
+
const lastCall = maplibregl.AttributionControl.mock.calls.at(-1)[0];
|
|
520
|
+
expect(Array.isArray(lastCall.customAttribution)).toBe(true);
|
|
521
|
+
expect(lastCall.customAttribution.length).toBe(3); // MapICGC + A + B
|
|
522
|
+
expect(lastCall.customAttribution.some((a) => a.includes('Fet amb MapICGC'))).toBe(true);
|
|
523
|
+
expect(lastCall.customAttribution).toContain('© A');
|
|
524
|
+
expect(lastCall.customAttribution).toContain('© B');
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
test('should pass position to map.addControl', () => {
|
|
528
|
+
mockMapInstance.addControl.mockClear();
|
|
529
|
+
instance.addAttributionControl({}, 'top-left');
|
|
530
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
531
|
+
expect.anything(),
|
|
532
|
+
'top-left'
|
|
533
|
+
);
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
describe('addNavigationControl', () => {
|
|
538
|
+
let instance;
|
|
539
|
+
|
|
540
|
+
beforeEach(async () => {
|
|
541
|
+
vi.clearAllMocks();
|
|
542
|
+
instance = await createMapInstance();
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test('should default position to "top-right"', () => {
|
|
546
|
+
mockMapInstance.addControl.mockClear();
|
|
547
|
+
instance.addNavigationControl({});
|
|
548
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
549
|
+
expect.anything(),
|
|
550
|
+
'top-right'
|
|
551
|
+
);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test('should use provided position', () => {
|
|
555
|
+
mockMapInstance.addControl.mockClear();
|
|
556
|
+
instance.addNavigationControl({}, 'bottom-right');
|
|
557
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
558
|
+
expect.anything(),
|
|
559
|
+
'bottom-right'
|
|
560
|
+
);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe('addScaleControl', () => {
|
|
565
|
+
let instance;
|
|
566
|
+
|
|
567
|
+
beforeEach(async () => {
|
|
568
|
+
vi.clearAllMocks();
|
|
569
|
+
instance = await createMapInstance();
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
test('should create a ScaleControl and add it to the map', () => {
|
|
573
|
+
mockMapInstance.addControl.mockClear();
|
|
574
|
+
instance.addScaleControl({ maxWidth: 100 }, 'bottom-left');
|
|
575
|
+
expect(maplibregl.ScaleControl).toHaveBeenCalledWith({ maxWidth: 100 });
|
|
576
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
577
|
+
expect.anything(),
|
|
578
|
+
'bottom-left'
|
|
579
|
+
);
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
describe('addGeolocateControl', () => {
|
|
584
|
+
let instance;
|
|
585
|
+
|
|
586
|
+
beforeEach(async () => {
|
|
587
|
+
vi.clearAllMocks();
|
|
588
|
+
instance = await createMapInstance();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('should default to high accuracy options when none provided', () => {
|
|
592
|
+
mockMapInstance.addControl.mockClear();
|
|
593
|
+
instance.addGeolocateControl();
|
|
594
|
+
expect(maplibregl.GeolocateControl).toHaveBeenCalled();
|
|
595
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
596
|
+
expect.anything(),
|
|
597
|
+
'top-right'
|
|
598
|
+
);
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
test('should use provided options and position', () => {
|
|
602
|
+
mockMapInstance.addControl.mockClear();
|
|
603
|
+
const opts = { positionOptions: { enableHighAccuracy: false } };
|
|
604
|
+
instance.addGeolocateControl(opts, 'bottom-left');
|
|
605
|
+
expect(maplibregl.GeolocateControl).toHaveBeenCalledWith(opts);
|
|
606
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
607
|
+
expect.anything(),
|
|
608
|
+
'bottom-left'
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
614
|
+
// LAYERS AND SOURCES
|
|
615
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
616
|
+
describe('Layers and sources', () => {
|
|
617
|
+
let instance;
|
|
618
|
+
|
|
619
|
+
beforeEach(async () => {
|
|
620
|
+
vi.clearAllMocks();
|
|
621
|
+
instance = await createMapInstance();
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
test('addLayer should delegate to map.addLayer', () => {
|
|
625
|
+
const layer = { id: 'test-layer', type: 'fill', source: 'test-source' };
|
|
626
|
+
instance.addLayer(layer);
|
|
627
|
+
expect(mockMapInstance.addLayer).toHaveBeenCalledWith(layer, undefined);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test('addLayer with layerIdOrder should delegate correctly', () => {
|
|
631
|
+
const layer = { id: 'test-layer', type: 'fill', source: 'test-source' };
|
|
632
|
+
instance.addLayer(layer, 'labels');
|
|
633
|
+
expect(mockMapInstance.addLayer).toHaveBeenCalledWith(layer, 'labels');
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
test('addSource should delegate to map.addSource', () => {
|
|
637
|
+
const source = { type: 'geojson', data: {} };
|
|
638
|
+
const result = instance.addSource('my-source', source);
|
|
639
|
+
expect(mockMapInstance.addSource).toHaveBeenCalledWith('my-source', source);
|
|
640
|
+
expect(result).toBe(instance); // returns this
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test('addImage should delegate to map.addImage and return this', () => {
|
|
644
|
+
const result = instance.addImage('icon', { data: [] }, { sdf: true });
|
|
645
|
+
expect(mockMapInstance.addImage).toHaveBeenCalledWith('icon', { data: [] }, { sdf: true });
|
|
646
|
+
expect(result).toBe(instance);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test('addSprite should delegate to map.addSprite and return this', () => {
|
|
650
|
+
const result = instance.addSprite('sprite-id', 'https://example.com/sprite', {});
|
|
651
|
+
expect(mockMapInstance.addSprite).toHaveBeenCalledWith('sprite-id', 'https://example.com/sprite', {});
|
|
652
|
+
expect(result).toBe(instance);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
test('removeLayer should delegate to map.removeLayer', () => {
|
|
656
|
+
instance.removeLayer('test-layer');
|
|
657
|
+
expect(mockMapInstance.removeLayer).toHaveBeenCalledWith('test-layer');
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test('removeSource should delegate to map.removeSource', () => {
|
|
661
|
+
instance.removeSource('test-source');
|
|
662
|
+
expect(mockMapInstance.removeSource).toHaveBeenCalledWith('test-source');
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test('removeControl should delegate to map.removeControl', () => {
|
|
666
|
+
const ctrl = { type: 'custom' };
|
|
667
|
+
instance.removeControl(ctrl);
|
|
668
|
+
expect(mockMapInstance.removeControl).toHaveBeenCalledWith(ctrl);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test('removeImage should delegate to map.removeImage', () => {
|
|
672
|
+
instance.removeImage('icon');
|
|
673
|
+
expect(mockMapInstance.removeImage).toHaveBeenCalledWith('icon');
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
678
|
+
// STYLE
|
|
679
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
680
|
+
describe('setStyle', () => {
|
|
681
|
+
let instance;
|
|
682
|
+
|
|
683
|
+
beforeEach(async () => {
|
|
684
|
+
vi.clearAllMocks();
|
|
685
|
+
instance = await createMapInstance();
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test('should delegate to map.setStyle with style only', () => {
|
|
689
|
+
instance.setStyle('https://example.com/style.json');
|
|
690
|
+
expect(mockMapInstance.setStyle).toHaveBeenCalledWith('https://example.com/style.json');
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test('should delegate to map.setStyle with style and options', () => {
|
|
694
|
+
const opts = { diff: true };
|
|
695
|
+
instance.setStyle('https://example.com/style.json', opts);
|
|
696
|
+
expect(mockMapInstance.setStyle).toHaveBeenCalledWith('https://example.com/style.json', opts);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test('should register a styledata listener', () => {
|
|
700
|
+
instance.setStyle('https://example.com/style.json');
|
|
701
|
+
expect(mockMapInstance.on).toHaveBeenCalledWith('styledata', expect.any(Function));
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
describe('setSky', () => {
|
|
706
|
+
let instance;
|
|
707
|
+
|
|
708
|
+
beforeEach(async () => {
|
|
709
|
+
vi.clearAllMocks();
|
|
710
|
+
instance = await createMapInstance();
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test('should delegate custom options to map.setSky', () => {
|
|
714
|
+
const skyOpts = { 'sky-color': '#ff0000' };
|
|
715
|
+
instance.setSky(skyOpts);
|
|
716
|
+
expect(mockMapInstance.setSky).toHaveBeenCalledWith(skyOpts);
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
test('should auto-detect orto sky when no options and style contains "orto"', () => {
|
|
720
|
+
mockMapInstance.style.stylesheet.id = 'icgc_orto';
|
|
721
|
+
instance.setSky();
|
|
722
|
+
expect(mockMapInstance.setSky).toHaveBeenCalled();
|
|
723
|
+
const arg = mockMapInstance.setSky.mock.calls[0][0];
|
|
724
|
+
expect(arg['sky-color']).toBe('#86bbd5');
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
test('should auto-detect dark sky when style contains "fosc"', () => {
|
|
728
|
+
mockMapInstance.style.stylesheet.id = 'icgc_fosc';
|
|
729
|
+
instance.setSky();
|
|
730
|
+
expect(mockMapInstance.setSky).toHaveBeenCalled();
|
|
731
|
+
const arg = mockMapInstance.setSky.mock.calls[0][0];
|
|
732
|
+
expect(arg['sky-color']).toBe('#232423');
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
737
|
+
// MARKERS
|
|
738
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
739
|
+
describe('addMarker', () => {
|
|
740
|
+
let instance;
|
|
741
|
+
|
|
742
|
+
beforeEach(async () => {
|
|
743
|
+
vi.clearAllMocks();
|
|
744
|
+
instance = await createMapInstance();
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test('should create a marker without popup when no text is provided', () => {
|
|
748
|
+
const marker = instance.addMarker({
|
|
749
|
+
options: { color: 'red' },
|
|
750
|
+
coord: [2.0, 41.5],
|
|
751
|
+
});
|
|
752
|
+
expect(maplibregl.Marker).toHaveBeenCalledWith({ color: 'red' });
|
|
753
|
+
expect(marker).toBeDefined();
|
|
754
|
+
// Popup should NOT have been created
|
|
755
|
+
expect(maplibregl.Popup).not.toHaveBeenCalled();
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
test('should create a marker with popup when text is provided', () => {
|
|
759
|
+
const marker = instance.addMarker({
|
|
760
|
+
options: { color: 'blue' },
|
|
761
|
+
coord: [2.0, 41.5],
|
|
762
|
+
text: '<h1>Hello</h1>',
|
|
763
|
+
textOffset: [0, -30],
|
|
764
|
+
});
|
|
765
|
+
expect(maplibregl.Marker).toHaveBeenCalledWith({ color: 'blue' });
|
|
766
|
+
expect(maplibregl.Popup).toHaveBeenCalledWith({ offset: [0, -30] });
|
|
767
|
+
expect(marker).toBeDefined();
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
test('should return null on error', () => {
|
|
771
|
+
maplibregl.Marker.mockImplementationOnce(() => {
|
|
772
|
+
throw new Error('test error');
|
|
773
|
+
});
|
|
774
|
+
const marker = instance.addMarker({
|
|
775
|
+
options: {},
|
|
776
|
+
coord: [2, 41],
|
|
777
|
+
});
|
|
778
|
+
expect(marker).toBeNull();
|
|
779
|
+
});
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
783
|
+
// MAP EVENTS
|
|
784
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
785
|
+
describe('on (event listener)', () => {
|
|
786
|
+
let instance;
|
|
787
|
+
|
|
788
|
+
beforeEach(async () => {
|
|
789
|
+
vi.clearAllMocks();
|
|
790
|
+
instance = await createMapInstance();
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
test('should call map.on inside a setTimeout (delayed)', async () => {
|
|
794
|
+
const callback = vi.fn();
|
|
795
|
+
instance.on('click', callback);
|
|
796
|
+
|
|
797
|
+
// The on() wraps the call in setTimeout(…, 100), so we need to wait
|
|
798
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
799
|
+
|
|
800
|
+
expect(mockMapInstance.on).toHaveBeenCalledWith('click', callback);
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
805
|
+
// EXPORT CONTROL
|
|
806
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
807
|
+
describe('addExportControl', () => {
|
|
808
|
+
let instance;
|
|
809
|
+
|
|
810
|
+
beforeEach(async () => {
|
|
811
|
+
vi.clearAllMocks();
|
|
812
|
+
instance = await createMapInstance();
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
test('should use default options when none provided', async () => {
|
|
816
|
+
const { MaplibreExportControl } = await import('@watergis/maplibre-gl-export');
|
|
817
|
+
mockMapInstance.addControl.mockClear();
|
|
818
|
+
instance.addExportControl();
|
|
819
|
+
expect(MaplibreExportControl).toHaveBeenCalled();
|
|
820
|
+
expect(mockMapInstance.addControl).toHaveBeenCalledWith(
|
|
821
|
+
expect.anything(),
|
|
822
|
+
'top-right'
|
|
823
|
+
);
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
test('should swap options/position when first arg is a string', async () => {
|
|
827
|
+
const { MaplibreExportControl } = await import('@watergis/maplibre-gl-export');
|
|
828
|
+
mockMapInstance.addControl.mockClear();
|
|
829
|
+
instance.addExportControl('bottom-left');
|
|
830
|
+
expect(mockMapInstance.addControl).toHaveBeenCalled();
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
835
|
+
// QUERYING
|
|
836
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
837
|
+
describe('Querying features', () => {
|
|
838
|
+
let instance;
|
|
839
|
+
|
|
840
|
+
beforeEach(async () => {
|
|
841
|
+
vi.clearAllMocks();
|
|
842
|
+
instance = await createMapInstance();
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
test('queryRenderedFeatures should delegate to map.queryRenderedFeatures', () => {
|
|
846
|
+
instance.queryRenderedFeatures([100, 200]);
|
|
847
|
+
expect(mockMapInstance.queryRenderedFeatures).toHaveBeenCalled();
|
|
848
|
+
expect(mockMapInstance.queryRenderedFeatures.mock.calls[0][0]).toEqual([100, 200]);
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
test('querySourceFeatures should delegate to map.querySourceFeatures', () => {
|
|
852
|
+
instance.querySourceFeatures('my-source');
|
|
853
|
+
expect(mockMapInstance.querySourceFeatures).toHaveBeenCalled();
|
|
854
|
+
expect(mockMapInstance.querySourceFeatures.mock.calls[0][0]).toBe('my-source');
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
859
|
+
// ERROR HANDLING
|
|
860
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
861
|
+
describe('Error handling', () => {
|
|
862
|
+
let instance;
|
|
863
|
+
|
|
864
|
+
beforeEach(async () => {
|
|
865
|
+
vi.clearAllMocks();
|
|
866
|
+
instance = await createMapInstance();
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
test('addLayer should not throw on error', () => {
|
|
870
|
+
mockMapInstance.addLayer.mockImplementationOnce(() => {
|
|
871
|
+
throw new Error('layer error');
|
|
872
|
+
});
|
|
873
|
+
expect(() => instance.addLayer({ id: 'bad' })).not.toThrow();
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
test('removeLayer should not throw on error', () => {
|
|
877
|
+
mockMapInstance.removeLayer.mockImplementationOnce(() => {
|
|
878
|
+
throw new Error('remove error');
|
|
879
|
+
});
|
|
880
|
+
expect(() => instance.removeLayer('nonexistent')).not.toThrow();
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
test('removeSource should not throw on error', () => {
|
|
884
|
+
mockMapInstance.removeSource.mockImplementationOnce(() => {
|
|
885
|
+
throw new Error('remove error');
|
|
886
|
+
});
|
|
887
|
+
expect(() => instance.removeSource('nonexistent')).not.toThrow();
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
test('setStyle should not throw on error', () => {
|
|
891
|
+
mockMapInstance.setStyle.mockImplementationOnce(() => {
|
|
892
|
+
throw new Error('style error');
|
|
893
|
+
});
|
|
894
|
+
expect(() => instance.setStyle('bad-url')).not.toThrow();
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
test('addControl should not throw on error', () => {
|
|
898
|
+
mockMapInstance.addControl.mockImplementationOnce(() => {
|
|
899
|
+
throw new Error('ctrl error');
|
|
900
|
+
});
|
|
901
|
+
expect(() => instance.addControl({}, 'top-right')).not.toThrow();
|
|
902
|
+
});
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
906
|
+
// ADDITIONAL DELEGATIONS (from old MapStyle.test.js / Map.test.js)
|
|
907
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
908
|
+
describe('Additional delegations', () => {
|
|
909
|
+
let instance;
|
|
910
|
+
|
|
911
|
+
beforeEach(async () => {
|
|
912
|
+
vi.clearAllMocks();
|
|
913
|
+
instance = await createMapInstance();
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
test('hasControl should delegate to map.hasControl', () => {
|
|
917
|
+
const ctrl = { type: 'custom' };
|
|
918
|
+
const result = instance.hasControl(ctrl);
|
|
919
|
+
expect(mockMapInstance.hasControl).toHaveBeenCalledWith(ctrl);
|
|
920
|
+
expect(result).toBe(true);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test('setEventedParent should delegate to map.setEventedParent', () => {
|
|
924
|
+
const parent = { id: 'parent' };
|
|
925
|
+
const data = { key: 'value' };
|
|
926
|
+
instance.setEventedParent(parent, data);
|
|
927
|
+
expect(mockMapInstance.setEventedParent).toHaveBeenCalledWith(parent, data);
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
test('setFeatureState should delegate to map.setFeatureState', () => {
|
|
931
|
+
const feature = { source: 'src', id: 1 };
|
|
932
|
+
const state = { selected: true };
|
|
933
|
+
instance.setFeatureState(feature, state);
|
|
934
|
+
expect(mockMapInstance.setFeatureState).toHaveBeenCalledWith(feature, state);
|
|
935
|
+
});
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
939
|
+
// _dealOrto3dStyle
|
|
940
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
941
|
+
describe('_dealOrto3dStyle', () => {
|
|
942
|
+
let instance;
|
|
943
|
+
|
|
944
|
+
beforeEach(async () => {
|
|
945
|
+
vi.clearAllMocks();
|
|
946
|
+
instance = await createMapInstance();
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
test('should set orto3d properties when name is "orto3d"', () => {
|
|
950
|
+
// hasControl returns false so addControl path runs
|
|
951
|
+
mockMapInstance.hasControl.mockReturnValue(false);
|
|
952
|
+
|
|
953
|
+
instance._dealOrto3dStyle('orto3d');
|
|
954
|
+
|
|
955
|
+
expect(mockMapInstance.setMaxZoom).toHaveBeenCalledWith(18.8);
|
|
956
|
+
expect(mockMapInstance.easeTo).toHaveBeenCalledWith({ pitch: 45 });
|
|
957
|
+
expect(mockMapInstance.setTerrain).toHaveBeenCalled();
|
|
958
|
+
expect(mockMapInstance.setSky).toHaveBeenCalledWith({
|
|
959
|
+
'sky-color': '#86bbd5',
|
|
960
|
+
'sky-horizon-blend': 0.3,
|
|
961
|
+
'horizon-color': '#ffffff33',
|
|
962
|
+
'horizon-fog-blend': 0.1,
|
|
963
|
+
'fog-ground-blend': 0.75,
|
|
964
|
+
'fog-color': '#c5d6d6',
|
|
965
|
+
});
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
test('should not throw when name is not "orto3d"', () => {
|
|
969
|
+
expect(() => instance._dealOrto3dStyle('topo')).not.toThrow();
|
|
970
|
+
});
|
|
971
|
+
});
|