@holoscript/cdn 1.0.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.
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Sprint 44 — @holoscript/holoscript-cdn acceptance tests
3
+ * Covers: defaultCDNConfig, detectOptimalTarget, checkXRSupport,
4
+ * HoloSceneRenderer constructor/render, HoloCDNConfig shape,
5
+ * type exports (HoloSceneTarget, HoloSceneFallback, HoloSceneLoadingState)
6
+ *
7
+ * Environment: jsdom (browser APIs available)
8
+ * NOTE: HoloSceneElement.ts is an empty stub; HoloSceneElement/registerHoloScene
9
+ * are not tested here.
10
+ */
11
+ import { describe, it, expect } from 'vitest';
12
+ import { defaultCDNConfig, HoloSceneRenderer } from '../index';
13
+ import { detectOptimalTarget, checkXRSupport, type HoloCDNConfig } from '../config';
14
+
15
+ // ═══════════════════════════════════════════════
16
+ // defaultCDNConfig
17
+ // ═══════════════════════════════════════════════
18
+ describe('defaultCDNConfig', () => {
19
+ it('is defined', () => {
20
+ expect(defaultCDNConfig).toBeDefined();
21
+ });
22
+
23
+ it('cdnBase is a valid URL string', () => {
24
+ expect(typeof defaultCDNConfig.cdnBase).toBe('string');
25
+ expect(defaultCDNConfig.cdnBase).toContain('holoscript');
26
+ });
27
+
28
+ it('defaultTarget is a string', () => {
29
+ expect(typeof defaultCDNConfig.defaultTarget).toBe('string');
30
+ expect(defaultCDNConfig.defaultTarget.length).toBeGreaterThan(0);
31
+ });
32
+
33
+ it('debug defaults to false', () => {
34
+ expect(defaultCDNConfig.debug).toBe(false);
35
+ });
36
+
37
+ it('loadTimeoutMs is a positive number', () => {
38
+ expect(typeof defaultCDNConfig.loadTimeoutMs).toBe('number');
39
+ expect(defaultCDNConfig.loadTimeoutMs).toBeGreaterThan(0);
40
+ });
41
+
42
+ it('has all HoloCDNConfig required fields', () => {
43
+ const config: HoloCDNConfig = defaultCDNConfig;
44
+ expect(config).toHaveProperty('cdnBase');
45
+ expect(config).toHaveProperty('defaultTarget');
46
+ expect(config).toHaveProperty('debug');
47
+ expect(config).toHaveProperty('loadTimeoutMs');
48
+ });
49
+ });
50
+
51
+ // ═══════════════════════════════════════════════
52
+ // detectOptimalTarget
53
+ // ═══════════════════════════════════════════════
54
+ describe('detectOptimalTarget', () => {
55
+ it('is a function', () => {
56
+ expect(typeof detectOptimalTarget).toBe('function');
57
+ });
58
+
59
+ it('returns a string', () => {
60
+ const target = detectOptimalTarget();
61
+ expect(typeof target).toBe('string');
62
+ expect(target.length).toBeGreaterThan(0);
63
+ });
64
+
65
+ it('returns a valid HoloSceneTarget value', () => {
66
+ const valid = [
67
+ 'webxr',
68
+ 'threejs',
69
+ 'babylon',
70
+ 'unity',
71
+ 'godot',
72
+ 'visionos',
73
+ 'android-xr',
74
+ 'auto',
75
+ 'webgpu',
76
+ ];
77
+ const target = detectOptimalTarget();
78
+ expect(valid).toContain(target);
79
+ });
80
+
81
+ it('returns "threejs" in jsdom (no WebXR or WebGPU)', () => {
82
+ // jsdom has no navigator.xr or navigator.gpu
83
+ const target = detectOptimalTarget();
84
+ expect(target).toBe('threejs');
85
+ });
86
+ });
87
+
88
+ // ═══════════════════════════════════════════════
89
+ // checkXRSupport
90
+ // ═══════════════════════════════════════════════
91
+ describe('checkXRSupport', () => {
92
+ it('is a function', () => {
93
+ expect(typeof checkXRSupport).toBe('function');
94
+ });
95
+
96
+ it('returns a Promise', () => {
97
+ const result = checkXRSupport('immersive-vr');
98
+ expect(result).toBeInstanceOf(Promise);
99
+ });
100
+
101
+ it('resolves to false in jsdom (no XR)', async () => {
102
+ const result = await checkXRSupport('immersive-vr');
103
+ expect(result).toBe(false);
104
+ });
105
+
106
+ it('resolves to false for immersive-ar in jsdom', async () => {
107
+ const result = await checkXRSupport('immersive-ar');
108
+ expect(result).toBe(false);
109
+ });
110
+ });
111
+
112
+ // ═══════════════════════════════════════════════
113
+ // HoloSceneRenderer
114
+ // ═══════════════════════════════════════════════
115
+ describe('HoloSceneRenderer', () => {
116
+ it('constructs with a DOM container', () => {
117
+ const container = document.createElement('div');
118
+ const renderer = new HoloSceneRenderer(container);
119
+ expect(renderer).toBeDefined();
120
+ });
121
+
122
+ it('render() returns a Promise', () => {
123
+ const container = document.createElement('div');
124
+ const renderer = new HoloSceneRenderer(container);
125
+ const result = renderer.render('cube { @color(red) }', {
126
+ target: 'threejs',
127
+ width: 640,
128
+ height: 480,
129
+ enableVR: false,
130
+ enableAR: false,
131
+ });
132
+ expect(result).toBeInstanceOf(Promise);
133
+ return result; // let it resolve
134
+ });
135
+
136
+ it('render() resolves without throwing for threejs target', async () => {
137
+ const container = document.createElement('div');
138
+ document.body.appendChild(container);
139
+ const renderer = new HoloSceneRenderer(container);
140
+ await expect(
141
+ renderer.render('cube { @color(red) }', {
142
+ target: 'threejs',
143
+ width: 320,
144
+ height: 240,
145
+ enableVR: false,
146
+ enableAR: false,
147
+ })
148
+ ).resolves.not.toThrow();
149
+ document.body.removeChild(container);
150
+ });
151
+
152
+ it('render() resolves for "auto" target', async () => {
153
+ const container = document.createElement('div');
154
+ const renderer = new HoloSceneRenderer(container);
155
+ await expect(
156
+ renderer.render('sphere { @color(blue) }', {
157
+ target: 'auto',
158
+ width: 100,
159
+ height: 100,
160
+ enableVR: false,
161
+ enableAR: false,
162
+ })
163
+ ).resolves.not.toThrow();
164
+ });
165
+
166
+ it('render() resolves for static target (unity)', async () => {
167
+ const container = document.createElement('div');
168
+ const renderer = new HoloSceneRenderer(container);
169
+ await expect(
170
+ renderer.render('cube {}', {
171
+ target: 'unity',
172
+ width: 100,
173
+ height: 100,
174
+ enableVR: false,
175
+ enableAR: false,
176
+ })
177
+ ).resolves.not.toThrow();
178
+ });
179
+
180
+ it('creates two renderers independently', () => {
181
+ const c1 = document.createElement('div');
182
+ const c2 = document.createElement('div');
183
+ const r1 = new HoloSceneRenderer(c1);
184
+ const r2 = new HoloSceneRenderer(c2);
185
+ expect(r1).toBeDefined();
186
+ expect(r2).toBeDefined();
187
+ });
188
+ });
package/src/config.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * CDN configuration and defaults
3
+ */
4
+
5
+ export interface HoloCDNConfig {
6
+ cdnBase: string;
7
+ defaultTarget: string;
8
+ debug: boolean;
9
+ loadTimeoutMs: number;
10
+ }
11
+
12
+ export const defaultCDNConfig: HoloCDNConfig = {
13
+ cdnBase: 'https://cdn.holoscript.net',
14
+ defaultTarget: 'threejs',
15
+ debug: false,
16
+ loadTimeoutMs: 10000,
17
+ };
18
+
19
+ export function detectOptimalTarget(): string {
20
+ if (typeof navigator === 'undefined') return 'threejs';
21
+
22
+ if ('xr' in navigator) {
23
+ const nav = navigator as any;
24
+ if (nav.xr?.isSessionSupported) {
25
+ return 'webxr';
26
+ }
27
+ }
28
+
29
+ if ('gpu' in navigator) {
30
+ return 'webgpu';
31
+ }
32
+
33
+ return 'threejs';
34
+ }
35
+
36
+ export async function checkXRSupport(mode: 'immersive-vr' | 'immersive-ar'): Promise<boolean> {
37
+ if (typeof navigator === 'undefined' || !('xr' in navigator)) return false;
38
+ try {
39
+ const nav = navigator as any;
40
+ return await nav.xr.isSessionSupported(mode);
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @holoscript/cdn — Browser CDN distribution
3
+ *
4
+ * Exposes <holo-scene> custom element for embedding HoloScript scenes
5
+ * in any webpage via a CDN script tag.
6
+ *
7
+ * Usage:
8
+ * <script src="https://cdn.holoscript.net/v4.min.js"></script>
9
+ * <holo-scene src="./my-world.hs" target="webxr" fallback="threejs"></holo-scene>
10
+ *
11
+ * @version 1.0.0
12
+ */
13
+
14
+ export { HoloSceneElement, registerHoloScene } from './HoloSceneElement.js';
15
+ export { HoloSceneRenderer } from './HoloSceneRenderer.js';
16
+ export { HoloCDNConfig, defaultCDNConfig } from './config.js';
17
+ export type { HoloSceneAttributes, HoloSceneTarget, HoloSceneFallback } from './types.js';
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * HoloScene custom element attribute types
3
+ */
4
+
5
+ export type HoloSceneTarget =
6
+ | 'webxr'
7
+ | 'threejs'
8
+ | 'babylon'
9
+ | 'unity'
10
+ | 'godot'
11
+ | 'visionos'
12
+ | 'android-xr'
13
+ | 'auto';
14
+
15
+ export type HoloSceneFallback = 'threejs' | 'canvas2d' | 'static-image' | 'none';
16
+
17
+ export type HoloSceneLoadingState = 'idle' | 'loading' | 'compiling' | 'rendering' | 'error';
18
+
19
+ export interface HoloSceneAttributes {
20
+ src?: string;
21
+ inline?: string;
22
+ target?: HoloSceneTarget;
23
+ fallback?: HoloSceneFallback;
24
+ width?: string | number;
25
+ height?: string | number;
26
+ autoplay?: boolean;
27
+ vr?: boolean;
28
+ ar?: boolean;
29
+ loading?: boolean;
30
+ class?: string;
31
+ }
32
+
33
+ export interface HoloSceneLoadEvent {
34
+ type: 'holo-load';
35
+ source: string;
36
+ target: HoloSceneTarget;
37
+ compileTimeMs: number;
38
+ }
39
+
40
+ export interface HoloSceneErrorEvent {
41
+ type: 'holo-error';
42
+ message: string;
43
+ source?: string;
44
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM"],
7
+ "types": ["node"],
8
+ "outDir": "./dist",
9
+ "rootDir": "./src",
10
+ "strict": true,
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true,
17
+ "resolveJsonModule": true
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import("tsup").Options | import("tsup").Options[] | ((overrideOptions: import("tsup").Options) => import("tsup").Options | import("tsup").Options[] | Promise<import("tsup").Options | import("tsup").Options[]>);
2
+ export default _default;
package/tsup.config.js ADDED
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'tsup';
2
+ export default defineConfig({
3
+ entry: ['src/index.ts'],
4
+ format: ['cjs', 'esm'],
5
+ dts: true,
6
+ clean: true,
7
+ sourcemap: true,
8
+ outDir: 'dist',
9
+ outExtension({ format }) {
10
+ return { js: format === 'esm' ? '.cdn.mjs' : '.cdn.js' };
11
+ },
12
+ });
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ outDir: 'dist',
10
+ outExtension({ format }) {
11
+ return { js: format === 'esm' ? '.cdn.mjs' : '.cdn.js' };
12
+ },
13
+ });
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ name: 'holoscript-cdn',
6
+ environment: 'jsdom',
7
+ globals: true,
8
+ },
9
+ });