Skip to content

Commit ea03e16

Browse files
increase test coverage
1 parent e9e24ec commit ea03e16

File tree

3 files changed

+193
-21
lines changed

3 files changed

+193
-21
lines changed

src/source/geojson_worker_source.test.ts

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,185 @@ import {StyleLayerIndex} from '../style/style_layer_index';
44
import {OverscaledTileID} from '../tile/tile_id';
55
import perf from '../util/performance';
66
import {type LayerSpecification} from '@maplibre/maplibre-gl-style-spec';
7-
import {type Actor} from '../util/actor';
8-
import {type WorkerTileParameters} from './worker_source';
7+
import {type Actor, type IActor} from '../util/actor';
8+
import {type TileParameters, type WorkerTileParameters, type WorkerTileResult} from './worker_source';
99
import {setPerformance, sleep} from '../util/test/util';
1010
import {type FakeServer, fakeServer} from 'nise';
1111
import {GEOJSON_TILE_LAYER_NAME} from '@maplibre/vt-pbf';
12+
import {SubdivisionGranularitySetting} from '../render/subdivision_granularity_settings';
13+
import {type WorkerTile} from './worker_tile';
1214

1315
const actor = {send: () => {}} as any as Actor;
1416

1517
beforeEach(() => {
1618
setPerformance();
1719
});
1820

21+
afterEach(() => {
22+
vi.clearAllMocks();
23+
});
24+
25+
describe('geojson tile worker source', () => {
26+
const actor: IActor = {sendAsync: () => Promise.resolve({})} as any as IActor;
27+
28+
test('GeoJSONWorkerSource.removeTile removes loaded tile', async () => {
29+
const source = new GeoJSONWorkerSource(actor, new StyleLayerIndex(), []);
30+
31+
source.tileState.loaded = {
32+
'0': {} as WorkerTile
33+
};
34+
35+
const res = await source.removeTile({
36+
source: 'source',
37+
uid: 0
38+
} as any as TileParameters);
39+
expect(res).toBeUndefined();
40+
41+
expect(source.tileState.loaded).toEqual({});
42+
});
43+
44+
test('GeoJSONWorkerSource.reloadTile reloads a previously-loaded tile', async () => {
45+
const source = new GeoJSONWorkerSource(actor, new StyleLayerIndex(), []);
46+
const parse = vi.fn().mockResolvedValue({} as WorkerTileResult);
47+
48+
source.tileState.loaded = {
49+
'0': {
50+
status: 'done',
51+
vectorTile: {},
52+
parse
53+
} as any as WorkerTile
54+
};
55+
56+
const reloadPromise = source.reloadTile({uid: 0} as any as WorkerTileParameters);
57+
expect(parse).toHaveBeenCalledTimes(1);
58+
await expect(reloadPromise).resolves.toBeTruthy();
59+
});
60+
61+
test('GeoJSONWorkerSource.reloadTile returns parse result without rawTileData when parsing state was already consumed', async () => {
62+
const source = new GeoJSONWorkerSource(actor, new StyleLayerIndex(), []);
63+
const parseResult = {buckets: []} as any as WorkerTileResult;
64+
const parse = vi.fn().mockResolvedValue(parseResult);
65+
66+
source.tileState.loaded = {
67+
'0': {
68+
status: 'parsing',
69+
vectorTile: {},
70+
parse
71+
} as any as WorkerTile
72+
};
73+
74+
const result = await source.reloadTile({uid: 0} as any as WorkerTileParameters);
75+
76+
expect(parse).toHaveBeenCalledTimes(1);
77+
expect(result).toBe(parseResult);
78+
expect(result.rawTileData).toBeUndefined();
79+
});
80+
81+
test('GeoJSONWorkerSource.loadTile reparses tile if reloadTile has been called during parsing', async () => {
82+
const layerIndex = new StyleLayerIndex([{
83+
id: 'test',
84+
source: 'source',
85+
'source-layer': '_geojsonTileLayer',
86+
type: 'symbol',
87+
layout: {
88+
'icon-image': 'hello',
89+
'text-font': ['StandardFont-Bold'],
90+
'text-field': '{name}'
91+
}
92+
}]);
93+
94+
const actor = {
95+
sendAsync: (message: {type: string; data: unknown}, abortController: AbortController) => {
96+
return new Promise((resolve, _reject) => {
97+
const res = setTimeout(() => {
98+
const response = message.type === 'getImages' ?
99+
{'hello': {width: 1, height: 1, data: new Uint8Array([0])}} :
100+
{'StandardFont-Bold': {width: 1, height: 1, data: new Uint8Array([0])}};
101+
resolve(response);
102+
}, 100);
103+
abortController.signal.addEventListener('abort', () => {
104+
clearTimeout(res);
105+
});
106+
});
107+
}
108+
};
109+
110+
const source = new GeoJSONWorkerSource(actor as any, layerIndex, ['hello']);
111+
112+
const geoJson = {
113+
type: 'FeatureCollection',
114+
features: [{
115+
type: 'Feature',
116+
id: 1,
117+
geometry: {
118+
type: 'Point',
119+
coordinates: [0, 0]
120+
},
121+
properties: {
122+
name: 'test'
123+
}
124+
}]
125+
} as GeoJSON.GeoJSON;
126+
127+
await source.loadData({source: 'source', data: geoJson} as LoadGeoJSONParameters);
128+
129+
source.loadTile({
130+
source: 'source',
131+
uid: 0,
132+
tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
133+
subdivisionGranularity: SubdivisionGranularitySetting.noSubdivision,
134+
} as any as WorkerTileParameters).then(() => expect(false).toBeTruthy());
135+
136+
// allow promise to run
137+
await sleep(0);
138+
139+
const res = await source.reloadTile({
140+
source: 'source',
141+
uid: 0,
142+
tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
143+
subdivisionGranularity: SubdivisionGranularitySetting.noSubdivision,
144+
} as any as WorkerTileParameters);
145+
146+
expect(res).toBeDefined();
147+
expect(res.rawTileData).toBeDefined();
148+
});
149+
150+
test('GeoJSONWorkerSource.loadTile returns null for an empty tile', async () => {
151+
const source = new GeoJSONWorkerSource(actor, new StyleLayerIndex(), []);
152+
await source.loadData({source: 'source', data: {type: 'FeatureCollection', features: []}} as LoadGeoJSONParameters);
153+
154+
const result = await source.loadTile({
155+
source: 'source',
156+
uid: 0,
157+
tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
158+
} as any as WorkerTileParameters);
159+
160+
expect(result).toBeNull();
161+
});
162+
163+
test('GeoJSONWorkerSource.loadTile throws error when data has not been loaded', async () => {
164+
const source = new GeoJSONWorkerSource(actor, new StyleLayerIndex(), []);
165+
166+
await expect(source.loadTile({
167+
source: 'source',
168+
uid: 0,
169+
tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
170+
} as any as WorkerTileParameters)).rejects.toThrowError(/Unable to parse the data into a cluster or geojson/);
171+
});
172+
173+
test('GeoJSONWorkerSource.abortTile aborts tile state', async () => {
174+
const source = new GeoJSONWorkerSource(actor, new StyleLayerIndex(), []);
175+
const abortSpy = vi.spyOn(source.tileState, 'abort');
176+
177+
await source.abortTile({
178+
source: 'source',
179+
uid: 0
180+
} as any as TileParameters);
181+
182+
expect(abortSpy).toHaveBeenCalledWith(0);
183+
});
184+
});
185+
19186
describe('reloadTile', () => {
20187
test('does not rebuild vector data unless data has changed', async () => {
21188
const layers = [

src/source/geojson_worker_source.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,24 +108,29 @@ export class GeoJSONWorkerSource implements WorkerSource {
108108
async loadTile(params: WorkerTileParameters): Promise<WorkerTileResult | null> {
109109
const {uid} = params;
110110

111-
const loadResult = this.loadVectorTile(params);
112-
if (!loadResult) return null;
113-
114-
const {vectorTile, rawData} = loadResult;
115-
116111
const workerTile = new WorkerTile(params);
117-
workerTile.vectorTile = vectorTile;
118-
this.tileState.markLoaded(uid, workerTile);
119-
120-
this.tileState.setParsing(uid, {rawData}); // Keep data so reloadTile can access if parse is canceled.
121112
workerTile.abort = new AbortController();
122113
try {
123-
const result = await workerTile.parse(vectorTile, this.layerIndex, this.availableImages, this.actor, params.subdivisionGranularity);
124-
// Transferring a copy of rawData because the worker needs to retain its copy.
125-
return extend({rawTileData: rawData.slice(0)}, result);
126-
} finally {
127-
delete workerTile.abort;
128-
this.tileState.clearParsing(uid);
114+
const loadResult = this.loadVectorTile(params);
115+
if (!loadResult) return null;
116+
117+
const {vectorTile, rawData} = loadResult;
118+
119+
workerTile.vectorTile = vectorTile;
120+
this.tileState.markLoaded(uid, workerTile);
121+
122+
this.tileState.setParsing(uid, {rawData}); // Keep data so reloadTile can access if parse is canceled.
123+
try {
124+
const result = await workerTile.parse(vectorTile, this.layerIndex, this.availableImages, this.actor, params.subdivisionGranularity);
125+
// Transferring a copy of rawData because the worker needs to retain its copy.
126+
return extend({rawTileData: rawData.slice(0)}, result);
127+
} finally {
128+
this.tileState.clearParsing(uid);
129+
}
130+
} catch (err) {
131+
workerTile.status = 'done';
132+
this.tileState.markLoaded(uid, workerTile);
133+
throw err;
129134
}
130135
}
131136

src/source/vector_tile_worker_source.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,10 @@ export class VectorTileWorkerSource implements WorkerSource {
212212
if (workerTile.status === 'parsing') {
213213
const result = await workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, params.subdivisionGranularity);
214214

215-
// if we have cancelled the original parse, make sure to pass the rawTileData from the original fetch
216-
const fetchingState = this.tileState.consumeParsing(uid);
217-
if (fetchingState) {
218-
const {rawData, cacheControl, resourceTiming} = fetchingState;
215+
// if we have cancelled the original parse, make sure to pass the rawTileData from the original parse
216+
const parsingState = this.tileState.consumeParsing(uid);
217+
if (parsingState) {
218+
const {rawData, cacheControl, resourceTiming} = parsingState;
219219
return extend({rawTileData: rawData.slice(0), encoding: params.encoding}, result, cacheControl, resourceTiming);
220220
}
221221

0 commit comments

Comments
 (0)