or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdinteraction-testing.mdlifecycle-testing.mdtest-generation.mdtesting-utilities.mdvisual-testing.md
tile.json

testing-utilities.mddocs/

Testing Utilities

Helper functions for WebGL uniform inspection, precision control, and GL context management. Essential utilities for debugging layers, validating shader uniforms, and ensuring consistent numeric comparisons in tests.

Capabilities

Layer Uniform Inspection

Extract and inspect WebGL uniform values from deck.gl layers.

/**
 * Extract uniform values set for a Layer in the underlying UniformBlock store
 * @param layer - The deck.gl layer to inspect
 * @param blockName - Optional name of specific uniform block to extract
 * @returns Object containing all uniform name-value pairs
 */
function getLayerUniforms(layer: Layer, blockName?: string): Record<string, UniformValue>;

Usage Examples:

import { getLayerUniforms, testInitializeLayer } from "@deck.gl/test-utils";
import { ScatterplotLayer } from "@deck.gl/layers";

// Create and initialize a layer
const result = testInitializeLayer({
  layer: new ScatterplotLayer({
    data: [{ position: [0, 0] }],
    getPosition: d => d.position,
    radiusScale: 10,
    radiusMinPixels: 5,
    getFillColor: [255, 0, 0]
  }),
  finalize: false
});

// Extract all uniforms from the layer
const uniforms = getLayerUniforms(result.layer);
console.log('All uniforms:', uniforms);
// Output: { radiusScale: 10, radiusMinPixels: 5, fillColor: [1, 0, 0, 1], ... }

// Extract uniforms from specific block
const projectionUniforms = getLayerUniforms(result.layer, 'projection');
console.log('Projection uniforms:', projectionUniforms);

// Validate specific uniform values in tests
if (uniforms.radiusScale !== 10) {
  throw new Error('radiusScale uniform not set correctly');
}

// Clean up
result.finalize();

Precision Control

Convert numbers to consistent precision for reliable float comparisons in tests.

/**
 * Convert all numbers in a structure to a given precision for reliable float comparisons
 * Converts data in-place and returns the modified input
 * @param input - Number, array of numbers, or object with numeric properties
 * @param precision - Number of significant digits to preserve (default: 11)
 * @returns Input with numbers converted to specified precision
 */
function toLowPrecision(input: number, precision?: number): number;
function toLowPrecision(input: number[], precision?: number): number[];
function toLowPrecision(input: Record<string, number>, precision?: number): Record<string, number>;

Usage Examples:

import { toLowPrecision } from "@deck.gl/test-utils";

// Convert single numbers
const a = 0.123456789012345;
const b = 0.123456789087654;

console.log(a === b);  // false - precision differences
console.log(toLowPrecision(a, 10) === toLowPrecision(b, 10));  // true

// Convert arrays
const coordinates = [
  -122.41999999999999,
  37.774999999999999
];
const rounded = toLowPrecision(coordinates, 8);
console.log(rounded);  // [-122.42, 37.775]

// Convert objects (modifies in-place)
const position = {
  x: 1.2345678901234567,
  y: 2.3456789012345678,
  z: 3.4567890123456789
};
toLowPrecision(position, 6);
console.log(position);  // { x: 1.23457, y: 2.34568, z: 3.45679 }

// Use in test assertions
function assertPositionsEqual(actual, expected, precision = 10) {
  const actualRounded = toLowPrecision([...actual], precision);
  const expectedRounded = toLowPrecision([...expected], precision);
  
  if (actualRounded[0] !== expectedRounded[0] || 
      actualRounded[1] !== expectedRounded[1]) {
    throw new Error(`Positions don't match: ${actualRounded} vs ${expectedRounded}`);
  }
}

// Test layer position calculations
const layer = new ScatterplotLayer({
  data: [{ pos: [-122.4, 37.8] }],
  getPosition: d => d.pos
});

// Get computed positions from layer
const positions = layer.getAttributeManager().attributes.positions.value;
assertPositionsEqual(positions.slice(0, 2), [-122.4, 37.8], 8);

WebGL Context and Device

Access to WebGL context and luma.gl device for advanced testing scenarios.

/**
 * WebGL context instance (WebGL2 preferred, WebGL1 fallback, or 1 if unavailable)
 * Use for direct WebGL operations or context feature detection
 */
const gl: WebGL2RenderingContext | WebGLRenderingContext | 1;

/**
 * luma.gl Device instance (WebGLDevice or NullDevice for headless environments)
 * Use for creating buffers, textures, and other WebGL resources in tests
 */
const device: Device;

Usage Examples:

import { gl, device, testInitializeLayer } from "@deck.gl/test-utils";

// Check WebGL capabilities in tests
function requiresWebGL2() {
  if (gl === 1 || !(gl instanceof WebGL2RenderingContext)) {
    throw new Error('This test requires WebGL2 support');
  }
}

// Use device for resource creation
function testCustomBuffer() {
  const buffer = device.createBuffer({
    data: new Float32Array([1, 2, 3, 4]),
    usage: gl.STATIC_DRAW
  });
  
  // Validate buffer creation
  if (!buffer) {
    throw new Error('Failed to create buffer');
  }
  
  // Clean up
  buffer.destroy();
}

// Feature detection for conditional tests  
function testWebGLFeatures() {
  console.log('WebGL version:', gl.VERSION);
  console.log('Vendor:', device.info.vendor);
  console.log('Renderer:', device.info.renderer);
  
  // Skip certain tests based on capabilities
  if (!device.features.has('webgl2')) {
    console.log('Skipping WebGL2-specific test');
    return;
  }
  
  // Run WebGL2-specific tests
}

// Custom layer testing with GL context
function testCustomLayer() {
  testInitializeLayer({
    layer: new MyCustomLayer({
      data: testData,
      // Custom props that depend on GL capabilities
      useInstancedArrays: device.features.has('instanced-arrays')
    }),
    onError: (error, title) => {
      console.error(`GL context error in ${title}:`, error);
      // Check for specific GL errors
      if (gl !== 1) {
        const glError = gl.getError();
        if (glError !== gl.NO_ERROR) {
          console.error('WebGL error code:', glError);
        }
      }
    }
  });
}

Testing Patterns

Uniform Validation

import { getLayerUniforms, testLayer } from "@deck.gl/test-utils";

testLayer({
  Layer: MyCustomLayer,
  testCases: [{
    title: "Uniform values are correct",
    props: {
      customScale: 2.5,
      customColor: [255, 128, 0]
    },
    onAfterUpdate: ({ layer }) => {
      const uniforms = getLayerUniforms(layer);
      
      // Validate numeric uniforms
      if (uniforms.customScale !== 2.5) {
        throw new Error(`Expected customScale=2.5, got ${uniforms.customScale}`);
      }
      
      // Validate color uniforms (colors are normalized to 0-1)
      const expectedColor = [1, 0.5, 0, 1];  // Normalized + alpha
      const actualColor = uniforms.customColor;
      
      for (let i = 0; i < 4; i++) {
        if (Math.abs(actualColor[i] - expectedColor[i]) > 0.01) {
          throw new Error(`Color component ${i} mismatch`);
        }
      }
    }
  }]
});

Precision-Safe Comparisons

import { toLowPrecision, testLayer } from "@deck.gl/test-utils";

testLayer({
  Layer: DataProcessingLayer,
  testCases: [{
    title: "Mathematical operations are precise",
    props: {
      data: [{ value: 0.1 + 0.2 }],  // JavaScript precision issue
      getValue: d => d.value
    },
    onAfterUpdate: ({ layer }) => {
      const processedData = layer.state.processedData;
      
      // Without precision control - this might fail:
      // if (processedData[0].computed === 0.3) { ... }
      
      // With precision control - reliable:
      const expected = toLowPrecision(0.3, 10);
      const actual = toLowPrecision(processedData[0].computed, 10);
      
      if (actual !== expected) {
        throw new Error(`Precision mismatch: ${actual} !== ${expected}`);
      }
    }
  }]
});

Resource Management Testing

import { device, testLayer } from "@deck.gl/test-utils";

// Track resource creation/destruction
let initialBufferCount = 0;
let initialTextureCount = 0;

testLayer({
  Layer: ResourceIntensiveLayer,
  onError: (error, title) => {
    // Enhanced error reporting with resource state
    console.error(`Error in ${title}:`, error);
    console.log('GL state:', {
      vendor: device.info.vendor,
      renderer: device.info.renderer,
      memory: device.info.memory
    });
  },
  testCases: [{
    title: "Resource cleanup validation",
    onBeforeUpdate: () => {
      // Record initial resource counts
      initialBufferCount = device.stats.get('Buffers Active').count;
      initialTextureCount = device.stats.get('Textures Active').count;
    },
    props: {
      data: largeDataset,
      // Properties that create many resources
    },
    onAfterUpdate: ({ layer }) => {
      // Validate layer created expected resources
      const currentBuffers = device.stats.get('Buffers Active').count;
      const currentTextures = device.stats.get('Textures Active').count;
      
      console.log('Resource delta:', {
        buffers: currentBuffers - initialBufferCount,
        textures: currentTextures - initialTextureCount
      });
    }
  }]
});