jest-environment-webgl-node 0.1.1 → 0.1.2
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 +4 -3
- package/dist/index.cjs +24 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +24 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
## [jest-environment-webgl-node-v0.1.
|
|
1
|
+
## [jest-environment-webgl-node-v0.1.2](https://github.com/bhouston/jest-gpu/compare/jest-environment-webgl-node-v0.1.1...jest-environment-webgl-node-v0.1.2) (2026-09-21)
|
|
2
2
|
|
|
3
3
|
### Bug Fixes
|
|
4
4
|
|
|
5
|
-
* **webgl:**
|
|
5
|
+
* **webgl:** detect backend conflicts against the initialized display ([e68573d](https://github.com/bhouston/jest-gpu/commit/e68573d1f5cd20e571d4e313ad244f6a8a41d513)), closes [#21](https://github.com/bhouston/jest-gpu/issues/21)
|
package/README.md
CHANGED
|
@@ -52,9 +52,10 @@ File fetching is enabled by default. Relative paths resolve from that test file'
|
|
|
52
52
|
(default: `process.cwd()`), while HTTP, data and blob URLs use Node's native `fetch`. Set
|
|
53
53
|
`fetch: false` to use native fetch for every URL. The environment never replaces host globals.
|
|
54
54
|
|
|
55
|
-
The native WebGL display itself is process-wide
|
|
56
|
-
projects that can share a Jest worker
|
|
57
|
-
|
|
55
|
+
The native WebGL display itself is process-wide and is created by the first context in a worker, so
|
|
56
|
+
set `backend` and `api` consistently across all projects that can share a Jest worker. The
|
|
57
|
+
environment throws if an explicit `backend` or `api` disagrees with the display already initialized
|
|
58
|
+
in that worker, including one initialized with platform defaults by a project that set no options.
|
|
58
59
|
|
|
59
60
|
Types for the installed globals: add `import 'jest-environment-webgl-node/globals';` to a `.d.ts`
|
|
60
61
|
file your tsconfig includes.
|
package/dist/index.cjs
CHANGED
|
@@ -95,11 +95,30 @@ const DOM_NAMES = [
|
|
|
95
95
|
...Object.keys(_onirenaud_node_webgl).filter((name) => name.startsWith("WebGL"))
|
|
96
96
|
];
|
|
97
97
|
let initializedWith;
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
/**
|
|
99
|
+
* The native EGL display is process-wide and node-webgl's `init()` is a no-op once it exists (the first
|
|
100
|
+
* context creates it lazily), so compare explicit options against the live display rather than only
|
|
101
|
+
* against what this module itself initialized.
|
|
102
|
+
*/
|
|
103
|
+
function initialize({ api, backend }) {
|
|
104
|
+
if (!api && !backend) return;
|
|
105
|
+
if (initializedWith && initializedWith.api === api && initializedWith.backend === backend) return;
|
|
106
|
+
const display = _onirenaud_node_webgl.getDisplayInfo();
|
|
107
|
+
if (display && (backend && backend !== "default" && backend !== display.backend || api && api !== "auto" && api !== display.api)) throw new Error(`node-webgl is already initialized with ${JSON.stringify({
|
|
108
|
+
api: display.api,
|
|
109
|
+
backend: display.backend
|
|
110
|
+
})}; cannot switch this Jest worker to ${JSON.stringify({
|
|
111
|
+
api,
|
|
112
|
+
backend
|
|
113
|
+
})}`);
|
|
114
|
+
_onirenaud_node_webgl.init({
|
|
115
|
+
api,
|
|
116
|
+
backend
|
|
117
|
+
});
|
|
118
|
+
initializedWith = {
|
|
119
|
+
api,
|
|
120
|
+
backend
|
|
121
|
+
};
|
|
103
122
|
}
|
|
104
123
|
/** Capture a fresh node-webgl DOM while restoring every host descriptor after installation. */
|
|
105
124
|
function createDOM(options) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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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"}
|
|
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\n/**\n * The native EGL display is process-wide and node-webgl's `init()` is a no-op once it exists (the first\n * context creates it lazily), so compare explicit options against the live display rather than only\n * against what this module itself initialized.\n */\nfunction initialize({ api, backend }: InitOptions): void {\n if (!api && !backend) return;\n if (initializedWith && initializedWith.api === api && initializedWith.backend === backend) return;\n const display = nodeWebGL.getDisplayInfo();\n const mismatch =\n display &&\n ((backend && backend !== 'default' && backend !== display.backend) ||\n (api && api !== 'auto' && api !== display.api));\n if (mismatch) {\n throw new Error(\n `node-webgl is already initialized with ${JSON.stringify({ api: display.api, backend: display.backend })}; ` +\n `cannot switch this Jest worker to ${JSON.stringify({ api, backend })}`,\n );\n }\n nodeWebGL.init({ api, backend });\n initializedWith = { api, backend };\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.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAQY,mBAAmB,cAAc;KAExC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAQY,mBAAmB,cAAc;KAExC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;qBA8IlC,yBAAyB;mBAC3B;mBACA;EAEjB,YAAY,QAAQ,uBAAuB,SAAS;EAqDrC,YAAY"}
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAQY,mBAAmB,cAAc;KAExC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;YAQY,mBAAmB,cAAc;KAExC,wBAAwB,6BAA6B;KACrD,qBAAqB,6BAA6B;qBA8IlC,yBAAyB;mBAC3B;mBACA;EAEjB,YAAY,QAAQ,uBAAuB,SAAS;EAqDrC,YAAY"}
|
package/dist/index.mjs
CHANGED
|
@@ -68,11 +68,30 @@ const DOM_NAMES = [
|
|
|
68
68
|
...Object.keys(nodeWebGL).filter((name) => name.startsWith("WebGL"))
|
|
69
69
|
];
|
|
70
70
|
let initializedWith;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
/**
|
|
72
|
+
* The native EGL display is process-wide and node-webgl's `init()` is a no-op once it exists (the first
|
|
73
|
+
* context creates it lazily), so compare explicit options against the live display rather than only
|
|
74
|
+
* against what this module itself initialized.
|
|
75
|
+
*/
|
|
76
|
+
function initialize({ api, backend }) {
|
|
77
|
+
if (!api && !backend) return;
|
|
78
|
+
if (initializedWith && initializedWith.api === api && initializedWith.backend === backend) return;
|
|
79
|
+
const display = nodeWebGL.getDisplayInfo();
|
|
80
|
+
if (display && (backend && backend !== "default" && backend !== display.backend || api && api !== "auto" && api !== display.api)) throw new Error(`node-webgl is already initialized with ${JSON.stringify({
|
|
81
|
+
api: display.api,
|
|
82
|
+
backend: display.backend
|
|
83
|
+
})}; cannot switch this Jest worker to ${JSON.stringify({
|
|
84
|
+
api,
|
|
85
|
+
backend
|
|
86
|
+
})}`);
|
|
87
|
+
nodeWebGL.init({
|
|
88
|
+
api,
|
|
89
|
+
backend
|
|
90
|
+
});
|
|
91
|
+
initializedWith = {
|
|
92
|
+
api,
|
|
93
|
+
backend
|
|
94
|
+
};
|
|
76
95
|
}
|
|
77
96
|
/** Capture a fresh node-webgl DOM while restoring every host descriptor after installation. */
|
|
78
97
|
function createDOM(options) {
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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":"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"}
|
|
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\n/**\n * The native EGL display is process-wide and node-webgl's `init()` is a no-op once it exists (the first\n * context creates it lazily), so compare explicit options against the live display rather than only\n * against what this module itself initialized.\n */\nfunction initialize({ api, backend }: InitOptions): void {\n if (!api && !backend) return;\n if (initializedWith && initializedWith.api === api && initializedWith.backend === backend) return;\n const display = nodeWebGL.getDisplayInfo();\n const mismatch =\n display &&\n ((backend && backend !== 'default' && backend !== display.backend) ||\n (api && api !== 'auto' && api !== display.api));\n if (mismatch) {\n throw new Error(\n `node-webgl is already initialized with ${JSON.stringify({ api: display.api, backend: display.backend })}; ` +\n `cannot switch this Jest worker to ${JSON.stringify({ api, backend })}`,\n );\n }\n nodeWebGL.init({ api, backend });\n initializedWith = { api, backend };\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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"}
|