import {vi, type Mock} from 'vitest'; type GL = WebGL2RenderingContext; /** * A null/stub WebGL2RenderingContext following the luma.gl NullDevice pattern. * All methods are vi.fn() stubs, all enum constants are correct values. * Methods that create resources return opaque stub objects. */ export class NullWebGL2RenderingContext { private readonly canvas: HTMLCanvasElement; private _contextAttributes: WebGLContextAttributes | null; private static _idCounter = 1; /** * WebGL1 constants */ VERSION: number = 0x1F02; MAX_TEXTURE_SIZE: number = 0x0D33; COLOR_ATTACHMENT0: number = 0x8CE0; FRAMEBUFFER: number = 0x8D40; RENDERBUFFER: number = 0x8D41; FRAMEBUFFER_COMPLETE: number = 0x8CD5; DEPTH_BUFFER_BIT: number = 0x00000100; STENCIL_BUFFER_BIT: number = 0x00000400; COLOR_BUFFER_BIT: number = 0x00004000; POINTS: number = 0; LINES: number = 1; LINE_LOOP: number = 2; LINE_STRIP: number = 3; TRIANGLES: number = 4; TRIANGLE_STRIP: number = 5; TRIANGLE_FAN: number = 6; ZERO: number = 0; ONE: number = 1; SRC_COLOR: number = 0x0300; ONE_MINUS_SRC_COLOR: number = 0x0301; SRC_ALPHA: number = 0x0302; ONE_MINUS_SRC_ALPHA: number = 0x0303; DST_ALPHA: number = 0x0304; ONE_MINUS_DST_ALPHA: number = 0x0305; DST_COLOR: number = 0x0306; ONE_MINUS_DST_COLOR: number = 0x0307; CONSTANT_COLOR: number = 0x8001; FUNC_ADD: number = 0x8006; BLEND: number = 0x0BE2; DEPTH_TEST: number = 0x0B71; STENCIL_TEST: number = 0x0B90; CULL_FACE: number = 0x0B44; SCISSOR_TEST: number = 0x0C11; FRONT: number = 0x0404; BACK: number = 0x0405; CW: number = 0x0900; CCW: number = 0x0901; NEVER: number = 0x0200; LESS: number = 0x0201; EQUAL: number = 0x0202; LEQUAL: number = 0x0203; GREATER: number = 0x0204; NOTEQUAL: number = 0x0205; GEQUAL: number = 0x0206; ALWAYS: number = 0x0207; KEEP: number = 0x1E00; REPLACE: number = 0x1E01; INCR: number = 0x1E02; DECR: number = 0x1E03; BYTE: number = 0x1400; UNSIGNED_BYTE: number = 0x1401; SHORT: number = 0x1402; UNSIGNED_SHORT: number = 0x1403; INT: number = 0x1404; UNSIGNED_INT: number = 0x1405; FLOAT: number = 0x1406; ALPHA: number = 0x1906; RGB: number = 0x1907; RGBA: number = 0x1908; DEPTH_COMPONENT: number = 0x1902; DEPTH_COMPONENT16: number = 0x81A5; DEPTH_STENCIL: number = 0x84F9; DEPTH_STENCIL_ATTACHMENT: number = 0x821A; DEPTH_ATTACHMENT: number = 0x8D00; TEXTURE_2D: number = 0x0DE1; TEXTURE_MAG_FILTER: number = 0x2800; TEXTURE_MIN_FILTER: number = 0x2801; TEXTURE_WRAP_S: number = 0x2802; TEXTURE_WRAP_T: number = 0x2803; LINEAR: number = 0x2601; NEAREST: number = 0x2600; LINEAR_MIPMAP_NEAREST: number = 0x2701; NEAREST_MIPMAP_LINEAR: number = 0x2702; REPEAT: number = 0x2901; CLAMP_TO_EDGE: number = 0x812F; TEXTURE0: number = 0x84C0; TEXTURE1: number = 0x84C1; TEXTURE2: number = 0x84C2; TEXTURE3: number = 0x84C3; TEXTURE4: number = 0x84C4; ARRAY_BUFFER: number = 0x8892; ELEMENT_ARRAY_BUFFER: number = 0x8893; STATIC_DRAW: number = 0x88E4; DYNAMIC_DRAW: number = 0x88E8; VERTEX_SHADER: number = 0x8B31; FRAGMENT_SHADER: number = 0x8B30; COMPILE_STATUS: number = 0x8B81; LINK_STATUS: number = 0x8B82; UNPACK_ALIGNMENT: number = 0x0CF5; UNPACK_FLIP_Y_WEBGL: number = 0x9240; UNPACK_PREMULTIPLY_ALPHA_WEBGL: number = 0x9241; MAX_TEXTURE_IMAGE_UNITS: number = 0x8872; /** * WebGL2-specific constants */ PIXEL_PACK_BUFFER: number = 0x88EB; PIXEL_UNPACK_BUFFER: number = 0x88EC; PIXEL_PACK_BUFFER_BINDING: number = 0x88ED; PIXEL_UNPACK_BUFFER_BINDING: number = 0x88EF; STREAM_READ: number = 0x88E1; STREAM_COPY: number = 0x88E2; STATIC_READ: number = 0x88E5; STATIC_COPY: number = 0x88E6; DYNAMIC_READ: number = 0x88E9; DYNAMIC_COPY: number = 0x88EA; HALF_FLOAT: number = 0x140B; RGB16F: number = 0x881B; RGBA16F: number = 0x881A; RGB32F: number = 0x8815; RGBA32F: number = 0x8814; R16F: number = 0x822D; RG16F: number = 0x822F; SYNC_GPU_COMMANDS_COMPLETE: number = 0x9117; ALREADY_SIGNALED: number = 0x911A; TIMEOUT_EXPIRED: number = 0x911B; CONDITION_SATISFIED: number = 0x911C; WAIT_FAILED: number = 0x911D; SYNC_FLUSH_COMMANDS_BIT: number = 0x00000001; READ_BUFFER: number = 0x0C02; constructor(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes) { this.canvas = canvas; this._contextAttributes = contextAttributes ?? null; } // Configurable getters so vi.spyOn(..., 'get') works in tests. get drawingBufferWidth(): number { return this.canvas?.width ?? 300; } get drawingBufferHeight(): number { return this.canvas?.height ?? 150; } // --- Methods that need non-trivial return values --- getParameter: Mock = vi.fn((pname: number) => { if (pname === this.VERSION) return 'WebGL 2.0'; if (pname === this.MAX_TEXTURE_SIZE) return 4096; if (pname === this.MAX_TEXTURE_IMAGE_UNITS) return 16; return 0; }); getExtension: Mock = vi.fn((_name: string): any => { // Return an object for extensions maplibre probes if (_name === 'EXT_texture_filter_anisotropic') return {MAX_TEXTURE_MAX_ANISOTROPY_EXT: 0x84FF}; if (_name === 'EXT_color_buffer_half_float') return {RGBA16F_EXT: 0x881A, RGB16F_EXT: 0x881B}; if (_name === 'EXT_color_buffer_float') return {}; return null; }) as Mock; getContextAttributes: Mock = vi.fn((): WebGLContextAttributes | null => this._contextAttributes); getSupportedExtensions: Mock = vi.fn((): string[] => []); isContextLost: Mock = vi.fn((): boolean => false); getShaderParameter: Mock = vi.fn((): any => true); getProgramParameter: Mock = vi.fn((): any => true); getAttribLocation: Mock = vi.fn((): number => 0); getShaderInfoLog: Mock = vi.fn((): string => ''); getProgramInfoLog: Mock = vi.fn((): string => ''); createBuffer: Mock = vi.fn((): WebGLBuffer => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createTexture: Mock = vi.fn((): WebGLTexture => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createFramebuffer: Mock = vi.fn((): WebGLFramebuffer => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createRenderbuffer: Mock = vi.fn((): WebGLRenderbuffer => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createProgram: Mock = vi.fn((): WebGLProgram => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createShader: Mock = vi.fn((): WebGLShader => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createVertexArray: Mock = vi.fn((): WebGLVertexArrayObject => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); createQuery: Mock = vi.fn((): WebGLQuery => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); fenceSync: Mock = vi.fn((): WebGLSync => ({__id: NullWebGL2RenderingContext._idCounter++} as any)); clientWaitSync: Mock = vi.fn((): number => this.CONDITION_SATISFIED); deleteSync: Mock = vi.fn(); checkFramebufferStatus: Mock = vi.fn((): number => this.FRAMEBUFFER_COMPLETE); /** WebGL2-specific method names (no-ops) */ bindVertexArray: Mock = vi.fn(); deleteVertexArray: Mock = vi.fn(); readBuffer: Mock = vi.fn(); getBufferSubData: Mock = vi.fn(); bindBufferBase: Mock = vi.fn(); bindBufferRange: Mock = vi.fn(); beginQuery: Mock = vi.fn(); endQuery: Mock = vi.fn(); getQuery: Mock = vi.fn(); getQueryParameter: Mock = vi.fn(); drawBuffers: Mock = vi.fn(); clearBufferfv: Mock = vi.fn(); clearBufferiv: Mock = vi.fn(); clearBufferuiv: Mock = vi.fn(); clearBufferfi: Mock = vi.fn(); vertexAttribIPointer: Mock = vi.fn(); vertexAttribDivisor: Mock = vi.fn(); drawArraysInstanced: Mock = vi.fn(); drawElementsInstanced: Mock = vi.fn(); texStorage2D: Mock = vi.fn(); texStorage3D: Mock = vi.fn(); texImage3D: Mock = vi.fn(); texSubImage3D: Mock = vi.fn(); renderbufferStorageMultisample: Mock = vi.fn(); blitFramebuffer: Mock = vi.fn(); invalidateFramebuffer: Mock = vi.fn(); invalidateSubFramebuffer: Mock = vi.fn(); uniform1ui: Mock = vi.fn(); uniform2ui: Mock = vi.fn(); uniform3ui: Mock = vi.fn(); uniform4ui: Mock = vi.fn(); uniformMatrix2x3fv: Mock = vi.fn(); uniformMatrix3x2fv: Mock = vi.fn(); uniformMatrix2x4fv: Mock = vi.fn(); uniformMatrix4x2fv: Mock = vi.fn(); uniformMatrix3x4fv: Mock = vi.fn(); uniformMatrix4x3fv: Mock = vi.fn(); /** All WebGL1 method names that return a generic stub value */ activeTexture: Mock = vi.fn(); attachShader: Mock = vi.fn(); bindAttribLocation: Mock = vi.fn(); bindBuffer: Mock = vi.fn(); bindFramebuffer: Mock = vi.fn(); bindRenderbuffer: Mock = vi.fn(); bindTexture: Mock = vi.fn(); blendColor: Mock = vi.fn(); blendEquation: Mock = vi.fn(); blendEquationSeparate: Mock = vi.fn(); blendFunc: Mock = vi.fn(); blendFuncSeparate: Mock = vi.fn(); bufferData: Mock = vi.fn(); bufferSubData: Mock = vi.fn(); clear: Mock = vi.fn(); clearColor: Mock = vi.fn(); clearDepth: Mock = vi.fn(); clearStencil: Mock = vi.fn(); colorMask: Mock = vi.fn(); compileShader: Mock = vi.fn(); copyTexImage2D: Mock = vi.fn(); copyTexSubImage2D: Mock = vi.fn(); cullFace: Mock = vi.fn(); deleteBuffer: Mock = vi.fn(); deleteFramebuffer: Mock = vi.fn(); deleteProgram: Mock = vi.fn(); deleteRenderbuffer: Mock = vi.fn(); deleteShader: Mock = vi.fn(); deleteTexture: Mock = vi.fn(); depthFunc: Mock = vi.fn(); depthMask: Mock = vi.fn(); depthRange: Mock = vi.fn(); detachShader: Mock = vi.fn(); disable: Mock = vi.fn(); disableVertexAttribArray: Mock = vi.fn(); drawArrays: Mock = vi.fn(); drawElements: Mock = vi.fn(); enable: Mock = vi.fn(); enableVertexAttribArray: Mock = vi.fn(); finish: Mock = vi.fn(); flush: Mock = vi.fn(); framebufferRenderbuffer: Mock = vi.fn(); framebufferTexture2D: Mock = vi.fn(); frontFace: Mock = vi.fn(); generateMipmap: Mock = vi.fn(); getActiveAttrib: Mock = vi.fn(); getActiveUniform: Mock = vi.fn(); getError: Mock = vi.fn(); getRenderbufferParameter: Mock = vi.fn(); getShaderPrecisionFormat: Mock = vi.fn(); getShaderSource: Mock = vi.fn(); getTexParameter: Mock = vi.fn(); getUniform: Mock = vi.fn(); getUniformLocation: Mock = vi.fn(); getVertexAttrib: Mock = vi.fn(); getVertexAttribOffset: Mock = vi.fn(); hint: Mock = vi.fn(); isBuffer: Mock = vi.fn(); isEnabled: Mock = vi.fn(); isFramebuffer: Mock = vi.fn(); isProgram: Mock = vi.fn(); isRenderbuffer: Mock = vi.fn(); isShader: Mock = vi.fn(); isTexture: Mock = vi.fn(); lineWidth: Mock = vi.fn(); linkProgram: Mock = vi.fn(); pixelStorei: Mock = vi.fn(); polygonOffset: Mock = vi.fn(); readPixels: Mock = vi.fn(); renderbufferStorage: Mock = vi.fn(); sampleCoverage: Mock = vi.fn(); scissor: Mock = vi.fn(); shaderSource: Mock = vi.fn(); stencilFunc: Mock = vi.fn(); stencilFuncSeparate: Mock = vi.fn(); stencilMask: Mock = vi.fn(); stencilMaskSeparate: Mock = vi.fn(); stencilOp: Mock = vi.fn(); stencilOpSeparate: Mock = vi.fn(); texParameterf: Mock = vi.fn(); texParameteri: Mock = vi.fn(); texImage2D: Mock = vi.fn(); texSubImage2D: Mock = vi.fn(); uniform1f: Mock = vi.fn(); uniform1fv: Mock = vi.fn(); uniform1i: Mock = vi.fn(); uniform1iv: Mock = vi.fn(); uniform2f: Mock = vi.fn(); uniform2fv: Mock = vi.fn(); uniform2i: Mock = vi.fn(); uniform2iv: Mock = vi.fn(); uniform3f: Mock = vi.fn(); uniform3fv: Mock = vi.fn(); uniform3i: Mock = vi.fn(); uniform3iv: Mock = vi.fn(); uniform4f: Mock = vi.fn(); uniform4fv: Mock = vi.fn(); uniform4i: Mock = vi.fn(); uniform4iv: Mock = vi.fn(); uniformMatrix2fv: Mock = vi.fn(); uniformMatrix3fv: Mock = vi.fn(); uniformMatrix4fv: Mock = vi.fn(); useProgram: Mock = vi.fn(); validateProgram: Mock = vi.fn(); vertexAttrib1f: Mock = vi.fn(); vertexAttrib1fv: Mock = vi.fn(); vertexAttrib2f: Mock = vi.fn(); vertexAttrib2fv: Mock = vi.fn(); vertexAttrib3f: Mock = vi.fn(); vertexAttrib3fv: Mock = vi.fn(); vertexAttrib4f: Mock = vi.fn(); vertexAttrib4fv: Mock = vi.fn(); vertexAttribPointer: Mock = vi.fn(); viewport: Mock = vi.fn(); } /** * Create a NullWebGL2RenderingContext suitable for use with maplibre's Context class. */ export function createNullGL(): WebGL2RenderingContext { const c = document.createElement('canvas'); return new NullWebGL2RenderingContext(c) as unknown as WebGL2RenderingContext; }