vitest-environment-webgl-node 1.0.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +67 -4
- package/dist/index.js.map +1 -1
- package/package.json +20 -11
- package/src/index.ts +67 -4
- package/CHANGELOG.md +0 -5
package/README.md
CHANGED
|
@@ -3,18 +3,46 @@
|
|
|
3
3
|
[![npm version][npm-badge]][npm-url]
|
|
4
4
|
[![npm downloads][downloads-badge]][npm-url]
|
|
5
5
|
[![Tests][tests-badge]][tests-url]
|
|
6
|
+
[![Coverage][coverage-badge]][coverage-url]
|
|
6
7
|
[![Discord][discord-badge]][discord-url]
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
[
|
|
9
|
+
Run real WebGL 1 and WebGL 2 tests in Vitest on Node, without a browser, Playwright or canvas mocks.
|
|
10
|
+
Headless GPU testing for CI: `canvas.getContext('webgl2')` returns a GPU-backed context, so three.js,
|
|
11
|
+
regl, PixiJS and plain WebGL code run unchanged. Native GPU testing is up to 2.4x faster than running
|
|
12
|
+
the same tests in a browser, see the
|
|
13
|
+
[blog post](https://ben3d.ca/blog/native-gpu-testing-for-vitest-and-jest).
|
|
14
|
+
Powered by [`@onirenaud/node-webgl`](https://github.com/RenaudRohlinger/node-webgl) (Chrome's ANGLE,
|
|
15
|
+
statically linked).
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pnpm add -D vitest vitest-environment-webgl-node
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
// vitest.config.ts
|
|
23
|
+
import { defineConfig } from 'vitest/config';
|
|
24
|
+
|
|
25
|
+
export default defineConfig({ test: { environment: 'webgl-node' } });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
// gl.test.ts
|
|
30
|
+
import { expect, it } from 'vitest';
|
|
31
|
+
|
|
32
|
+
it('clears to red on a real GPU', () => {
|
|
33
|
+
const canvas = document.createElement('canvas');
|
|
34
|
+
canvas.width = canvas.height = 4;
|
|
35
|
+
const gl = canvas.getContext('webgl2')!;
|
|
36
|
+
gl.clearColor(1, 0, 0, 1);
|
|
37
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
38
|
+
const pixel = new Uint8Array(4);
|
|
39
|
+
gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
|
|
40
|
+
expect([...pixel]).toEqual([255, 0, 0, 255]);
|
|
41
|
+
});
|
|
42
|
+
```
|
|
13
43
|
|
|
14
44
|
The environment installs `window`, `document`, `HTMLCanvasElement`, `Image`, `requestAnimationFrame`,
|
|
15
45
|
the `WebGL*` classes and a file-reading `fetch()` before each test file, and removes them afterwards.
|
|
16
|
-
`document.createElement('canvas').getContext('webgl2')` returns a GPU-backed context, so three.js,
|
|
17
|
-
regl, pixi and plain WebGL code run unchanged.
|
|
18
46
|
|
|
19
47
|
Using Jest instead? See the equivalent
|
|
20
48
|
[`jest-environment-webgl-node`](https://github.com/bhouston/jest-gpu/tree/main/packages/jest-environment-webgl-node)
|
|
@@ -25,11 +53,26 @@ in [jest-gpu](https://github.com/bhouston/jest-gpu).
|
|
|
25
53
|
As of 1.0.0, the public API (the environment export and its options) is considered stable.
|
|
26
54
|
Breaking changes will be released as a new major version.
|
|
27
55
|
|
|
28
|
-
##
|
|
56
|
+
## Compared to alternatives
|
|
29
57
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
58
|
+
| Approach | Real rendering | Notes |
|
|
59
|
+
| --------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
+
| jsdom plus a canvas mock (`jest-webgl-canvas-mock`, etc.) | No | Fast and light, but `getContext()` returns stubs: you can assert calls, not pixels or shader results. |
|
|
61
|
+
| Raw [`gl`](https://github.com/stackgl/headless-gl) | Yes | WebGL 1 only (per node-webgl's docs), runs on the system OpenGL driver, and you wire up the DOM globals yourself. |
|
|
62
|
+
| Vitest browser mode or Playwright | Yes | A real browser: heavier to install and slower to start, and the right choice when you need real browser APIs. |
|
|
63
|
+
| vitest-environment-webgl-node | Yes | ANGLE in the Node process, WebGL 1 and 2. Not a browser: only canvas, `Image`, `fetch` and `requestAnimationFrame` are provided. |
|
|
64
|
+
|
|
65
|
+
## Supported versions and platforms
|
|
66
|
+
|
|
67
|
+
- Vitest 4 and 5 (CI tests both). Vitest 3 is not supported.
|
|
68
|
+
- Node 22 or later. CI runs Node 22 and 26 on `macos-latest` and `ubuntu-latest`.
|
|
69
|
+
- macOS uses Metal. Linux uses Mesa (see below). Windows uses Direct3D 11 per the
|
|
70
|
+
[node-webgl docs](https://github.com/RenaudRohlinger/node-webgl#platforms); this repo's CI does not run on Windows.
|
|
71
|
+
|
|
72
|
+
## Useful for
|
|
73
|
+
|
|
74
|
+
three.js `WebGLRenderer`, Babylon.js, regl, PixiJS, deck.gl and plain WebGL code. The demos exercise three.js and
|
|
75
|
+
Babylon.js; node-webgl's docs name regl, PixiJS and deck.gl. These are compatible use cases, not endorsements.
|
|
33
76
|
|
|
34
77
|
## Usage
|
|
35
78
|
|
|
@@ -60,7 +103,6 @@ it('renders', () => {
|
|
|
60
103
|
|
|
61
104
|
Options are those of node-webgl's `init()` (`backend`, `api`) and `installDOM()`
|
|
62
105
|
(`baseDir`, `fetch`, `devicePixelRatio`, `innerWidth`, `innerHeight`, `frameInterval`).
|
|
63
|
-
Pair with [`vitest-screenshot`](../vitest-screenshot) for pixel-diff assertions.
|
|
64
106
|
|
|
65
107
|
For a Node-only TypeScript project, opt into types for the installed globals by adding
|
|
66
108
|
`import 'vitest-environment-webgl-node/globals';` to a `.d.ts` file your tsconfig includes. Do not include
|
|
@@ -71,6 +113,39 @@ node-webgl canvas at the library boundary when necessary.
|
|
|
71
113
|
On Linux CI install Mesa (`apt-get install libegl1 libgles2 libgl1-mesa-dri`) and set
|
|
72
114
|
`LIBGL_ALWAYS_SOFTWARE=1`. macOS and Windows use prebuilt binaries.
|
|
73
115
|
|
|
116
|
+
## Image snapshots
|
|
117
|
+
|
|
118
|
+
Pair with [`vitest-screenshot`](https://github.com/bhouston/vitest-gpu/tree/main/packages/vitest-screenshot) to diff the canvas against a committed baseline:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import * as THREE from 'three';
|
|
122
|
+
import { expect, it } from 'vitest';
|
|
123
|
+
import { extendMatchers } from 'vitest-screenshot';
|
|
124
|
+
|
|
125
|
+
extendMatchers();
|
|
126
|
+
|
|
127
|
+
it('renders a cube', async () => {
|
|
128
|
+
const canvas = document.createElement('canvas');
|
|
129
|
+
const renderer = new THREE.WebGLRenderer({ canvas });
|
|
130
|
+
renderer.setSize(256, 256, false);
|
|
131
|
+
const scene = new THREE.Scene();
|
|
132
|
+
const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
|
|
133
|
+
camera.position.z = 3;
|
|
134
|
+
scene.add(new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshNormalMaterial()));
|
|
135
|
+
|
|
136
|
+
renderer.render(scene, camera);
|
|
137
|
+
await expect(canvas).toMatchScreenshot('cube.png');
|
|
138
|
+
renderer.dispose();
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## See also
|
|
143
|
+
|
|
144
|
+
- [`vitest-environment-webgpu-node`](https://github.com/bhouston/vitest-gpu/tree/main/packages/vitest-environment-webgpu-node) for headless WebGPU.
|
|
145
|
+
- [`vitest-screenshot`](https://github.com/bhouston/vitest-gpu/tree/main/packages/vitest-screenshot) for `toMatchScreenshot()`.
|
|
146
|
+
- [`demo/`](https://github.com/bhouston/vitest-gpu/tree/main/demo/test/webgl) for WebGL contexts, uploads, readbacks, three.js and Babylon.js tests, and the
|
|
147
|
+
[repository README](https://github.com/bhouston/vitest-gpu#readme).
|
|
148
|
+
|
|
74
149
|
## Author
|
|
75
150
|
|
|
76
151
|
Created by [Ben Houston](https://ben3d.ca) and sponsored by [Land of Assets](https://landofassets.com).
|
|
@@ -86,3 +161,9 @@ MIT
|
|
|
86
161
|
[tests-url]: https://github.com/bhouston/vitest-gpu/actions/workflows/ci.yml
|
|
87
162
|
[discord-badge]: https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white
|
|
88
163
|
[discord-url]: https://discord.gg/fwupDN493R
|
|
164
|
+
[coverage-badge]: https://codecov.io/gh/bhouston/vitest-gpu/branch/main/graph/badge.svg
|
|
165
|
+
[coverage-url]: https://codecov.io/gh/bhouston/vitest-gpu
|
|
166
|
+
|
|
167
|
+
## Resource cleanup
|
|
168
|
+
|
|
169
|
+
Environment teardown destroys contexts first created through `Canvas.getContext()` during the test file, including DOM canvases, `OffscreenCanvas` and imported canvas factories. It also cancels pending callbacks scheduled through its animation-frame shim. Contexts created directly with the WebGL context constructors, or before setup, remain the caller's responsibility: call `destroy()` on them. Dispose renderers and canvases during a long-running file to release resources sooner.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAM/B,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,iBAAiB,CAAC;;;;kBAkF/C,OAAO,UAAU,iBAAsB;QAAE,SAAS,CAAC,EAAE,gBAAgB,CAAA;KAAE;oBAyFrE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDisplayInfo, init, installDOM } from '@onirenaud/node-webgl';
|
|
1
|
+
import { Canvas, getDisplayInfo, init, installDOM, } from '@onirenaud/node-webgl';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { extname, resolve } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -90,11 +90,64 @@ export default {
|
|
|
90
90
|
}
|
|
91
91
|
init({ backend, api });
|
|
92
92
|
}
|
|
93
|
+
const contexts = new Set();
|
|
94
|
+
const frames = new Set();
|
|
95
|
+
let disposed = false;
|
|
96
|
+
const dispose = () => {
|
|
97
|
+
disposed = true;
|
|
98
|
+
for (const frame of frames)
|
|
99
|
+
clearTimeout(frame);
|
|
100
|
+
frames.clear();
|
|
101
|
+
for (const context of contexts)
|
|
102
|
+
context.destroy();
|
|
103
|
+
contexts.clear();
|
|
104
|
+
};
|
|
105
|
+
const hasFrame = global.requestAnimationFrame !== undefined;
|
|
93
106
|
const before = snapshot(global);
|
|
94
107
|
const nested = [];
|
|
95
108
|
try {
|
|
96
109
|
// node-webgl remembers fetch installation at module scope, which cannot represent separate Vitest environments.
|
|
97
110
|
installDOM({ ...dom, fetch: false });
|
|
111
|
+
// Intercept the shared canvas API so imported factories and OffscreenCanvas are covered too.
|
|
112
|
+
const canvasBefore = snapshot(Canvas.prototype);
|
|
113
|
+
nested.push({ target: Canvas.prototype, before: canvasBefore });
|
|
114
|
+
const getContext = Canvas.prototype.getContext;
|
|
115
|
+
Canvas.prototype.getContext = function (...args) {
|
|
116
|
+
if (disposed)
|
|
117
|
+
throw new Error('WebGL environment has been torn down');
|
|
118
|
+
// node-webgl exposes the cached context at runtime but strips it from its declarations.
|
|
119
|
+
const previous = this._ctx;
|
|
120
|
+
const context = getContext.apply(this, args);
|
|
121
|
+
if (context && context !== previous)
|
|
122
|
+
contexts.add(context);
|
|
123
|
+
return context;
|
|
124
|
+
};
|
|
125
|
+
nested.at(-1).changed = changedProperties(canvasBefore, Canvas.prototype);
|
|
126
|
+
// Own only the rAF shim installed by this setup; preserve host implementations.
|
|
127
|
+
if (!hasFrame) {
|
|
128
|
+
Object.defineProperty(global, 'requestAnimationFrame', {
|
|
129
|
+
configurable: true,
|
|
130
|
+
writable: true,
|
|
131
|
+
value: (callback) => {
|
|
132
|
+
if (disposed)
|
|
133
|
+
return 0;
|
|
134
|
+
const frame = setTimeout(() => {
|
|
135
|
+
frames.delete(frame);
|
|
136
|
+
callback(performance.now());
|
|
137
|
+
}, dom.frameInterval ?? 16).unref();
|
|
138
|
+
frames.add(frame);
|
|
139
|
+
return frame;
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
Object.defineProperty(global, 'cancelAnimationFrame', {
|
|
143
|
+
configurable: true,
|
|
144
|
+
writable: true,
|
|
145
|
+
value: (frame) => {
|
|
146
|
+
frames.delete(frame);
|
|
147
|
+
clearTimeout(frame);
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
98
151
|
if (dom.fetch !== false)
|
|
99
152
|
installFetch(global, dom.baseDir ?? process.cwd());
|
|
100
153
|
// node-webgl's window/document have no event methods; engines like Babylon.js register resize/blur listeners.
|
|
@@ -110,6 +163,7 @@ export default {
|
|
|
110
163
|
}
|
|
111
164
|
}
|
|
112
165
|
catch (error) {
|
|
166
|
+
dispose();
|
|
113
167
|
for (const item of nested.toReversed())
|
|
114
168
|
restore(item.target, item.before, item.changed ?? changedProperties(item.before, item.target));
|
|
115
169
|
restore(global, before, changedProperties(before, global));
|
|
@@ -118,9 +172,18 @@ export default {
|
|
|
118
172
|
const changed = changedProperties(before, global);
|
|
119
173
|
return {
|
|
120
174
|
teardown(g) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
175
|
+
if (disposed)
|
|
176
|
+
return;
|
|
177
|
+
try {
|
|
178
|
+
dispose();
|
|
179
|
+
}
|
|
180
|
+
finally {
|
|
181
|
+
for (const item of nested.toReversed())
|
|
182
|
+
restore(item.target, item.before, item.changed);
|
|
183
|
+
restore(g, before, changed);
|
|
184
|
+
nested.length = 0;
|
|
185
|
+
before.clear();
|
|
186
|
+
}
|
|
124
187
|
},
|
|
125
188
|
};
|
|
126
189
|
},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-environment-webgl-node",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Vitest environment
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Vitest environment for real headless WebGL 1/2 tests in Node (ANGLE via @onirenaud/node-webgl): no browser, Playwright or canvas mocks",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"babylonjs",
|
|
7
|
+
"canvas",
|
|
8
|
+
"ci",
|
|
9
|
+
"gpu",
|
|
10
|
+
"gpu-testing",
|
|
6
11
|
"headless",
|
|
12
|
+
"headless-gl",
|
|
13
|
+
"node",
|
|
14
|
+
"pixijs",
|
|
15
|
+
"regl",
|
|
7
16
|
"testing",
|
|
8
17
|
"three.js",
|
|
9
18
|
"vitest",
|
|
10
19
|
"vitest-environment",
|
|
11
20
|
"webgl",
|
|
21
|
+
"webgl-testing",
|
|
12
22
|
"webgl2"
|
|
13
23
|
],
|
|
14
24
|
"homepage": "https://github.com/bhouston/vitest-gpu#readme",
|
|
@@ -29,8 +39,7 @@
|
|
|
29
39
|
"dist/**/*.d.ts",
|
|
30
40
|
"dist/**/*.d.ts.map",
|
|
31
41
|
"src/**/*.ts",
|
|
32
|
-
"!src/**/*.test.ts"
|
|
33
|
-
"CHANGELOG.md"
|
|
42
|
+
"!src/**/*.test.ts"
|
|
34
43
|
],
|
|
35
44
|
"type": "module",
|
|
36
45
|
"main": "./dist/index.js",
|
|
@@ -47,17 +56,17 @@
|
|
|
47
56
|
"publishConfig": {
|
|
48
57
|
"access": "public"
|
|
49
58
|
},
|
|
50
|
-
"scripts": {
|
|
51
|
-
"build": "tsc",
|
|
52
|
-
"tsc": "tsc"
|
|
53
|
-
},
|
|
54
59
|
"dependencies": {
|
|
55
60
|
"@onirenaud/node-webgl": "^0.2.0"
|
|
56
61
|
},
|
|
57
62
|
"peerDependencies": {
|
|
58
|
-
"vitest": ">=
|
|
63
|
+
"vitest": ">=4 <6"
|
|
59
64
|
},
|
|
60
65
|
"engines": {
|
|
61
|
-
"node": ">=
|
|
66
|
+
"node": ">=22"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc",
|
|
70
|
+
"tsc": "tsc"
|
|
62
71
|
}
|
|
63
|
-
}
|
|
72
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Canvas,
|
|
3
|
+
getDisplayInfo,
|
|
4
|
+
init,
|
|
5
|
+
installDOM,
|
|
6
|
+
type InitOptions,
|
|
7
|
+
type InstallDOMOptions,
|
|
8
|
+
} from '@onirenaud/node-webgl';
|
|
2
9
|
import { readFile } from 'node:fs/promises';
|
|
3
10
|
import { extname, resolve } from 'node:path';
|
|
4
11
|
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import type { Environment } from 'vitest/
|
|
12
|
+
import type { Environment } from 'vitest/runtime';
|
|
6
13
|
|
|
7
14
|
/** Set under `test.environmentOptions.webglNode` in your Vitest config. */
|
|
8
15
|
export type WebglNodeOptions = InitOptions & InstallDOMOptions;
|
|
@@ -103,11 +110,59 @@ export default {
|
|
|
103
110
|
}
|
|
104
111
|
init({ backend, api });
|
|
105
112
|
}
|
|
113
|
+
const contexts = new Set<NonNullable<ReturnType<Canvas['getContext']>>>();
|
|
114
|
+
const frames = new Set<ReturnType<typeof setTimeout>>();
|
|
115
|
+
let disposed = false;
|
|
116
|
+
const dispose = () => {
|
|
117
|
+
disposed = true;
|
|
118
|
+
for (const frame of frames) clearTimeout(frame);
|
|
119
|
+
frames.clear();
|
|
120
|
+
for (const context of contexts) context.destroy();
|
|
121
|
+
contexts.clear();
|
|
122
|
+
};
|
|
123
|
+
const hasFrame = (global as unknown as Record<string, unknown>).requestAnimationFrame !== undefined;
|
|
106
124
|
const before = snapshot(global);
|
|
107
125
|
const nested: { target: object; before: PropertySnapshot; changed?: Set<PropertyKey> }[] = [];
|
|
108
126
|
try {
|
|
109
127
|
// node-webgl remembers fetch installation at module scope, which cannot represent separate Vitest environments.
|
|
110
128
|
installDOM({ ...dom, fetch: false });
|
|
129
|
+
// Intercept the shared canvas API so imported factories and OffscreenCanvas are covered too.
|
|
130
|
+
const canvasBefore = snapshot(Canvas.prototype);
|
|
131
|
+
nested.push({ target: Canvas.prototype, before: canvasBefore });
|
|
132
|
+
const getContext = Canvas.prototype.getContext;
|
|
133
|
+
Canvas.prototype.getContext = function (this: Canvas, ...args: Parameters<Canvas['getContext']>) {
|
|
134
|
+
if (disposed) throw new Error('WebGL environment has been torn down');
|
|
135
|
+
// node-webgl exposes the cached context at runtime but strips it from its declarations.
|
|
136
|
+
const previous = (this as Canvas & { _ctx?: ReturnType<Canvas['getContext']> })._ctx;
|
|
137
|
+
const context = getContext.apply(this, args);
|
|
138
|
+
if (context && context !== previous) contexts.add(context);
|
|
139
|
+
return context;
|
|
140
|
+
} as Canvas['getContext'];
|
|
141
|
+
nested.at(-1)!.changed = changedProperties(canvasBefore, Canvas.prototype);
|
|
142
|
+
// Own only the rAF shim installed by this setup; preserve host implementations.
|
|
143
|
+
if (!hasFrame) {
|
|
144
|
+
Object.defineProperty(global, 'requestAnimationFrame', {
|
|
145
|
+
configurable: true,
|
|
146
|
+
writable: true,
|
|
147
|
+
value: (callback: (time: number) => void) => {
|
|
148
|
+
if (disposed) return 0;
|
|
149
|
+
const frame = setTimeout(() => {
|
|
150
|
+
frames.delete(frame);
|
|
151
|
+
callback(performance.now());
|
|
152
|
+
}, dom.frameInterval ?? 16).unref();
|
|
153
|
+
frames.add(frame);
|
|
154
|
+
return frame;
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
Object.defineProperty(global, 'cancelAnimationFrame', {
|
|
158
|
+
configurable: true,
|
|
159
|
+
writable: true,
|
|
160
|
+
value: (frame: ReturnType<typeof setTimeout>) => {
|
|
161
|
+
frames.delete(frame);
|
|
162
|
+
clearTimeout(frame);
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
}
|
|
111
166
|
if (dom.fetch !== false) installFetch(global, dom.baseDir ?? process.cwd());
|
|
112
167
|
// node-webgl's window/document have no event methods; engines like Babylon.js register resize/blur listeners.
|
|
113
168
|
const { window, document } = global as unknown as Record<string, Record<string, unknown>>;
|
|
@@ -120,6 +175,7 @@ export default {
|
|
|
120
175
|
nested.at(-1)!.changed = changedProperties(targetBefore, target);
|
|
121
176
|
}
|
|
122
177
|
} catch (error) {
|
|
178
|
+
dispose();
|
|
123
179
|
for (const item of nested.toReversed())
|
|
124
180
|
restore(item.target, item.before, item.changed ?? changedProperties(item.before, item.target));
|
|
125
181
|
restore(global, before, changedProperties(before, global));
|
|
@@ -128,8 +184,15 @@ export default {
|
|
|
128
184
|
const changed = changedProperties(before, global);
|
|
129
185
|
return {
|
|
130
186
|
teardown(g: Record<string, unknown>) {
|
|
131
|
-
|
|
132
|
-
|
|
187
|
+
if (disposed) return;
|
|
188
|
+
try {
|
|
189
|
+
dispose();
|
|
190
|
+
} finally {
|
|
191
|
+
for (const item of nested.toReversed()) restore(item.target, item.before, item.changed!);
|
|
192
|
+
restore(g, before, changed);
|
|
193
|
+
nested.length = 0;
|
|
194
|
+
before.clear();
|
|
195
|
+
}
|
|
133
196
|
},
|
|
134
197
|
};
|
|
135
198
|
},
|
package/CHANGELOG.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
## [vitest-environment-webgl-node-v1.0.1](https://github.com/bhouston/vitest-gpu/compare/vitest-environment-webgl-node-v1.0.0...vitest-environment-webgl-node-v1.0.1) (2026-09-25)
|
|
2
|
-
|
|
3
|
-
### Bug Fixes
|
|
4
|
-
|
|
5
|
-
* drop deprecated transformMode from GPU environments ([28f736a](https://github.com/bhouston/vitest-gpu/commit/28f736ad629a2a3ca023839cadce460d0d5c6937)), closes [#54](https://github.com/bhouston/vitest-gpu/issues/54)
|