1 | import fs from 'fs';
|
2 | import path from 'path';
|
3 | import vt from '@mapbox/vector-tile';
|
4 | import Protobuf from 'pbf';
|
5 | import VectorTileWorkerSource from '../source/vector_tile_worker_source';
|
6 | import StyleLayerIndex from '../style/style_layer_index';
|
7 | import {fakeServer, FakeServer} from 'nise';
|
8 | import Actor from '../util/actor';
|
9 | import {TileParameters, WorkerTileParameters} from './worker_source';
|
10 | import WorkerTile from './worker_tile';
|
11 | import {setPerformance} from '../util/test/util';
|
12 |
|
13 | describe('vector tile worker source', () => {
|
14 | const actor = {send: () => {}} as any as Actor;
|
15 | let server: FakeServer;
|
16 | let originalGetEntriesByName;
|
17 | let originalMeasure;
|
18 | let originalMark;
|
19 |
|
20 | beforeEach(() => {
|
21 | global.fetch = null;
|
22 | server = fakeServer.create();
|
23 | setPerformance();
|
24 | originalGetEntriesByName = window.performance.getEntriesByName;
|
25 | originalMeasure = window.performance.measure;
|
26 | originalMark = window.performance.mark;
|
27 |
|
28 | });
|
29 |
|
30 | afterEach(() => {
|
31 | server.restore();
|
32 | jest.clearAllMocks();
|
33 | window.performance.getEntriesByName = originalGetEntriesByName;
|
34 | window.performance.measure = originalMeasure;
|
35 | window.performance.mark = originalMark;
|
36 | });
|
37 | test('VectorTileWorkerSource#abortTile aborts pending request', () => {
|
38 | const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
39 |
|
40 | source.loadTile({
|
41 | source: 'source',
|
42 | uid: 0,
|
43 | tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
|
44 | request: {url: 'http://localhost:2900/abort'}
|
45 | } as any as WorkerTileParameters, (err, res) => {
|
46 | expect(err).toBeFalsy();
|
47 | expect(res).toBeFalsy();
|
48 | });
|
49 |
|
50 | source.abortTile({
|
51 | source: 'source',
|
52 | uid: 0
|
53 | } as any as TileParameters, (err, res) => {
|
54 | expect(err).toBeFalsy();
|
55 | expect(res).toBeFalsy();
|
56 | });
|
57 |
|
58 | expect(source.loading).toEqual({});
|
59 | });
|
60 |
|
61 | test('VectorTileWorkerSource#removeTile removes loaded tile', () => {
|
62 | const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
63 |
|
64 | source.loaded = {
|
65 | '0': {} as WorkerTile
|
66 | };
|
67 |
|
68 | source.removeTile({
|
69 | source: 'source',
|
70 | uid: 0
|
71 | } as any as TileParameters, (err, res) => {
|
72 | expect(err).toBeFalsy();
|
73 | expect(res).toBeFalsy();
|
74 | });
|
75 |
|
76 | expect(source.loaded).toEqual({});
|
77 | });
|
78 |
|
79 | test('VectorTileWorkerSource#reloadTile reloads a previously-loaded tile', () => {
|
80 | const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
81 | const parse = jest.fn();
|
82 |
|
83 | source.loaded = {
|
84 | '0': {
|
85 | status: 'done',
|
86 | vectorTile: {},
|
87 | parse
|
88 | } as any as WorkerTile
|
89 | };
|
90 |
|
91 | const callback = jest.fn();
|
92 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback);
|
93 | expect(parse).toHaveBeenCalledTimes(1);
|
94 |
|
95 | parse.mock.calls[0][4]();
|
96 | expect(callback).toHaveBeenCalledTimes(1);
|
97 | });
|
98 |
|
99 | test('VectorTileWorkerSource#reloadTile queues a reload when parsing is in progress', () => {
|
100 | const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
101 | const parse = jest.fn();
|
102 |
|
103 | source.loaded = {
|
104 | '0': {
|
105 | status: 'done',
|
106 | vectorTile: {},
|
107 | parse
|
108 | } as any as WorkerTile
|
109 | };
|
110 |
|
111 | const callback1 = jest.fn();
|
112 | const callback2 = jest.fn();
|
113 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback1);
|
114 | expect(parse).toHaveBeenCalledTimes(1);
|
115 |
|
116 | source.loaded[0].status = 'parsing';
|
117 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback2);
|
118 | expect(parse).toHaveBeenCalledTimes(1);
|
119 |
|
120 | parse.mock.calls[0][4]();
|
121 | expect(parse).toHaveBeenCalledTimes(2);
|
122 | expect(callback1).toHaveBeenCalledTimes(1);
|
123 | expect(callback2).toHaveBeenCalledTimes(0);
|
124 |
|
125 | parse.mock.calls[1][4]();
|
126 | expect(callback1).toHaveBeenCalledTimes(1);
|
127 | expect(callback2).toHaveBeenCalledTimes(1);
|
128 | });
|
129 |
|
130 | test('VectorTileWorkerSource#reloadTile handles multiple pending reloads', () => {
|
131 |
|
132 | const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
133 | const parse = jest.fn();
|
134 |
|
135 | source.loaded = {
|
136 | '0': {
|
137 | status: 'done',
|
138 | vectorTile: {},
|
139 | parse
|
140 | } as any as WorkerTile
|
141 | };
|
142 |
|
143 | const callback1 = jest.fn();
|
144 | const callback2 = jest.fn();
|
145 | const callback3 = jest.fn();
|
146 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback1);
|
147 | expect(parse).toHaveBeenCalledTimes(1);
|
148 |
|
149 | source.loaded[0].status = 'parsing';
|
150 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback2);
|
151 | expect(parse).toHaveBeenCalledTimes(1);
|
152 |
|
153 | parse.mock.calls[0][4]();
|
154 | expect(parse).toHaveBeenCalledTimes(2);
|
155 | expect(callback1).toHaveBeenCalledTimes(1);
|
156 | expect(callback2).toHaveBeenCalledTimes(0);
|
157 | expect(callback3).toHaveBeenCalledTimes(0);
|
158 |
|
159 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback3);
|
160 | expect(parse).toHaveBeenCalledTimes(2);
|
161 | expect(callback1).toHaveBeenCalledTimes(1);
|
162 | expect(callback2).toHaveBeenCalledTimes(0);
|
163 | expect(callback3).toHaveBeenCalledTimes(0);
|
164 |
|
165 | parse.mock.calls[1][4]();
|
166 | expect(parse).toHaveBeenCalledTimes(3);
|
167 | expect(callback1).toHaveBeenCalledTimes(1);
|
168 | expect(callback2).toHaveBeenCalledTimes(1);
|
169 | expect(callback3).toHaveBeenCalledTimes(0);
|
170 |
|
171 | parse.mock.calls[2][4]();
|
172 | expect(callback1).toHaveBeenCalledTimes(1);
|
173 | expect(callback2).toHaveBeenCalledTimes(1);
|
174 | expect(callback3).toHaveBeenCalledTimes(1);
|
175 |
|
176 | });
|
177 |
|
178 | test('VectorTileWorkerSource#reloadTile does not reparse tiles with no vectorTile data but does call callback', () => {
|
179 | const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
|
180 | const parse = jest.fn();
|
181 |
|
182 | source.loaded = {
|
183 | '0': {
|
184 | status: 'done',
|
185 | parse
|
186 | } as any as WorkerTile
|
187 | };
|
188 |
|
189 | const callback = jest.fn();
|
190 |
|
191 | source.reloadTile({uid: 0} as any as WorkerTileParameters, callback);
|
192 | expect(parse).not.toHaveBeenCalled();
|
193 | expect(callback).toHaveBeenCalledTimes(1);
|
194 |
|
195 | });
|
196 |
|
197 | test('VectorTileWorkerSource provides resource timing information', done => {
|
198 | const rawTileData = fs.readFileSync(path.join(__dirname, '/../../test/unit/assets/mbsv5-6-18-23.vector.pbf'));
|
199 |
|
200 | function loadVectorData(params, callback) {
|
201 | return callback(null, {
|
202 | vectorTile: new vt.VectorTile(new Protobuf(rawTileData)),
|
203 | rawData: rawTileData,
|
204 | cacheControl: null,
|
205 | expires: null
|
206 | });
|
207 | }
|
208 |
|
209 | const exampleResourceTiming = {
|
210 | connectEnd: 473,
|
211 | connectStart: 473,
|
212 | decodedBodySize: 86494,
|
213 | domainLookupEnd: 473,
|
214 | domainLookupStart: 473,
|
215 | duration: 341,
|
216 | encodedBodySize: 52528,
|
217 | entryType: 'resource',
|
218 | fetchStart: 473.5,
|
219 | initiatorType: 'xmlhttprequest',
|
220 | name: 'http://localhost:2900/faketile.pbf',
|
221 | nextHopProtocol: 'http/1.1',
|
222 | redirectEnd: 0,
|
223 | redirectStart: 0,
|
224 | requestStart: 477,
|
225 | responseEnd: 815,
|
226 | responseStart: 672,
|
227 | secureConnectionStart: 0
|
228 | };
|
229 |
|
230 | const layerIndex = new StyleLayerIndex([{
|
231 | id: 'test',
|
232 | source: 'source',
|
233 | 'source-layer': 'test',
|
234 | type: 'fill'
|
235 | }]);
|
236 |
|
237 | const source = new VectorTileWorkerSource(actor, layerIndex, [], loadVectorData);
|
238 |
|
239 | window.performance.getEntriesByName = jest.fn().mockReturnValue([ exampleResourceTiming ]);
|
240 |
|
241 | source.loadTile({
|
242 | source: 'source',
|
243 | uid: 0,
|
244 | tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
|
245 | request: {url: 'http://localhost:2900/faketile.pbf', collectResourceTiming: true}
|
246 | } as any as WorkerTileParameters, (err, res) => {
|
247 | expect(err).toBeFalsy();
|
248 | expect(res.resourceTiming[0]).toEqual(exampleResourceTiming);
|
249 | done();
|
250 | });
|
251 | });
|
252 |
|
253 | test('VectorTileWorkerSource provides resource timing information (fallback method)', done => {
|
254 | const rawTileData = fs.readFileSync(path.join(__dirname, '/../../test/unit/assets/mbsv5-6-18-23.vector.pbf'));
|
255 |
|
256 | function loadVectorData(params, callback) {
|
257 | return callback(null, {
|
258 | vectorTile: new vt.VectorTile(new Protobuf(rawTileData)),
|
259 | rawData: rawTileData,
|
260 | cacheControl: null,
|
261 | expires: null
|
262 | });
|
263 | }
|
264 |
|
265 | const layerIndex = new StyleLayerIndex([{
|
266 | id: 'test',
|
267 | source: 'source',
|
268 | 'source-layer': 'test',
|
269 | type: 'fill'
|
270 | }]);
|
271 |
|
272 | const source = new VectorTileWorkerSource(actor, layerIndex, [], loadVectorData);
|
273 |
|
274 | const sampleMarks = [100, 350];
|
275 | const marks = {};
|
276 | const measures = {};
|
277 | window.performance.getEntriesByName = jest.fn().mockImplementation(name => (measures[name] || []));
|
278 | window.performance.mark = jest.fn().mockImplementation(name => {
|
279 | marks[name] = sampleMarks.shift();
|
280 | return null;
|
281 | });
|
282 | window.performance.measure = jest.fn().mockImplementation((name, start, end) => {
|
283 | measures[name] = measures[name] || [];
|
284 | measures[name].push({
|
285 | duration: marks[end] - marks[start],
|
286 | entryType: 'measure',
|
287 | name,
|
288 | startTime: marks[start]
|
289 | });
|
290 | return null;
|
291 | });
|
292 |
|
293 | source.loadTile({
|
294 | source: 'source',
|
295 | uid: 0,
|
296 | tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
|
297 | request: {url: 'http://localhost:2900/faketile.pbf', collectResourceTiming: true}
|
298 | } as any as WorkerTileParameters, (err, res) => {
|
299 | expect(err).toBeFalsy();
|
300 | expect(res.resourceTiming[0]).toEqual(
|
301 | {'duration': 250, 'entryType': 'measure', 'name': 'http://localhost:2900/faketile.pbf', 'startTime': 100}
|
302 | );
|
303 | done();
|
304 | });
|
305 | });
|
306 | });
|