import {beforeEach, afterEach, test, expect, vi} from 'vitest'; import {createMap, beforeMapTest} from '../../util/test/util.ts'; import {GPUInitializationError} from '../../util/gpu_initialization_error.ts'; let originalGetContext: typeof HTMLCanvasElement.prototype.getContext; beforeEach(() => { beforeMapTest(); global.fetch = null; originalGetContext = HTMLCanvasElement.prototype.getContext; }); afterEach(() => { HTMLCanvasElement.prototype.getContext = originalGetContext; }); test('does not fire "webglcontextlost" after remove has been called', () => { const map = createMap(); const canvas = map.getCanvas(); const spy = vi.fn(); map.on('webglcontextlost', spy); map.remove(); // Dispatch the event manually because at the time of this writing, gl does not support // the WEBGL_lose_context extension. canvas.dispatchEvent(new window.Event('webglcontextlost')); expect(spy).not.toHaveBeenCalled(); }); test('handles "webglcontextlost" when map is created without style', () => { // This test verifies fix for #7022 - map should not throw when WebGL context // is lost before the style is loaded (i.e., when style is null/undefined) const map = createMap({deleteStyle: true}); const canvas = map.getCanvas(); const spy = vi.fn(); map.on('webglcontextlost', spy); // Dispatch the event manually because at the time of this writing, gl does not support // the WEBGL_lose_context extension. expect(() => { canvas.dispatchEvent(new window.Event('webglcontextlost')); }).not.toThrow(); expect(spy).toHaveBeenCalled(); map.remove(); }); test('handles "webglcontextrestored" when map is created without style', async () => { const map = createMap({deleteStyle: true}); const canvas = map.getCanvas(); const contextLostPromise = map.once('webglcontextlost'); canvas.dispatchEvent(new window.Event('webglcontextlost')); await contextLostPromise; expect(() => { canvas.dispatchEvent(new window.Event('webglcontextrestored')); }).not.toThrow(); map.remove(); }); test('style methods do not throw after WebGL context loss', async () => { const map = createMap(); const canvas = map.getCanvas(); const contextLostPromise = map.once('webglcontextlost'); canvas.dispatchEvent(new window.Event('webglcontextlost')); await contextLostPromise; expect(() => { map.getLayer('missing-layer'); map.getSource('missing-source'); map.getLayersOrder(); map.listImages(); map.setFilter('missing-layer', null); map.setPaintProperty('missing-layer', 'background-color', '#000000'); }).not.toThrow(); map.remove(); }); test('does not fire "webglcontextrestored" after remove has been called', async () => { const map = createMap(); const canvas = map.getCanvas(); const contextLostPromise = map.once('webglcontextlost'); // Dispatch the event manually because at the time of this writing, gl does not support // the WEBGL_lose_context extension. canvas.dispatchEvent(new window.Event('webglcontextlost')); await contextLostPromise; const spy = vi.fn(); map.on('webglcontextrestored', spy); map.remove(); canvas.dispatchEvent(new window.Event('webglcontextrestored')); expect(spy).not.toHaveBeenCalled(); }); test('WebGL2 context creation error fires ErrorEvent with structured GPUInitializationError', () => { HTMLCanvasElement.prototype.getContext = function (type: string) { if (type === 'webgl2') { const errorEvent = new Event('webglcontextcreationerror'); (errorEvent as any).statusMessage = 'mocked webglcontextcreationerror message'; (this as HTMLCanvasElement).dispatchEvent(errorEvent); return null; } }; const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); createMap({canvasContextAttributes: {antialias: true}}); const err = consoleErrorSpy.mock.calls[0][0]; expect(err.constructor).toBe(GPUInitializationError); expect(err.message).toBe('WebGL2 is required to display this map. We are sorry, but it seems that your browser does not support WebGL2, a technology for rendering 3D graphics on the web. Read more on https://wiki.openstreetmap.org/wiki/This_map_requires_WebGL'); expect(err.statusMessage).toBe('mocked webglcontextcreationerror message'); expect(err.requestedAttributes.antialias).toBe(true); consoleErrorSpy.mockRestore(); }); test('GPUInitializationError has null statusMessage when no webglcontextcreationerror is dispatched', () => { HTMLCanvasElement.prototype.getContext = function (_type: string) { return null; }; const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); createMap(); const err = consoleErrorSpy.mock.calls[0][0]; expect(err.constructor).toBe(GPUInitializationError); expect(err.statusMessage).toBeNull(); consoleErrorSpy.mockRestore(); }); test('Hit WebGL max drawing buffer limit', () => { // Simulate a device with MAX_TEXTURE_SIZE=16834 and max rendering area of ~32Mpx const container = window.document.createElement('div'); Object.defineProperty(container, 'clientWidth', {value: 8000}); Object.defineProperty(container, 'clientHeight', {value: 4500}); const map = createMap({container, maxCanvasSize: [16834, 16834], pixelRatio: 1}); vi.spyOn(map.painter.context.gl, 'drawingBufferWidth', 'get').mockReturnValue(7536); vi.spyOn(map.painter.context.gl, 'drawingBufferHeight', 'get').mockReturnValue(4239); map.resize(); expect(map.getCanvas().width).toBe(7536); expect(map.getCanvas().height).toBe(4239); // Check if maxCanvasSize is updated expect(map._maxCanvasSize).toEqual([7536, 4239]); });