1 | import Style from './style';
|
2 | import SourceCache from '../source/source_cache';
|
3 | import StyleLayer from './style_layer';
|
4 | import Transform from '../geo/transform';
|
5 | import {extend} from '../util/util';
|
6 | import {RequestManager} from '../util/request_manager';
|
7 | import {Event, Evented} from '../util/evented';
|
8 | import {
|
9 | setRTLTextPlugin,
|
10 | clearRTLTextPlugin,
|
11 | evented as rtlTextPluginEvented
|
12 | } from '../source/rtl_text_plugin';
|
13 | import browser from '../util/browser';
|
14 | import {OverscaledTileID} from '../source/tile_id';
|
15 | import {fakeXhr, fakeServer} from 'nise';
|
16 | import {WorkerGlobalScopeInterface} from '../util/web_worker';
|
17 | import EvaluationParameters from './evaluation_parameters';
|
18 | import {LayerSpecification, GeoJSONSourceSpecification, FilterSpecification, SourceSpecification} from '../style-spec/types.g';
|
19 | import {SourceClass} from '../source/source';
|
20 | import GeoJSONSource from '../source/geojson_source';
|
21 |
|
22 | function createStyleJSON(properties?) {
|
23 | return extend({
|
24 | 'version': 8,
|
25 | 'sources': {},
|
26 | 'layers': []
|
27 | }, properties);
|
28 | }
|
29 |
|
30 | function createSource() {
|
31 | return {
|
32 | type: 'vector',
|
33 | minzoom: 1,
|
34 | maxzoom: 10,
|
35 | attribution: 'MapLibre',
|
36 | tiles: ['http://example.com/{z}/{x}/{y}.png']
|
37 | } as any as SourceSpecification;
|
38 | }
|
39 |
|
40 | function createGeoJSONSource() {
|
41 | return {
|
42 | 'type': 'geojson',
|
43 | 'data': {
|
44 | 'type': 'FeatureCollection',
|
45 | 'features': []
|
46 | }
|
47 | };
|
48 | }
|
49 |
|
50 | class StubMap extends Evented {
|
51 | style: Style;
|
52 | transform: Transform;
|
53 | private _requestManager: RequestManager;
|
54 |
|
55 | constructor() {
|
56 | super();
|
57 | this.transform = new Transform();
|
58 | this._requestManager = new RequestManager();
|
59 | }
|
60 |
|
61 | _getMapId() {
|
62 | return 1;
|
63 | }
|
64 |
|
65 | getPixelRatio() {
|
66 | return 1;
|
67 | }
|
68 | }
|
69 |
|
70 | const getStubMap = () => new StubMap() as any;
|
71 |
|
72 | function createStyle(map = getStubMap()) {
|
73 | const style = new Style(map);
|
74 | map.style = style;
|
75 | return style;
|
76 | }
|
77 |
|
78 | let sinonFakeXMLServer;
|
79 | let sinonFakeServer;
|
80 | let _self;
|
81 | let mockConsoleError;
|
82 |
|
83 | beforeEach(() => {
|
84 | global.fetch = null;
|
85 | sinonFakeServer = fakeServer.create();
|
86 | sinonFakeXMLServer = fakeXhr.useFakeXMLHttpRequest();
|
87 |
|
88 | _self = {
|
89 | addEventListener() {}
|
90 | } as any as WorkerGlobalScopeInterface & typeof globalThis;
|
91 | global.self = _self;
|
92 |
|
93 | mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => { });
|
94 | });
|
95 |
|
96 | afterEach(() => {
|
97 | sinonFakeXMLServer.restore();
|
98 | sinonFakeServer.restore();
|
99 |
|
100 | global.self = undefined;
|
101 |
|
102 | mockConsoleError.mockRestore();
|
103 | });
|
104 |
|
105 | describe('Style', () => {
|
106 | test('registers plugin state change listener', () => {
|
107 | clearRTLTextPlugin();
|
108 |
|
109 | jest.spyOn(Style, 'registerForPluginStateChange');
|
110 | const style = new Style(getStubMap());
|
111 | const mockStyleDispatcherBroadcast = jest.spyOn(style.dispatcher, 'broadcast');
|
112 | expect(Style.registerForPluginStateChange).toHaveBeenCalledTimes(1);
|
113 |
|
114 | setRTLTextPlugin('/plugin.js', undefined);
|
115 | expect(mockStyleDispatcherBroadcast.mock.calls[0][0]).toBe('syncRTLPluginState');
|
116 | expect(mockStyleDispatcherBroadcast.mock.calls[0][1]).toEqual({
|
117 | pluginStatus: 'deferred',
|
118 | pluginURL: 'http://localhost/plugin.js',
|
119 | });
|
120 | });
|
121 |
|
122 | test('loads plugin immediately if already registered', done => {
|
123 | clearRTLTextPlugin();
|
124 | sinonFakeServer.respondWith('/plugin.js', 'doesn\'t matter');
|
125 | setRTLTextPlugin('/plugin.js', (error) => {
|
126 | expect(error).toMatch(/Cannot set the state of the rtl-text-plugin when not in the web-worker context/);
|
127 | done();
|
128 | });
|
129 | sinonFakeServer.respond();
|
130 | new Style(createStyleJSON());
|
131 | });
|
132 | });
|
133 |
|
134 | describe('Style#loadURL', () => {
|
135 | test('fires "dataloading"', () => {
|
136 | const style = new Style(getStubMap());
|
137 | const spy = jest.fn();
|
138 |
|
139 | style.on('dataloading', spy);
|
140 | style.loadURL('style.json');
|
141 |
|
142 | expect(spy).toHaveBeenCalledTimes(1);
|
143 | expect(spy.mock.calls[0][0].target).toBe(style);
|
144 | expect(spy.mock.calls[0][0].dataType).toBe('style');
|
145 | });
|
146 |
|
147 | test('transforms style URL before request', () => {
|
148 | const map = getStubMap();
|
149 | const spy = jest.spyOn(map._requestManager, 'transformRequest');
|
150 |
|
151 | const style = new Style(map);
|
152 | style.loadURL('style.json');
|
153 |
|
154 | expect(spy).toHaveBeenCalledTimes(1);
|
155 | expect(spy.mock.calls[0][0]).toBe('style.json');
|
156 | expect(spy.mock.calls[0][1]).toBe('Style');
|
157 | });
|
158 |
|
159 | test('validates the style', done => {
|
160 | const style = new Style(getStubMap());
|
161 |
|
162 | style.on('error', ({error}) => {
|
163 | expect(error).toBeTruthy();
|
164 | expect(error.message).toMatch(/version/);
|
165 | done();
|
166 | });
|
167 |
|
168 | style.loadURL('style.json');
|
169 | sinonFakeServer.respondWith(JSON.stringify(createStyleJSON({version: 'invalid'})));
|
170 | sinonFakeServer.respond();
|
171 | });
|
172 |
|
173 | test('cancels pending requests if removed', () => {
|
174 | const style = new Style(getStubMap());
|
175 | style.loadURL('style.json');
|
176 | style._remove();
|
177 | expect(sinonFakeServer.lastRequest.aborted).toBe(true);
|
178 | });
|
179 | });
|
180 |
|
181 | describe('Style#loadJSON', () => {
|
182 | test('fires "dataloading" (synchronously)', () => {
|
183 | const style = new Style(getStubMap());
|
184 | const spy = jest.fn();
|
185 |
|
186 | style.on('dataloading', spy);
|
187 | style.loadJSON(createStyleJSON());
|
188 |
|
189 | expect(spy).toHaveBeenCalledTimes(1);
|
190 | expect(spy.mock.calls[0][0].target).toBe(style);
|
191 | expect(spy.mock.calls[0][0].dataType).toBe('style');
|
192 | });
|
193 |
|
194 | test('fires "data" (asynchronously)', done => {
|
195 | const style = new Style(getStubMap());
|
196 |
|
197 | style.loadJSON(createStyleJSON());
|
198 |
|
199 | style.on('data', (e) => {
|
200 | expect(e.target).toBe(style);
|
201 | expect(e.dataType).toBe('style');
|
202 | done();
|
203 | });
|
204 | });
|
205 |
|
206 | test('fires "data" when the sprite finishes loading', done => {
|
207 |
|
208 |
|
209 |
|
210 | jest.spyOn(browser, 'getImageData');
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 | const requests = [];
|
217 | sinonFakeXMLServer.onCreate = req => { requests.push(req); };
|
218 | const respond = () => {
|
219 | let req = requests.find(req => req.url === 'http://example.com/sprite.png');
|
220 | req.setStatus(200);
|
221 | req.response = new ArrayBuffer(8);
|
222 | req.onload();
|
223 |
|
224 | req = requests.find(req => req.url === 'http://example.com/sprite.json');
|
225 | req.setStatus(200);
|
226 | req.response = '{}';
|
227 | req.onload();
|
228 | };
|
229 |
|
230 | const style = new Style(getStubMap());
|
231 |
|
232 | style.loadJSON({
|
233 | 'version': 8,
|
234 | 'sources': {},
|
235 | 'layers': [],
|
236 | 'sprite': 'http://example.com/sprite'
|
237 | });
|
238 |
|
239 | style.once('error', (e) => expect(e).toBeFalsy());
|
240 |
|
241 | style.once('data', (e) => {
|
242 | expect(e.target).toBe(style);
|
243 | expect(e.dataType).toBe('style');
|
244 |
|
245 | style.once('data', (e) => {
|
246 | expect(e.target).toBe(style);
|
247 | expect(e.dataType).toBe('style');
|
248 | done();
|
249 | });
|
250 |
|
251 | respond();
|
252 | });
|
253 | });
|
254 |
|
255 | test('validates the style', done => {
|
256 | const style = new Style(getStubMap());
|
257 |
|
258 | style.on('error', ({error}) => {
|
259 | expect(error).toBeTruthy();
|
260 | expect(error.message).toMatch(/version/);
|
261 | done();
|
262 | });
|
263 |
|
264 | style.loadJSON(createStyleJSON({version: 'invalid'}));
|
265 | });
|
266 |
|
267 | test('creates sources', done => {
|
268 | const style = createStyle();
|
269 |
|
270 | style.on('style.load', () => {
|
271 | expect(style.sourceCaches['mapLibre'] instanceof SourceCache).toBeTruthy();
|
272 | done();
|
273 | });
|
274 |
|
275 | style.loadJSON(extend(createStyleJSON(), {
|
276 | 'sources': {
|
277 | 'mapLibre': {
|
278 | 'type': 'vector',
|
279 | 'tiles': []
|
280 | }
|
281 | }
|
282 | }));
|
283 | });
|
284 |
|
285 | test('creates layers', done => {
|
286 | const style = createStyle();
|
287 |
|
288 | style.on('style.load', () => {
|
289 | expect(style.getLayer('fill') instanceof StyleLayer).toBeTruthy();
|
290 | done();
|
291 | });
|
292 |
|
293 | style.loadJSON({
|
294 | 'version': 8,
|
295 | 'sources': {
|
296 | 'foo': {
|
297 | 'type': 'vector'
|
298 | }
|
299 | },
|
300 | 'layers': [{
|
301 | 'id': 'fill',
|
302 | 'source': 'foo',
|
303 | 'source-layer': 'source-layer',
|
304 | 'type': 'fill'
|
305 | }]
|
306 | });
|
307 | });
|
308 |
|
309 | test('transforms sprite json and image URLs before request', done => {
|
310 | const map = getStubMap();
|
311 | const transformSpy = jest.spyOn(map._requestManager, 'transformRequest');
|
312 | const style = createStyle(map);
|
313 |
|
314 | style.on('style.load', () => {
|
315 | expect(transformSpy).toHaveBeenCalledTimes(2);
|
316 | expect(transformSpy.mock.calls[0][0]).toBe('http://example.com/sprites/bright-v8.json');
|
317 | expect(transformSpy.mock.calls[0][1]).toBe('SpriteJSON');
|
318 | expect(transformSpy.mock.calls[1][0]).toBe('http://example.com/sprites/bright-v8.png');
|
319 | expect(transformSpy.mock.calls[1][1]).toBe('SpriteImage');
|
320 | done();
|
321 | });
|
322 |
|
323 | style.loadJSON(extend(createStyleJSON(), {
|
324 | 'sprite': 'http://example.com/sprites/bright-v8'
|
325 | }));
|
326 | });
|
327 |
|
328 | test('emits an error on non-existant vector source layer', done => {
|
329 | const style = createStyle();
|
330 | style.loadJSON(createStyleJSON({
|
331 | sources: {
|
332 | '-source-id-': {type: 'vector', tiles: []}
|
333 | },
|
334 | layers: []
|
335 | }));
|
336 |
|
337 | style.on('style.load', () => {
|
338 | style.removeSource('-source-id-');
|
339 |
|
340 | const source = createSource();
|
341 | source['vector_layers'] = [{id: 'green'}];
|
342 | style.addSource('-source-id-', source);
|
343 | style.addLayer({
|
344 | 'id': '-layer-id-',
|
345 | 'type': 'circle',
|
346 | 'source': '-source-id-',
|
347 | 'source-layer': '-source-layer-'
|
348 | });
|
349 | style.update({} as EvaluationParameters);
|
350 | });
|
351 |
|
352 | style.on('error', (event) => {
|
353 | const err = event.error;
|
354 | expect(err).toBeTruthy();
|
355 | expect(err.toString().indexOf('-source-layer-') !== -1).toBeTruthy();
|
356 | expect(err.toString().indexOf('-source-id-') !== -1).toBeTruthy();
|
357 | expect(err.toString().indexOf('-layer-id-') !== -1).toBeTruthy();
|
358 |
|
359 | done();
|
360 | });
|
361 | });
|
362 |
|
363 | test('sets up layer event forwarding', done => {
|
364 | const style = new Style(getStubMap());
|
365 | style.loadJSON(createStyleJSON({
|
366 | layers: [{
|
367 | id: 'background',
|
368 | type: 'background'
|
369 | }]
|
370 | }));
|
371 |
|
372 | style.on('error', (e) => {
|
373 | expect(e.layer).toEqual({id: 'background'});
|
374 | expect(e.mapLibre).toBeTruthy();
|
375 | done();
|
376 | });
|
377 |
|
378 | style.on('style.load', () => {
|
379 | style._layers.background.fire(new Event('error', {mapLibre: true}));
|
380 | });
|
381 | });
|
382 | });
|
383 |
|
384 | describe('Style#_remove', () => {
|
385 | test('removes cache sources and clears their tiles', done => {
|
386 | const style = new Style(getStubMap());
|
387 | style.loadJSON(createStyleJSON({
|
388 | sources: {'source-id': createGeoJSONSource()}
|
389 | }));
|
390 |
|
391 | style.on('style.load', () => {
|
392 | const sourceCache = style.sourceCaches['source-id'];
|
393 | jest.spyOn(sourceCache, 'setEventedParent');
|
394 | jest.spyOn(sourceCache, 'onRemove');
|
395 | jest.spyOn(sourceCache, 'clearTiles');
|
396 |
|
397 | style._remove();
|
398 |
|
399 | expect(sourceCache.setEventedParent).toHaveBeenCalledWith(null);
|
400 | expect(sourceCache.onRemove).toHaveBeenCalledWith(style.map);
|
401 | expect(sourceCache.clearTiles).toHaveBeenCalled();
|
402 |
|
403 | done();
|
404 | });
|
405 | });
|
406 |
|
407 | test('deregisters plugin listener', done => {
|
408 | const style = new Style(getStubMap());
|
409 | style.loadJSON(createStyleJSON());
|
410 | const mockStyleDispatcherBroadcast = jest.spyOn(style.dispatcher, 'broadcast');
|
411 |
|
412 | style.on('style.load', () => {
|
413 | style._remove();
|
414 |
|
415 | rtlTextPluginEvented.fire(new Event('pluginStateChange'));
|
416 | expect(mockStyleDispatcherBroadcast).not.toHaveBeenCalledWith('syncRTLPluginState');
|
417 | done();
|
418 | });
|
419 | });
|
420 | });
|
421 |
|
422 | describe('Style#update', () => {
|
423 | test('on error', done => {
|
424 | const style = createStyle();
|
425 | style.loadJSON({
|
426 | 'version': 8,
|
427 | 'sources': {
|
428 | 'source': {
|
429 | 'type': 'vector'
|
430 | }
|
431 | },
|
432 | 'layers': [{
|
433 | 'id': 'second',
|
434 | 'source': 'source',
|
435 | 'source-layer': 'source-layer',
|
436 | 'type': 'fill'
|
437 | }]
|
438 | });
|
439 |
|
440 | style.on('error', (error) => { expect(error).toBeFalsy(); });
|
441 |
|
442 | style.on('style.load', () => {
|
443 | style.addLayer({id: 'first', source: 'source', type: 'fill', 'source-layer': 'source-layer'}, 'second');
|
444 | style.addLayer({id: 'third', source: 'source', type: 'fill', 'source-layer': 'source-layer'});
|
445 | style.removeLayer('second');
|
446 |
|
447 | style.dispatcher.broadcast = function(key, value) {
|
448 | expect(key).toBe('updateLayers');
|
449 | expect(value['layers'].map((layer) => { return layer.id; })).toEqual(['first', 'third']);
|
450 | expect(value['removedIds']).toEqual(['second']);
|
451 | done();
|
452 | };
|
453 |
|
454 | style.update({} as EvaluationParameters);
|
455 | });
|
456 | });
|
457 | });
|
458 |
|
459 | describe('Style#setState', () => {
|
460 | test('throw before loaded', () => {
|
461 | const style = new Style(getStubMap());
|
462 | expect(() => style.setState(createStyleJSON())).toThrow(/load/i);
|
463 | });
|
464 |
|
465 | test('do nothing if there are no changes', done => {
|
466 | const style = createStyle();
|
467 | style.loadJSON(createStyleJSON());
|
468 | jest.spyOn(style, 'addLayer').mockImplementation(() => done('test failed'));
|
469 | jest.spyOn(style, 'removeLayer').mockImplementation(() => done('test failed'));
|
470 | jest.spyOn(style, 'setPaintProperty').mockImplementation(() => done('test failed'));
|
471 | jest.spyOn(style, 'setLayoutProperty').mockImplementation(() => done('test failed'));
|
472 | jest.spyOn(style, 'setFilter').mockImplementation(() => done('test failed'));
|
473 | jest.spyOn(style, 'addSource').mockImplementation(() => done('test failed'));
|
474 | jest.spyOn(style, 'removeSource').mockImplementation(() => done('test failed'));
|
475 | jest.spyOn(style, 'setGeoJSONSourceData').mockImplementation(() => done('test failed'));
|
476 | jest.spyOn(style, 'setLayerZoomRange').mockImplementation(() => done('test failed'));
|
477 | jest.spyOn(style, 'setLight').mockImplementation(() => done('test failed'));
|
478 | style.on('style.load', () => {
|
479 | const didChange = style.setState(createStyleJSON());
|
480 | expect(didChange).toBeFalsy();
|
481 | done();
|
482 | });
|
483 | });
|
484 |
|
485 | test('Issue #3893: compare new source options against originally provided options rather than normalized properties', done => {
|
486 | sinonFakeServer.respondWith('/tilejson.json', JSON.stringify({
|
487 | tiles: ['http://tiles.server']
|
488 | }));
|
489 | const initial = createStyleJSON();
|
490 | initial.sources.mySource = {
|
491 | type: 'raster',
|
492 | url: '/tilejson.json'
|
493 | };
|
494 | const style = new Style(getStubMap());
|
495 | style.loadJSON(initial);
|
496 | style.on('style.load', () => {
|
497 | jest.spyOn(style, 'removeSource').mockImplementation(() => done('test failed: removeSource called'));
|
498 | jest.spyOn(style, 'addSource').mockImplementation(() => done('test failed: addSource called'));
|
499 | style.setState(initial);
|
500 | done();
|
501 | });
|
502 | sinonFakeServer.respond();
|
503 | });
|
504 |
|
505 | test('return true if there is a change', done => {
|
506 | const initialState = createStyleJSON();
|
507 | const nextState = createStyleJSON({
|
508 | sources: {
|
509 | foo: {
|
510 | type: 'geojson',
|
511 | data: {type: 'FeatureCollection', features: []}
|
512 | }
|
513 | }
|
514 | });
|
515 |
|
516 | const style = new Style(getStubMap());
|
517 | style.loadJSON(initialState);
|
518 | style.on('style.load', () => {
|
519 | const didChange = style.setState(nextState);
|
520 | expect(didChange).toBeTruthy();
|
521 | expect(style.stylesheet).toEqual(nextState);
|
522 | done();
|
523 | });
|
524 | });
|
525 |
|
526 | test('sets GeoJSON source data if different', done => {
|
527 | const initialState = createStyleJSON({
|
528 | 'sources': {'source-id': createGeoJSONSource()}
|
529 | });
|
530 |
|
531 | const geoJSONSourceData = {
|
532 | 'type': 'FeatureCollection',
|
533 | 'features': [
|
534 | {
|
535 | 'type': 'Feature',
|
536 | 'geometry': {
|
537 | 'type': 'Point',
|
538 | 'coordinates': [125.6, 10.1]
|
539 | }
|
540 | }
|
541 | ]
|
542 | };
|
543 |
|
544 | const nextState = createStyleJSON({
|
545 | 'sources': {
|
546 | 'source-id': {
|
547 | 'type': 'geojson',
|
548 | 'data': geoJSONSourceData
|
549 | }
|
550 | }
|
551 | });
|
552 |
|
553 | const style = new Style(getStubMap());
|
554 | style.loadJSON(initialState);
|
555 |
|
556 | style.on('style.load', () => {
|
557 | const geoJSONSource = style.sourceCaches['source-id'].getSource() as GeoJSONSource;
|
558 | const mockStyleSetGeoJSONSourceDate = jest.spyOn(style, 'setGeoJSONSourceData');
|
559 | const mockGeoJSONSourceSetData = jest.spyOn(geoJSONSource, 'setData');
|
560 | const didChange = style.setState(nextState);
|
561 |
|
562 | expect(mockStyleSetGeoJSONSourceDate).toHaveBeenCalledWith('source-id', geoJSONSourceData);
|
563 | expect(mockGeoJSONSourceSetData).toHaveBeenCalledWith(geoJSONSourceData);
|
564 | expect(didChange).toBeTruthy();
|
565 | expect(style.stylesheet).toEqual(nextState);
|
566 | done();
|
567 | });
|
568 | });
|
569 | });
|
570 |
|
571 | describe('Style#addSource', () => {
|
572 | test('throw before loaded', () => {
|
573 | const style = new Style(getStubMap());
|
574 | expect(() => style.addSource('source-id', createSource())).toThrow(/load/i);
|
575 | });
|
576 |
|
577 | test('throw if missing source type', done => {
|
578 | const style = new Style(getStubMap());
|
579 | style.loadJSON(createStyleJSON());
|
580 |
|
581 | const source = createSource();
|
582 | delete source.type;
|
583 |
|
584 | style.on('style.load', () => {
|
585 | expect(() => style.addSource('source-id', source)).toThrow(/type/i);
|
586 | done();
|
587 | });
|
588 | });
|
589 |
|
590 | test('fires "data" event', done => {
|
591 | const style = createStyle();
|
592 | style.loadJSON(createStyleJSON());
|
593 | const source = createSource();
|
594 | style.once('data', () => { done(); });
|
595 | style.on('style.load', () => {
|
596 | style.addSource('source-id', source);
|
597 | style.update({} as EvaluationParameters);
|
598 | });
|
599 | });
|
600 |
|
601 | test('throws on duplicates', done => {
|
602 | const style = createStyle();
|
603 | style.loadJSON(createStyleJSON());
|
604 | const source = createSource();
|
605 | style.on('style.load', () => {
|
606 | style.addSource('source-id', source);
|
607 | expect(() => {
|
608 | style.addSource('source-id', source);
|
609 | }).toThrow(/Source "source-id" already exists./);
|
610 | done();
|
611 | });
|
612 | });
|
613 |
|
614 | test('sets up source event forwarding', done => {
|
615 | let one = 0;
|
616 | let two = 0;
|
617 | let three = 0;
|
618 | let four = 0;
|
619 | const checkVisited = () => {
|
620 | if (one === 1 && two === 1 && three === 1 && four === 1) {
|
621 | done();
|
622 | }
|
623 | };
|
624 | const style = createStyle();
|
625 | style.loadJSON(createStyleJSON({
|
626 | layers: [{
|
627 | id: 'background',
|
628 | type: 'background'
|
629 | }]
|
630 | }));
|
631 | const source = createSource();
|
632 |
|
633 | style.on('style.load', () => {
|
634 | style.on('error', () => {
|
635 | one = 1;
|
636 | checkVisited();
|
637 | });
|
638 | style.on('data', (e) => {
|
639 | if (e.sourceDataType === 'metadata' && e.dataType === 'source') {
|
640 | two = 1;
|
641 | checkVisited();
|
642 | } else if (e.sourceDataType === 'content' && e.dataType === 'source') {
|
643 | three = 1;
|
644 | checkVisited();
|
645 | } else {
|
646 | four = 1;
|
647 | checkVisited();
|
648 | }
|
649 | });
|
650 |
|
651 | style.addSource('source-id', source);
|
652 | style.sourceCaches['source-id'].fire(new Event('error'));
|
653 | style.sourceCaches['source-id'].fire(new Event('data'));
|
654 | });
|
655 | });
|
656 | });
|
657 |
|
658 | describe('Style#removeSource', () => {
|
659 | test('throw before loaded', () => {
|
660 | const style = new Style(getStubMap());
|
661 | expect(() => style.removeSource('source-id')).toThrow(/load/i);
|
662 | });
|
663 |
|
664 | test('fires "data" event', done => {
|
665 | const style = new Style(getStubMap());
|
666 | style.loadJSON(createStyleJSON());
|
667 | const source = createSource();
|
668 | style.once('data', () => { done(); });
|
669 | style.on('style.load', () => {
|
670 | style.addSource('source-id', source);
|
671 | style.removeSource('source-id');
|
672 | style.update({} as EvaluationParameters);
|
673 | });
|
674 | });
|
675 |
|
676 | test('clears tiles', done => {
|
677 | const style = new Style(getStubMap());
|
678 | style.loadJSON(createStyleJSON({
|
679 | sources: {'source-id': createGeoJSONSource()}
|
680 | }));
|
681 |
|
682 | style.on('style.load', () => {
|
683 | const sourceCache = style.sourceCaches['source-id'];
|
684 | jest.spyOn(sourceCache, 'clearTiles');
|
685 | style.removeSource('source-id');
|
686 | expect(sourceCache.clearTiles).toHaveBeenCalledTimes(1);
|
687 | done();
|
688 | });
|
689 | });
|
690 |
|
691 | test('throws on non-existence', done => {
|
692 | const style = new Style(getStubMap());
|
693 | style.loadJSON(createStyleJSON());
|
694 | style.on('style.load', () => {
|
695 | expect(() => {
|
696 | style.removeSource('source-id');
|
697 | }).toThrow(/There is no source with this ID/);
|
698 | done();
|
699 | });
|
700 | });
|
701 |
|
702 | function createStyle(callback) {
|
703 | const style = new Style(getStubMap());
|
704 | style.loadJSON(createStyleJSON({
|
705 | 'sources': {
|
706 | 'mapLibre-source': createGeoJSONSource()
|
707 | },
|
708 | 'layers': [{
|
709 | 'id': 'mapLibre-layer',
|
710 | 'type': 'circle',
|
711 | 'source': 'mapLibre-source',
|
712 | 'source-layer': 'whatever'
|
713 | }]
|
714 | }));
|
715 | style.on('style.load', () => {
|
716 | style.update(1 as any as EvaluationParameters);
|
717 | callback(style);
|
718 | });
|
719 | return style;
|
720 | }
|
721 |
|
722 | test('throws if source is in use', done => {
|
723 | createStyle((style) => {
|
724 | style.on('error', (event) => {
|
725 | expect(event.error.message.includes('"mapLibre-source"')).toBeTruthy();
|
726 | expect(event.error.message.includes('"mapLibre-layer"')).toBeTruthy();
|
727 | done();
|
728 | });
|
729 | style.removeSource('mapLibre-source');
|
730 | });
|
731 | });
|
732 |
|
733 | test('does not throw if source is not in use', done => {
|
734 | createStyle((style) => {
|
735 | style.on('error', () => {
|
736 | done('test failed');
|
737 | });
|
738 | style.removeLayer('mapLibre-layer');
|
739 | style.removeSource('mapLibre-source');
|
740 | done();
|
741 | });
|
742 | });
|
743 |
|
744 | test('tears down source event forwarding', done => {
|
745 | const style = new Style(getStubMap());
|
746 | style.loadJSON(createStyleJSON());
|
747 | const source = createSource();
|
748 |
|
749 | style.on('style.load', () => {
|
750 | style.addSource('source-id', source);
|
751 | const sourceCache = style.sourceCaches['source-id'];
|
752 |
|
753 | style.removeSource('source-id');
|
754 |
|
755 |
|
756 | sourceCache.on('error', () => {});
|
757 |
|
758 | style.on('data', () => { expect(false).toBeTruthy(); });
|
759 | style.on('error', () => { expect(false).toBeTruthy(); });
|
760 | sourceCache.fire(new Event('data'));
|
761 | sourceCache.fire(new Event('error'));
|
762 |
|
763 | done();
|
764 | });
|
765 | });
|
766 | });
|
767 |
|
768 | describe('Style#setGeoJSONSourceData', () => {
|
769 | const geoJSON = {type: 'FeatureCollection', features: []} as GeoJSON.GeoJSON;
|
770 |
|
771 | test('throws before loaded', () => {
|
772 | const style = new Style(getStubMap());
|
773 | expect(() => style.setGeoJSONSourceData('source-id', geoJSON)).toThrow(/load/i);
|
774 | });
|
775 |
|
776 | test('throws on non-existence', done => {
|
777 | const style = new Style(getStubMap());
|
778 | style.loadJSON(createStyleJSON());
|
779 | style.on('style.load', () => {
|
780 | expect(() => style.setGeoJSONSourceData('source-id', geoJSON)).toThrow(/There is no source with this ID/);
|
781 | done();
|
782 | });
|
783 | });
|
784 | });
|
785 |
|
786 | describe('Style#addLayer', () => {
|
787 | test('throw before loaded', () => {
|
788 | const style = new Style(getStubMap());
|
789 | expect(() => style.addLayer({id: 'background', type: 'background'})).toThrow(/load/i);
|
790 | });
|
791 |
|
792 | test('sets up layer event forwarding', done => {
|
793 | const style = new Style(getStubMap());
|
794 | style.loadJSON(createStyleJSON());
|
795 |
|
796 | style.on('error', (e) => {
|
797 | expect(e.layer).toEqual({id: 'background'});
|
798 | expect(e.mapLibre).toBeTruthy();
|
799 | done();
|
800 | });
|
801 |
|
802 | style.on('style.load', () => {
|
803 | style.addLayer({
|
804 | id: 'background',
|
805 | type: 'background'
|
806 | });
|
807 | style._layers.background.fire(new Event('error', {mapLibre: true}));
|
808 | });
|
809 | });
|
810 |
|
811 | test('throws on non-existant vector source layer', done => {
|
812 | const style = createStyle();
|
813 | style.loadJSON(createStyleJSON({
|
814 | sources: {
|
815 |
|
816 | dummy: {type: 'vector', tiles: []}
|
817 | }
|
818 | }));
|
819 |
|
820 | style.on('style.load', () => {
|
821 | const source = createSource();
|
822 | source['vector_layers'] = [{id: 'green'}];
|
823 | style.addSource('-source-id-', source);
|
824 | style.addLayer({
|
825 | 'id': '-layer-id-',
|
826 | 'type': 'circle',
|
827 | 'source': '-source-id-',
|
828 | 'source-layer': '-source-layer-'
|
829 | });
|
830 | });
|
831 |
|
832 | style.on('error', (event) => {
|
833 | const err = event.error;
|
834 |
|
835 | expect(err).toBeTruthy();
|
836 | expect(err.toString().indexOf('-source-layer-') !== -1).toBeTruthy();
|
837 | expect(err.toString().indexOf('-source-id-') !== -1).toBeTruthy();
|
838 | expect(err.toString().indexOf('-layer-id-') !== -1).toBeTruthy();
|
839 |
|
840 | done();
|
841 | });
|
842 | });
|
843 |
|
844 | test('emits error on invalid layer', done => {
|
845 | const style = new Style(getStubMap());
|
846 | style.loadJSON(createStyleJSON());
|
847 | style.on('style.load', () => {
|
848 | style.on('error', () => {
|
849 | expect(style.getLayer('background')).toBeFalsy();
|
850 | done();
|
851 | });
|
852 | style.addLayer({
|
853 | id: 'background',
|
854 | type: 'background',
|
855 | paint: {
|
856 | 'background-opacity': 5
|
857 | }
|
858 | });
|
859 | });
|
860 | });
|
861 |
|
862 | test('#4040 does not mutate source property when provided inline', done => {
|
863 | const style = new Style(getStubMap());
|
864 | style.loadJSON(createStyleJSON());
|
865 | style.on('style.load', () => {
|
866 | const source = {
|
867 | 'type': 'geojson',
|
868 | 'data': {
|
869 | 'type': 'Point',
|
870 | 'coordinates': [ 0, 0]
|
871 | }
|
872 | };
|
873 | const layer = {id: 'inline-source-layer', type: 'circle', source} as any as LayerSpecification;
|
874 | style.addLayer(layer);
|
875 | expect((layer as any).source).toEqual(source);
|
876 | done();
|
877 | });
|
878 | });
|
879 |
|
880 | test('reloads source', done => {
|
881 | const style = createStyle();
|
882 | style.loadJSON(extend(createStyleJSON(), {
|
883 | 'sources': {
|
884 | 'mapLibre': {
|
885 | 'type': 'vector',
|
886 | 'tiles': []
|
887 | }
|
888 | }
|
889 | }));
|
890 | const layer = {
|
891 | 'id': 'symbol',
|
892 | 'type': 'symbol',
|
893 | 'source': 'mapLibre',
|
894 | 'source-layer': 'libremap',
|
895 | 'filter': ['==', 'id', 0]
|
896 | } as LayerSpecification;
|
897 |
|
898 | style.on('data', (e) => {
|
899 | if (e.dataType === 'source' && e.sourceDataType === 'content') {
|
900 | style.sourceCaches['mapLibre'].reload = function() { done(); };
|
901 | style.addLayer(layer);
|
902 | style.update({} as EvaluationParameters);
|
903 | }
|
904 | });
|
905 | });
|
906 |
|
907 | test('#3895 reloads source (instead of clearing) if adding this layer with the same type, immediately after removing it', done => {
|
908 | const style = createStyle();
|
909 | style.loadJSON(extend(createStyleJSON(), {
|
910 | 'sources': {
|
911 | 'mapLibre': {
|
912 | 'type': 'vector',
|
913 | 'tiles': []
|
914 | }
|
915 | },
|
916 | layers: [{
|
917 | 'id': 'my-layer',
|
918 | 'type': 'symbol',
|
919 | 'source': 'mapLibre',
|
920 | 'source-layer': 'libremap',
|
921 | 'filter': ['==', 'id', 0]
|
922 | }]
|
923 | }));
|
924 |
|
925 | const layer = {
|
926 | 'id': 'my-layer',
|
927 | 'type': 'symbol',
|
928 | 'source': 'mapLibre',
|
929 | 'source-layer': 'libremap'
|
930 | }as LayerSpecification;
|
931 |
|
932 | style.on('data', (e) => {
|
933 | if (e.dataType === 'source' && e.sourceDataType === 'content') {
|
934 | style.sourceCaches['mapLibre'].reload = function() { done(); };
|
935 | style.sourceCaches['mapLibre'].clearTiles = function() { done('test failed'); };
|
936 | style.removeLayer('my-layer');
|
937 | style.addLayer(layer);
|
938 | style.update({} as EvaluationParameters);
|
939 | }
|
940 | });
|
941 |
|
942 | });
|
943 |
|
944 | test('clears source (instead of reloading) if adding this layer with a different type, immediately after removing it', done => {
|
945 | const style = createStyle();
|
946 | style.loadJSON(extend(createStyleJSON(), {
|
947 | 'sources': {
|
948 | 'mapLibre': {
|
949 | 'type': 'vector',
|
950 | 'tiles': []
|
951 | }
|
952 | },
|
953 | layers: [{
|
954 | 'id': 'my-layer',
|
955 | 'type': 'symbol',
|
956 | 'source': 'mapLibre',
|
957 | 'source-layer': 'libremap',
|
958 | 'filter': ['==', 'id', 0]
|
959 | }]
|
960 | }));
|
961 |
|
962 | const layer = {
|
963 | 'id': 'my-layer',
|
964 | 'type': 'circle',
|
965 | 'source': 'mapLibre',
|
966 | 'source-layer': 'libremap'
|
967 | }as LayerSpecification;
|
968 | style.on('data', (e) => {
|
969 | if (e.dataType === 'source' && e.sourceDataType === 'content') {
|
970 | style.sourceCaches['mapLibre'].reload = function() { done('test failed'); };
|
971 | style.sourceCaches['mapLibre'].clearTiles = function() { done(); };
|
972 | style.removeLayer('my-layer');
|
973 | style.addLayer(layer);
|
974 | style.update({} as EvaluationParameters);
|
975 | }
|
976 | });
|
977 |
|
978 | });
|
979 |
|
980 | test('fires "data" event', done => {
|
981 | const style = new Style(getStubMap());
|
982 | style.loadJSON(createStyleJSON());
|
983 | const layer = {id: 'background', type: 'background'} as LayerSpecification;
|
984 |
|
985 | style.once('data', () => { done(); });
|
986 |
|
987 | style.on('style.load', () => {
|
988 | style.addLayer(layer);
|
989 | style.update({} as EvaluationParameters);
|
990 | });
|
991 | });
|
992 |
|
993 | test('emits error on duplicates', done => {
|
994 | const style = new Style(getStubMap());
|
995 | style.loadJSON(createStyleJSON());
|
996 | const layer = {id: 'background', type: 'background'} as LayerSpecification;
|
997 |
|
998 | style.on('error', (e) => {
|
999 | expect(e.error.message).toMatch(/already exists/);
|
1000 | done();
|
1001 | });
|
1002 |
|
1003 | style.on('style.load', () => {
|
1004 | style.addLayer(layer);
|
1005 | style.addLayer(layer);
|
1006 | });
|
1007 | });
|
1008 |
|
1009 | test('adds to the end by default', done => {
|
1010 | const style = new Style(getStubMap());
|
1011 | style.loadJSON(createStyleJSON({
|
1012 | layers: [{
|
1013 | id: 'a',
|
1014 | type: 'background'
|
1015 | }, {
|
1016 | id: 'b',
|
1017 | type: 'background'
|
1018 | }]
|
1019 | }));
|
1020 | const layer = {id: 'c', type: 'background'} as LayerSpecification;
|
1021 |
|
1022 | style.on('style.load', () => {
|
1023 | style.addLayer(layer);
|
1024 | expect(style._order).toEqual(['a', 'b', 'c']);
|
1025 | done();
|
1026 | });
|
1027 | });
|
1028 |
|
1029 | test('adds before the given layer', done => {
|
1030 | const style = new Style(getStubMap());
|
1031 | style.loadJSON(createStyleJSON({
|
1032 | layers: [{
|
1033 | id: 'a',
|
1034 | type: 'background'
|
1035 | }, {
|
1036 | id: 'b',
|
1037 | type: 'background'
|
1038 | }]
|
1039 | }));
|
1040 | const layer = {id: 'c', type: 'background'} as LayerSpecification;
|
1041 |
|
1042 | style.on('style.load', () => {
|
1043 | style.addLayer(layer, 'a');
|
1044 | expect(style._order).toEqual(['c', 'a', 'b']);
|
1045 | done();
|
1046 | });
|
1047 | });
|
1048 |
|
1049 | test('fire error if before layer does not exist', done => {
|
1050 | const style = new Style(getStubMap());
|
1051 | style.loadJSON(createStyleJSON({
|
1052 | layers: [{
|
1053 | id: 'a',
|
1054 | type: 'background'
|
1055 | }, {
|
1056 | id: 'b',
|
1057 | type: 'background'
|
1058 | }]
|
1059 | }));
|
1060 | const layer = {id: 'c', type: 'background'} as LayerSpecification;
|
1061 |
|
1062 | style.on('style.load', () => {
|
1063 | style.on('error', (error) => {
|
1064 | expect(error.error.message).toMatch(/Cannot add layer "c" before non-existing layer "z"./);
|
1065 | done();
|
1066 | });
|
1067 | style.addLayer(layer, 'z');
|
1068 | });
|
1069 | });
|
1070 |
|
1071 | test('fires an error on non-existant source layer', done => {
|
1072 | const style = new Style(getStubMap());
|
1073 | style.loadJSON(extend(createStyleJSON(), {
|
1074 | sources: {
|
1075 | dummy: {
|
1076 | type: 'geojson',
|
1077 | data: {type: 'FeatureCollection', features: []}
|
1078 | }
|
1079 | }
|
1080 | }));
|
1081 |
|
1082 | const layer = {
|
1083 | id: 'dummy',
|
1084 | type: 'fill',
|
1085 | source: 'dummy',
|
1086 | 'source-layer': 'dummy'
|
1087 | }as LayerSpecification;
|
1088 |
|
1089 | style.on('style.load', () => {
|
1090 | style.on('error', ({error}) => {
|
1091 | expect(error.message).toMatch(/does not exist on source/);
|
1092 | done();
|
1093 | });
|
1094 | style.addLayer(layer);
|
1095 | });
|
1096 |
|
1097 | });
|
1098 | });
|
1099 |
|
1100 | describe('Style#removeLayer', () => {
|
1101 | test('throw before loaded', () => {
|
1102 | const style = new Style(getStubMap());
|
1103 | expect(() => style.removeLayer('background')).toThrow(/load/i);
|
1104 | });
|
1105 |
|
1106 | test('fires "data" event', done => {
|
1107 | const style = new Style(getStubMap());
|
1108 | style.loadJSON(createStyleJSON());
|
1109 | const layer = {id: 'background', type: 'background'} as LayerSpecification;
|
1110 |
|
1111 | style.once('data', () => { done(); });
|
1112 |
|
1113 | style.on('style.load', () => {
|
1114 | style.addLayer(layer);
|
1115 | style.removeLayer('background');
|
1116 | style.update({} as EvaluationParameters);
|
1117 | });
|
1118 | });
|
1119 |
|
1120 | test('tears down layer event forwarding', done => {
|
1121 | const style = new Style(getStubMap());
|
1122 | style.loadJSON(createStyleJSON({
|
1123 | layers: [{
|
1124 | id: 'background',
|
1125 | type: 'background'
|
1126 | }]
|
1127 | }));
|
1128 |
|
1129 | style.on('error', () => {
|
1130 | done('test failed');
|
1131 | });
|
1132 |
|
1133 | style.on('style.load', () => {
|
1134 | const layer = style._layers.background;
|
1135 | style.removeLayer('background');
|
1136 |
|
1137 |
|
1138 | layer.on('error', () => {});
|
1139 |
|
1140 | layer.fire(new Event('error', {mapLibre: true}));
|
1141 | done();
|
1142 | });
|
1143 | });
|
1144 |
|
1145 | test('fires an error on non-existence', done => {
|
1146 | const style = new Style(getStubMap());
|
1147 | style.loadJSON(createStyleJSON());
|
1148 |
|
1149 | style.on('style.load', () => {
|
1150 | style.on('error', ({error}) => {
|
1151 | expect(error.message).toMatch(/Cannot remove non-existing layer "background"./);
|
1152 | done();
|
1153 | });
|
1154 | style.removeLayer('background');
|
1155 | });
|
1156 | });
|
1157 |
|
1158 | test('removes from the order', done => {
|
1159 | const style = new Style(getStubMap());
|
1160 | style.loadJSON(createStyleJSON({
|
1161 | layers: [{
|
1162 | id: 'a',
|
1163 | type: 'background'
|
1164 | }, {
|
1165 | id: 'b',
|
1166 | type: 'background'
|
1167 | }]
|
1168 | }));
|
1169 |
|
1170 | style.on('style.load', () => {
|
1171 | style.removeLayer('a');
|
1172 | expect(style._order).toEqual(['b']);
|
1173 | done();
|
1174 | });
|
1175 | });
|
1176 |
|
1177 | test('does not remove dereffed layers', done => {
|
1178 | const style = new Style(getStubMap());
|
1179 | style.loadJSON(createStyleJSON({
|
1180 | layers: [{
|
1181 | id: 'a',
|
1182 | type: 'background'
|
1183 | }, {
|
1184 | id: 'b',
|
1185 | ref: 'a'
|
1186 | }]
|
1187 | }));
|
1188 |
|
1189 | style.on('style.load', () => {
|
1190 | style.removeLayer('a');
|
1191 | expect(style.getLayer('a')).toBeUndefined();
|
1192 | expect(style.getLayer('b')).toBeDefined();
|
1193 | done();
|
1194 | });
|
1195 | });
|
1196 | });
|
1197 |
|
1198 | describe('Style#moveLayer', () => {
|
1199 | test('throw before loaded', () => {
|
1200 | const style = new Style(getStubMap());
|
1201 | expect(() => style.moveLayer('background')).toThrow(/load/i);
|
1202 | });
|
1203 |
|
1204 | test('fires "data" event', done => {
|
1205 | const style = new Style(getStubMap());
|
1206 | style.loadJSON(createStyleJSON());
|
1207 | const layer = {id: 'background', type: 'background'} as LayerSpecification;
|
1208 |
|
1209 | style.once('data', () => { done(); });
|
1210 |
|
1211 | style.on('style.load', () => {
|
1212 | style.addLayer(layer);
|
1213 | style.moveLayer('background');
|
1214 | style.update({} as EvaluationParameters);
|
1215 | });
|
1216 | });
|
1217 |
|
1218 | test('fires an error on non-existence', done => {
|
1219 | const style = new Style(getStubMap());
|
1220 | style.loadJSON(createStyleJSON());
|
1221 |
|
1222 | style.on('style.load', () => {
|
1223 | style.on('error', ({error}) => {
|
1224 | expect(error.message).toMatch(/does not exist in the map\'s style and cannot be moved/);
|
1225 | done();
|
1226 | });
|
1227 | style.moveLayer('background');
|
1228 | });
|
1229 | });
|
1230 |
|
1231 | test('changes the order', done => {
|
1232 | const style = new Style(getStubMap());
|
1233 | style.loadJSON(createStyleJSON({
|
1234 | layers: [
|
1235 | {id: 'a', type: 'background'},
|
1236 | {id: 'b', type: 'background'},
|
1237 | {id: 'c', type: 'background'}
|
1238 | ]
|
1239 | }));
|
1240 |
|
1241 | style.on('style.load', () => {
|
1242 | style.moveLayer('a', 'c');
|
1243 | expect(style._order).toEqual(['b', 'a', 'c']);
|
1244 | done();
|
1245 | });
|
1246 | });
|
1247 |
|
1248 | test('moves to existing location', done => {
|
1249 | const style = new Style(getStubMap());
|
1250 | style.loadJSON(createStyleJSON({
|
1251 | layers: [
|
1252 | {id: 'a', type: 'background'},
|
1253 | {id: 'b', type: 'background'},
|
1254 | {id: 'c', type: 'background'}
|
1255 | ]
|
1256 | }));
|
1257 |
|
1258 | style.on('style.load', () => {
|
1259 | style.moveLayer('b', 'b');
|
1260 | expect(style._order).toEqual(['a', 'b', 'c']);
|
1261 | done();
|
1262 | });
|
1263 | });
|
1264 | });
|
1265 |
|
1266 | describe('Style#setPaintProperty', () => {
|
1267 | test('#4738 postpones source reload until layers have been broadcast to workers', done => {
|
1268 | const style = new Style(getStubMap());
|
1269 | style.loadJSON(extend(createStyleJSON(), {
|
1270 | 'sources': {
|
1271 | 'geojson': {
|
1272 | 'type': 'geojson',
|
1273 | 'data': {'type': 'FeatureCollection', 'features': []}
|
1274 | }
|
1275 | },
|
1276 | 'layers': [
|
1277 | {
|
1278 | 'id': 'circle',
|
1279 | 'type': 'circle',
|
1280 | 'source': 'geojson'
|
1281 | }
|
1282 | ]
|
1283 | }));
|
1284 |
|
1285 | const tr = new Transform();
|
1286 | tr.resize(512, 512);
|
1287 |
|
1288 | style.once('style.load', () => {
|
1289 | style.update(tr.zoom as any as EvaluationParameters);
|
1290 | const sourceCache = style.sourceCaches['geojson'];
|
1291 | const source = style.getSource('geojson');
|
1292 |
|
1293 | let begun = false;
|
1294 | let styleUpdateCalled = false;
|
1295 |
|
1296 | (source as any).on('data', (e) => setTimeout(() => {
|
1297 | if (!begun && sourceCache.loaded()) {
|
1298 | begun = true;
|
1299 | jest.spyOn(sourceCache, 'reload').mockImplementation(() => {
|
1300 | expect(styleUpdateCalled).toBeTruthy();
|
1301 | done();
|
1302 | });
|
1303 |
|
1304 | (source as any).setData({'type': 'FeatureCollection', 'features': []});
|
1305 | style.setPaintProperty('circle', 'circle-color', {type: 'identity', property: 'foo'});
|
1306 | }
|
1307 |
|
1308 | if (begun && e.sourceDataType === 'content') {
|
1309 |
|
1310 |
|
1311 |
|
1312 |
|
1313 | setTimeout(() => {
|
1314 | styleUpdateCalled = true;
|
1315 | style.update({} as EvaluationParameters);
|
1316 | }, 50);
|
1317 | }
|
1318 | }));
|
1319 | });
|
1320 | });
|
1321 |
|
1322 | test('#5802 clones the input', done => {
|
1323 | const style = new Style(getStubMap());
|
1324 | style.loadJSON({
|
1325 | 'version': 8,
|
1326 | 'sources': {},
|
1327 | 'layers': [
|
1328 | {
|
1329 | 'id': 'background',
|
1330 | 'type': 'background'
|
1331 | }
|
1332 | ]
|
1333 | });
|
1334 |
|
1335 | style.on('style.load', () => {
|
1336 | const value = {stops: [[0, 'red'], [10, 'blue']]};
|
1337 | style.setPaintProperty('background', 'background-color', value);
|
1338 | expect(style.getPaintProperty('background', 'background-color')).not.toBe(value);
|
1339 | expect(style._changed).toBeTruthy();
|
1340 |
|
1341 | style.update({} as EvaluationParameters);
|
1342 | expect(style._changed).toBeFalsy();
|
1343 |
|
1344 | value.stops[0][0] = 1;
|
1345 | style.setPaintProperty('background', 'background-color', value);
|
1346 | expect(style._changed).toBeTruthy();
|
1347 |
|
1348 | done();
|
1349 | });
|
1350 | });
|
1351 |
|
1352 | test('respects validate option', done => {
|
1353 | const style = new Style(getStubMap());
|
1354 | style.loadJSON({
|
1355 | 'version': 8,
|
1356 | 'sources': {},
|
1357 | 'layers': [
|
1358 | {
|
1359 | 'id': 'background',
|
1360 | 'type': 'background'
|
1361 | }
|
1362 | ]
|
1363 | });
|
1364 |
|
1365 | style.on('style.load', () => {
|
1366 | const backgroundLayer = style.getLayer('background');
|
1367 | const validate = jest.spyOn(backgroundLayer, '_validate');
|
1368 |
|
1369 | style.setPaintProperty('background', 'background-color', 'notacolor', {validate: false});
|
1370 | expect(validate.mock.calls[0][4]).toEqual({validate: false});
|
1371 | expect(mockConsoleError).not.toHaveBeenCalled();
|
1372 |
|
1373 | expect(style._changed).toBeTruthy();
|
1374 | style.update({} as EvaluationParameters);
|
1375 |
|
1376 | style.setPaintProperty('background', 'background-color', 'alsonotacolor');
|
1377 | expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
1378 | expect(validate.mock.calls[1][4]).toEqual({});
|
1379 |
|
1380 | done();
|
1381 | });
|
1382 | });
|
1383 | });
|
1384 |
|
1385 | describe('Style#getPaintProperty', () => {
|
1386 | test('#5802 clones the output', done => {
|
1387 | const style = new Style(getStubMap());
|
1388 | style.loadJSON({
|
1389 | 'version': 8,
|
1390 | 'sources': {},
|
1391 | 'layers': [
|
1392 | {
|
1393 | 'id': 'background',
|
1394 | 'type': 'background'
|
1395 | }
|
1396 | ]
|
1397 | });
|
1398 |
|
1399 | style.on('style.load', () => {
|
1400 | style.setPaintProperty('background', 'background-color', {stops: [[0, 'red'], [10, 'blue']]});
|
1401 | style.update({} as EvaluationParameters);
|
1402 | expect(style._changed).toBeFalsy();
|
1403 |
|
1404 | const value = style.getPaintProperty('background', 'background-color');
|
1405 | value['stops'][0][0] = 1;
|
1406 | style.setPaintProperty('background', 'background-color', value);
|
1407 | expect(style._changed).toBeTruthy();
|
1408 |
|
1409 | done();
|
1410 | });
|
1411 | });
|
1412 | });
|
1413 |
|
1414 | describe('Style#setLayoutProperty', () => {
|
1415 | test('#5802 clones the input', done => {
|
1416 | const style = new Style(getStubMap());
|
1417 | style.loadJSON({
|
1418 | 'version': 8,
|
1419 | 'sources': {
|
1420 | 'geojson': {
|
1421 | 'type': 'geojson',
|
1422 | 'data': {
|
1423 | 'type': 'FeatureCollection',
|
1424 | 'features': []
|
1425 | }
|
1426 | }
|
1427 | },
|
1428 | 'layers': [
|
1429 | {
|
1430 | 'id': 'line',
|
1431 | 'type': 'line',
|
1432 | 'source': 'geojson'
|
1433 | }
|
1434 | ]
|
1435 | });
|
1436 |
|
1437 | style.on('style.load', () => {
|
1438 | const value = {stops: [[0, 'butt'], [10, 'round']]};
|
1439 | style.setLayoutProperty('line', 'line-cap', value);
|
1440 | expect(style.getLayoutProperty('line', 'line-cap')).not.toBe(value);
|
1441 | expect(style._changed).toBeTruthy();
|
1442 |
|
1443 | style.update({} as EvaluationParameters);
|
1444 | expect(style._changed).toBeFalsy();
|
1445 |
|
1446 | value.stops[0][0] = 1;
|
1447 | style.setLayoutProperty('line', 'line-cap', value);
|
1448 | expect(style._changed).toBeTruthy();
|
1449 |
|
1450 | done();
|
1451 | });
|
1452 | });
|
1453 |
|
1454 | test('respects validate option', done => {
|
1455 | const style = new Style(getStubMap());
|
1456 | style.loadJSON({
|
1457 | 'version': 8,
|
1458 | 'sources': {
|
1459 | 'geojson': {
|
1460 | 'type': 'geojson',
|
1461 | 'data': {
|
1462 | 'type': 'FeatureCollection',
|
1463 | 'features': []
|
1464 | }
|
1465 | }
|
1466 | },
|
1467 | 'layers': [
|
1468 | {
|
1469 | 'id': 'line',
|
1470 | 'type': 'line',
|
1471 | 'source': 'geojson'
|
1472 | }
|
1473 | ]
|
1474 | });
|
1475 |
|
1476 | style.on('style.load', () => {
|
1477 | const lineLayer = style.getLayer('line');
|
1478 | const validate = jest.spyOn(lineLayer, '_validate');
|
1479 |
|
1480 | style.setLayoutProperty('line', 'line-cap', 'invalidcap', {validate: false});
|
1481 | expect(validate.mock.calls[0][4]).toEqual({validate: false});
|
1482 | expect(mockConsoleError).not.toHaveBeenCalled();
|
1483 | expect(style._changed).toBeTruthy();
|
1484 | style.update({} as EvaluationParameters);
|
1485 |
|
1486 | style.setLayoutProperty('line', 'line-cap', 'differentinvalidcap');
|
1487 | expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
1488 | expect(validate.mock.calls[1][4]).toEqual({});
|
1489 |
|
1490 | done();
|
1491 | });
|
1492 | });
|
1493 | });
|
1494 |
|
1495 | describe('Style#getLayoutProperty', () => {
|
1496 | test('#5802 clones the output', done => {
|
1497 | const style = new Style(getStubMap());
|
1498 | style.loadJSON({
|
1499 | 'version': 8,
|
1500 | 'sources': {
|
1501 | 'geojson': {
|
1502 | 'type': 'geojson',
|
1503 | 'data': {
|
1504 | 'type': 'FeatureCollection',
|
1505 | 'features': []
|
1506 | }
|
1507 | }
|
1508 | },
|
1509 | 'layers': [
|
1510 | {
|
1511 | 'id': 'line',
|
1512 | 'type': 'line',
|
1513 | 'source': 'geojson'
|
1514 | }
|
1515 | ]
|
1516 | });
|
1517 |
|
1518 | style.on('style.load', () => {
|
1519 | style.setLayoutProperty('line', 'line-cap', {stops: [[0, 'butt'], [10, 'round']]});
|
1520 | style.update({} as EvaluationParameters);
|
1521 | expect(style._changed).toBeFalsy();
|
1522 |
|
1523 | const value = style.getLayoutProperty('line', 'line-cap');
|
1524 | value.stops[0][0] = 1;
|
1525 | style.setLayoutProperty('line', 'line-cap', value);
|
1526 | expect(style._changed).toBeTruthy();
|
1527 |
|
1528 | done();
|
1529 | });
|
1530 | });
|
1531 | });
|
1532 |
|
1533 | describe('Style#setFilter', () => {
|
1534 | test('throws if style is not loaded', () => {
|
1535 | const style = new Style(getStubMap());
|
1536 | expect(() => style.setFilter('symbol', ['==', 'id', 1])).toThrow(/load/i);
|
1537 | });
|
1538 |
|
1539 | function createStyle() {
|
1540 | const style = new Style(getStubMap());
|
1541 | style.loadJSON({
|
1542 | version: 8,
|
1543 | sources: {
|
1544 | geojson: createGeoJSONSource() as GeoJSONSourceSpecification
|
1545 | },
|
1546 | layers: [
|
1547 | {id: 'symbol', type: 'symbol', source: 'geojson', filter: ['==', 'id', 0]}
|
1548 | ]
|
1549 | });
|
1550 | return style;
|
1551 | }
|
1552 |
|
1553 | test('sets filter', done => {
|
1554 | const style = createStyle();
|
1555 |
|
1556 | style.on('style.load', () => {
|
1557 | style.dispatcher.broadcast = function(key, value) {
|
1558 | expect(key).toBe('updateLayers');
|
1559 | expect(value['layers'][0].id).toBe('symbol');
|
1560 | expect(value['layers'][0].filter).toEqual(['==', 'id', 1]);
|
1561 | done();
|
1562 | };
|
1563 |
|
1564 | style.setFilter('symbol', ['==', 'id', 1]);
|
1565 | expect(style.getFilter('symbol')).toEqual(['==', 'id', 1]);
|
1566 | style.update({} as EvaluationParameters);
|
1567 | });
|
1568 | });
|
1569 |
|
1570 | test('gets a clone of the filter', done => {
|
1571 | const style = createStyle();
|
1572 |
|
1573 | style.on('style.load', () => {
|
1574 | const filter1 = ['==', 'id', 1] as FilterSpecification;
|
1575 | style.setFilter('symbol', filter1);
|
1576 | const filter2 = style.getFilter('symbol');
|
1577 | const filter3 = style.getLayer('symbol').filter;
|
1578 |
|
1579 | expect(filter1).not.toBe(filter2);
|
1580 | expect(filter1).not.toBe(filter3);
|
1581 | expect(filter2).not.toBe(filter3);
|
1582 |
|
1583 | done();
|
1584 | });
|
1585 | });
|
1586 |
|
1587 | test('sets again mutated filter', done => {
|
1588 | const style = createStyle();
|
1589 |
|
1590 | style.on('style.load', () => {
|
1591 | const filter = ['==', 'id', 1] as FilterSpecification;
|
1592 | style.setFilter('symbol', filter);
|
1593 | style.update({} as EvaluationParameters);
|
1594 |
|
1595 | style.dispatcher.broadcast = function(key, value) {
|
1596 | expect(key).toBe('updateLayers');
|
1597 | expect(value['layers'][0].id).toBe('symbol');
|
1598 | expect(value['layers'][0].filter).toEqual(['==', 'id', 2]);
|
1599 | done();
|
1600 | };
|
1601 | filter[2] = 2;
|
1602 | style.setFilter('symbol', filter);
|
1603 | style.update({} as EvaluationParameters);
|
1604 | });
|
1605 | });
|
1606 |
|
1607 | test('unsets filter', done => {
|
1608 | const style = createStyle();
|
1609 | style.on('style.load', () => {
|
1610 | style.setFilter('symbol', null);
|
1611 | expect(style.getLayer('symbol').serialize()['filter']).toBeUndefined();
|
1612 | done();
|
1613 | });
|
1614 | });
|
1615 |
|
1616 | test('emits if invalid', done => {
|
1617 | const style = createStyle();
|
1618 | style.on('style.load', () => {
|
1619 | style.on('error', () => {
|
1620 | expect(style.getLayer('symbol').serialize()['filter']).toEqual(['==', 'id', 0]);
|
1621 | done();
|
1622 | });
|
1623 | style.setFilter('symbol', ['==', '$type', 1]);
|
1624 | });
|
1625 | });
|
1626 |
|
1627 | test('fires an error if layer not found', done => {
|
1628 | const style = createStyle();
|
1629 |
|
1630 | style.on('style.load', () => {
|
1631 | style.on('error', ({error}) => {
|
1632 | expect(error.message).toMatch(/Cannot filter non-existing layer "non-existant"./);
|
1633 | done();
|
1634 | });
|
1635 | style.setFilter('non-existant', ['==', 'id', 1]);
|
1636 | });
|
1637 | });
|
1638 |
|
1639 | test('validates filter by default', done => {
|
1640 | const style = createStyle();
|
1641 | style.on('style.load', () => {
|
1642 | style.setFilter('symbol', 'notafilter' as any as FilterSpecification);
|
1643 | expect(style.getFilter('symbol')).toEqual(['==', 'id', 0]);
|
1644 | expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
1645 | style.update({} as EvaluationParameters);
|
1646 | done();
|
1647 | });
|
1648 | });
|
1649 |
|
1650 | test('respects validate option', done => {
|
1651 | const style = createStyle();
|
1652 |
|
1653 | style.on('style.load', () => {
|
1654 | style.dispatcher.broadcast = function(key, value) {
|
1655 | expect(key).toBe('updateLayers');
|
1656 | expect(value['layers'][0].id).toBe('symbol');
|
1657 | expect(value['layers'][0].filter).toBe('notafilter');
|
1658 | done();
|
1659 | };
|
1660 |
|
1661 | style.setFilter('symbol', 'notafilter' as any as FilterSpecification, {validate: false});
|
1662 | expect(style.getFilter('symbol')).toBe('notafilter');
|
1663 | style.update({} as EvaluationParameters);
|
1664 | });
|
1665 | });
|
1666 | });
|
1667 |
|
1668 | describe('Style#setLayerZoomRange', () => {
|
1669 | test('throw before loaded', () => {
|
1670 | const style = new Style(getStubMap());
|
1671 | expect(() => style.setLayerZoomRange('symbol', 5, 12)).toThrow(/load/i);
|
1672 | });
|
1673 |
|
1674 | function createStyle() {
|
1675 | const style = new Style(getStubMap());
|
1676 | style.loadJSON({
|
1677 | 'version': 8,
|
1678 | 'sources': {
|
1679 | 'geojson': createGeoJSONSource() as GeoJSONSourceSpecification
|
1680 | },
|
1681 | 'layers': [{
|
1682 | 'id': 'symbol',
|
1683 | 'type': 'symbol',
|
1684 | 'source': 'geojson'
|
1685 | }]
|
1686 | });
|
1687 | return style;
|
1688 | }
|
1689 |
|
1690 | test('sets zoom range', done => {
|
1691 | const style = createStyle();
|
1692 |
|
1693 | style.on('style.load', () => {
|
1694 | style.dispatcher.broadcast = function(key, value) {
|
1695 | expect(key).toBe('updateLayers');
|
1696 | expect(value['layers'].map((layer) => { return layer.id; })).toEqual(['symbol']);
|
1697 | done();
|
1698 | };
|
1699 | style.setLayerZoomRange('symbol', 5, 12);
|
1700 | expect(style.getLayer('symbol').minzoom).toBe(5);
|
1701 | expect(style.getLayer('symbol').maxzoom).toBe(12);
|
1702 | style.update({} as EvaluationParameters);
|
1703 | });
|
1704 | });
|
1705 |
|
1706 | test('fires an error if layer not found', done => {
|
1707 | const style = createStyle();
|
1708 | style.on('style.load', () => {
|
1709 | style.on('error', ({error}) => {
|
1710 | expect(error.message).toMatch(/Cannot set the zoom range of non-existing layer "non-existant"./);
|
1711 | done();
|
1712 | });
|
1713 | style.setLayerZoomRange('non-existant', 5, 12);
|
1714 | });
|
1715 | });
|
1716 |
|
1717 | test('does not reload raster source', done => {
|
1718 | const style = new Style(getStubMap());
|
1719 | style.loadJSON({
|
1720 | 'version': 8,
|
1721 | 'sources': {
|
1722 | 'raster': {
|
1723 | type: 'raster',
|
1724 | tiles: ['http://tiles.server']
|
1725 | }
|
1726 | },
|
1727 | 'layers': [{
|
1728 | 'id': 'raster',
|
1729 | 'type': 'raster',
|
1730 | 'source': 'raster'
|
1731 | }]
|
1732 | });
|
1733 |
|
1734 | style.on('style.load', () => {
|
1735 | jest.spyOn(style, '_reloadSource');
|
1736 |
|
1737 | style.setLayerZoomRange('raster', 5, 12);
|
1738 | style.update(0 as any as EvaluationParameters);
|
1739 | expect(style._reloadSource).not.toHaveBeenCalled();
|
1740 | done();
|
1741 | });
|
1742 | });
|
1743 | });
|
1744 |
|
1745 | describe('Style#queryRenderedFeatures', () => {
|
1746 |
|
1747 | let style;
|
1748 | let transform;
|
1749 |
|
1750 | beforeEach((callback) => {
|
1751 | style = new Style(getStubMap());
|
1752 | transform = new Transform();
|
1753 | transform.resize(512, 512);
|
1754 | function queryMapLibreFeatures(layers, serializedLayers, getFeatureState, queryGeom, cameraQueryGeom, scale, params) {
|
1755 | const features = {
|
1756 | 'land': [{
|
1757 | type: 'Feature',
|
1758 | layer: style._layers.land.serialize(),
|
1759 | geometry: {
|
1760 | type: 'Polygon'
|
1761 | }
|
1762 | }, {
|
1763 | type: 'Feature',
|
1764 | layer: style._layers.land.serialize(),
|
1765 | geometry: {
|
1766 | type: 'Point'
|
1767 | }
|
1768 | }],
|
1769 | 'landref': [{
|
1770 | type: 'Feature',
|
1771 | layer: style._layers.landref.serialize(),
|
1772 | geometry: {
|
1773 | type: 'Line'
|
1774 | }
|
1775 | }]
|
1776 | };
|
1777 |
|
1778 |
|
1779 | for (const layer in features) {
|
1780 | features[layer] = features[layer].map((feature, featureIndex) =>
|
1781 | ({feature, featureIndex}));
|
1782 | }
|
1783 |
|
1784 | if (params.layers) {
|
1785 | for (const l in features) {
|
1786 | if (params.layers.indexOf(l) < 0) {
|
1787 | delete features[l];
|
1788 | }
|
1789 | }
|
1790 | }
|
1791 |
|
1792 | return features;
|
1793 | }
|
1794 |
|
1795 | style.loadJSON({
|
1796 | 'version': 8,
|
1797 | 'sources': {
|
1798 | 'mapLibre': {
|
1799 | 'type': 'geojson',
|
1800 | 'data': {type: 'FeatureCollection', features: []}
|
1801 | },
|
1802 | 'other': {
|
1803 | 'type': 'geojson',
|
1804 | 'data': {type: 'FeatureCollection', features: []}
|
1805 | }
|
1806 | },
|
1807 | 'layers': [{
|
1808 | 'id': 'land',
|
1809 | 'type': 'line',
|
1810 | 'source': 'mapLibre',
|
1811 | 'source-layer': 'water',
|
1812 | 'layout': {
|
1813 | 'line-cap': 'round'
|
1814 | },
|
1815 | 'paint': {
|
1816 | 'line-color': 'red'
|
1817 | },
|
1818 | 'metadata': {
|
1819 | 'something': 'else'
|
1820 | }
|
1821 | }, {
|
1822 | 'id': 'landref',
|
1823 | 'ref': 'land',
|
1824 | 'paint': {
|
1825 | 'line-color': 'blue'
|
1826 | }
|
1827 | } as any as LayerSpecification, {
|
1828 | 'id': 'land--other',
|
1829 | 'type': 'line',
|
1830 | 'source': 'other',
|
1831 | 'source-layer': 'water',
|
1832 | 'layout': {
|
1833 | 'line-cap': 'round'
|
1834 | },
|
1835 | 'paint': {
|
1836 | 'line-color': 'red'
|
1837 | },
|
1838 | 'metadata': {
|
1839 | 'something': 'else'
|
1840 | }
|
1841 | }]
|
1842 | });
|
1843 |
|
1844 | style.on('style.load', () => {
|
1845 | style.sourceCaches.mapLibre.tilesIn = () => {
|
1846 | return [{
|
1847 | tile: {queryRenderedFeatures: queryMapLibreFeatures},
|
1848 | tileID: new OverscaledTileID(0, 0, 0, 0, 0),
|
1849 | queryGeometry: [],
|
1850 | scale: 1
|
1851 | }];
|
1852 | };
|
1853 | style.sourceCaches.other.tilesIn = () => {
|
1854 | return [];
|
1855 | };
|
1856 |
|
1857 | style.sourceCaches.mapLibre.transform = transform;
|
1858 | style.sourceCaches.other.transform = transform;
|
1859 |
|
1860 | style.update(0 as any as EvaluationParameters);
|
1861 | style._updateSources(transform);
|
1862 | callback();
|
1863 | });
|
1864 | });
|
1865 |
|
1866 | afterEach(() => {
|
1867 | style = undefined;
|
1868 | transform = undefined;
|
1869 | });
|
1870 |
|
1871 | test('returns feature type', () => {
|
1872 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {}, transform);
|
1873 | expect(results[0].geometry.type).toBe('Line');
|
1874 | });
|
1875 |
|
1876 | test('filters by `layers` option', () => {
|
1877 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {layers: ['land']}, transform);
|
1878 | expect(results).toHaveLength(2);
|
1879 | });
|
1880 |
|
1881 | test('checks type of `layers` option', () => {
|
1882 | let errors = 0;
|
1883 | jest.spyOn(style, 'fire').mockImplementation((event) => {
|
1884 | if (event['error'] && event['error'].message.includes('parameters.layers must be an Array.')) {
|
1885 | errors++;
|
1886 | }
|
1887 | });
|
1888 | style.queryRenderedFeatures([{x: 0, y: 0}], {layers:'string'}, transform);
|
1889 | expect(errors).toBe(1);
|
1890 | });
|
1891 |
|
1892 | test('includes layout properties', () => {
|
1893 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {}, transform);
|
1894 | const layout = results[0].layer.layout;
|
1895 | expect(layout['line-cap']).toBe('round');
|
1896 | });
|
1897 |
|
1898 | test('includes paint properties', () => {
|
1899 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {}, transform);
|
1900 | expect(results[2].layer.paint['line-color']).toBe('red');
|
1901 | });
|
1902 |
|
1903 | test('includes metadata', () => {
|
1904 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {}, transform);
|
1905 |
|
1906 | const layer = results[1].layer;
|
1907 | expect(layer.metadata.something).toBe('else');
|
1908 |
|
1909 | });
|
1910 |
|
1911 | test('include multiple layers', () => {
|
1912 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {layers: ['land', 'landref']}, transform);
|
1913 | expect(results).toHaveLength(3);
|
1914 | });
|
1915 |
|
1916 | test('does not query sources not implicated by `layers` parameter', () => {
|
1917 | style.sourceCaches.mapLibre.queryRenderedFeatures = function() { expect(true).toBe(false); };
|
1918 | style.queryRenderedFeatures([{x: 0, y: 0}], {layers: ['land--other']}, transform);
|
1919 | });
|
1920 |
|
1921 | test('fires an error if layer included in params does not exist on the style', () => {
|
1922 | let errors = 0;
|
1923 | jest.spyOn(style, 'fire').mockImplementation((event) => {
|
1924 | if (event['error'] && event['error'].message.includes('does not exist in the map\'s style and cannot be queried for features.')) errors++;
|
1925 | });
|
1926 | const results = style.queryRenderedFeatures([{x: 0, y: 0}], {layers:['merp']}, transform);
|
1927 | expect(errors).toBe(1);
|
1928 | expect(results).toHaveLength(0);
|
1929 | });
|
1930 | });
|
1931 |
|
1932 | describe('Style defers ...', () => {
|
1933 | test('... expensive methods', done => {
|
1934 | const style = new Style(getStubMap());
|
1935 | style.loadJSON(createStyleJSON({
|
1936 | 'sources': {
|
1937 | 'streets': createGeoJSONSource(),
|
1938 | 'terrain': createGeoJSONSource()
|
1939 | }
|
1940 | }));
|
1941 |
|
1942 | style.on('style.load', () => {
|
1943 | style.update({} as EvaluationParameters);
|
1944 |
|
1945 |
|
1946 | const mockStyleFire = jest.spyOn(style, 'fire');
|
1947 | jest.spyOn(style, '_reloadSource');
|
1948 | jest.spyOn(style, '_updateWorkerLayers');
|
1949 |
|
1950 | style.addLayer({id: 'first', type: 'symbol', source: 'streets'});
|
1951 | style.addLayer({id: 'second', type: 'symbol', source: 'streets'});
|
1952 | style.addLayer({id: 'third', type: 'symbol', source: 'terrain'});
|
1953 |
|
1954 | style.setPaintProperty('first', 'text-color', 'black');
|
1955 | style.setPaintProperty('first', 'text-halo-color', 'white');
|
1956 |
|
1957 | expect(style.fire).not.toHaveBeenCalled();
|
1958 | expect(style._reloadSource).not.toHaveBeenCalled();
|
1959 | expect(style._updateWorkerLayers).not.toHaveBeenCalled();
|
1960 |
|
1961 | style.update({} as EvaluationParameters);
|
1962 |
|
1963 | expect(mockStyleFire.mock.calls[0][0]['type']).toBe('data');
|
1964 |
|
1965 |
|
1966 | expect(style._reloadSource).toHaveBeenCalledTimes(2);
|
1967 | expect(style._reloadSource).toHaveBeenCalledWith('streets');
|
1968 | expect(style._reloadSource).toHaveBeenCalledWith('terrain');
|
1969 |
|
1970 |
|
1971 | expect(style._updateWorkerLayers).toHaveBeenCalledTimes(1);
|
1972 |
|
1973 | done();
|
1974 | });
|
1975 | });
|
1976 | });
|
1977 |
|
1978 | describe('Style#query*Features', () => {
|
1979 |
|
1980 |
|
1981 |
|
1982 |
|
1983 | let style;
|
1984 | let onError;
|
1985 | let transform;
|
1986 |
|
1987 | beforeEach((callback) => {
|
1988 | transform = new Transform();
|
1989 | transform.resize(100, 100);
|
1990 | style = new Style(getStubMap());
|
1991 | style.loadJSON({
|
1992 | 'version': 8,
|
1993 | 'sources': {
|
1994 | 'geojson': createGeoJSONSource()
|
1995 | },
|
1996 | 'layers': [{
|
1997 | 'id': 'symbol',
|
1998 | 'type': 'symbol',
|
1999 | 'source': 'geojson'
|
2000 | }]
|
2001 | });
|
2002 |
|
2003 | onError = jest.fn();
|
2004 |
|
2005 | style.on('error', onError)
|
2006 | .on('style.load', () => {
|
2007 | callback();
|
2008 | });
|
2009 | });
|
2010 |
|
2011 | test('querySourceFeatures emits an error on incorrect filter', () => {
|
2012 | expect(style.querySourceFeatures([10, 100], {filter: 7}, transform)).toEqual([]);
|
2013 | expect(onError.mock.calls[0][0].error.message).toMatch(/querySourceFeatures\.filter/);
|
2014 | });
|
2015 |
|
2016 | test('queryRenderedFeatures emits an error on incorrect filter', () => {
|
2017 | expect(style.queryRenderedFeatures([{x: 0, y: 0}], {filter: 7}, transform)).toEqual([]);
|
2018 | expect(onError.mock.calls[0][0].error.message).toMatch(/queryRenderedFeatures\.filter/);
|
2019 | });
|
2020 |
|
2021 | test('querySourceFeatures not raise validation errors if validation was disabled', () => {
|
2022 | let errors = 0;
|
2023 | jest.spyOn(style, 'fire').mockImplementation((event) => {
|
2024 | if (event['error']) {
|
2025 | errors++;
|
2026 | }
|
2027 | });
|
2028 | style.queryRenderedFeatures([{x: 0, y: 0}], {filter: 'invalidFilter', validate: false}, transform);
|
2029 | expect(errors).toBe(0);
|
2030 | });
|
2031 |
|
2032 | test('querySourceFeatures not raise validation errors if validation was disabled', () => {
|
2033 | let errors = 0;
|
2034 | jest.spyOn(style, 'fire').mockImplementation((event) => {
|
2035 | if (event['error']) errors++;
|
2036 | });
|
2037 | style.querySourceFeatures([{x: 0, y: 0}], {filter: 'invalidFilter', validate: false}, transform);
|
2038 | expect(errors).toBe(0);
|
2039 | });
|
2040 | });
|
2041 |
|
2042 | describe('Style#addSourceType', () => {
|
2043 | const _types = {'existing' () {}};
|
2044 |
|
2045 | jest.spyOn(Style, 'getSourceType').mockImplementation(name => _types[name]);
|
2046 | jest.spyOn(Style, 'setSourceType').mockImplementation((name, create) => {
|
2047 | _types[name] = create;
|
2048 | });
|
2049 |
|
2050 | test('adds factory function', done => {
|
2051 | const style = new Style(getStubMap());
|
2052 | const sourceType = function () {} as any as SourceClass;
|
2053 |
|
2054 |
|
2055 | style.dispatcher.broadcast = function (type) {
|
2056 | if (type === 'loadWorkerSource') {
|
2057 | done('test failed');
|
2058 | }
|
2059 | };
|
2060 |
|
2061 | style.addSourceType('foo', sourceType, () => {
|
2062 | expect(_types['foo']).toBe(sourceType);
|
2063 | done();
|
2064 | });
|
2065 | });
|
2066 |
|
2067 | test('triggers workers to load worker source code', done => {
|
2068 | const style = new Style(getStubMap());
|
2069 | const sourceType = function () {} as any as SourceClass;
|
2070 | sourceType.workerSourceURL = 'worker-source.js' as any as URL;
|
2071 |
|
2072 | style.dispatcher.broadcast = function (type, params) {
|
2073 | if (type === 'loadWorkerSource') {
|
2074 | expect(_types['bar']).toBe(sourceType);
|
2075 | expect(params['name']).toBe('bar');
|
2076 | expect(params['url']).toBe('worker-source.js');
|
2077 | done();
|
2078 | }
|
2079 | };
|
2080 |
|
2081 | style.addSourceType('bar', sourceType, (err) => { expect(err).toBeFalsy(); });
|
2082 | });
|
2083 |
|
2084 | test('refuses to add new type over existing name', done => {
|
2085 | const style = new Style(getStubMap());
|
2086 | const sourceType = function () {} as any as SourceClass;
|
2087 | style.addSourceType('existing', sourceType, (err) => {
|
2088 | expect(err).toBeTruthy();
|
2089 | done();
|
2090 | });
|
2091 | });
|
2092 | });
|
2093 |
|
2094 | describe('Style#hasTransitions', () => {
|
2095 | test('returns false when the style is loading', () => {
|
2096 | const style = new Style(getStubMap());
|
2097 | expect(style.hasTransitions()).toBe(false);
|
2098 | });
|
2099 |
|
2100 | test('returns true when a property is transitioning', done => {
|
2101 | const style = new Style(getStubMap());
|
2102 | style.loadJSON({
|
2103 | 'version': 8,
|
2104 | 'sources': {},
|
2105 | 'layers': [{
|
2106 | 'id': 'background',
|
2107 | 'type': 'background'
|
2108 | }]
|
2109 | });
|
2110 |
|
2111 | style.on('style.load', () => {
|
2112 | style.setPaintProperty('background', 'background-color', 'blue');
|
2113 | style.update({transition: {duration: 300, delay: 0}} as EvaluationParameters);
|
2114 | expect(style.hasTransitions()).toBe(true);
|
2115 | done();
|
2116 | });
|
2117 | });
|
2118 |
|
2119 | test('returns false when a property is not transitioning', done => {
|
2120 | const style = new Style(getStubMap());
|
2121 | style.loadJSON({
|
2122 | 'version': 8,
|
2123 | 'sources': {},
|
2124 | 'layers': [{
|
2125 | 'id': 'background',
|
2126 | 'type': 'background'
|
2127 | }]
|
2128 | });
|
2129 |
|
2130 | style.on('style.load', () => {
|
2131 | style.setPaintProperty('background', 'background-color', 'blue');
|
2132 | style.update({transition: {duration: 0, delay: 0}} as EvaluationParameters);
|
2133 | expect(style.hasTransitions()).toBe(false);
|
2134 | done();
|
2135 | });
|
2136 | });
|
2137 | });
|