• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 27017296180

05 Jun 2026 01:20PM UTC coverage: 70.704% (+0.1%) from 70.564%
27017296180

push

github

web-flow
chore: Minor API completions and doc improvements (#2665)

8861 of 14167 branches covered (62.55%)

Branch coverage included in aggregate %.

174 of 196 new or added lines in 15 files covered. (88.78%)

1 existing line in 1 file now uncovered.

18353 of 24323 relevant lines covered (75.46%)

4341.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.24
/modules/test-utils/src/create-test-device.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import type {Device, CanvasContextProps, DeviceProps} from '@luma.gl/core';
6
import {luma, log} from '@luma.gl/core';
7
import {webgl2Adapter, WebGLDevice} from '@luma.gl/webgl';
8
import {webgpuAdapter, WebGPUDevice} from '@luma.gl/webgpu';
9
import {nullAdapter} from './null-device/null-adapter';
10
import {NullDevice} from './null-device/null-device';
11

12
const DEFAULT_CANVAS_CONTEXT_PROPS: CanvasContextProps = {width: 1, height: 1};
95✔
13
const TEST_DEVICE_CACHE_KEY = '__lumaTestDeviceCache';
95✔
14

15
type TestDeviceCache = {
16
  /** A null device intended for testing - @note Only available after getTestDevices() has completed */
17
  nullDevicePromise: Promise<NullDevice> | null;
18
  /** This WebGL Device can be used directly but will not have WebGL debugging initialized */
19
  webglDevicePromise: Promise<WebGLDevice> | null;
20
  /** A shared offscreen WebGL device for presentation-context tests */
21
  presentationWebglDevicePromise: Promise<WebGLDevice | null> | null;
22
  /** WebGPU Devices intended for testing, keyed by featureLevel */
23
  webgpuDevicePromises: Partial<
24
    Record<NonNullable<DeviceProps['featureLevel']>, Promise<WebGPUDevice | null>>
25
  >;
26
};
27

28
declare global {
29
  interface Window {
30
    [TEST_DEVICE_CACHE_KEY]?: TestDeviceCache;
31
  }
32
}
33

34
const testDeviceCache = getOrCreateTestDeviceCache();
95✔
35

36
type LostAwareDevice = {
37
  isLost: boolean;
38
};
39

40
type TestDeviceType = 'webgl' | 'webgpu' | 'webgpu-core' | 'webgpu-max' | 'null' | 'unknown';
41

42
/**
43
 * Returns available test devices for the requested backend types.
44
 * @param types Backend types to create. `'webgpu'` preserves the legacy max-feature WebGPU test device.
45
 */
46
export async function getTestDevices(
47
  types: Readonly<TestDeviceType[]> = ['webgl', 'webgpu']
80✔
48
): Promise<Device[]> {
49
  const promises = types.map(type => getTestDevice(type));
173✔
50
  const devices = await Promise.all(promises);
80✔
51
  return devices.filter(device => device !== null);
173✔
52
}
53

54
/**
55
 * Returns a test device for one backend type, or `null` when that backend is unavailable.
56
 * @param type Backend type to create.
57
 */
58
export async function getTestDevice(type: TestDeviceType): Promise<Device | null> {
59
  switch (type) {
372!
60
    case 'webgl':
61
      return getOrCreateWebGLTestDevicePromise();
150✔
62
    case 'webgpu':
63
      return getWebGPUTestDevice('max');
140✔
64
    case 'webgpu-core':
NEW
65
      return getWebGPUTestDevice('core');
×
66
    case 'webgpu-max':
NEW
67
      return getWebGPUTestDevice('max');
×
68
    case 'null':
69
      return getOrCreateNullTestDevicePromise();
82✔
70
    case 'unknown':
71
      return null;
×
72
  }
73
}
74

75
/**
76
 * Returns a WebGPU test device for one feature level, or `null` when WebGPU is unavailable.
77
 * @param featureLevel WebGPU feature level to request. Defaults to `'max'` for existing tests.
78
 */
79
export async function getWebGPUTestDevice(
80
  featureLevel: NonNullable<DeviceProps['featureLevel']> = 'max'
220✔
81
): Promise<WebGPUDevice | null> {
82
  return _refreshLostCachedTestDevice(
220✔
83
    () => getOrCreateWebGPUTestDevicePromise(featureLevel),
220✔
84
    () => {
NEW
85
      delete testDeviceCache.webgpuDevicePromises[featureLevel];
×
86
    }
87
  );
88
}
89

90
/**
91
 * Returns available WebGPU test devices for the requested feature levels.
92
 * @param featureLevels WebGPU feature levels to request. Defaults to both `'core'` and `'max'`.
93
 */
94
export async function getWebGPUTestDevices(
95
  featureLevels: Readonly<NonNullable<DeviceProps['featureLevel']>[]> = ['core', 'max']
2✔
96
): Promise<WebGPUDevice[]> {
97
  const devices = await Promise.all(
2✔
98
    featureLevels.map(featureLevel => getWebGPUTestDevice(featureLevel))
4✔
99
  );
100

101
  return devices.filter((device): device is WebGPUDevice => device !== null);
4✔
102
}
103

104
/** returns WebGL device promise, if available */
105
export async function getWebGLTestDevice(): Promise<WebGLDevice> {
106
  return _refreshLostCachedTestDevice(getOrCreateWebGLTestDevicePromise, () => {
174✔
107
    testDeviceCache.webglDevicePromise = null;
×
108
  });
109
}
110

111
/** returns an offscreen WebGL device promise for presentation-context tests, if available */
112
export async function getPresentationWebGLTestDevice(): Promise<WebGLDevice | null> {
113
  return getOrCreatePresentationWebGLTestDevicePromise();
5✔
114
}
115

116
/** returns null device promise, if available */
117
export async function getNullTestDevice(): Promise<NullDevice> {
118
  return getOrCreateNullTestDevicePromise();
16✔
119
}
120

121
function getOrCreateWebGPUTestDevicePromise(
122
  featureLevel: NonNullable<DeviceProps['featureLevel']>
123
): Promise<WebGPUDevice | null> {
124
  testDeviceCache.webgpuDevicePromises[featureLevel] ||= makeWebGPUTestDevice(featureLevel);
220✔
125
  return testDeviceCache.webgpuDevicePromises[featureLevel];
220✔
126
}
127

128
function getOrCreateWebGLTestDevicePromise(): Promise<WebGLDevice> {
129
  testDeviceCache.webglDevicePromise ||= makeWebGLTestDevice();
324✔
130
  return testDeviceCache.webglDevicePromise;
324✔
131
}
132

133
function getOrCreatePresentationWebGLTestDevicePromise(): Promise<WebGLDevice | null> {
134
  testDeviceCache.presentationWebglDevicePromise ||= makePresentationWebGLTestDevice();
5✔
135
  return testDeviceCache.presentationWebglDevicePromise;
5✔
136
}
137

138
function getOrCreateNullTestDevicePromise(): Promise<NullDevice> {
139
  testDeviceCache.nullDevicePromise ||= makeNullTestDevice();
98✔
140
  return testDeviceCache.nullDevicePromise;
98✔
141
}
142

143
async function makeWebGPUTestDevice(
144
  featureLevel: NonNullable<DeviceProps['featureLevel']>
145
): Promise<WebGPUDevice | null> {
146
  const webgpuDeviceResolvers = withResolvers<WebGPUDevice | null>();
53✔
147
  try {
53✔
148
    const webgpuDevice = (await luma.createDevice({
53✔
149
      id: `webgpu-${featureLevel}-test-device`,
150
      type: 'webgpu',
151
      featureLevel,
152
      adapters: [webgpuAdapter],
153
      createCanvasContext: DEFAULT_CANVAS_CONTEXT_PROPS,
154
      debug: true
155
    })) as unknown as WebGPUDevice;
156
    webgpuDevice.lost.finally(() => {
53✔
157
      if (testDeviceCache.webgpuDevicePromises[featureLevel] === webgpuDeviceResolvers.promise) {
7!
NEW
158
        delete testDeviceCache.webgpuDevicePromises[featureLevel];
×
159
      }
160
    });
161
    webgpuDeviceResolvers.resolve(webgpuDevice);
53✔
162
  } catch (error) {
163
    log.error(String(error))();
×
164
    // @ts-ignore TODO
165
    webgpuDeviceResolvers.resolve(null);
×
166
  }
167
  return webgpuDeviceResolvers.promise;
53✔
168
}
169

170
/** returns WebGL device promise, if available */
171
async function makeWebGLTestDevice(): Promise<WebGLDevice> {
172
  const webglDeviceResolvers = withResolvers<WebGLDevice>();
69✔
173
  try {
69✔
174
    const webglDevice = (await luma.createDevice({
69✔
175
      id: 'webgl-test-device',
176
      type: 'webgl',
177
      adapters: [webgl2Adapter],
178
      createCanvasContext: DEFAULT_CANVAS_CONTEXT_PROPS,
179
      debug: true
180
    })) as unknown as WebGLDevice;
181
    webglDevice.lost.finally(() => {
69✔
182
      if (testDeviceCache.webglDevicePromise === webglDeviceResolvers.promise) {
6!
183
        testDeviceCache.webglDevicePromise = null;
×
184
      }
185
    });
186
    webglDeviceResolvers.resolve(webglDevice);
69✔
187
  } catch (error) {
188
    log.error(String(error))();
×
189
    // @ts-ignore TODO
190
    webglDeviceResolvers.resolve(null);
×
191
  }
192
  return webglDeviceResolvers.promise;
69✔
193
}
194

195
async function makePresentationWebGLTestDevice(): Promise<WebGLDevice | null> {
196
  if (typeof OffscreenCanvas === 'undefined') {
1!
197
    return null;
×
198
  }
199

200
  const presentationWebGLDeviceResolvers = withResolvers<WebGLDevice | null>();
1✔
201
  try {
1✔
202
    const webglDevice = (await luma.createDevice({
1✔
203
      id: 'webgl-presentation-context-test-device',
204
      type: 'webgl',
205
      adapters: [webgl2Adapter],
206
      createCanvasContext: {canvas: new OffscreenCanvas(4, 4)},
207
      debug: true
208
    })) as unknown as WebGLDevice;
209
    webglDevice.lost.finally(() => {
1✔
210
      if (
1!
211
        testDeviceCache.presentationWebglDevicePromise === presentationWebGLDeviceResolvers.promise
212
      ) {
213
        testDeviceCache.presentationWebglDevicePromise = null;
×
214
      }
215
    });
216
    presentationWebGLDeviceResolvers.resolve(webglDevice);
1✔
217
  } catch (error) {
218
    log.error(String(error))();
×
219
    presentationWebGLDeviceResolvers.resolve(null);
×
220
  }
221
  return presentationWebGLDeviceResolvers.promise;
1✔
222
}
223

224
/** returns null device promise, if available */
225
async function makeNullTestDevice(): Promise<NullDevice> {
226
  const nullDeviceResolvers = withResolvers<NullDevice>();
25✔
227
  try {
25✔
228
    const nullDevice = (await luma.createDevice({
25✔
229
      id: 'null-test-device',
230
      type: 'null',
231
      adapters: [nullAdapter],
232
      createCanvasContext: DEFAULT_CANVAS_CONTEXT_PROPS,
233
      debug: true
234
    })) as unknown as NullDevice;
235
    nullDeviceResolvers.resolve(nullDevice);
25✔
236
  } catch (error) {
237
    log.error(String(error))();
×
238
    // @ts-ignore TODO
239
    testDeviceCache.nullDevicePromise = Promise.resolve(null);
×
240
  }
241
  return nullDeviceResolvers.promise;
25✔
242
}
243

244
// HELPERS
245

246
export async function _refreshLostCachedTestDevice<DeviceT extends LostAwareDevice | null>(
247
  getOrCreateDevice: () => Promise<DeviceT>,
248
  clearCachedDevice: () => void
249
): Promise<DeviceT> {
250
  const device = await getOrCreateDevice();
395✔
251
  if (device?.isLost) {
395✔
252
    clearCachedDevice();
1✔
253
    return getOrCreateDevice();
1✔
254
  }
255
  return device;
394✔
256
}
257

258
function getOrCreateTestDeviceCache(): TestDeviceCache {
259
  const rootObject = globalThis as typeof globalThis & {
95✔
260
    [TEST_DEVICE_CACHE_KEY]?: TestDeviceCache;
261
  };
262

263
  rootObject[TEST_DEVICE_CACHE_KEY] ||= {
95✔
264
    nullDevicePromise: null,
265
    webglDevicePromise: null,
266
    presentationWebglDevicePromise: null,
267
    webgpuDevicePromises: {}
268
  };
269

270
  return rootObject[TEST_DEVICE_CACHE_KEY];
95✔
271
}
272

273
// TODO - replace with Promise.withResolvers once we upgrade TS baseline
274
function withResolvers<T>(): {
275
  promise: Promise<T>;
276
  resolve: (t: T) => void;
277
  reject: (error: Error) => void;
278
} {
279
  let resolve;
280
  let reject;
281
  const promise = new Promise<T>((_resolve, _reject) => {
148✔
282
    resolve = _resolve;
148✔
283
    reject = _reject;
148✔
284
  });
285
  // @ts-ignore Assigned in callback.
286
  return {promise, resolve, reject};
148✔
287
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc