diff --git a/providers/maplibre/src/index.js b/providers/maplibre/src/index.js
index 367ac83b..495ac221 100755
--- a/providers/maplibre/src/index.js
+++ b/providers/maplibre/src/index.js
@@ -40,7 +40,7 @@ export default function createMapLibreProvider (config = {}) {
const mapFramework = await import(/* webpackChunkName: "im-maplibre-framework" */ 'maplibre-gl')
if (config.workerUrl) {
- mapFramework.workerUrl = config.workerUrl
+ mapFramework.setWorkerUrl(config.workerUrl)
}
const MapProvider = (await import(/* webpackChunkName: "im-maplibre-provider" */ './maplibreProvider.js')).default
diff --git a/providers/maplibre/src/index.test.js b/providers/maplibre/src/index.test.js
index de337715..a54915e1 100644
--- a/providers/maplibre/src/index.test.js
+++ b/providers/maplibre/src/index.test.js
@@ -1,8 +1,9 @@
import createMapLibreProvider from './index.js'
import { getWebGL } from './utils/detectWebgl.js'
+import * as maplibreGl from 'maplibre-gl'
jest.mock('./utils/detectWebgl.js', () => ({ getWebGL: jest.fn() }))
-jest.mock('maplibre-gl', () => ({ VERSION: '3.x' }))
+jest.mock('maplibre-gl', () => ({ VERSION: '3.x', setWorkerUrl: jest.fn() }))
jest.mock('./maplibreProvider.js', () => ({ default: class MockProvider {} }))
describe('createMapLibreProvider', () => {
@@ -11,7 +12,7 @@ describe('createMapLibreProvider', () => {
})
afterEach(() => {
- jest.restoreAllMocks()
+ jest.clearAllMocks()
})
test('checkDeviceCapabilities: WebGL enabled, modern browser, no IE → isSupported true', () => {
@@ -75,21 +76,15 @@ describe('createMapLibreProvider', () => {
expect(mapProviderConfig).toEqual({ crs: 'EPSG:4326' })
})
- test('load sets workerUrl on mapFramework when provided', async () => {
- await jest.isolateModulesAsync(async () => {
- const { default: createProvider } = await import('./index.js')
- const { mapFramework } = await createProvider({ workerUrl: '/assets/maplibre-gl-csp-worker.js' }).load()
+ test('load calls setWorkerUrl on mapFramework when workerUrl is provided', async () => {
+ await createMapLibreProvider({ workerUrl: '/assets/maplibre-gl-csp-worker.js' }).load()
- expect(mapFramework.workerUrl).toBe('/assets/maplibre-gl-csp-worker.js')
- })
+ expect(maplibreGl.setWorkerUrl).toHaveBeenCalledWith('/assets/maplibre-gl-csp-worker.js')
})
- test('load does not set workerUrl on mapFramework when not provided', async () => {
- await jest.isolateModulesAsync(async () => {
- const { default: createProvider } = await import('./index.js')
- const { mapFramework } = await createProvider().load()
+ test('load does not call setWorkerUrl on mapFramework when workerUrl is not provided', async () => {
+ await createMapLibreProvider().load()
- expect(mapFramework.workerUrl).toBeUndefined()
- })
+ expect(maplibreGl.setWorkerUrl).not.toHaveBeenCalled()
})
})
diff --git a/providers/maplibre/src/utils/rasteriseToImageData.js b/providers/maplibre/src/utils/rasteriseToImageData.js
index 94776684..20f1a7f9 100644
--- a/providers/maplibre/src/utils/rasteriseToImageData.js
+++ b/providers/maplibre/src/utils/rasteriseToImageData.js
@@ -10,8 +10,7 @@ const SVG_ERROR_PREVIEW_LENGTH = 80
*/
export const rasteriseToImageData = (svgString, width, height) =>
new Promise((resolve, reject) => {
- const blob = new Blob([svgString], { type: 'image/svg+xml' })
- const url = URL.createObjectURL(blob)
+ const url = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`
const img = new Image(width, height)
img.onload = () => {
const canvas = document.createElement('canvas')
@@ -19,11 +18,9 @@ export const rasteriseToImageData = (svgString, width, height) =>
canvas.height = height
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, width, height)
- URL.revokeObjectURL(url)
resolve(ctx.getImageData(0, 0, width, height))
}
img.onerror = () => {
- URL.revokeObjectURL(url)
reject(new Error(`Failed to rasterise SVG: ${svgString.slice(0, SVG_ERROR_PREVIEW_LENGTH)}`))
}
img.src = url
diff --git a/providers/maplibre/src/utils/rasteriseToImageData.test.js b/providers/maplibre/src/utils/rasteriseToImageData.test.js
index 58f78dd5..3ecb6bb1 100644
--- a/providers/maplibre/src/utils/rasteriseToImageData.test.js
+++ b/providers/maplibre/src/utils/rasteriseToImageData.test.js
@@ -9,20 +9,25 @@ const ERROR_PREVIEW_LENGTH = 80
// Length chosen to be well over ERROR_PREVIEW_LENGTH so truncation is exercised
const LONG_CONTENT_LENGTH = 200
-beforeAll(() => {
- globalThis.URL.createObjectURL = jest.fn(() => 'blob:mock')
- globalThis.URL.revokeObjectURL = jest.fn()
+let imageInstances = []
+beforeAll(() => {
HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
drawImage: jest.fn(),
getImageData: jest.fn((_x, _y, w, h) => ({ width: w, height: h }))
}))
+})
+
+beforeEach(() => {
+ imageInstances = []
+ jest.clearAllMocks()
globalThis.Image = class {
constructor (w, h) {
this.width = w
this.height = h
this._src = ''
+ imageInstances.push(this)
}
get src () { return this._src }
@@ -30,40 +35,34 @@ beforeAll(() => {
}
})
-beforeEach(() => {
- jest.clearAllMocks()
- globalThis.URL.createObjectURL.mockReturnValue('blob:mock')
-})
-
describe('rasteriseToImageData', () => {
- it('resolves with ImageData at the requested dimensions, draws via canvas, and revokes the blob URL', async () => {
+ it('resolves with ImageData at the requested dimensions and draws via canvas', async () => {
const getContext = HTMLCanvasElement.prototype.getContext
const result = await rasteriseToImageData(SVG, WIDTH, HEIGHT)
expect(result).toMatchObject({ width: WIDTH, height: HEIGHT })
- expect(globalThis.URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
- expect(globalThis.URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock')
const { drawImage, getImageData } = getContext.mock.results[0].value
expect(drawImage).toHaveBeenCalledWith(expect.any(Object), 0, 0, WIDTH, HEIGHT)
expect(getImageData).toHaveBeenCalledWith(0, 0, WIDTH, HEIGHT)
})
- it('rejects with a truncated SVG preview and revokes the blob URL on error', async () => {
- const originalImage = globalThis.Image
+ it('sets img.src to a data URI', async () => {
+ await rasteriseToImageData(SVG, WIDTH, HEIGHT)
+ const src = imageInstances[0]._src
+ expect(src).toMatch(/^data:image\/svg\+xml;charset=utf-8,/)
+ expect(src).toContain(encodeURIComponent(SVG))
+ })
+
+ it('rejects with a truncated SVG preview on error', async () => {
globalThis.Image = class {
constructor (w, h) { this.width = w; this.height = h; this._src = '' }
get src () { return this._src }
set src (val) { this._src = val; this.onerror?.() }
}
- try {
- const longSvg = ``
- const error = await rasteriseToImageData(longSvg, WIDTH, HEIGHT).catch(e => e)
- expect(error.message).toMatch('Failed to rasterise SVG')
- const preview = error.message.replace('Failed to rasterise SVG: ', '')
- expect(preview).toHaveLength(ERROR_PREVIEW_LENGTH)
- expect(preview).toBe(longSvg.slice(0, ERROR_PREVIEW_LENGTH))
- expect(globalThis.URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock')
- } finally {
- globalThis.Image = originalImage
- }
+ const longSvg = ``
+ const error = await rasteriseToImageData(longSvg, WIDTH, HEIGHT).catch(e => e)
+ expect(error.message).toMatch('Failed to rasterise SVG')
+ const preview = error.message.replace('Failed to rasterise SVG: ', '')
+ expect(preview).toHaveLength(ERROR_PREVIEW_LENGTH)
+ expect(preview).toBe(longSvg.slice(0, ERROR_PREVIEW_LENGTH))
})
})
diff --git a/providers/maplibre/src/utils/symbolImages.test.js b/providers/maplibre/src/utils/symbolImages.test.js
index f7eeb1f5..b89bc5e7 100644
--- a/providers/maplibre/src/utils/symbolImages.test.js
+++ b/providers/maplibre/src/utils/symbolImages.test.js
@@ -230,18 +230,18 @@ describe('registerSymbols — null results and caching', () => {
const uniqueRatio = 7
const map1 = makeMap()
- const blobCallsBefore = globalThis.URL.createObjectURL.mock.calls.length
+ const getContextCallsBefore = HTMLCanvasElement.prototype.getContext.mock.calls.length
await registerSymbols(map1, [{ symbol: 'pin' }], mapStyle, symbolRegistry, uniqueRatio)
- const blobCallsAfterFirst = globalThis.URL.createObjectURL.mock.calls.length
- // Rasterisation ran — blob was created
- expect(blobCallsAfterFirst).toBeGreaterThan(blobCallsBefore)
+ const getContextCallsAfterFirst = HTMLCanvasElement.prototype.getContext.mock.calls.length
+ // Rasterisation ran — canvas was used
+ expect(getContextCallsAfterFirst).toBeGreaterThan(getContextCallsBefore)
// Second call with a fresh map (hasImage → false) but same ratio → cache hit
const map2 = makeMap()
await registerSymbols(map2, [{ symbol: 'pin' }], mapStyle, symbolRegistry, uniqueRatio)
- const blobCallsAfterSecond = globalThis.URL.createObjectURL.mock.calls.length
- // No new blob created — rasterisation was skipped via cache
- expect(blobCallsAfterSecond).toBe(blobCallsAfterFirst)
+ const getContextCallsAfterSecond = HTMLCanvasElement.prototype.getContext.mock.calls.length
+ // No new canvas — rasterisation was skipped via cache
+ expect(getContextCallsAfterSecond).toBe(getContextCallsAfterFirst)
// addImage still called because map2 has no pre-registered images
expect(map2.addImage).toHaveBeenCalledTimes(2)
})