import {beforeEach, test, expect, vi} from 'vitest'; import {createMap, beforeMapTest} from '../../util/test/util.ts'; import {type StyleImageInterface} from '../../style/style_image.ts'; import {EvaluationParameters} from '../../style/evaluation_parameters.ts'; import {MessageType} from '../../util/actor_messages.ts'; beforeEach(() => { beforeMapTest(); global.fetch = null; vi.spyOn(console, 'warn').mockImplementation(() => {}); }); test('listImages', async () => { const map = createMap(); await map.once('load'); expect(map.listImages()).toHaveLength(0); map.addImage('img', {width: 1, height: 1, data: new Uint8Array(4)}); const images = map.listImages(); expect(images).toHaveLength(1); expect(images[0]).toBe('img'); }); test('listImages throws an error if called before "load"', () => { const map = createMap(); expect(() => { map.listImages(); }).toThrow(Error); }); test('map fires `styleimagemissing` for missing icons without resolving the current request', async () => { const map = createMap(); const id = 'missing-image'; const sampleImage = {width: 2, height: 1, data: new Uint8Array(8)}; let called: string; map.on('styleimagemissing', e => { map.addImage(e.id, sampleImage); called = e.id; }); expect(map.hasImage(id)).toBeFalsy(); const generatedImage = await map.style.imageManager.getImages([id]); expect(generatedImage[id]).toBeUndefined(); expect(called).toBe(id); expect(map.hasImage(id)).toBeTruthy(); }); test('map resolves missing icons with an async missing style image resolver', async () => { const map = createMap(); const id = 'missing-style-image-resolver'; const sampleImage = {width: 2, height: 1, data: new Uint8Array(8)}; const missingImageEventSpy = vi.fn(); let called: string; map.on('styleimagemissing', missingImageEventSpy); map.setMissingStyleImageResolver(async (imageId) => { await Promise.resolve(); called = imageId; map.addImage(imageId, sampleImage); }); expect(map.hasImage(id)).toBeFalsy(); const generatedImage = await map.style.imageManager.getImages([id]); expect(generatedImage[id].data.width).toEqual(sampleImage.width); expect(generatedImage[id].data.height).toEqual(sampleImage.height); expect(generatedImage[id].data.data).toEqual(sampleImage.data); expect(called).toBe(id); expect(map.hasImage(id)).toBeTruthy(); expect(missingImageEventSpy).not.toHaveBeenCalled(); }); test('map fires `styleimagemissing` when missing style image resolver returns no image', async () => { const map = createMap(); const id = 'missing-style-image-resolver-fallback'; const resolver = vi.fn(async () => undefined); const missingImageEventSpy = vi.fn(); map.setMissingStyleImageResolver(resolver); map.on('styleimagemissing', missingImageEventSpy); const generatedImage = await map.style.imageManager.getImages([id]); expect(resolver).toHaveBeenCalledWith(id); expect(generatedImage[id]).toBeUndefined(); expect(missingImageEventSpy).toHaveBeenCalledTimes(1); expect(missingImageEventSpy.mock.calls[0][0].id).toBe(id); expect(map.hasImage(id)).toBeFalsy(); }); test('map keeps missing style image resolver after replacing the style', async () => { const map = createMap(); await map.once('load'); const id = 'missing-style-image-resolver-after-set-style'; const sampleImage = {width: 2, height: 1, data: new Uint8Array(8)}; map.setMissingStyleImageResolver(async (imageId) => { map.addImage(imageId, sampleImage); }); map.setStyle({ version: 8, sources: {}, layers: [] }); map.style.imageManager.setLoaded(true); const generatedImages = await map.style.imageManager.getImages([id]); expect(generatedImages[id].data.width).toEqual(sampleImage.width); expect(map.hasImage(id)).toBeTruthy(); }); test('map getImage matches addImage, uintArray', () => { const map = createMap(); const id = 'add-get-uint'; const inputImage = {width: 2, height: 1, data: new Uint8Array(8)}; map.addImage(id, inputImage); expect(map.hasImage(id)).toBeTruthy(); const gotImage = map.getImage(id); expect(gotImage.data.width).toEqual(inputImage.width); expect(gotImage.data.height).toEqual(inputImage.height); expect(gotImage.sdf).toBe(false); }); test('map getImage matches addImage, uintClampedArray', () => { const map = createMap(); const id = 'add-get-uint-clamped'; const inputImage = {width: 1, height: 2, data: new Uint8ClampedArray(8)}; map.addImage(id, inputImage); expect(map.hasImage(id)).toBeTruthy(); const gotImage = map.getImage(id); expect(gotImage.data.width).toEqual(inputImage.width); expect(gotImage.data.height).toEqual(inputImage.height); expect(gotImage.sdf).toBe(false); }); test('map getImage matches addImage, ImageData', () => { const map = createMap(); const id = 'add-get-image-data'; const inputImage = new ImageData(1, 3); map.addImage(id, inputImage); expect(map.hasImage(id)).toBeTruthy(); const gotImage = map.getImage(id); expect(gotImage.data.width).toEqual(inputImage.width); expect(gotImage.data.height).toEqual(inputImage.height); expect(gotImage.sdf).toBe(false); }); test('map getImage matches addImage, StyleImageInterface uint', () => { const map = createMap(); const id = 'add-get-style-image-iface-uint'; const inputImage: StyleImageInterface = { width: 3, height: 1, data: new Uint8Array(12) }; map.addImage(id, inputImage); expect(map.hasImage(id)).toBeTruthy(); const gotImage = map.getImage(id); expect(gotImage.data.width).toEqual(inputImage.width); expect(gotImage.data.height).toEqual(inputImage.height); expect(gotImage.sdf).toBe(false); }); test('map getImage matches addImage, StyleImageInterface clamped', () => { const map = createMap(); const id = 'add-get-style-image-iface-clamped'; const inputImage: StyleImageInterface = { width: 4, height: 1, data: new Uint8ClampedArray(16) }; map.addImage(id, inputImage); expect(map.hasImage(id)).toBeTruthy(); const gotImage = map.getImage(id); expect(gotImage.data.width).toEqual(inputImage.width); expect(gotImage.data.height).toEqual(inputImage.height); expect(gotImage.sdf).toBe(false); }); test('map getImage matches addImage, StyleImageInterface SDF', () => { const map = createMap(); const id = 'add-get-style-image-iface-sdf'; const inputImage: StyleImageInterface = { width: 5, height: 1, data: new Uint8Array(20) }; map.addImage(id, inputImage, {sdf: true}); expect(map.hasImage(id)).toBeTruthy(); const gotImage = map.getImage(id); expect(gotImage.data.width).toEqual(inputImage.width); expect(gotImage.data.height).toEqual(inputImage.height); expect(gotImage.sdf).toBe(true); }); test('map does not fire `styleimagemissing` for empty icon values', async () => { const map = createMap(); await map.once('load'); map.addSource('foo', { type: 'geojson', data: {type: 'Point', coordinates: [0, 0]} }); map.addLayer({ id: 'foo', type: 'symbol', source: 'foo', layout: { 'icon-image': ['case', true, '', ''] } }); const spy = vi.fn(); map.on('styleimagemissing', spy); await map.once('idle'); expect(spy).not.toHaveBeenCalled(); }); test('setImages broadcasts even when getImages is called between addImage and update', async () => { const map = createMap(); await map.once('load'); const broadcastSpy = vi.fn().mockResolvedValue({}); map.style.dispatcher.broadcast = broadcastSpy; map.addImage('new-image', {width: 1, height: 1, data: new Uint8Array(4)}); await map.style.getImages('0', { icons: ['some-other-image'], source: 'test-source', tileID: {key: 'test-tile'} as any, type: 'icons', }); map.style.update(new EvaluationParameters(0)); const setImagesCalls = broadcastSpy.mock.calls.filter( (c) => c[0] === MessageType.setImages ); expect(setImagesCalls.length).toBeGreaterThanOrEqual(1); expect(setImagesCalls.flatMap((c) => c[1])).toContain('new-image'); }); test('setImages broadcasts after missing style image resolver adds an image', async () => { const map = createMap(); await map.once('load'); const broadcastSpy = vi.fn().mockResolvedValue({}); map.style.dispatcher.broadcast = broadcastSpy; map.setMissingStyleImageResolver((id) => { map.addImage(id, {width: 1, height: 1, data: new Uint8Array(4)}); }); await map.style.getImages('0', { icons: ['missing-image'], source: 'test-source', tileID: {key: 'test-tile'} as any, type: 'icons', }); map.style.update(new EvaluationParameters(0)); const setImagesCalls = broadcastSpy.mock.calls.filter( (c) => c[0] === MessageType.setImages ); expect(setImagesCalls.length).toBeGreaterThanOrEqual(1); expect(setImagesCalls.flatMap((c) => c[1])).toContain('missing-image'); });