jest-environment-webgl-node 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ben Houston
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # jest-environment-webgl-node
2
+
3
+ Real, headless WebGL 1 and WebGL 2 inside Jest, with no browser. Powered by
4
+ [`@onirenaud/node-webgl`](https://github.com/RenaudRohlinger/node-webgl) (Chrome's ANGLE, statically linked).
5
+
6
+ The environment installs `window`, `document`, `HTMLCanvasElement`, `Image`, `requestAnimationFrame`,
7
+ the `WebGL*` classes and a file-reading `fetch()` on the test's sandbox global, and removes them
8
+ after the test file finishes. `document.createElement('canvas').getContext('webgl2')` returns a
9
+ GPU-backed context, so three.js, regl, pixi and plain WebGL code run unchanged.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ pnpm add -D jest jest-environment-webgl-node
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```js
20
+ // jest.config.mjs
21
+ export default {
22
+ testEnvironment: 'jest-environment-webgl-node',
23
+ testEnvironmentOptions: { backend: 'swiftshader', innerWidth: 800, innerHeight: 600 },
24
+ };
25
+ ```
26
+
27
+ ```ts
28
+ import * as THREE from 'three';
29
+ import { expect, it } from '@jest/globals';
30
+
31
+ it('renders', () => {
32
+ const canvas = document.createElement('canvas');
33
+ const renderer = new THREE.WebGLRenderer({ canvas });
34
+ renderer.setSize(64, 64, false);
35
+ renderer.render(new THREE.Scene(), new THREE.PerspectiveCamera());
36
+
37
+ const gl = renderer.getContext();
38
+ const pixels = new Uint8Array(4);
39
+ gl.readPixels(32, 32, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
40
+ expect(pixels[3]).toBe(255);
41
+ });
42
+ ```
43
+
44
+ `testEnvironmentOptions` are those of node-webgl's `init()` (`backend`, `api`) and `installDOM()`
45
+ (`baseDir`, `fetch`, `devicePixelRatio`, `innerWidth`, `innerHeight`, `frameInterval`). Because
46
+ `installDOM()` writes to the host `globalThis` and is idempotent, only the **first** test file's
47
+ options take effect for the underlying host install; every test file's sandbox still gets the
48
+ same installed globals copied in.
49
+
50
+ Types for the installed globals: add `import 'jest-environment-webgl-node/globals';` to a `.d.ts`
51
+ file your tsconfig includes.
52
+
53
+ This package ships both an ESM and a CommonJS build, so `require('jest-environment-webgl-node')`
54
+ works from a plain CommonJS test file. `NODE_OPTIONS=--experimental-vm-modules` is only needed when
55
+ a test file itself imports an ESM-only library.
56
+
57
+ On Linux CI install Mesa (`apt-get install libegl1 libgles2 libgl1-mesa-dri`) and set
58
+ `LIBGL_ALWAYS_SOFTWARE=1`. macOS and Windows use prebuilt binaries.
59
+
60
+ ## Author
61
+
62
+ Created by [Ben Houston](https://ben3d.ca) and sponsored by [Land of Assets](https://landofassets.com).
63
+
64
+ ## License
65
+
66
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,54 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ let _onirenaud_node_webgl = require("@onirenaud/node-webgl");
6
+ let jest_environment_node = require("jest-environment-node");
7
+ //#region src/index.ts
8
+ let domNames;
9
+ function ensureHostDOM(options) {
10
+ if (domNames) return domNames;
11
+ const { backend, api, ...dom } = options;
12
+ if (backend || api) (0, _onirenaud_node_webgl.init)({
13
+ backend,
14
+ api
15
+ });
16
+ const before = new Set(Object.getOwnPropertyNames(globalThis));
17
+ (0, _onirenaud_node_webgl.installDOM)(dom);
18
+ domNames = Object.getOwnPropertyNames(globalThis).filter((key) => !before.has(key));
19
+ return domNames;
20
+ }
21
+ var WebglEnvironment = class extends jest_environment_node.TestEnvironment {
22
+ added;
23
+ constructor(config, context) {
24
+ super(config, context);
25
+ const nativeAdded = !Object.getOwnPropertyNames(this.global).includes("_native");
26
+ if (nativeAdded) Object.defineProperty(this.global, "_native", {
27
+ value: void 0,
28
+ writable: true,
29
+ configurable: true,
30
+ enumerable: false
31
+ });
32
+ const options = config.projectConfig.testEnvironmentOptions;
33
+ const names = ensureHostDOM(options);
34
+ const g = this.global;
35
+ this.added = names.filter((key) => !(key in g));
36
+ for (const key of this.added) g[key] = globalThis[key];
37
+ if (nativeAdded) this.added.push("_native");
38
+ g.window = g;
39
+ g.self = g;
40
+ for (const target of [g.window, g.document]) {
41
+ target.addEventListener ??= () => {};
42
+ target.removeEventListener ??= () => {};
43
+ }
44
+ }
45
+ async teardown() {
46
+ const g = this.global;
47
+ for (const key of this.added) delete g[key];
48
+ await super.teardown();
49
+ }
50
+ };
51
+ //#endregion
52
+ exports.default = WebglEnvironment;
53
+
54
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["TestEnvironment"],"sources":["../src/index.ts"],"sourcesContent":["import { init, installDOM, type InitOptions, type InstallDOMOptions } from '@onirenaud/node-webgl';\nimport { TestEnvironment } from 'jest-environment-node';\n\n/** Set under `testEnvironmentOptions` in your Jest config. */\nexport type WebglNodeOptions = InitOptions & InstallDOMOptions;\n\n// Typed from TestEnvironment's own constructor rather than importing `@jest/environment`\n// directly: that package is only a transitive dependency (of jest-environment-node), not\n// resolvable from this package's own node_modules under pnpm's strict layout.\ntype JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];\ntype EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];\n\n// node-webgl's installDOM() writes to the HOST globalThis (not a target you pass in) and is\n// idempotent (it skips names already defined). So it can only usefully run once per process: on\n// the first WebglEnvironment constructed here, snapshot the host globals before/after the call and\n// remember which names it added. Every later environment (this test file or another one sharing\n// the worker) copies that same fixed list into its own sandbox global instead of calling\n// installDOM() again. This means only the FIRST test file's `testEnvironmentOptions` actually\n// affect the host install (backend/api selection, DOM option defaults); later test files'\n// options for those are ignored, matching how node-webgl itself behaves.\nlet domNames: readonly string[] | undefined;\n\nfunction ensureHostDOM(options: WebglNodeOptions): readonly string[] {\n if (domNames) return domNames;\n const { backend, api, ...dom } = options;\n if (backend || api) init({ backend, api });\n const before = new Set(Object.getOwnPropertyNames(globalThis));\n installDOM(dom);\n domNames = Object.getOwnPropertyNames(globalThis).filter((key) => !before.has(key));\n return domNames;\n}\n\nexport default class WebglEnvironment extends TestEnvironment {\n private readonly added: string[];\n\n constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {\n super(config, context);\n // @babylonjs/core defines a `_native` accessor on `self` at module load; on Jest's proxied\n // sandbox global that defineProperty violates a proxy invariant, so pre-seed the property to\n // satisfy Babylon's hasOwnProperty guard.\n // `Object.hasOwn`/`in` report false negatives here: jest-environment-node's sandbox global is\n // a Proxy over a vm context, and reads of freshly-added properties from outside the sandbox\n // (as this constructor runs) don't go through the fast path those use. getOwnPropertyNames is\n // unaffected, so use that instead.\n const nativeAdded = !Object.getOwnPropertyNames(this.global).includes('_native');\n if (nativeAdded) {\n Object.defineProperty(this.global, '_native', {\n value: undefined,\n writable: true,\n configurable: true,\n enumerable: false,\n });\n }\n // Jest always populates `testEnvironmentOptions` (defaulting to `{}`) by the time a real\n // config reaches an environment; `super()` above already relies on that itself.\n const options = config.projectConfig.testEnvironmentOptions as WebglNodeOptions;\n const names = ensureHostDOM(options);\n const g = this.global as unknown as Record<string, unknown>;\n this.added = names.filter((key) => !(key in g));\n for (const key of this.added) g[key] = (globalThis as unknown as Record<string, unknown>)[key];\n if (nativeAdded) this.added.push('_native');\n g.window = g;\n g.self = g;\n // node-webgl's window/document have no event methods; engines like Babylon.js register\n // resize/blur listeners on them. installDOM() always installs both, so both are present here.\n for (const target of [g.window, g.document] as Record<string, unknown>[]) {\n target.addEventListener ??= () => {};\n target.removeEventListener ??= () => {};\n }\n }\n\n override async teardown(): Promise<void> {\n const g = this.global as unknown as Record<string, unknown>;\n for (const key of this.added) delete g[key];\n await super.teardown();\n }\n}\n"],"mappings":";;;;;;;AAoBA,IAAI;AAEJ,SAAS,cAAc,SAA8C;CACnE,IAAI,UAAU,OAAO;CACrB,MAAM,EAAE,SAAS,KAAK,GAAG,QAAQ;CACjC,IAAI,WAAW,KAAK,CAAA,GAAA,sBAAA,KAAA,CAAK;EAAE;EAAS;CAAI,CAAC;CACzC,MAAM,SAAS,IAAI,IAAI,OAAO,oBAAoB,UAAU,CAAC;CAC7D,CAAA,GAAA,sBAAA,WAAA,CAAW,GAAG;CACd,WAAW,OAAO,oBAAoB,UAAU,CAAC,CAAC,QAAQ,QAAQ,CAAC,OAAO,IAAI,GAAG,CAAC;CAClF,OAAO;AACT;AAEA,IAAqB,mBAArB,cAA8CA,sBAAAA,gBAAgB;CAC5D;CAEA,YAAY,QAA+B,SAA6B;EACtE,MAAM,QAAQ,OAAO;EAQrB,MAAM,cAAc,CAAC,OAAO,oBAAoB,KAAK,MAAM,CAAC,CAAC,SAAS,SAAS;EAC/E,IAAI,aACF,OAAO,eAAe,KAAK,QAAQ,WAAW;GAC5C,OAAO,KAAA;GACP,UAAU;GACV,cAAc;GACd,YAAY;EACd,CAAC;EAIH,MAAM,UAAU,OAAO,cAAc;EACrC,MAAM,QAAQ,cAAc,OAAO;EACnC,MAAM,IAAI,KAAK;EACf,KAAK,QAAQ,MAAM,QAAQ,QAAQ,EAAE,OAAO,EAAE;EAC9C,KAAK,MAAM,OAAO,KAAK,OAAO,EAAE,OAAQ,WAAkD;EAC1F,IAAI,aAAa,KAAK,MAAM,KAAK,SAAS;EAC1C,EAAE,SAAS;EACX,EAAE,OAAO;EAGT,KAAK,MAAM,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAgC;GACxE,OAAO,2BAA2B,CAAC;GACnC,OAAO,8BAA8B,CAAC;EACxC;CACF;CAEA,MAAe,WAA0B;EACvC,MAAM,IAAI,KAAK;EACf,KAAK,MAAM,OAAO,KAAK,OAAO,OAAO,EAAE;EACvC,MAAM,MAAM,SAAS;CACvB;AACF"}
@@ -0,0 +1,14 @@
1
+ import { InitOptions, InstallDOMOptions } from "@onirenaud/node-webgl";
2
+ import { TestEnvironment } from "jest-environment-node";
3
+ //#region src/index.d.ts
4
+ /** Set under `testEnvironmentOptions` in your Jest config. */
5
+ export type WebglNodeOptions = InitOptions & InstallDOMOptions;
6
+ type JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];
7
+ type EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];
8
+ export default class WebglEnvironment extends TestEnvironment {
9
+ private readonly added;
10
+ constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
11
+ teardown(): Promise<void>;
12
+ }
13
+ //#endregion
14
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAIY,mBAAmB,cAAc;KAKxC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;qBAsBlC,yBAAyB;mBAC3B;EAEjB,YAAY,QAAQ,uBAAuB,SAAS;EAoCrC,YAAY"}
@@ -0,0 +1,14 @@
1
+ import { InitOptions, InstallDOMOptions } from "@onirenaud/node-webgl";
2
+ import { TestEnvironment } from "jest-environment-node";
3
+ //#region src/index.d.ts
4
+ /** Set under `testEnvironmentOptions` in your Jest config. */
5
+ export type WebglNodeOptions = InitOptions & InstallDOMOptions;
6
+ type JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];
7
+ type EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];
8
+ export default class WebglEnvironment extends TestEnvironment {
9
+ private readonly added;
10
+ constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
11
+ teardown(): Promise<void>;
12
+ }
13
+ //#endregion
14
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAIY,mBAAmB,cAAc;KAKxC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;qBAsBlC,yBAAyB;mBAC3B;EAEjB,YAAY,QAAQ,uBAAuB,SAAS;EAoCrC,YAAY"}
package/dist/index.mjs ADDED
@@ -0,0 +1,50 @@
1
+ import { init, installDOM } from "@onirenaud/node-webgl";
2
+ import { TestEnvironment } from "jest-environment-node";
3
+ //#region src/index.ts
4
+ let domNames;
5
+ function ensureHostDOM(options) {
6
+ if (domNames) return domNames;
7
+ const { backend, api, ...dom } = options;
8
+ if (backend || api) init({
9
+ backend,
10
+ api
11
+ });
12
+ const before = new Set(Object.getOwnPropertyNames(globalThis));
13
+ installDOM(dom);
14
+ domNames = Object.getOwnPropertyNames(globalThis).filter((key) => !before.has(key));
15
+ return domNames;
16
+ }
17
+ var WebglEnvironment = class extends TestEnvironment {
18
+ added;
19
+ constructor(config, context) {
20
+ super(config, context);
21
+ const nativeAdded = !Object.getOwnPropertyNames(this.global).includes("_native");
22
+ if (nativeAdded) Object.defineProperty(this.global, "_native", {
23
+ value: void 0,
24
+ writable: true,
25
+ configurable: true,
26
+ enumerable: false
27
+ });
28
+ const options = config.projectConfig.testEnvironmentOptions;
29
+ const names = ensureHostDOM(options);
30
+ const g = this.global;
31
+ this.added = names.filter((key) => !(key in g));
32
+ for (const key of this.added) g[key] = globalThis[key];
33
+ if (nativeAdded) this.added.push("_native");
34
+ g.window = g;
35
+ g.self = g;
36
+ for (const target of [g.window, g.document]) {
37
+ target.addEventListener ??= () => {};
38
+ target.removeEventListener ??= () => {};
39
+ }
40
+ }
41
+ async teardown() {
42
+ const g = this.global;
43
+ for (const key of this.added) delete g[key];
44
+ await super.teardown();
45
+ }
46
+ };
47
+ //#endregion
48
+ export { WebglEnvironment as default };
49
+
50
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { init, installDOM, type InitOptions, type InstallDOMOptions } from '@onirenaud/node-webgl';\nimport { TestEnvironment } from 'jest-environment-node';\n\n/** Set under `testEnvironmentOptions` in your Jest config. */\nexport type WebglNodeOptions = InitOptions & InstallDOMOptions;\n\n// Typed from TestEnvironment's own constructor rather than importing `@jest/environment`\n// directly: that package is only a transitive dependency (of jest-environment-node), not\n// resolvable from this package's own node_modules under pnpm's strict layout.\ntype JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];\ntype EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];\n\n// node-webgl's installDOM() writes to the HOST globalThis (not a target you pass in) and is\n// idempotent (it skips names already defined). So it can only usefully run once per process: on\n// the first WebglEnvironment constructed here, snapshot the host globals before/after the call and\n// remember which names it added. Every later environment (this test file or another one sharing\n// the worker) copies that same fixed list into its own sandbox global instead of calling\n// installDOM() again. This means only the FIRST test file's `testEnvironmentOptions` actually\n// affect the host install (backend/api selection, DOM option defaults); later test files'\n// options for those are ignored, matching how node-webgl itself behaves.\nlet domNames: readonly string[] | undefined;\n\nfunction ensureHostDOM(options: WebglNodeOptions): readonly string[] {\n if (domNames) return domNames;\n const { backend, api, ...dom } = options;\n if (backend || api) init({ backend, api });\n const before = new Set(Object.getOwnPropertyNames(globalThis));\n installDOM(dom);\n domNames = Object.getOwnPropertyNames(globalThis).filter((key) => !before.has(key));\n return domNames;\n}\n\nexport default class WebglEnvironment extends TestEnvironment {\n private readonly added: string[];\n\n constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {\n super(config, context);\n // @babylonjs/core defines a `_native` accessor on `self` at module load; on Jest's proxied\n // sandbox global that defineProperty violates a proxy invariant, so pre-seed the property to\n // satisfy Babylon's hasOwnProperty guard.\n // `Object.hasOwn`/`in` report false negatives here: jest-environment-node's sandbox global is\n // a Proxy over a vm context, and reads of freshly-added properties from outside the sandbox\n // (as this constructor runs) don't go through the fast path those use. getOwnPropertyNames is\n // unaffected, so use that instead.\n const nativeAdded = !Object.getOwnPropertyNames(this.global).includes('_native');\n if (nativeAdded) {\n Object.defineProperty(this.global, '_native', {\n value: undefined,\n writable: true,\n configurable: true,\n enumerable: false,\n });\n }\n // Jest always populates `testEnvironmentOptions` (defaulting to `{}`) by the time a real\n // config reaches an environment; `super()` above already relies on that itself.\n const options = config.projectConfig.testEnvironmentOptions as WebglNodeOptions;\n const names = ensureHostDOM(options);\n const g = this.global as unknown as Record<string, unknown>;\n this.added = names.filter((key) => !(key in g));\n for (const key of this.added) g[key] = (globalThis as unknown as Record<string, unknown>)[key];\n if (nativeAdded) this.added.push('_native');\n g.window = g;\n g.self = g;\n // node-webgl's window/document have no event methods; engines like Babylon.js register\n // resize/blur listeners on them. installDOM() always installs both, so both are present here.\n for (const target of [g.window, g.document] as Record<string, unknown>[]) {\n target.addEventListener ??= () => {};\n target.removeEventListener ??= () => {};\n }\n }\n\n override async teardown(): Promise<void> {\n const g = this.global as unknown as Record<string, unknown>;\n for (const key of this.added) delete g[key];\n await super.teardown();\n }\n}\n"],"mappings":";;;AAoBA,IAAI;AAEJ,SAAS,cAAc,SAA8C;CACnE,IAAI,UAAU,OAAO;CACrB,MAAM,EAAE,SAAS,KAAK,GAAG,QAAQ;CACjC,IAAI,WAAW,KAAK,KAAK;EAAE;EAAS;CAAI,CAAC;CACzC,MAAM,SAAS,IAAI,IAAI,OAAO,oBAAoB,UAAU,CAAC;CAC7D,WAAW,GAAG;CACd,WAAW,OAAO,oBAAoB,UAAU,CAAC,CAAC,QAAQ,QAAQ,CAAC,OAAO,IAAI,GAAG,CAAC;CAClF,OAAO;AACT;AAEA,IAAqB,mBAArB,cAA8C,gBAAgB;CAC5D;CAEA,YAAY,QAA+B,SAA6B;EACtE,MAAM,QAAQ,OAAO;EAQrB,MAAM,cAAc,CAAC,OAAO,oBAAoB,KAAK,MAAM,CAAC,CAAC,SAAS,SAAS;EAC/E,IAAI,aACF,OAAO,eAAe,KAAK,QAAQ,WAAW;GAC5C,OAAO,KAAA;GACP,UAAU;GACV,cAAc;GACd,YAAY;EACd,CAAC;EAIH,MAAM,UAAU,OAAO,cAAc;EACrC,MAAM,QAAQ,cAAc,OAAO;EACnC,MAAM,IAAI,KAAK;EACf,KAAK,QAAQ,MAAM,QAAQ,QAAQ,EAAE,OAAO,EAAE;EAC9C,KAAK,MAAM,OAAO,KAAK,OAAO,EAAE,OAAQ,WAAkD;EAC1F,IAAI,aAAa,KAAK,MAAM,KAAK,SAAS;EAC1C,EAAE,SAAS;EACX,EAAE,OAAO;EAGT,KAAK,MAAM,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAgC;GACxE,OAAO,2BAA2B,CAAC;GACnC,OAAO,8BAA8B,CAAC;EACxC;CACF;CAEA,MAAe,WAA0B;EACvC,MAAM,IAAI,KAAK;EACf,KAAK,MAAM,OAAO,KAAK,OAAO,OAAO,EAAE;EACvC,MAAM,MAAM,SAAS;CACvB;AACF"}
package/globals.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Types for the globals `jest-environment-webgl-node` installs (node-webgl's `installDOM()`).
3
+ * Pull in with `import 'jest-environment-webgl-node/globals';` from a `.d.ts` your tsconfig includes.
4
+ */
5
+ import type {
6
+ Canvas,
7
+ Image as NodeImage,
8
+ ImageBitmap as NodeImageBitmap,
9
+ ImageData as NodeImageData,
10
+ WebGL2RenderingContext as NodeWebGL2RenderingContext,
11
+ WebGLRenderingContext as NodeWebGLRenderingContext,
12
+ createImageBitmap as nodeCreateImageBitmap,
13
+ } from '@onirenaud/node-webgl';
14
+
15
+ type Listener = (type: string, listener: (event: unknown) => void, options?: unknown) => void;
16
+
17
+ interface ShimDocument {
18
+ createElement(tag: 'canvas'): Canvas;
19
+ createElement(tag: 'img'): NodeImage;
20
+ createElement(tag: string): unknown;
21
+ createElementNS(namespace: string, tag: 'canvas'): Canvas;
22
+ createElementNS(namespace: string, tag: string): unknown;
23
+ addEventListener: Listener;
24
+ removeEventListener: Listener;
25
+ }
26
+
27
+ interface ShimWindow {
28
+ innerWidth: number;
29
+ innerHeight: number;
30
+ devicePixelRatio: number;
31
+ addEventListener: Listener;
32
+ removeEventListener: Listener;
33
+ }
34
+
35
+ declare global {
36
+ var window: ShimWindow & typeof globalThis;
37
+ var self: ShimWindow & typeof globalThis;
38
+ var document: ShimDocument;
39
+ var innerWidth: number;
40
+ var innerHeight: number;
41
+ var devicePixelRatio: number;
42
+ var HTMLCanvasElement: typeof Canvas;
43
+ var OffscreenCanvas: typeof Canvas;
44
+ var HTMLImageElement: typeof NodeImage;
45
+ var Image: typeof NodeImage;
46
+ var ImageData: typeof NodeImageData;
47
+ var ImageBitmap: typeof NodeImageBitmap;
48
+ var createImageBitmap: typeof nodeCreateImageBitmap;
49
+ var WebGLRenderingContext: typeof NodeWebGLRenderingContext;
50
+ var WebGL2RenderingContext: typeof NodeWebGL2RenderingContext;
51
+ function requestAnimationFrame(callback: (time: number) => void): number;
52
+ function cancelAnimationFrame(handle: number): void;
53
+ }
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "jest-environment-webgl-node",
3
+ "version": "0.1.0",
4
+ "description": "Jest environment that provides real headless WebGL 1/2 (ANGLE via @onirenaud/node-webgl) in Node",
5
+ "keywords": [
6
+ "headless",
7
+ "jest",
8
+ "jest-environment",
9
+ "testing",
10
+ "three.js",
11
+ "webgl",
12
+ "webgl2"
13
+ ],
14
+ "homepage": "https://github.com/bhouston/jest-gpu#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/bhouston/jest-gpu/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Ben Houston <ben@ben3d.ca> (https://ben3d.ca)",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/bhouston/jest-gpu.git",
23
+ "directory": "packages/jest-environment-webgl-node"
24
+ },
25
+ "files": [
26
+ "globals.d.ts",
27
+ "dist/**/*.cjs",
28
+ "dist/**/*.cjs.map",
29
+ "dist/**/*.mjs",
30
+ "dist/**/*.mjs.map",
31
+ "dist/**/*.d.cts",
32
+ "dist/**/*.d.cts.map",
33
+ "dist/**/*.d.mts",
34
+ "dist/**/*.d.mts.map",
35
+ "CHANGELOG.md"
36
+ ],
37
+ "type": "module",
38
+ "main": "./dist/index.cjs",
39
+ "module": "./dist/index.mjs",
40
+ "types": "./dist/index.d.mts",
41
+ "exports": {
42
+ ".": {
43
+ "import": {
44
+ "types": "./dist/index.d.mts",
45
+ "default": "./dist/index.mjs"
46
+ },
47
+ "require": {
48
+ "types": "./dist/index.d.cts",
49
+ "default": "./dist/index.cjs"
50
+ }
51
+ },
52
+ "./globals": {
53
+ "types": "./globals.d.ts"
54
+ }
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "scripts": {
60
+ "build": "tsdown",
61
+ "tsc": "tsc --noEmit"
62
+ },
63
+ "dependencies": {
64
+ "@onirenaud/node-webgl": "^0.2.0"
65
+ },
66
+ "peerDependencies": {
67
+ "jest": ">=29 <31",
68
+ "jest-environment-node": ">=29 <31"
69
+ },
70
+ "engines": {
71
+ "node": ">=22.12.0"
72
+ }
73
+ }