vitest-environment-webgl-node 0.1.1 → 0.1.3
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/CHANGELOG.md +2 -2
- package/README.md +17 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +103 -12
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/index.ts +124 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
## [vitest-environment-webgl-node-v0.1.
|
|
1
|
+
## [vitest-environment-webgl-node-v0.1.3](https://github.com/bhouston/vitest-gpu/compare/vitest-environment-webgl-node-v0.1.2...vitest-environment-webgl-node-v0.1.3) (2026-09-21)
|
|
2
2
|
|
|
3
3
|
### Bug Fixes
|
|
4
4
|
|
|
5
|
-
*
|
|
5
|
+
* support public types across TypeScript versions ([0f3df06](https://github.com/bhouston/vitest-gpu/commit/0f3df067f28308e0154045304ecbcaf2ba840fcc)), closes [#24](https://github.com/bhouston/vitest-gpu/issues/24)
|
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ the `WebGL*` classes and a file-reading `fetch()` before each test file, and rem
|
|
|
8
8
|
`document.createElement('canvas').getContext('webgl2')` returns a GPU-backed context, so three.js,
|
|
9
9
|
regl, pixi and plain WebGL code run unchanged.
|
|
10
10
|
|
|
11
|
+
Using Jest instead? See the equivalent
|
|
12
|
+
[`jest-environment-webgl-node`](https://github.com/bhouston/jest-gpu/tree/main/packages/jest-environment-webgl-node)
|
|
13
|
+
in [jest-gpu](https://github.com/bhouston/jest-gpu).
|
|
14
|
+
|
|
11
15
|
## Install
|
|
12
16
|
|
|
13
17
|
```sh
|
|
@@ -43,7 +47,19 @@ Options are those of node-webgl's `init()` (`backend`, `api`) and `installDOM()`
|
|
|
43
47
|
(`baseDir`, `fetch`, `devicePixelRatio`, `innerWidth`, `innerHeight`, `frameInterval`).
|
|
44
48
|
Pair with [`vitest-screenshot`](../vitest-screenshot) for pixel-diff assertions.
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
For a Node-only TypeScript project, opt into types for the installed globals by adding
|
|
51
|
+
`import 'vitest-environment-webgl-node/globals';` to a `.d.ts` file your tsconfig includes. Do not include
|
|
52
|
+
this entry in projects that load TypeScript's DOM library: the real browser globals and node-webgl's shims
|
|
53
|
+
intentionally have different types. DOM projects can use their standard global declarations and cast a
|
|
54
|
+
node-webgl canvas at the library boundary when necessary.
|
|
47
55
|
|
|
48
56
|
On Linux CI install Mesa (`apt-get install libegl1 libgles2 libgl1-mesa-dri`) and set
|
|
49
57
|
`LIBGL_ALWAYS_SOFTWARE=1`. macOS and Windows use prebuilt binaries.
|
|
58
|
+
|
|
59
|
+
## Author
|
|
60
|
+
|
|
61
|
+
Created by [Ben Houston](https://ben3d.ca) and sponsored by [Land of Assets](https://landofassets.com).
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { type InitOptions, type InstallDOMOptions } from '@onirenaud/node-webgl';
|
|
2
|
-
import type { Environment } from 'vitest/environments';
|
|
3
2
|
/** Set under `test.environmentOptions.webglNode` in your Vitest config. */
|
|
4
3
|
export type WebglNodeOptions = InitOptions & InstallDOMOptions;
|
|
5
|
-
declare const _default:
|
|
4
|
+
declare const _default: {
|
|
5
|
+
name: string;
|
|
6
|
+
transformMode: "ssr";
|
|
7
|
+
viteEnvironment: "ssr";
|
|
8
|
+
setup(global: typeof globalThis, { webglNode }: {
|
|
9
|
+
webglNode?: WebglNodeOptions;
|
|
10
|
+
}): {
|
|
11
|
+
teardown(g: Record<string, unknown>): void;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
6
14
|
export default _default;
|
|
7
15
|
//# sourceMappingURL=index.d.ts.map
|
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,EAAoB,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAMnG,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,iBAAiB,CAAC;;;;;kBAmF/C,OAAO,UAAU,iBAAsB;QAAE,SAAS,CAAC,EAAE,gBAAgB,CAAA;KAAE;oBA2BrE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,117 @@
|
|
|
1
1
|
import { init, installDOM } from '@onirenaud/node-webgl';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { extname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const mimeTypes = {
|
|
6
|
+
'.basis': 'application/octet-stream',
|
|
7
|
+
'.bin': 'application/octet-stream',
|
|
8
|
+
'.drc': 'application/octet-stream',
|
|
9
|
+
'.exr': 'image/x-exr',
|
|
10
|
+
'.fbx': 'application/octet-stream',
|
|
11
|
+
'.gif': 'image/gif',
|
|
12
|
+
'.glb': 'model/gltf-binary',
|
|
13
|
+
'.gltf': 'model/gltf+json',
|
|
14
|
+
'.hdr': 'image/vnd.radiance',
|
|
15
|
+
'.jpeg': 'image/jpeg',
|
|
16
|
+
'.jpg': 'image/jpeg',
|
|
17
|
+
'.js': 'text/javascript',
|
|
18
|
+
'.json': 'application/json',
|
|
19
|
+
'.ktx': 'image/ktx',
|
|
20
|
+
'.ktx2': 'image/ktx2',
|
|
21
|
+
'.mjs': 'text/javascript',
|
|
22
|
+
'.mtl': 'text/plain',
|
|
23
|
+
'.obj': 'text/plain',
|
|
24
|
+
'.png': 'image/png',
|
|
25
|
+
'.svg': 'image/svg+xml',
|
|
26
|
+
'.txt': 'text/plain',
|
|
27
|
+
'.wasm': 'application/wasm',
|
|
28
|
+
'.webp': 'image/webp',
|
|
29
|
+
};
|
|
30
|
+
const snapshot = (target) => new Map(Reflect.ownKeys(target).map((key) => [key, Object.getOwnPropertyDescriptor(target, key)]));
|
|
31
|
+
const changedProperties = (before, target) => {
|
|
32
|
+
const changed = new Set();
|
|
33
|
+
for (const key of new Set([...before.keys(), ...Reflect.ownKeys(target)])) {
|
|
34
|
+
const previous = before.get(key);
|
|
35
|
+
const current = Object.getOwnPropertyDescriptor(target, key);
|
|
36
|
+
if (previous?.configurable !== current?.configurable ||
|
|
37
|
+
previous?.enumerable !== current?.enumerable ||
|
|
38
|
+
previous?.get !== current?.get ||
|
|
39
|
+
previous?.set !== current?.set ||
|
|
40
|
+
previous?.value !== current?.value ||
|
|
41
|
+
previous?.writable !== current?.writable) {
|
|
42
|
+
changed.add(key);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return changed;
|
|
46
|
+
};
|
|
47
|
+
const restore = (target, before, changed) => {
|
|
48
|
+
for (const key of changed) {
|
|
49
|
+
const descriptor = before.get(key);
|
|
50
|
+
if (descriptor)
|
|
51
|
+
Object.defineProperty(target, key, descriptor);
|
|
52
|
+
else
|
|
53
|
+
Reflect.deleteProperty(target, key);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const installFetch = (global, baseDir) => {
|
|
57
|
+
const originalFetch = global.fetch;
|
|
58
|
+
const localFetch = async (input, requestInit) => {
|
|
59
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
60
|
+
if (/^(https?|data|blob):/i.test(url))
|
|
61
|
+
return originalFetch(input, requestInit);
|
|
62
|
+
const path = url.startsWith('file:') ? fileURLToPath(url) : resolve(baseDir, url.replace(/[?#].*$/, ''));
|
|
63
|
+
try {
|
|
64
|
+
const bytes = await readFile(path);
|
|
65
|
+
return new Response(bytes, {
|
|
66
|
+
status: 200,
|
|
67
|
+
headers: { 'content-type': mimeTypes[extname(path).toLowerCase()] ?? 'application/octet-stream' },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return new Response(null, { status: 404, statusText: 'Not Found' });
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
Object.defineProperty(global, 'fetch', { value: localFetch, writable: true, configurable: true });
|
|
75
|
+
};
|
|
2
76
|
export default {
|
|
3
77
|
name: 'webgl-node',
|
|
78
|
+
transformMode: 'ssr',
|
|
4
79
|
viteEnvironment: 'ssr',
|
|
5
80
|
setup(global, { webglNode = {} }) {
|
|
6
81
|
const { backend, api, ...dom } = webglNode;
|
|
7
82
|
if (backend || api)
|
|
8
83
|
init({ backend, api });
|
|
9
|
-
const before =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
84
|
+
const before = snapshot(global);
|
|
85
|
+
const nested = [];
|
|
86
|
+
try {
|
|
87
|
+
// node-webgl remembers fetch installation at module scope, which cannot represent separate Vitest environments.
|
|
88
|
+
installDOM({ ...dom, fetch: false });
|
|
89
|
+
if (dom.fetch !== false)
|
|
90
|
+
installFetch(global, dom.baseDir ?? process.cwd());
|
|
91
|
+
// node-webgl's window/document have no event methods; engines like Babylon.js register resize/blur listeners.
|
|
92
|
+
const { window, document } = global;
|
|
93
|
+
for (const target of [window, document]) {
|
|
94
|
+
if (!target)
|
|
95
|
+
continue;
|
|
96
|
+
const targetBefore = snapshot(target);
|
|
97
|
+
nested.push({ target, before: targetBefore });
|
|
98
|
+
target.addEventListener ??= () => { };
|
|
99
|
+
target.removeEventListener ??= () => { };
|
|
100
|
+
nested.at(-1).changed = changedProperties(targetBefore, target);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
for (const item of nested.toReversed())
|
|
105
|
+
restore(item.target, item.before, item.changed ?? changedProperties(item.before, item.target));
|
|
106
|
+
restore(global, before, changedProperties(before, global));
|
|
107
|
+
throw error;
|
|
18
108
|
}
|
|
19
|
-
const
|
|
109
|
+
const changed = changedProperties(before, global);
|
|
20
110
|
return {
|
|
21
111
|
teardown(g) {
|
|
22
|
-
for (const
|
|
23
|
-
|
|
112
|
+
for (const item of nested.toReversed())
|
|
113
|
+
restore(item.target, item.before, item.changed);
|
|
114
|
+
restore(g, before, changed);
|
|
24
115
|
},
|
|
25
116
|
};
|
|
26
117
|
},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,UAAU,EAA4C,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-environment-webgl-node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Vitest environment that provides real headless WebGL 1/2 (ANGLE via @onirenaud/node-webgl) in Node",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"headless",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"dist/**/*.js.map",
|
|
29
29
|
"dist/**/*.d.ts",
|
|
30
30
|
"dist/**/*.d.ts.map",
|
|
31
|
+
"src/**/*.ts",
|
|
32
|
+
"!src/**/*.test.ts",
|
|
31
33
|
"CHANGELOG.md"
|
|
32
34
|
],
|
|
33
35
|
"type": "module",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { init, installDOM, type InitOptions, type InstallDOMOptions } from '@onirenaud/node-webgl';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { extname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import type { Environment } from 'vitest/environments';
|
|
6
|
+
|
|
7
|
+
/** Set under `test.environmentOptions.webglNode` in your Vitest config. */
|
|
8
|
+
export type WebglNodeOptions = InitOptions & InstallDOMOptions;
|
|
9
|
+
|
|
10
|
+
const mimeTypes: Record<string, string> = {
|
|
11
|
+
'.basis': 'application/octet-stream',
|
|
12
|
+
'.bin': 'application/octet-stream',
|
|
13
|
+
'.drc': 'application/octet-stream',
|
|
14
|
+
'.exr': 'image/x-exr',
|
|
15
|
+
'.fbx': 'application/octet-stream',
|
|
16
|
+
'.gif': 'image/gif',
|
|
17
|
+
'.glb': 'model/gltf-binary',
|
|
18
|
+
'.gltf': 'model/gltf+json',
|
|
19
|
+
'.hdr': 'image/vnd.radiance',
|
|
20
|
+
'.jpeg': 'image/jpeg',
|
|
21
|
+
'.jpg': 'image/jpeg',
|
|
22
|
+
'.js': 'text/javascript',
|
|
23
|
+
'.json': 'application/json',
|
|
24
|
+
'.ktx': 'image/ktx',
|
|
25
|
+
'.ktx2': 'image/ktx2',
|
|
26
|
+
'.mjs': 'text/javascript',
|
|
27
|
+
'.mtl': 'text/plain',
|
|
28
|
+
'.obj': 'text/plain',
|
|
29
|
+
'.png': 'image/png',
|
|
30
|
+
'.svg': 'image/svg+xml',
|
|
31
|
+
'.txt': 'text/plain',
|
|
32
|
+
'.wasm': 'application/wasm',
|
|
33
|
+
'.webp': 'image/webp',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type PropertySnapshot = Map<PropertyKey, PropertyDescriptor>;
|
|
37
|
+
|
|
38
|
+
const snapshot = (target: object): PropertySnapshot =>
|
|
39
|
+
new Map(Reflect.ownKeys(target).map((key) => [key, Object.getOwnPropertyDescriptor(target, key)!]));
|
|
40
|
+
|
|
41
|
+
const changedProperties = (before: PropertySnapshot, target: object): Set<PropertyKey> => {
|
|
42
|
+
const changed = new Set<PropertyKey>();
|
|
43
|
+
for (const key of new Set([...before.keys(), ...Reflect.ownKeys(target)])) {
|
|
44
|
+
const previous = before.get(key);
|
|
45
|
+
const current = Object.getOwnPropertyDescriptor(target, key);
|
|
46
|
+
if (
|
|
47
|
+
previous?.configurable !== current?.configurable ||
|
|
48
|
+
previous?.enumerable !== current?.enumerable ||
|
|
49
|
+
previous?.get !== current?.get ||
|
|
50
|
+
previous?.set !== current?.set ||
|
|
51
|
+
previous?.value !== current?.value ||
|
|
52
|
+
previous?.writable !== current?.writable
|
|
53
|
+
) {
|
|
54
|
+
changed.add(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return changed;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const restore = (target: object, before: PropertySnapshot, changed: Iterable<PropertyKey>): void => {
|
|
61
|
+
for (const key of changed) {
|
|
62
|
+
const descriptor = before.get(key);
|
|
63
|
+
if (descriptor) Object.defineProperty(target, key, descriptor);
|
|
64
|
+
else Reflect.deleteProperty(target, key);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const installFetch = (global: typeof globalThis, baseDir: string): void => {
|
|
69
|
+
const originalFetch = global.fetch;
|
|
70
|
+
const localFetch = async (input: string | URL | Request, requestInit?: RequestInit): Promise<Response> => {
|
|
71
|
+
const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;
|
|
72
|
+
if (/^(https?|data|blob):/i.test(url)) return originalFetch(input, requestInit);
|
|
73
|
+
const path = url.startsWith('file:') ? fileURLToPath(url) : resolve(baseDir, url.replace(/[?#].*$/, ''));
|
|
74
|
+
try {
|
|
75
|
+
const bytes = await readFile(path);
|
|
76
|
+
return new Response(bytes, {
|
|
77
|
+
status: 200,
|
|
78
|
+
headers: { 'content-type': mimeTypes[extname(path).toLowerCase()] ?? 'application/octet-stream' },
|
|
79
|
+
});
|
|
80
|
+
} catch {
|
|
81
|
+
return new Response(null, { status: 404, statusText: 'Not Found' });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
Object.defineProperty(global, 'fetch', { value: localFetch, writable: true, configurable: true });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export default {
|
|
88
|
+
name: 'webgl-node',
|
|
89
|
+
transformMode: 'ssr',
|
|
90
|
+
viteEnvironment: 'ssr',
|
|
91
|
+
setup(global: typeof globalThis, { webglNode = {} }: { webglNode?: WebglNodeOptions }) {
|
|
92
|
+
const { backend, api, ...dom } = webglNode;
|
|
93
|
+
if (backend || api) init({ backend, api });
|
|
94
|
+
const before = snapshot(global);
|
|
95
|
+
const nested: { target: object; before: PropertySnapshot; changed?: Set<PropertyKey> }[] = [];
|
|
96
|
+
try {
|
|
97
|
+
// node-webgl remembers fetch installation at module scope, which cannot represent separate Vitest environments.
|
|
98
|
+
installDOM({ ...dom, fetch: false });
|
|
99
|
+
if (dom.fetch !== false) installFetch(global, dom.baseDir ?? process.cwd());
|
|
100
|
+
// node-webgl's window/document have no event methods; engines like Babylon.js register resize/blur listeners.
|
|
101
|
+
const { window, document } = global as unknown as Record<string, Record<string, unknown>>;
|
|
102
|
+
for (const target of [window, document]) {
|
|
103
|
+
if (!target) continue;
|
|
104
|
+
const targetBefore = snapshot(target);
|
|
105
|
+
nested.push({ target, before: targetBefore });
|
|
106
|
+
target.addEventListener ??= () => {};
|
|
107
|
+
target.removeEventListener ??= () => {};
|
|
108
|
+
nested.at(-1)!.changed = changedProperties(targetBefore, target);
|
|
109
|
+
}
|
|
110
|
+
} catch (error) {
|
|
111
|
+
for (const item of nested.toReversed())
|
|
112
|
+
restore(item.target, item.before, item.changed ?? changedProperties(item.before, item.target));
|
|
113
|
+
restore(global, before, changedProperties(before, global));
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
const changed = changedProperties(before, global);
|
|
117
|
+
return {
|
|
118
|
+
teardown(g: Record<string, unknown>) {
|
|
119
|
+
for (const item of nested.toReversed()) restore(item.target, item.before, item.changed!);
|
|
120
|
+
restore(g, before, changed);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
} satisfies Environment;
|