jest-environment-webgl-node 0.1.0 → 0.1.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/CHANGELOG.md +5 -0
- package/README.md +19 -4
- package/dist/index.cjs +184 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +162 -30
- package/dist/index.mjs.map +1 -1
- package/globals.d.ts +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
## [jest-environment-webgl-node-v0.1.1](https://github.com/bhouston/jest-gpu/compare/jest-environment-webgl-node-v0.1.0...jest-environment-webgl-node-v0.1.1) (2026-09-21)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **webgl:** isolate each Jest environment ([42e711e](https://github.com/bhouston/jest-gpu/commit/42e711e1cf45da303a7a0b13a38d7d48681f31d2)), closes [#19](https://github.com/bhouston/jest-gpu/issues/19)
|
package/README.md
CHANGED
|
@@ -42,10 +42,19 @@ it('renders', () => {
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
`testEnvironmentOptions` are those of node-webgl's `init()` (`backend`, `api`) and `installDOM()`
|
|
45
|
-
(`baseDir`, `fetch`, `devicePixelRatio`, `innerWidth`, `innerHeight`, `frameInterval`).
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
(`baseDir`, `fetch`, `devicePixelRatio`, `innerWidth`, `innerHeight`, `frameInterval`). Each test
|
|
46
|
+
file receives a fresh document, DOM event state, viewport values, file-fetch base directory and
|
|
47
|
+
animation-frame queue. Animation frames use Jest's sandbox timers, so `jest.useFakeTimers()` and
|
|
48
|
+
`jest.advanceTimersByTime()` control them; pending frames are cancelled during environment
|
|
49
|
+
teardown.
|
|
50
|
+
|
|
51
|
+
File fetching is enabled by default. Relative paths resolve from that test file's `baseDir`
|
|
52
|
+
(default: `process.cwd()`), while HTTP, data and blob URLs use Node's native `fetch`. Set
|
|
53
|
+
`fetch: false` to use native fetch for every URL. The environment never replaces host globals.
|
|
54
|
+
|
|
55
|
+
The native WebGL display itself is process-wide. Set `backend` and `api` consistently across all
|
|
56
|
+
projects that can share a Jest worker; the environment reports a conflict if two explicit option
|
|
57
|
+
sets request different native initialization in one worker.
|
|
49
58
|
|
|
50
59
|
Types for the installed globals: add `import 'jest-environment-webgl-node/globals';` to a `.d.ts`
|
|
51
60
|
file your tsconfig includes.
|
|
@@ -57,6 +66,12 @@ a test file itself imports an ESM-only library.
|
|
|
57
66
|
On Linux CI install Mesa (`apt-get install libegl1 libgles2 libgl1-mesa-dri`) and set
|
|
58
67
|
`LIBGL_ALWAYS_SOFTWARE=1`. macOS and Windows use prebuilt binaries.
|
|
59
68
|
|
|
69
|
+
## Using Vitest?
|
|
70
|
+
|
|
71
|
+
The equivalent package for Vitest is
|
|
72
|
+
[`vitest-environment-webgl-node`](https://github.com/bhouston/vitest-gpu/tree/main/packages/vitest-environment-webgl-node)
|
|
73
|
+
from [vitest-gpu](https://github.com/bhouston/vitest-gpu).
|
|
74
|
+
|
|
60
75
|
## Author
|
|
61
76
|
|
|
62
77
|
Created by [Ben Houston](https://ben3d.ca) and sponsored by [Land of Assets](https://landofassets.com).
|
package/dist/index.cjs
CHANGED
|
@@ -2,48 +2,203 @@ Object.defineProperties(exports, {
|
|
|
2
2
|
__esModule: { value: true },
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __create = Object.create;
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
14
|
+
key = keys[i];
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: ((k) => from[k]).bind(null, key),
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
//#endregion
|
|
27
|
+
let node_fs_promises = require("node:fs/promises");
|
|
28
|
+
let node_path = require("node:path");
|
|
29
|
+
let node_url = require("node:url");
|
|
5
30
|
let _onirenaud_node_webgl = require("@onirenaud/node-webgl");
|
|
31
|
+
_onirenaud_node_webgl = __toESM(_onirenaud_node_webgl, 1);
|
|
6
32
|
let jest_environment_node = require("jest-environment-node");
|
|
7
33
|
//#region src/index.ts
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
const WEBGL_OPTION_NAMES = /* @__PURE__ */ new Set([
|
|
35
|
+
"api",
|
|
36
|
+
"backend",
|
|
37
|
+
"baseDir",
|
|
38
|
+
"devicePixelRatio",
|
|
39
|
+
"fetch",
|
|
40
|
+
"frameInterval",
|
|
41
|
+
"innerHeight",
|
|
42
|
+
"innerWidth"
|
|
43
|
+
]);
|
|
44
|
+
const MIME = {
|
|
45
|
+
".basis": "application/octet-stream",
|
|
46
|
+
".bin": "application/octet-stream",
|
|
47
|
+
".drc": "application/octet-stream",
|
|
48
|
+
".exr": "image/x-exr",
|
|
49
|
+
".fbx": "application/octet-stream",
|
|
50
|
+
".gif": "image/gif",
|
|
51
|
+
".glb": "model/gltf-binary",
|
|
52
|
+
".gltf": "model/gltf+json",
|
|
53
|
+
".hdr": "image/vnd.radiance",
|
|
54
|
+
".jpeg": "image/jpeg",
|
|
55
|
+
".jpg": "image/jpeg",
|
|
56
|
+
".js": "text/javascript",
|
|
57
|
+
".json": "application/json",
|
|
58
|
+
".ktx": "image/ktx",
|
|
59
|
+
".ktx2": "image/ktx2",
|
|
60
|
+
".mjs": "text/javascript",
|
|
61
|
+
".mtl": "text/plain",
|
|
62
|
+
".obj": "text/plain",
|
|
63
|
+
".png": "image/png",
|
|
64
|
+
".svg": "image/svg+xml",
|
|
65
|
+
".txt": "text/plain",
|
|
66
|
+
".wasm": "application/wasm",
|
|
67
|
+
".webp": "image/webp"
|
|
68
|
+
};
|
|
69
|
+
const DOM_NAMES = [
|
|
70
|
+
"window",
|
|
71
|
+
"self",
|
|
72
|
+
"HTMLCanvasElement",
|
|
73
|
+
"OffscreenCanvas",
|
|
74
|
+
"HTMLImageElement",
|
|
75
|
+
"Image",
|
|
76
|
+
"ImageData",
|
|
77
|
+
"ImageBitmap",
|
|
78
|
+
"createImageBitmap",
|
|
79
|
+
"FileReader",
|
|
80
|
+
"HTMLElement",
|
|
81
|
+
"Element",
|
|
82
|
+
"Node",
|
|
83
|
+
"devicePixelRatio",
|
|
84
|
+
"innerWidth",
|
|
85
|
+
"innerHeight",
|
|
86
|
+
"screen",
|
|
87
|
+
"requestAnimationFrame",
|
|
88
|
+
"cancelAnimationFrame",
|
|
89
|
+
"getComputedStyle",
|
|
90
|
+
"matchMedia",
|
|
91
|
+
"scrollTo",
|
|
92
|
+
"alert",
|
|
93
|
+
"location",
|
|
94
|
+
"document",
|
|
95
|
+
...Object.keys(_onirenaud_node_webgl).filter((name) => name.startsWith("WebGL"))
|
|
96
|
+
];
|
|
97
|
+
let initializedWith;
|
|
98
|
+
function initialize(options) {
|
|
99
|
+
if (!options.api && !options.backend) return;
|
|
100
|
+
if (initializedWith && (options.api !== initializedWith.api || options.backend !== initializedWith.backend)) throw new Error(`node-webgl is already initialized with ${JSON.stringify(initializedWith)}; cannot switch this Jest worker to ${JSON.stringify(options)}`);
|
|
101
|
+
_onirenaud_node_webgl.init(options);
|
|
102
|
+
initializedWith = options;
|
|
103
|
+
}
|
|
104
|
+
/** Capture a fresh node-webgl DOM while restoring every host descriptor after installation. */
|
|
105
|
+
function createDOM(options) {
|
|
106
|
+
const before = Object.getOwnPropertyDescriptors(globalThis);
|
|
107
|
+
for (const name of DOM_NAMES) Reflect.deleteProperty(globalThis, name);
|
|
108
|
+
try {
|
|
109
|
+
_onirenaud_node_webgl.installDOM({
|
|
110
|
+
...options,
|
|
111
|
+
fetch: false
|
|
112
|
+
});
|
|
113
|
+
const after = Object.getOwnPropertyDescriptors(globalThis);
|
|
114
|
+
return new Map(DOM_NAMES.flatMap((name) => after[name] ? [[name, after[name]]] : []));
|
|
115
|
+
} finally {
|
|
116
|
+
for (const name of Object.getOwnPropertyNames(globalThis)) if (!(name in before)) Reflect.deleteProperty(globalThis, name);
|
|
117
|
+
Object.defineProperties(globalThis, before);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function environmentConfig(config) {
|
|
121
|
+
return {
|
|
122
|
+
...config,
|
|
123
|
+
projectConfig: {
|
|
124
|
+
...config.projectConfig,
|
|
125
|
+
testEnvironmentOptions: Object.fromEntries(Object.entries(config.projectConfig.testEnvironmentOptions).filter(([name]) => !WEBGL_OPTION_NAMES.has(name)))
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function localFetch(baseDir, nativeFetch) {
|
|
130
|
+
return async (input, init) => {
|
|
131
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
132
|
+
if (/^(https?|data|blob):/i.test(url)) return nativeFetch(input, init);
|
|
133
|
+
const path = url.startsWith("file:") ? (0, node_url.fileURLToPath)(url) : (0, node_path.resolve)(baseDir, url.replace(/[?#].*$/, ""));
|
|
134
|
+
try {
|
|
135
|
+
const bytes = await (0, node_fs_promises.readFile)(path);
|
|
136
|
+
return new Response(bytes, {
|
|
137
|
+
status: 200,
|
|
138
|
+
headers: { "content-type": MIME[(0, node_path.extname)(path).toLowerCase()] ?? "application/octet-stream" }
|
|
139
|
+
});
|
|
140
|
+
} catch {
|
|
141
|
+
return new Response(null, {
|
|
142
|
+
status: 404,
|
|
143
|
+
statusText: "Not Found"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
20
147
|
}
|
|
21
148
|
var WebglEnvironment = class extends jest_environment_node.TestEnvironment {
|
|
22
|
-
added;
|
|
149
|
+
added = [];
|
|
150
|
+
animationFrames = /* @__PURE__ */ new Map();
|
|
23
151
|
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
152
|
const options = config.projectConfig.testEnvironmentOptions;
|
|
33
|
-
|
|
153
|
+
super(environmentConfig(config), context);
|
|
154
|
+
const { api, backend, ...domOptions } = options;
|
|
155
|
+
initialize({
|
|
156
|
+
api,
|
|
157
|
+
backend
|
|
158
|
+
});
|
|
34
159
|
const g = this.global;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
160
|
+
if (!Object.getOwnPropertyNames(g).includes("_native")) {
|
|
161
|
+
Object.defineProperty(g, "_native", {
|
|
162
|
+
value: void 0,
|
|
163
|
+
writable: true,
|
|
164
|
+
configurable: true,
|
|
165
|
+
enumerable: false
|
|
166
|
+
});
|
|
167
|
+
this.added.push("_native");
|
|
168
|
+
}
|
|
169
|
+
for (const [name, descriptor] of createDOM(domOptions)) {
|
|
170
|
+
if (!(name in g)) this.added.push(name);
|
|
171
|
+
Object.defineProperty(g, name, descriptor);
|
|
172
|
+
}
|
|
38
173
|
g.window = g;
|
|
39
174
|
g.self = g;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
175
|
+
g.document.defaultView = g;
|
|
176
|
+
const events = new EventTarget();
|
|
177
|
+
g.addEventListener = events.addEventListener.bind(events);
|
|
178
|
+
g.removeEventListener = events.removeEventListener.bind(events);
|
|
179
|
+
g.dispatchEvent = events.dispatchEvent.bind(events);
|
|
180
|
+
this.added.push("addEventListener", "removeEventListener", "dispatchEvent");
|
|
181
|
+
const interval = domOptions.frameInterval ?? 16;
|
|
182
|
+
g.requestAnimationFrame = (callback) => {
|
|
183
|
+
const id = g.setTimeout(() => {
|
|
184
|
+
this.animationFrames.delete(id);
|
|
185
|
+
callback(g.performance.now());
|
|
186
|
+
}, interval);
|
|
187
|
+
this.animationFrames.set(id, g.clearTimeout);
|
|
188
|
+
return id;
|
|
189
|
+
};
|
|
190
|
+
g.cancelAnimationFrame = (id) => {
|
|
191
|
+
const clear = this.animationFrames.get(id);
|
|
192
|
+
this.animationFrames.delete(id);
|
|
193
|
+
(clear ?? g.clearTimeout)(id);
|
|
194
|
+
};
|
|
195
|
+
const nativeFetch = globalThis.fetch.bind(globalThis);
|
|
196
|
+
g.fetch = domOptions.fetch === false ? nativeFetch : localFetch(domOptions.baseDir ?? process.cwd(), nativeFetch);
|
|
44
197
|
}
|
|
45
198
|
async teardown() {
|
|
46
199
|
const g = this.global;
|
|
200
|
+
for (const [id, clear] of this.animationFrames) clear(id);
|
|
201
|
+
this.animationFrames.clear();
|
|
47
202
|
for (const key of this.added) delete g[key];
|
|
48
203
|
await super.teardown();
|
|
49
204
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["nodeWebGL","fileURLToPath","resolvePath","readFile","extname","TestEnvironment"],"sources":["../src/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { extname, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as nodeWebGL from '@onirenaud/node-webgl';\nimport type { InitOptions, 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\ntype JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];\ntype EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];\ntype Sandbox = Record<string, unknown>;\n\nconst WEBGL_OPTION_NAMES = new Set([\n 'api',\n 'backend',\n 'baseDir',\n 'devicePixelRatio',\n 'fetch',\n 'frameInterval',\n 'innerHeight',\n 'innerWidth',\n]);\n\nconst MIME: Record<string, string> = {\n '.basis': 'application/octet-stream',\n '.bin': 'application/octet-stream',\n '.drc': 'application/octet-stream',\n '.exr': 'image/x-exr',\n '.fbx': 'application/octet-stream',\n '.gif': 'image/gif',\n '.glb': 'model/gltf-binary',\n '.gltf': 'model/gltf+json',\n '.hdr': 'image/vnd.radiance',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.ktx': 'image/ktx',\n '.ktx2': 'image/ktx2',\n '.mjs': 'text/javascript',\n '.mtl': 'text/plain',\n '.obj': 'text/plain',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain',\n '.wasm': 'application/wasm',\n '.webp': 'image/webp',\n};\n\nconst DOM_NAMES = [\n 'window',\n 'self',\n 'HTMLCanvasElement',\n 'OffscreenCanvas',\n 'HTMLImageElement',\n 'Image',\n 'ImageData',\n 'ImageBitmap',\n 'createImageBitmap',\n 'FileReader',\n 'HTMLElement',\n 'Element',\n 'Node',\n 'devicePixelRatio',\n 'innerWidth',\n 'innerHeight',\n 'screen',\n 'requestAnimationFrame',\n 'cancelAnimationFrame',\n 'getComputedStyle',\n 'matchMedia',\n 'scrollTo',\n 'alert',\n 'location',\n 'document',\n ...Object.keys(nodeWebGL).filter((name) => name.startsWith('WebGL')),\n] as const;\n\nlet initializedWith: InitOptions | undefined;\n\nfunction initialize(options: InitOptions): void {\n if (!options.api && !options.backend) return;\n if (initializedWith && (options.api !== initializedWith.api || options.backend !== initializedWith.backend)) {\n throw new Error(\n `node-webgl is already initialized with ${JSON.stringify(initializedWith)}; ` +\n `cannot switch this Jest worker to ${JSON.stringify(options)}`,\n );\n }\n nodeWebGL.init(options);\n initializedWith = options;\n}\n\n/** Capture a fresh node-webgl DOM while restoring every host descriptor after installation. */\nfunction createDOM(options: InstallDOMOptions): Map<string, PropertyDescriptor> {\n const before = Object.getOwnPropertyDescriptors(globalThis);\n for (const name of DOM_NAMES) Reflect.deleteProperty(globalThis, name);\n\n try {\n nodeWebGL.installDOM({ ...options, fetch: false });\n const after = Object.getOwnPropertyDescriptors(globalThis);\n return new Map(\n DOM_NAMES.flatMap((name): [string, PropertyDescriptor][] => (after[name] ? [[name, after[name]]] : [])),\n );\n } finally {\n for (const name of Object.getOwnPropertyNames(globalThis)) {\n if (!(name in before)) Reflect.deleteProperty(globalThis, name);\n }\n Object.defineProperties(globalThis, before);\n }\n}\n\nfunction environmentConfig(config: JestEnvironmentConfig): JestEnvironmentConfig {\n return {\n ...config,\n projectConfig: {\n ...config.projectConfig,\n testEnvironmentOptions: Object.fromEntries(\n Object.entries(config.projectConfig.testEnvironmentOptions).filter(([name]) => !WEBGL_OPTION_NAMES.has(name)),\n ),\n },\n };\n}\n\nfunction localFetch(baseDir: string, nativeFetch: typeof fetch): typeof fetch {\n return async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n if (/^(https?|data|blob):/i.test(url)) return nativeFetch(input, init);\n const path = url.startsWith('file:') ? fileURLToPath(url) : resolvePath(baseDir, url.replace(/[?#].*$/, ''));\n try {\n const bytes = await readFile(path);\n return new Response(bytes, {\n status: 200,\n headers: { 'content-type': MIME[extname(path).toLowerCase()] ?? 'application/octet-stream' },\n });\n } catch {\n return new Response(null, { status: 404, statusText: 'Not Found' });\n }\n };\n}\n\nexport default class WebglEnvironment extends TestEnvironment {\n private readonly added: string[] = [];\n private readonly animationFrames = new Map<ReturnType<typeof setTimeout>, typeof clearTimeout>();\n\n constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {\n const options = config.projectConfig.testEnvironmentOptions as WebglNodeOptions;\n super(environmentConfig(config), context);\n\n const { api, backend, ...domOptions } = options;\n initialize({ api, backend });\n\n const g = this.global as unknown as Sandbox;\n const nativeAdded = !Object.getOwnPropertyNames(g).includes('_native');\n if (nativeAdded) {\n Object.defineProperty(g, '_native', {\n value: undefined,\n writable: true,\n configurable: true,\n enumerable: false,\n });\n this.added.push('_native');\n }\n\n for (const [name, descriptor] of createDOM(domOptions)) {\n if (!(name in g)) this.added.push(name);\n Object.defineProperty(g, name, descriptor);\n }\n\n g.window = g;\n g.self = g;\n (g.document as Record<string, unknown>).defaultView = g;\n\n const events = new EventTarget();\n g.addEventListener = events.addEventListener.bind(events);\n g.removeEventListener = events.removeEventListener.bind(events);\n g.dispatchEvent = events.dispatchEvent.bind(events);\n this.added.push('addEventListener', 'removeEventListener', 'dispatchEvent');\n\n const interval = domOptions.frameInterval ?? 16;\n g.requestAnimationFrame = (callback: (time: number) => void) => {\n const id = (g.setTimeout as typeof setTimeout)(() => {\n this.animationFrames.delete(id);\n callback((g.performance as { now(): number }).now());\n }, interval);\n this.animationFrames.set(id, g.clearTimeout as typeof clearTimeout);\n return id;\n };\n g.cancelAnimationFrame = (id: ReturnType<typeof setTimeout>) => {\n const clear = this.animationFrames.get(id);\n this.animationFrames.delete(id);\n (clear ?? (g.clearTimeout as typeof clearTimeout))(id);\n };\n\n const nativeFetch = globalThis.fetch.bind(globalThis);\n g.fetch = domOptions.fetch === false ? nativeFetch : localFetch(domOptions.baseDir ?? process.cwd(), nativeFetch);\n }\n\n override async teardown(): Promise<void> {\n const g = this.global as unknown as Sandbox;\n for (const [id, clear] of this.animationFrames) clear(id);\n this.animationFrames.clear();\n for (const key of this.added) delete g[key];\n await super.teardown();\n }\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -7,6 +7,7 @@ type JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];
|
|
|
7
7
|
type EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];
|
|
8
8
|
export default class WebglEnvironment extends TestEnvironment {
|
|
9
9
|
private readonly added;
|
|
10
|
+
private readonly animationFrames;
|
|
10
11
|
constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
|
|
11
12
|
teardown(): Promise<void>;
|
|
12
13
|
}
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAQY,mBAAmB,cAAc;KAExC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;qBAmIlC,yBAAyB;mBAC3B;mBACA;EAEjB,YAAY,QAAQ,uBAAuB,SAAS;EAqDrC,YAAY"}
|
package/dist/index.d.mts
CHANGED
|
@@ -7,6 +7,7 @@ type JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];
|
|
|
7
7
|
type EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];
|
|
8
8
|
export default class WebglEnvironment extends TestEnvironment {
|
|
9
9
|
private readonly added;
|
|
10
|
+
private readonly animationFrames;
|
|
10
11
|
constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
|
|
11
12
|
teardown(): Promise<void>;
|
|
12
13
|
}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAQY,mBAAmB,cAAc;KAExC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;qBAmIlC,yBAAyB;mBAC3B;mBACA;EAEjB,YAAY,QAAQ,uBAAuB,SAAS;EAqDrC,YAAY"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,45 +1,177 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { extname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import * as nodeWebGL from "@onirenaud/node-webgl";
|
|
2
5
|
import { TestEnvironment } from "jest-environment-node";
|
|
3
6
|
//#region src/index.ts
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
const WEBGL_OPTION_NAMES = /* @__PURE__ */ new Set([
|
|
8
|
+
"api",
|
|
9
|
+
"backend",
|
|
10
|
+
"baseDir",
|
|
11
|
+
"devicePixelRatio",
|
|
12
|
+
"fetch",
|
|
13
|
+
"frameInterval",
|
|
14
|
+
"innerHeight",
|
|
15
|
+
"innerWidth"
|
|
16
|
+
]);
|
|
17
|
+
const MIME = {
|
|
18
|
+
".basis": "application/octet-stream",
|
|
19
|
+
".bin": "application/octet-stream",
|
|
20
|
+
".drc": "application/octet-stream",
|
|
21
|
+
".exr": "image/x-exr",
|
|
22
|
+
".fbx": "application/octet-stream",
|
|
23
|
+
".gif": "image/gif",
|
|
24
|
+
".glb": "model/gltf-binary",
|
|
25
|
+
".gltf": "model/gltf+json",
|
|
26
|
+
".hdr": "image/vnd.radiance",
|
|
27
|
+
".jpeg": "image/jpeg",
|
|
28
|
+
".jpg": "image/jpeg",
|
|
29
|
+
".js": "text/javascript",
|
|
30
|
+
".json": "application/json",
|
|
31
|
+
".ktx": "image/ktx",
|
|
32
|
+
".ktx2": "image/ktx2",
|
|
33
|
+
".mjs": "text/javascript",
|
|
34
|
+
".mtl": "text/plain",
|
|
35
|
+
".obj": "text/plain",
|
|
36
|
+
".png": "image/png",
|
|
37
|
+
".svg": "image/svg+xml",
|
|
38
|
+
".txt": "text/plain",
|
|
39
|
+
".wasm": "application/wasm",
|
|
40
|
+
".webp": "image/webp"
|
|
41
|
+
};
|
|
42
|
+
const DOM_NAMES = [
|
|
43
|
+
"window",
|
|
44
|
+
"self",
|
|
45
|
+
"HTMLCanvasElement",
|
|
46
|
+
"OffscreenCanvas",
|
|
47
|
+
"HTMLImageElement",
|
|
48
|
+
"Image",
|
|
49
|
+
"ImageData",
|
|
50
|
+
"ImageBitmap",
|
|
51
|
+
"createImageBitmap",
|
|
52
|
+
"FileReader",
|
|
53
|
+
"HTMLElement",
|
|
54
|
+
"Element",
|
|
55
|
+
"Node",
|
|
56
|
+
"devicePixelRatio",
|
|
57
|
+
"innerWidth",
|
|
58
|
+
"innerHeight",
|
|
59
|
+
"screen",
|
|
60
|
+
"requestAnimationFrame",
|
|
61
|
+
"cancelAnimationFrame",
|
|
62
|
+
"getComputedStyle",
|
|
63
|
+
"matchMedia",
|
|
64
|
+
"scrollTo",
|
|
65
|
+
"alert",
|
|
66
|
+
"location",
|
|
67
|
+
"document",
|
|
68
|
+
...Object.keys(nodeWebGL).filter((name) => name.startsWith("WebGL"))
|
|
69
|
+
];
|
|
70
|
+
let initializedWith;
|
|
71
|
+
function initialize(options) {
|
|
72
|
+
if (!options.api && !options.backend) return;
|
|
73
|
+
if (initializedWith && (options.api !== initializedWith.api || options.backend !== initializedWith.backend)) throw new Error(`node-webgl is already initialized with ${JSON.stringify(initializedWith)}; cannot switch this Jest worker to ${JSON.stringify(options)}`);
|
|
74
|
+
nodeWebGL.init(options);
|
|
75
|
+
initializedWith = options;
|
|
76
|
+
}
|
|
77
|
+
/** Capture a fresh node-webgl DOM while restoring every host descriptor after installation. */
|
|
78
|
+
function createDOM(options) {
|
|
79
|
+
const before = Object.getOwnPropertyDescriptors(globalThis);
|
|
80
|
+
for (const name of DOM_NAMES) Reflect.deleteProperty(globalThis, name);
|
|
81
|
+
try {
|
|
82
|
+
nodeWebGL.installDOM({
|
|
83
|
+
...options,
|
|
84
|
+
fetch: false
|
|
85
|
+
});
|
|
86
|
+
const after = Object.getOwnPropertyDescriptors(globalThis);
|
|
87
|
+
return new Map(DOM_NAMES.flatMap((name) => after[name] ? [[name, after[name]]] : []));
|
|
88
|
+
} finally {
|
|
89
|
+
for (const name of Object.getOwnPropertyNames(globalThis)) if (!(name in before)) Reflect.deleteProperty(globalThis, name);
|
|
90
|
+
Object.defineProperties(globalThis, before);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function environmentConfig(config) {
|
|
94
|
+
return {
|
|
95
|
+
...config,
|
|
96
|
+
projectConfig: {
|
|
97
|
+
...config.projectConfig,
|
|
98
|
+
testEnvironmentOptions: Object.fromEntries(Object.entries(config.projectConfig.testEnvironmentOptions).filter(([name]) => !WEBGL_OPTION_NAMES.has(name)))
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function localFetch(baseDir, nativeFetch) {
|
|
103
|
+
return async (input, init) => {
|
|
104
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
105
|
+
if (/^(https?|data|blob):/i.test(url)) return nativeFetch(input, init);
|
|
106
|
+
const path = url.startsWith("file:") ? fileURLToPath(url) : resolve(baseDir, url.replace(/[?#].*$/, ""));
|
|
107
|
+
try {
|
|
108
|
+
const bytes = await readFile(path);
|
|
109
|
+
return new Response(bytes, {
|
|
110
|
+
status: 200,
|
|
111
|
+
headers: { "content-type": MIME[extname(path).toLowerCase()] ?? "application/octet-stream" }
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
114
|
+
return new Response(null, {
|
|
115
|
+
status: 404,
|
|
116
|
+
statusText: "Not Found"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
16
120
|
}
|
|
17
121
|
var WebglEnvironment = class extends TestEnvironment {
|
|
18
|
-
added;
|
|
122
|
+
added = [];
|
|
123
|
+
animationFrames = /* @__PURE__ */ new Map();
|
|
19
124
|
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
125
|
const options = config.projectConfig.testEnvironmentOptions;
|
|
29
|
-
|
|
126
|
+
super(environmentConfig(config), context);
|
|
127
|
+
const { api, backend, ...domOptions } = options;
|
|
128
|
+
initialize({
|
|
129
|
+
api,
|
|
130
|
+
backend
|
|
131
|
+
});
|
|
30
132
|
const g = this.global;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
133
|
+
if (!Object.getOwnPropertyNames(g).includes("_native")) {
|
|
134
|
+
Object.defineProperty(g, "_native", {
|
|
135
|
+
value: void 0,
|
|
136
|
+
writable: true,
|
|
137
|
+
configurable: true,
|
|
138
|
+
enumerable: false
|
|
139
|
+
});
|
|
140
|
+
this.added.push("_native");
|
|
141
|
+
}
|
|
142
|
+
for (const [name, descriptor] of createDOM(domOptions)) {
|
|
143
|
+
if (!(name in g)) this.added.push(name);
|
|
144
|
+
Object.defineProperty(g, name, descriptor);
|
|
145
|
+
}
|
|
34
146
|
g.window = g;
|
|
35
147
|
g.self = g;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
148
|
+
g.document.defaultView = g;
|
|
149
|
+
const events = new EventTarget();
|
|
150
|
+
g.addEventListener = events.addEventListener.bind(events);
|
|
151
|
+
g.removeEventListener = events.removeEventListener.bind(events);
|
|
152
|
+
g.dispatchEvent = events.dispatchEvent.bind(events);
|
|
153
|
+
this.added.push("addEventListener", "removeEventListener", "dispatchEvent");
|
|
154
|
+
const interval = domOptions.frameInterval ?? 16;
|
|
155
|
+
g.requestAnimationFrame = (callback) => {
|
|
156
|
+
const id = g.setTimeout(() => {
|
|
157
|
+
this.animationFrames.delete(id);
|
|
158
|
+
callback(g.performance.now());
|
|
159
|
+
}, interval);
|
|
160
|
+
this.animationFrames.set(id, g.clearTimeout);
|
|
161
|
+
return id;
|
|
162
|
+
};
|
|
163
|
+
g.cancelAnimationFrame = (id) => {
|
|
164
|
+
const clear = this.animationFrames.get(id);
|
|
165
|
+
this.animationFrames.delete(id);
|
|
166
|
+
(clear ?? g.clearTimeout)(id);
|
|
167
|
+
};
|
|
168
|
+
const nativeFetch = globalThis.fetch.bind(globalThis);
|
|
169
|
+
g.fetch = domOptions.fetch === false ? nativeFetch : localFetch(domOptions.baseDir ?? process.cwd(), nativeFetch);
|
|
40
170
|
}
|
|
41
171
|
async teardown() {
|
|
42
172
|
const g = this.global;
|
|
173
|
+
for (const [id, clear] of this.animationFrames) clear(id);
|
|
174
|
+
this.animationFrames.clear();
|
|
43
175
|
for (const key of this.added) delete g[key];
|
|
44
176
|
await super.teardown();
|
|
45
177
|
}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["resolvePath"],"sources":["../src/index.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { extname, resolve as resolvePath } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport * as nodeWebGL from '@onirenaud/node-webgl';\nimport type { InitOptions, 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\ntype JestEnvironmentConfig = ConstructorParameters<typeof TestEnvironment>[0];\ntype EnvironmentContext = ConstructorParameters<typeof TestEnvironment>[1];\ntype Sandbox = Record<string, unknown>;\n\nconst WEBGL_OPTION_NAMES = new Set([\n 'api',\n 'backend',\n 'baseDir',\n 'devicePixelRatio',\n 'fetch',\n 'frameInterval',\n 'innerHeight',\n 'innerWidth',\n]);\n\nconst MIME: Record<string, string> = {\n '.basis': 'application/octet-stream',\n '.bin': 'application/octet-stream',\n '.drc': 'application/octet-stream',\n '.exr': 'image/x-exr',\n '.fbx': 'application/octet-stream',\n '.gif': 'image/gif',\n '.glb': 'model/gltf-binary',\n '.gltf': 'model/gltf+json',\n '.hdr': 'image/vnd.radiance',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.ktx': 'image/ktx',\n '.ktx2': 'image/ktx2',\n '.mjs': 'text/javascript',\n '.mtl': 'text/plain',\n '.obj': 'text/plain',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.txt': 'text/plain',\n '.wasm': 'application/wasm',\n '.webp': 'image/webp',\n};\n\nconst DOM_NAMES = [\n 'window',\n 'self',\n 'HTMLCanvasElement',\n 'OffscreenCanvas',\n 'HTMLImageElement',\n 'Image',\n 'ImageData',\n 'ImageBitmap',\n 'createImageBitmap',\n 'FileReader',\n 'HTMLElement',\n 'Element',\n 'Node',\n 'devicePixelRatio',\n 'innerWidth',\n 'innerHeight',\n 'screen',\n 'requestAnimationFrame',\n 'cancelAnimationFrame',\n 'getComputedStyle',\n 'matchMedia',\n 'scrollTo',\n 'alert',\n 'location',\n 'document',\n ...Object.keys(nodeWebGL).filter((name) => name.startsWith('WebGL')),\n] as const;\n\nlet initializedWith: InitOptions | undefined;\n\nfunction initialize(options: InitOptions): void {\n if (!options.api && !options.backend) return;\n if (initializedWith && (options.api !== initializedWith.api || options.backend !== initializedWith.backend)) {\n throw new Error(\n `node-webgl is already initialized with ${JSON.stringify(initializedWith)}; ` +\n `cannot switch this Jest worker to ${JSON.stringify(options)}`,\n );\n }\n nodeWebGL.init(options);\n initializedWith = options;\n}\n\n/** Capture a fresh node-webgl DOM while restoring every host descriptor after installation. */\nfunction createDOM(options: InstallDOMOptions): Map<string, PropertyDescriptor> {\n const before = Object.getOwnPropertyDescriptors(globalThis);\n for (const name of DOM_NAMES) Reflect.deleteProperty(globalThis, name);\n\n try {\n nodeWebGL.installDOM({ ...options, fetch: false });\n const after = Object.getOwnPropertyDescriptors(globalThis);\n return new Map(\n DOM_NAMES.flatMap((name): [string, PropertyDescriptor][] => (after[name] ? [[name, after[name]]] : [])),\n );\n } finally {\n for (const name of Object.getOwnPropertyNames(globalThis)) {\n if (!(name in before)) Reflect.deleteProperty(globalThis, name);\n }\n Object.defineProperties(globalThis, before);\n }\n}\n\nfunction environmentConfig(config: JestEnvironmentConfig): JestEnvironmentConfig {\n return {\n ...config,\n projectConfig: {\n ...config.projectConfig,\n testEnvironmentOptions: Object.fromEntries(\n Object.entries(config.projectConfig.testEnvironmentOptions).filter(([name]) => !WEBGL_OPTION_NAMES.has(name)),\n ),\n },\n };\n}\n\nfunction localFetch(baseDir: string, nativeFetch: typeof fetch): typeof fetch {\n return async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n if (/^(https?|data|blob):/i.test(url)) return nativeFetch(input, init);\n const path = url.startsWith('file:') ? fileURLToPath(url) : resolvePath(baseDir, url.replace(/[?#].*$/, ''));\n try {\n const bytes = await readFile(path);\n return new Response(bytes, {\n status: 200,\n headers: { 'content-type': MIME[extname(path).toLowerCase()] ?? 'application/octet-stream' },\n });\n } catch {\n return new Response(null, { status: 404, statusText: 'Not Found' });\n }\n };\n}\n\nexport default class WebglEnvironment extends TestEnvironment {\n private readonly added: string[] = [];\n private readonly animationFrames = new Map<ReturnType<typeof setTimeout>, typeof clearTimeout>();\n\n constructor(config: JestEnvironmentConfig, context: EnvironmentContext) {\n const options = config.projectConfig.testEnvironmentOptions as WebglNodeOptions;\n super(environmentConfig(config), context);\n\n const { api, backend, ...domOptions } = options;\n initialize({ api, backend });\n\n const g = this.global as unknown as Sandbox;\n const nativeAdded = !Object.getOwnPropertyNames(g).includes('_native');\n if (nativeAdded) {\n Object.defineProperty(g, '_native', {\n value: undefined,\n writable: true,\n configurable: true,\n enumerable: false,\n });\n this.added.push('_native');\n }\n\n for (const [name, descriptor] of createDOM(domOptions)) {\n if (!(name in g)) this.added.push(name);\n Object.defineProperty(g, name, descriptor);\n }\n\n g.window = g;\n g.self = g;\n (g.document as Record<string, unknown>).defaultView = g;\n\n const events = new EventTarget();\n g.addEventListener = events.addEventListener.bind(events);\n g.removeEventListener = events.removeEventListener.bind(events);\n g.dispatchEvent = events.dispatchEvent.bind(events);\n this.added.push('addEventListener', 'removeEventListener', 'dispatchEvent');\n\n const interval = domOptions.frameInterval ?? 16;\n g.requestAnimationFrame = (callback: (time: number) => void) => {\n const id = (g.setTimeout as typeof setTimeout)(() => {\n this.animationFrames.delete(id);\n callback((g.performance as { now(): number }).now());\n }, interval);\n this.animationFrames.set(id, g.clearTimeout as typeof clearTimeout);\n return id;\n };\n g.cancelAnimationFrame = (id: ReturnType<typeof setTimeout>) => {\n const clear = this.animationFrames.get(id);\n this.animationFrames.delete(id);\n (clear ?? (g.clearTimeout as typeof clearTimeout))(id);\n };\n\n const nativeFetch = globalThis.fetch.bind(globalThis);\n g.fetch = domOptions.fetch === false ? nativeFetch : localFetch(domOptions.baseDir ?? process.cwd(), nativeFetch);\n }\n\n override async teardown(): Promise<void> {\n const g = this.global as unknown as Sandbox;\n for (const [id, clear] of this.animationFrames) clear(id);\n this.animationFrames.clear();\n for (const key of this.added) delete g[key];\n await super.teardown();\n }\n}\n"],"mappings":";;;;;;AAcA,MAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,OAA+B;CACnC,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,OAAO;CACP,SAAS;CACT,QAAQ;CACR,SAAS;CACT,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,SAAS;CACT,SAAS;AACX;AAEA,MAAM,YAAY;CAChB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,QAAQ,SAAS,KAAK,WAAW,OAAO,CAAC;AACrE;AAEA,IAAI;AAEJ,SAAS,WAAW,SAA4B;CAC9C,IAAI,CAAC,QAAQ,OAAO,CAAC,QAAQ,SAAS;CACtC,IAAI,oBAAoB,QAAQ,QAAQ,gBAAgB,OAAO,QAAQ,YAAY,gBAAgB,UACjG,MAAM,IAAI,MACR,0CAA0C,KAAK,UAAU,eAAe,EAAE,sCACnC,KAAK,UAAU,OAAO,GAC/D;CAEF,UAAU,KAAK,OAAO;CACtB,kBAAkB;AACpB;;AAGA,SAAS,UAAU,SAA6D;CAC9E,MAAM,SAAS,OAAO,0BAA0B,UAAU;CAC1D,KAAK,MAAM,QAAQ,WAAW,QAAQ,eAAe,YAAY,IAAI;CAErE,IAAI;EACF,UAAU,WAAW;GAAE,GAAG;GAAS,OAAO;EAAM,CAAC;EACjD,MAAM,QAAQ,OAAO,0BAA0B,UAAU;EACzD,OAAO,IAAI,IACT,UAAU,SAAS,SAA0C,MAAM,QAAQ,CAAC,CAAC,MAAM,MAAM,KAAK,CAAC,IAAI,CAAC,CAAE,CACxG;CACF,UAAU;EACR,KAAK,MAAM,QAAQ,OAAO,oBAAoB,UAAU,GACtD,IAAI,EAAE,QAAQ,SAAS,QAAQ,eAAe,YAAY,IAAI;EAEhE,OAAO,iBAAiB,YAAY,MAAM;CAC5C;AACF;AAEA,SAAS,kBAAkB,QAAsD;CAC/E,OAAO;EACL,GAAG;EACH,eAAe;GACb,GAAG,OAAO;GACV,wBAAwB,OAAO,YAC7B,OAAO,QAAQ,OAAO,cAAc,sBAAsB,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,mBAAmB,IAAI,IAAI,CAAC,CAC9G;EACF;CACF;AACF;AAEA,SAAS,WAAW,SAAiB,aAAyC;CAC5E,OAAO,OAAO,OAA+B,SAA0C;EACrF,MAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,iBAAiB,MAAM,MAAM,OAAO,MAAM;EAC1F,IAAI,wBAAwB,KAAK,GAAG,GAAG,OAAO,YAAY,OAAO,IAAI;EACrE,MAAM,OAAO,IAAI,WAAW,OAAO,IAAI,cAAc,GAAG,IAAIA,QAAY,SAAS,IAAI,QAAQ,WAAW,EAAE,CAAC;EAC3G,IAAI;GACF,MAAM,QAAQ,MAAM,SAAS,IAAI;GACjC,OAAO,IAAI,SAAS,OAAO;IACzB,QAAQ;IACR,SAAS,EAAE,gBAAgB,KAAK,QAAQ,IAAI,CAAC,CAAC,YAAY,MAAM,2BAA2B;GAC7F,CAAC;EACH,QAAQ;GACN,OAAO,IAAI,SAAS,MAAM;IAAE,QAAQ;IAAK,YAAY;GAAY,CAAC;EACpE;CACF;AACF;AAEA,IAAqB,mBAArB,cAA8C,gBAAgB;CAC5D,QAAmC,CAAC;CACpC,kCAAmC,IAAI,IAAwD;CAE/F,YAAY,QAA+B,SAA6B;EACtE,MAAM,UAAU,OAAO,cAAc;EACrC,MAAM,kBAAkB,MAAM,GAAG,OAAO;EAExC,MAAM,EAAE,KAAK,SAAS,GAAG,eAAe;EACxC,WAAW;GAAE;GAAK;EAAQ,CAAC;EAE3B,MAAM,IAAI,KAAK;EAEf,IAAI,CADiB,OAAO,oBAAoB,CAAC,CAAC,CAAC,SAAS,SAAS,GACpD;GACf,OAAO,eAAe,GAAG,WAAW;IAClC,OAAO,KAAA;IACP,UAAU;IACV,cAAc;IACd,YAAY;GACd,CAAC;GACD,KAAK,MAAM,KAAK,SAAS;EAC3B;EAEA,KAAK,MAAM,CAAC,MAAM,eAAe,UAAU,UAAU,GAAG;GACtD,IAAI,EAAE,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI;GACtC,OAAO,eAAe,GAAG,MAAM,UAAU;EAC3C;EAEA,EAAE,SAAS;EACX,EAAE,OAAO;EACT,EAAG,SAAqC,cAAc;EAEtD,MAAM,SAAS,IAAI,YAAY;EAC/B,EAAE,mBAAmB,OAAO,iBAAiB,KAAK,MAAM;EACxD,EAAE,sBAAsB,OAAO,oBAAoB,KAAK,MAAM;EAC9D,EAAE,gBAAgB,OAAO,cAAc,KAAK,MAAM;EAClD,KAAK,MAAM,KAAK,oBAAoB,uBAAuB,eAAe;EAE1E,MAAM,WAAW,WAAW,iBAAiB;EAC7C,EAAE,yBAAyB,aAAqC;GAC9D,MAAM,KAAM,EAAE,iBAAuC;IACnD,KAAK,gBAAgB,OAAO,EAAE;IAC9B,SAAU,EAAE,YAAkC,IAAI,CAAC;GACrD,GAAG,QAAQ;GACX,KAAK,gBAAgB,IAAI,IAAI,EAAE,YAAmC;GAClE,OAAO;EACT;EACA,EAAE,wBAAwB,OAAsC;GAC9D,MAAM,QAAQ,KAAK,gBAAgB,IAAI,EAAE;GACzC,KAAK,gBAAgB,OAAO,EAAE;GAC9B,CAAC,SAAU,EAAE,aAAA,CAAsC,EAAE;EACvD;EAEA,MAAM,cAAc,WAAW,MAAM,KAAK,UAAU;EACpD,EAAE,QAAQ,WAAW,UAAU,QAAQ,cAAc,WAAW,WAAW,WAAW,QAAQ,IAAI,GAAG,WAAW;CAClH;CAEA,MAAe,WAA0B;EACvC,MAAM,IAAI,KAAK;EACf,KAAK,MAAM,CAAC,IAAI,UAAU,KAAK,iBAAiB,MAAM,EAAE;EACxD,KAAK,gBAAgB,MAAM;EAC3B,KAAK,MAAM,OAAO,KAAK,OAAO,OAAO,EAAE;EACvC,MAAM,MAAM,SAAS;CACvB;AACF"}
|
package/globals.d.ts
CHANGED
|
@@ -48,6 +48,6 @@ declare global {
|
|
|
48
48
|
var createImageBitmap: typeof nodeCreateImageBitmap;
|
|
49
49
|
var WebGLRenderingContext: typeof NodeWebGLRenderingContext;
|
|
50
50
|
var WebGL2RenderingContext: typeof NodeWebGL2RenderingContext;
|
|
51
|
-
function requestAnimationFrame(callback: (time: number) => void):
|
|
52
|
-
function cancelAnimationFrame(handle:
|
|
51
|
+
function requestAnimationFrame(callback: (time: number) => void): ReturnType<typeof setTimeout>;
|
|
52
|
+
function cancelAnimationFrame(handle: ReturnType<typeof setTimeout>): void;
|
|
53
53
|
}
|