Skip to content

Commit 463d5fb

Browse files
committed
docs: mise à jour du changelog pour l'issue 7149
1 parent 2848fff commit 463d5fb

File tree

6 files changed

+341
-61
lines changed

6 files changed

+341
-61
lines changed

src/source/raster_tile_source.test.ts

Lines changed: 158 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {fakeServer, type FakeServer} from 'nise';
77
import {type Tile} from '../tile/tile';
88
import {stubAjaxGetImage, waitForEvent} from '../util/test/util';
99
import {type MapSourceDataEvent} from '../ui/events';
10+
import * as loadTileJSONModule from './load_tilejson';
1011

1112
function createSource(options, transformCallback?) {
1213
const source = new RasterTileSource('id', options, {send() {}} as any as Dispatcher, options.eventedParent);
@@ -30,7 +31,7 @@ describe('RasterTileSource', () => {
3031
});
3132

3233
afterEach(() => {
33-
server.restore();
34+
vi.restoreAllMocks();
3435
});
3536

3637
test('transforms request for TileJSON URL', () => {
@@ -340,27 +341,174 @@ describe('RasterTileSource', () => {
340341
expect(tile.state).toBe('unloaded');
341342
});
342343

343-
test('setUrl aborts in-flight raster tile requests through TileManager', () => {
344+
test('loads tile after previous abort flag was set', async () => {
345+
server.respondWith('/source.json', JSON.stringify({
346+
minzoom: 0,
347+
maxzoom: 22,
348+
attribution: 'MapLibre',
349+
tiles: ['http://example.com/{z}/{x}/{y}.png'],
350+
bounds: [-47, -7, -45, -5]
351+
}));
352+
server.respondWith('http://example.com/10/5/5.png',
353+
[200, {'Content-Type': 'image/png', 'Content-Length': 1}, '0']
354+
);
355+
356+
const source = createSource({url: '/source.json'});
357+
source.map.painter = {context: {}, getTileTexture: () => ({update: () => {}})} as any;
358+
359+
const sourcePromise = waitForEvent(source, 'data', (e: MapSourceDataEvent) => e.sourceDataType === 'metadata');
360+
server.respond();
361+
await sourcePromise;
362+
363+
const tile = {
364+
tileID: new OverscaledTileID(10, 0, 10, 5, 5),
365+
state: 'loading',
366+
aborted: true,
367+
setExpiryData() {}
368+
} as any as Tile;
369+
370+
const tilePromise = source.loadTile(tile);
371+
server.respond();
372+
await tilePromise;
373+
374+
expect(tile.state).toBe('loaded');
375+
expect(tile.aborted).toBe(false);
376+
});
377+
378+
test('setSourceProperty aborts in-flight TileJSON request, emits abortPendingTileRequests, then calls load(true)', () => {
344379
const source = createSource({
345380
tiles: ['http://example.com/{z}/{x}/{y}.png']
346381
});
347382

348-
const abortAllRequests = vi.fn();
383+
const requestAbort = vi.fn();
384+
(source as any)._tileJSONRequest = {abort: requestAbort};
349385

350-
(source.map as any).style = {
351-
tileManagers: {
352-
[source.id]: {abortAllRequests}
353-
}
354-
};
386+
const isAbortEvent = (e: any) => e?.abortPendingTileRequests === true || e?.data?.abortPendingTileRequests === true;
387+
const sequence: string[] = [];
388+
389+
source.on('data', (e: any) => {
390+
if (isAbortEvent(e)) sequence.push('abort-event');
391+
});
392+
393+
const callback = vi.fn(() => sequence.push('callback'));
394+
const loadSpy = vi.spyOn(source, 'load').mockImplementation(async (...args: any[]) => {
395+
sequence.push('load');
396+
expect(args[0]).toBe(true);
397+
return undefined as any;
398+
});
399+
400+
source.setSourceProperty(callback);
401+
402+
expect(requestAbort).toHaveBeenCalledTimes(1);
403+
expect((source as any)._tileJSONRequest).toBeNull();
404+
expect(callback).toHaveBeenCalledTimes(1);
405+
expect(loadSpy).toHaveBeenCalledTimes(1);
406+
expect(loadSpy).toHaveBeenCalledWith(true);
407+
expect(sequence).toEqual(['abort-event', 'callback', 'load']);
408+
});
409+
410+
test('setSourceProperty emits abortPendingTileRequests even without in-flight TileJSON request', () => {
411+
const source = createSource({
412+
tiles: ['http://example.com/{z}/{x}/{y}.png']
413+
});
414+
415+
const isAbortEvent = (e: any) => e?.abortPendingTileRequests === true || e?.data?.abortPendingTileRequests === true;
416+
let sawAbortEvent = false;
417+
418+
source.on('data', (e: any) => {
419+
if (isAbortEvent(e)) sawAbortEvent = true;
420+
});
355421

356422
const loadSpy = vi.spyOn(source, 'load').mockResolvedValue(undefined as any);
357-
loadSpy.mockClear();
423+
424+
source.setSourceProperty(() => {});
425+
426+
expect(sawAbortEvent).toBe(true);
427+
expect(loadSpy).toHaveBeenCalledTimes(1);
428+
expect(loadSpy).toHaveBeenCalledWith(true);
429+
});
430+
431+
test('setUrl emits abortPendingTileRequests and calls load(true)', () => {
432+
const source = createSource({
433+
tiles: ['http://example.com/{z}/{x}/{y}.png']
434+
});
435+
436+
const loadSpy = vi.spyOn(source, 'load').mockResolvedValue(undefined as any);
437+
const events: Array<any> = [];
438+
source.on('data', (e: any) => events.push(e));
358439

359440
source.setUrl('http://localhost:2900/source2.json');
360441

361-
expect(abortAllRequests).toHaveBeenCalledTimes(1);
442+
expect(
443+
events.some((e) => e?.abortPendingTileRequests === true || e?.data?.abortPendingTileRequests === true)
444+
).toBe(true);
445+
expect(loadSpy).toHaveBeenCalledWith(true);
446+
});
447+
448+
test('setUrl emits abortPendingTileRequests and updates url (no TileManager internals)', () => {
449+
const source = createSource({
450+
tiles: ['http://example.com/{z}/{x}/{y}.png']
451+
});
452+
453+
const loadSpy = vi.spyOn(source, 'load').mockResolvedValue(undefined as any);
454+
const events: Array<any> = [];
455+
source.on('data', (e: any) => events.push(e));
456+
457+
source.setUrl('http://localhost:2900/source2.json');
458+
459+
expect(
460+
events.some((e) => e?.abortPendingTileRequests === true || e?.data?.abortPendingTileRequests === true)
461+
).toBe(true);
362462
expect(source.url).toBe('http://localhost:2900/source2.json');
463+
expect((source as any)._options.url).toBe('http://localhost:2900/source2.json');
363464
expect(loadSpy).toHaveBeenCalledWith(true);
364465
});
365466

467+
test('load emits error event on TileJSON network error (non-abort)', async () => {
468+
const source = createSource({
469+
tiles: ['http://example.com/{z}/{x}/{y}.png']
470+
});
471+
472+
vi.spyOn(loadTileJSONModule, 'loadTileJson').mockRejectedValueOnce(new Error('network failure'));
473+
474+
const onError = vi.fn();
475+
source.on('error', onError);
476+
477+
await source.load(true);
478+
479+
expect(onError).toHaveBeenCalledTimes(1);
480+
});
481+
482+
test('load ignores AbortError from TileJSON request', async () => {
483+
const source = createSource({
484+
tiles: ['http://example.com/{z}/{x}/{y}.png']
485+
});
486+
487+
const abortError = new Error('aborted');
488+
(abortError as any).name = 'AbortError';
489+
vi.spyOn(loadTileJSONModule, 'loadTileJson').mockRejectedValueOnce(abortError);
490+
491+
const onError = vi.fn();
492+
source.on('error', onError);
493+
494+
await source.load(true);
495+
496+
expect(onError).not.toHaveBeenCalled();
497+
});
498+
499+
test('load emits error event when TileJSON is malformed (parser rejection)', async () => {
500+
const source = createSource({
501+
tiles: ['http://example.com/{z}/{x}/{y}.png']
502+
});
503+
504+
vi.spyOn(loadTileJSONModule, 'loadTileJson').mockRejectedValueOnce(new Error('Invalid TileJSON payload'));
505+
506+
const onError = vi.fn();
507+
source.on('error', onError);
508+
509+
await source.load(true);
510+
511+
expect(onError).toHaveBeenCalledTimes(1);
512+
});
513+
366514
});

src/source/raster_tile_source.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,13 @@ export class RasterTileSource extends Evented implements Source {
139139
this._tileJSONRequest = null;
140140
}
141141

142-
callback();
142+
// Demande d'annulation des tuiles en vol, sans dépendre de map.style.tileManagers
143+
this.fire(new Event('data', {
144+
dataType: 'source',
145+
abortPendingTileRequests: true
146+
} as any));
143147

148+
callback();
144149
this.load(true);
145150
}
146151

@@ -160,15 +165,9 @@ export class RasterTileSource extends Evented implements Source {
160165
/**
161166
* Sets the source `url` property and re-renders the map.
162167
*
163-
* @param url - A URL to a TileJSON resource. Supported protocols are `http:` and `https:`.
168+
* @param url - A URL to a TileJSON resource. Supported protocols are `http:` and `https:`.
164169
*/
165170
setUrl(url: string): this {
166-
// Cancels all in-flight raster tile requests for this source
167-
const tileManager = this.map?.style?.tileManagers?.[this.id];
168-
if (tileManager && typeof tileManager.abortAllRequests === 'function') {
169-
tileManager.abortAllRequests();
170-
}
171-
172171
this.setSourceProperty(() => {
173172
this.url = url;
174173
this._options.url = url;
@@ -187,6 +186,8 @@ export class RasterTileSource extends Evented implements Source {
187186

188187
async loadTile(tile: Tile): Promise<void> {
189188
const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
189+
// A tile can be reused after a previous abort; clear stale abort state before a new request.
190+
tile.aborted = false;
190191
tile.abortController = new AbortController();
191192
try {
192193
const response = await ImageRequest.getImage(this.map._requestManager.transformRequest(url, ResourceType.Tile), tile.abortController, this.map._refreshExpiredTiles);

0 commit comments

Comments
 (0)