UNPKG

12.4 kBPlain TextView Raw
1import {fakeServer, FakeServer} from 'nise';
2import {Source} from './source';
3import VectorTileSource from './vector_tile_source';
4import Tile from './tile';
5import {OverscaledTileID} from './tile_id';
6import {Evented} from '../util/evented';
7import {RequestManager} from '../util/request_manager';
8import fixturesSource from '../../test/unit/assets/source.json';
9import {getMockDispatcher, getWrapDispatcher} from '../util/test/util';
10import Map from '../ui/map';
11
12function createSource(options, transformCallback?, clearTiles = () => {}) {
13 const source = new VectorTileSource('id', options, getMockDispatcher(), options.eventedParent);
14 source.onAdd({
15 transform: {showCollisionBoxes: false},
16 _getMapId: () => 1,
17 _requestManager: new RequestManager(transformCallback),
18 style: {sourceCaches: {id: {clearTiles}}},
19 getPixelRatio() { return 1; }
20 } as any as Map);
21
22 source.on('error', (e) => {
23 throw e.error;
24 });
25
26 return source;
27}
28
29describe('VectorTileSource', () => {
30 let server: FakeServer;
31 beforeEach(() => {
32 global.fetch = null;
33 server = fakeServer.create();
34 });
35
36 afterEach(() => {
37 server.restore();
38 });
39
40 test('can be constructed from TileJSON', done => {
41 const source = createSource({
42 minzoom: 1,
43 maxzoom: 10,
44 attribution: 'Maplibre',
45 tiles: ['http://example.com/{z}/{x}/{y}.png']
46 });
47
48 source.on('data', (e) => {
49 if (e.sourceDataType === 'metadata') {
50 expect(source.tiles).toEqual(['http://example.com/{z}/{x}/{y}.png']);
51 expect(source.minzoom).toBe(1);
52 expect(source.maxzoom).toBe(10);
53 expect((source as Source).attribution).toBe('Maplibre');
54 done();
55 }
56 });
57 });
58
59 test('can be constructed from a TileJSON URL', done => {
60 server.respondWith('/source.json', JSON.stringify(fixturesSource));
61
62 const source = createSource({url: '/source.json'});
63
64 source.on('data', (e) => {
65 if (e.sourceDataType === 'metadata') {
66 expect(source.tiles).toEqual(['http://example.com/{z}/{x}/{y}.png']);
67 expect(source.minzoom).toBe(1);
68 expect(source.maxzoom).toBe(10);
69 expect((source as Source).attribution).toBe('Maplibre');
70 done();
71 }
72 });
73
74 server.respond();
75 });
76
77 test('transforms the request for TileJSON URL', () => {
78 server.respondWith('/source.json', JSON.stringify(fixturesSource));
79 const transformSpy = jest.fn().mockImplementation((url) => {
80 return {url};
81 });
82
83 createSource({url: '/source.json'}, transformSpy);
84 server.respond();
85 expect(transformSpy).toHaveBeenCalledWith('/source.json', 'Source');
86 });
87
88 test('fires event with metadata property', done => {
89 server.respondWith('/source.json', JSON.stringify(fixturesSource));
90 const source = createSource({url: '/source.json'});
91 source.on('data', (e) => {
92 if (e.sourceDataType === 'content') done();
93 });
94 server.respond();
95 });
96
97 test('fires "dataloading" event', done => {
98 server.respondWith('/source.json', JSON.stringify(fixturesSource));
99 const evented = new Evented();
100 let dataloadingFired = false;
101 evented.on('dataloading', () => {
102 dataloadingFired = true;
103 });
104 const source = createSource({url: '/source.json', eventedParent: evented});
105 source.on('data', (e) => {
106 if (e.sourceDataType === 'metadata') {
107 if (!dataloadingFired) done('test failed: dataloading not fired');
108 done();
109 }
110 });
111 server.respond();
112 });
113
114 test('serialize URL', () => {
115 const source = createSource({
116 url: 'http://localhost:2900/source.json'
117 });
118 expect(source.serialize()).toEqual({
119 type: 'vector',
120 url: 'http://localhost:2900/source.json'
121 });
122 });
123
124 test('serialize TileJSON', () => {
125 const source = createSource({
126 minzoom: 1,
127 maxzoom: 10,
128 attribution: 'Maplibre',
129 tiles: ['http://example.com/{z}/{x}/{y}.png']
130 });
131 expect(source.serialize()).toEqual({
132 type: 'vector',
133 minzoom: 1,
134 maxzoom: 10,
135 attribution: 'Maplibre',
136 tiles: ['http://example.com/{z}/{x}/{y}.png']
137 });
138 });
139
140 function testScheme(scheme, expectedURL) {
141 test(`scheme "${scheme}"`, done => {
142 const source = createSource({
143 minzoom: 1,
144 maxzoom: 10,
145 attribution: 'Maplibre',
146 tiles: ['http://example.com/{z}/{x}/{y}.png'],
147 scheme
148 });
149
150 source.dispatcher = getWrapDispatcher()({
151 send(type, params) {
152 expect(type).toBe('loadTile');
153 expect(expectedURL).toBe(params.request.url);
154 done();
155 }
156 });
157
158 source.on('data', (e) => {
159 if (e.sourceDataType === 'metadata') source.loadTile({
160 tileID: new OverscaledTileID(10, 0, 10, 5, 5)
161 } as any as Tile, () => {});
162 });
163 });
164 }
165
166 testScheme('xyz', 'http://example.com/10/5/5.png');
167 testScheme('tms', 'http://example.com/10/5/1018.png');
168
169 test('transforms tile urls before requesting', done => {
170 server.respondWith('/source.json', JSON.stringify(fixturesSource));
171
172 const source = createSource({url: '/source.json'});
173 const transformSpy = jest.spyOn(source.map._requestManager, 'transformRequest');
174 source.on('data', (e) => {
175 if (e.sourceDataType === 'metadata') {
176 const tile = {
177 tileID: new OverscaledTileID(10, 0, 10, 5, 5),
178 state: 'loading',
179 loadVectorData () {},
180 setExpiryData() {}
181 } as any as Tile;
182 source.loadTile(tile, () => {});
183 expect(transformSpy).toHaveBeenCalledTimes(1);
184 expect(transformSpy).toHaveBeenCalledWith('http://example.com/10/5/5.png', 'Tile');
185 done();
186 }
187 });
188
189 server.respond();
190 });
191
192 test('reloads a loading tile properly', done => {
193 const source = createSource({
194 tiles: ['http://example.com/{z}/{x}/{y}.png']
195 });
196 const events = [];
197 source.dispatcher = getWrapDispatcher()({
198 send(type, params, cb) {
199 events.push(type);
200 if (cb) setTimeout(cb, 0);
201 return 1;
202 }
203 });
204
205 source.on('data', (e) => {
206 if (e.sourceDataType === 'metadata') {
207 const tile = {
208 tileID: new OverscaledTileID(10, 0, 10, 5, 5),
209 state: 'loading',
210 loadVectorData () {
211 this.state = 'loaded';
212 events.push('tileLoaded');
213 },
214 setExpiryData() {}
215 } as any as Tile;
216 source.loadTile(tile, () => {});
217 expect(tile.state).toBe('loading');
218 source.loadTile(tile, () => {
219 expect(events).toEqual(
220 ['loadTile', 'tileLoaded', 'enforceCacheSizeLimit', 'reloadTile', 'tileLoaded']
221 );
222 done();
223 });
224 }
225 });
226 });
227
228 test('respects TileJSON.bounds', done => {
229 const source = createSource({
230 minzoom: 0,
231 maxzoom: 22,
232 attribution: 'Maplibre',
233 tiles: ['http://example.com/{z}/{x}/{y}.png'],
234 bounds: [-47, -7, -45, -5]
235 });
236 source.on('data', (e) => {
237 if (e.sourceDataType === 'metadata') {
238 expect(source.hasTile(new OverscaledTileID(8, 0, 8, 96, 132))).toBeFalsy();
239 expect(source.hasTile(new OverscaledTileID(8, 0, 8, 95, 132))).toBeTruthy();
240 done();
241 }
242 });
243 });
244
245 test('does not error on invalid bounds', done => {
246 const source = createSource({
247 minzoom: 0,
248 maxzoom: 22,
249 attribution: 'Maplibre',
250 tiles: ['http://example.com/{z}/{x}/{y}.png'],
251 bounds: [-47, -7, -45, 91]
252 });
253
254 source.on('data', (e) => {
255 if (e.sourceDataType === 'metadata') {
256 expect(source.tileBounds.bounds).toEqual({_sw:{lng: -47, lat: -7}, _ne:{lng: -45, lat: 90}});
257 done();
258 }
259 });
260 });
261
262 test('respects TileJSON.bounds when loaded from TileJSON', done => {
263 server.respondWith('/source.json', JSON.stringify({
264 minzoom: 0,
265 maxzoom: 22,
266 attribution: 'Maplibre',
267 tiles: ['http://example.com/{z}/{x}/{y}.png'],
268 bounds: [-47, -7, -45, -5]
269 }));
270 const source = createSource({url: '/source.json'});
271
272 source.on('data', (e) => {
273 if (e.sourceDataType === 'metadata') {
274 expect(source.hasTile(new OverscaledTileID(8, 0, 8, 96, 132))).toBeFalsy();
275 expect(source.hasTile(new OverscaledTileID(8, 0, 8, 95, 132))).toBeTruthy();
276 done();
277 }
278 });
279 server.respond();
280 });
281
282 test('respects collectResourceTiming parameter on source', done => {
283 const source = createSource({
284 tiles: ['http://example.com/{z}/{x}/{y}.png'],
285 collectResourceTiming: true
286 });
287 source.dispatcher = getWrapDispatcher()({
288 send(type, params, cb) {
289 expect(params.request.collectResourceTiming).toBeTruthy();
290 setTimeout(cb, 0);
291 done();
292
293 // do nothing for cache size check dispatch
294 source.dispatcher = getMockDispatcher();
295
296 return 1;
297 }
298 });
299
300 source.on('data', (e) => {
301 if (e.sourceDataType === 'metadata') {
302 const tile = {
303 tileID: new OverscaledTileID(10, 0, 10, 5, 5),
304 state: 'loading',
305 loadVectorData () {},
306 setExpiryData() {}
307 } as any as Tile;
308 source.loadTile(tile, () => {});
309 }
310 });
311 });
312
313 test('cancels TileJSON request if removed', () => {
314 const source = createSource({url: '/source.json'});
315 source.onRemove();
316 expect((server as any).lastRequest.aborted).toBe(true);
317 });
318
319 test('supports url property updates', () => {
320 const source = createSource({
321 url: 'http://localhost:2900/source.json'
322 });
323 source.setUrl('http://localhost:2900/source2.json');
324 expect(source.serialize()).toEqual({
325 type: 'vector',
326 url: 'http://localhost:2900/source2.json'
327 });
328 });
329
330 test('supports tiles property updates', () => {
331 const source = createSource({
332 minzoom: 1,
333 maxzoom: 10,
334 attribution: 'Maplibre',
335 tiles: ['http://example.com/{z}/{x}/{y}.png']
336 });
337 source.setTiles(['http://example2.com/{z}/{x}/{y}.png']);
338 expect(source.serialize()).toEqual({
339 type: 'vector',
340 minzoom: 1,
341 maxzoom: 10,
342 attribution: 'Maplibre',
343 tiles: ['http://example2.com/{z}/{x}/{y}.png']
344 });
345 });
346
347 test('setTiles only clears the cache once the TileJSON has reloaded', done => {
348 const clearTiles = jest.fn();
349 const source = createSource({tiles: ['http://example.com/{z}/{x}/{y}.pbf']}, undefined, clearTiles);
350 source.setTiles(['http://example2.com/{z}/{x}/{y}.pbf']);
351 expect(clearTiles.mock.calls).toHaveLength(0);
352 source.once('data', () => {
353 expect(clearTiles.mock.calls).toHaveLength(1);
354 done();
355 });
356 });
357});