@webspatial/core-sdk 1.6.1 → 2.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.
Files changed (320) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +10 -0
  3. package/dist/JSBCommand.d.ts +398 -0
  4. package/dist/JSBCommand.js +668 -0
  5. package/dist/JSBCommand.js.map +1 -0
  6. package/dist/Spatial.d.ts +63 -0
  7. package/dist/Spatial.js +60 -0
  8. package/dist/Spatial.js.map +1 -0
  9. package/dist/SpatialObject.d.ts +19 -0
  10. package/dist/SpatialObject.js +44 -0
  11. package/dist/SpatialObject.js.map +1 -0
  12. package/dist/SpatialScene.d.ts +51 -0
  13. package/dist/SpatialScene.js +74 -0
  14. package/dist/SpatialScene.js.map +1 -0
  15. package/dist/SpatialSession.d.ts +143 -0
  16. package/dist/SpatialSession.js +173 -0
  17. package/dist/SpatialSession.js.map +1 -0
  18. package/dist/SpatialWebEvent.d.ts +8 -0
  19. package/dist/SpatialWebEvent.js +18 -0
  20. package/dist/SpatialWebEvent.js.map +1 -0
  21. package/dist/SpatialWebEventCreator.d.ts +11 -0
  22. package/dist/SpatialWebEventCreator.js +11 -0
  23. package/dist/SpatialWebEventCreator.js.map +1 -0
  24. package/dist/Spatialized2DElement.d.ts +42 -0
  25. package/dist/Spatialized2DElement.js +47 -0
  26. package/dist/Spatialized2DElement.js.map +1 -0
  27. package/dist/SpatializedDynamic3DElement.d.ts +7 -0
  28. package/dist/SpatializedDynamic3DElement.js +43 -0
  29. package/dist/SpatializedDynamic3DElement.js.map +1 -0
  30. package/dist/SpatializedElement.d.ts +7 -0
  31. package/dist/SpatializedElement.js +206 -0
  32. package/dist/SpatializedElement.js.map +1 -0
  33. package/dist/SpatializedElementCreator.d.ts +15 -0
  34. package/dist/SpatializedElementCreator.js +47 -0
  35. package/dist/SpatializedElementCreator.js.map +1 -0
  36. package/dist/SpatializedStatic3DElement.d.ts +195 -0
  37. package/dist/SpatializedStatic3DElement.js +327 -0
  38. package/dist/SpatializedStatic3DElement.js.map +1 -0
  39. package/dist/WebMsgCommand.d.ts +7 -0
  40. package/dist/WebMsgCommand.js +23 -0
  41. package/dist/WebMsgCommand.js.map +1 -0
  42. package/dist/index.d.ts +35 -1101
  43. package/dist/index.js +21 -3393
  44. package/dist/index.js.map +1 -1
  45. package/dist/install-polyfills.d.ts +2 -0
  46. package/dist/install-polyfills.js +13 -0
  47. package/dist/install-polyfills.js.map +1 -0
  48. package/dist/isSSREnv.d.ts +3 -0
  49. package/dist/isSSREnv.js +6 -0
  50. package/dist/isSSREnv.js.map +1 -0
  51. package/dist/motion/AnimationObject.d.ts +7 -0
  52. package/dist/motion/AnimationObject.js +140 -0
  53. package/dist/motion/AnimationObject.js.map +1 -0
  54. package/dist/motion/compute/index.d.ts +10 -0
  55. package/dist/motion/compute/index.js +19 -0
  56. package/dist/motion/compute/index.js.map +1 -0
  57. package/dist/motion/compute/motionPropertyValues.d.ts +19 -0
  58. package/dist/motion/compute/motionPropertyValues.js +50 -0
  59. package/dist/motion/compute/motionPropertyValues.js.map +1 -0
  60. package/dist/motion/compute/normalize.d.ts +31 -0
  61. package/dist/motion/compute/normalize.js +201 -0
  62. package/dist/motion/compute/normalize.js.map +1 -0
  63. package/dist/motion/compute/validate.d.ts +22 -0
  64. package/dist/motion/compute/validate.js +103 -0
  65. package/dist/motion/compute/validate.js.map +1 -0
  66. package/dist/motion/index.d.ts +8 -0
  67. package/dist/motion/index.js +6 -0
  68. package/dist/motion/index.js.map +1 -0
  69. package/dist/motion/native/serializeMotionTimeline.d.ts +12 -0
  70. package/dist/motion/native/serializeMotionTimeline.js +21 -0
  71. package/dist/motion/native/serializeMotionTimeline.js.map +1 -0
  72. package/dist/physicalMetrics-W5uZ5xKb.d.ts +48 -0
  73. package/dist/physicalMetrics.d.ts +1 -0
  74. package/dist/physicalMetrics.js +58 -0
  75. package/dist/physicalMetrics.js.map +1 -0
  76. package/dist/platform-adapter/CommandResultUtils.d.ts +12 -0
  77. package/dist/platform-adapter/CommandResultUtils.js +21 -0
  78. package/dist/platform-adapter/CommandResultUtils.js.map +1 -0
  79. package/dist/platform-adapter/createPlatformSync.d.ts +16 -0
  80. package/dist/platform-adapter/createPlatformSync.js +30 -0
  81. package/dist/platform-adapter/createPlatformSync.js.map +1 -0
  82. package/dist/platform-adapter/index.d.ts +12 -0
  83. package/dist/platform-adapter/index.js +10 -0
  84. package/dist/platform-adapter/index.js.map +1 -0
  85. package/dist/platform-adapter/interface.d.ts +7 -0
  86. package/dist/platform-adapter/interface.js +1 -0
  87. package/dist/platform-adapter/interface.js.map +1 -0
  88. package/dist/platform-adapter/pico-os/PicoOSPlatform.d.ts +25 -0
  89. package/dist/platform-adapter/pico-os/PicoOSPlatform.js +168 -0
  90. package/dist/platform-adapter/pico-os/PicoOSPlatform.js.map +1 -0
  91. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.d.ts +66 -0
  92. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js +372 -0
  93. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js.map +1 -0
  94. package/dist/platform-adapter/spatialRequestMetadata.d.ts +6 -0
  95. package/dist/platform-adapter/spatialRequestMetadata.js +39 -0
  96. package/dist/platform-adapter/spatialRequestMetadata.js.map +1 -0
  97. package/dist/platform-adapter/spatialSceneQuery.d.ts +12 -0
  98. package/dist/platform-adapter/spatialSceneQuery.js +12 -0
  99. package/dist/platform-adapter/spatialSceneQuery.js.map +1 -0
  100. package/dist/platform-adapter/vision-os/VisionOSPlatform.d.ts +19 -0
  101. package/dist/platform-adapter/vision-os/VisionOSPlatform.js +77 -0
  102. package/dist/platform-adapter/vision-os/VisionOSPlatform.js.map +1 -0
  103. package/dist/platform-runtime.d.ts +12 -0
  104. package/dist/platform-runtime.js +14 -0
  105. package/dist/platform-runtime.js.map +1 -0
  106. package/dist/reality/Attachment.d.ts +19 -0
  107. package/dist/reality/Attachment.js +39 -0
  108. package/dist/reality/Attachment.js.map +1 -0
  109. package/dist/reality/component/ModelComponent.d.ts +14 -0
  110. package/dist/reality/component/ModelComponent.js +11 -0
  111. package/dist/reality/component/ModelComponent.js.map +1 -0
  112. package/dist/reality/component/SpatialComponent.d.ts +8 -0
  113. package/dist/reality/component/SpatialComponent.js +19 -0
  114. package/dist/reality/component/SpatialComponent.js.map +1 -0
  115. package/dist/reality/component/index.d.ts +8 -0
  116. package/dist/reality/component/index.js +3 -0
  117. package/dist/reality/component/index.js.map +1 -0
  118. package/dist/reality/entity/SpatialEntity.d.ts +7 -0
  119. package/dist/reality/entity/SpatialEntity.js +291 -0
  120. package/dist/reality/entity/SpatialEntity.js.map +1 -0
  121. package/dist/reality/entity/SpatialModelEntity.d.ts +17 -0
  122. package/dist/reality/entity/SpatialModelEntity.js +17 -0
  123. package/dist/reality/entity/SpatialModelEntity.js.map +1 -0
  124. package/dist/reality/entity/index.d.ts +8 -0
  125. package/dist/reality/entity/index.js +3 -0
  126. package/dist/reality/entity/index.js.map +1 -0
  127. package/dist/reality/geometry/SpatialBoxGeometry.d.ts +16 -0
  128. package/dist/reality/geometry/SpatialBoxGeometry.js +13 -0
  129. package/dist/reality/geometry/SpatialBoxGeometry.js.map +1 -0
  130. package/dist/reality/geometry/SpatialConeGeometry.d.ts +16 -0
  131. package/dist/reality/geometry/SpatialConeGeometry.js +13 -0
  132. package/dist/reality/geometry/SpatialConeGeometry.js.map +1 -0
  133. package/dist/reality/geometry/SpatialCylinderGeometry.d.ts +16 -0
  134. package/dist/reality/geometry/SpatialCylinderGeometry.js +13 -0
  135. package/dist/reality/geometry/SpatialCylinderGeometry.js.map +1 -0
  136. package/dist/reality/geometry/SpatialGeometry.d.ts +7 -0
  137. package/dist/reality/geometry/SpatialGeometry.js +13 -0
  138. package/dist/reality/geometry/SpatialGeometry.js.map +1 -0
  139. package/dist/reality/geometry/SpatialPlaneGeometry.d.ts +16 -0
  140. package/dist/reality/geometry/SpatialPlaneGeometry.js +13 -0
  141. package/dist/reality/geometry/SpatialPlaneGeometry.js.map +1 -0
  142. package/dist/reality/geometry/SpatialSphereGeometry.d.ts +16 -0
  143. package/dist/reality/geometry/SpatialSphereGeometry.js +13 -0
  144. package/dist/reality/geometry/SpatialSphereGeometry.js.map +1 -0
  145. package/dist/reality/geometry/index.d.ts +12 -0
  146. package/dist/reality/geometry/index.js +7 -0
  147. package/dist/reality/geometry/index.js.map +1 -0
  148. package/dist/reality/index.d.ts +18 -0
  149. package/dist/reality/index.js +7 -0
  150. package/dist/reality/index.js.map +1 -0
  151. package/dist/reality/material/SpatialMaterial.d.ts +7 -0
  152. package/dist/reality/material/SpatialMaterial.js +13 -0
  153. package/dist/reality/material/SpatialMaterial.js.map +1 -0
  154. package/dist/reality/material/SpatialUnlitMaterial.d.ts +16 -0
  155. package/dist/reality/material/SpatialUnlitMaterial.js +16 -0
  156. package/dist/reality/material/SpatialUnlitMaterial.js.map +1 -0
  157. package/dist/reality/material/index.d.ts +8 -0
  158. package/dist/reality/material/index.js +3 -0
  159. package/dist/reality/material/index.js.map +1 -0
  160. package/dist/reality/realityCreator.d.ts +22 -0
  161. package/dist/reality/realityCreator.js +89 -0
  162. package/dist/reality/realityCreator.js.map +1 -0
  163. package/dist/reality/resource/SpatialModelAsset.d.ts +15 -0
  164. package/dist/reality/resource/SpatialModelAsset.js +12 -0
  165. package/dist/reality/resource/SpatialModelAsset.js.map +1 -0
  166. package/dist/reality/resource/SpatialTextureResource.d.ts +16 -0
  167. package/dist/reality/resource/SpatialTextureResource.js +16 -0
  168. package/dist/reality/resource/SpatialTextureResource.js.map +1 -0
  169. package/dist/reality/resource/index.d.ts +9 -0
  170. package/dist/reality/resource/index.js +3 -0
  171. package/dist/reality/resource/index.js.map +1 -0
  172. package/dist/runtime/WebSpatialRuntimeError.d.ts +10 -0
  173. package/dist/runtime/WebSpatialRuntimeError.js +14 -0
  174. package/dist/runtime/WebSpatialRuntimeError.js.map +1 -0
  175. package/dist/runtime/capability-data.d.ts +12 -0
  176. package/dist/runtime/capability-data.js +134 -0
  177. package/dist/runtime/capability-data.js.map +1 -0
  178. package/dist/runtime/index.d.ts +8 -0
  179. package/dist/runtime/index.js +27 -0
  180. package/dist/runtime/index.js.map +1 -0
  181. package/dist/runtime/jsbAdapterPlatform.d.ts +9 -0
  182. package/dist/runtime/jsbAdapterPlatform.js +38 -0
  183. package/dist/runtime/jsbAdapterPlatform.js.map +1 -0
  184. package/dist/runtime/keys.d.ts +34 -0
  185. package/dist/runtime/keys.js +122 -0
  186. package/dist/runtime/keys.js.map +1 -0
  187. package/dist/runtime/semver.d.ts +8 -0
  188. package/dist/runtime/semver.js +32 -0
  189. package/dist/runtime/semver.js.map +1 -0
  190. package/dist/runtime/supports.d.ts +22 -0
  191. package/dist/runtime/supports.js +78 -0
  192. package/dist/runtime/supports.js.map +1 -0
  193. package/dist/runtime/types.d.ts +12 -0
  194. package/dist/runtime/types.js +1 -0
  195. package/dist/runtime/types.js.map +1 -0
  196. package/dist/runtime/userAgent.d.ts +20 -0
  197. package/dist/runtime/userAgent.js +41 -0
  198. package/dist/runtime/userAgent.js.map +1 -0
  199. package/dist/scene-polyfill.d.ts +18 -0
  200. package/dist/scene-polyfill.js +480 -0
  201. package/dist/scene-polyfill.js.map +1 -0
  202. package/dist/spatial-host.d.ts +13 -0
  203. package/dist/spatial-host.js +18 -0
  204. package/dist/spatial-host.js.map +1 -0
  205. package/dist/spatial-window-polyfill.d.ts +3 -0
  206. package/dist/spatial-window-polyfill.js +155 -0
  207. package/dist/spatial-window-polyfill.js.map +1 -0
  208. package/dist/spatializedElementMotion-_0XWPQCE.d.ts +1009 -0
  209. package/dist/types/animation.d.ts +7 -0
  210. package/dist/types/animation.js +1 -0
  211. package/dist/types/animation.js.map +1 -0
  212. package/dist/types/global.d.ts +38 -0
  213. package/dist/types/global.js +1 -0
  214. package/dist/types/global.js.map +1 -0
  215. package/dist/types/internal.d.ts +7 -0
  216. package/dist/types/internal.js +12 -0
  217. package/dist/types/internal.js.map +1 -0
  218. package/dist/types/motion/index.d.ts +7 -0
  219. package/dist/types/motion/index.js +3 -0
  220. package/dist/types/motion/index.js.map +1 -0
  221. package/dist/types/motion/spatializedElementMotion.d.ts +7 -0
  222. package/dist/types/motion/spatializedElementMotion.js +1 -0
  223. package/dist/types/motion/spatializedElementMotion.js.map +1 -0
  224. package/dist/types/motion/spatializedMotion.d.ts +7 -0
  225. package/dist/types/motion/spatializedMotion.js +16 -0
  226. package/dist/types/motion/spatializedMotion.js.map +1 -0
  227. package/dist/types/motion/spatializedPlayback.d.ts +11 -0
  228. package/dist/types/motion/spatializedPlayback.js +1 -0
  229. package/dist/types/motion/spatializedPlayback.js.map +1 -0
  230. package/dist/types/motion/spatializedVisual.d.ts +35 -0
  231. package/dist/types/motion/spatializedVisual.js +1 -0
  232. package/dist/types/motion/spatializedVisual.js.map +1 -0
  233. package/dist/types/types.d.ts +7 -0
  234. package/dist/types/types.js +117 -0
  235. package/dist/types/types.js.map +1 -0
  236. package/dist/utils.d.ts +44 -0
  237. package/dist/utils.js +86 -0
  238. package/dist/utils.js.map +1 -0
  239. package/package.json +20 -4
  240. package/src/JSBCommand.ts +148 -91
  241. package/src/Spatial.ts +0 -21
  242. package/src/SpatialObject.ts +10 -0
  243. package/src/SpatialScene.ts +1 -5
  244. package/src/SpatialSession.ts +17 -3
  245. package/src/Spatialized2DElement.ts +5 -0
  246. package/src/SpatializedDynamic3DElement.ts +5 -1
  247. package/src/SpatializedElement.ts +29 -0
  248. package/src/SpatializedElementCreator.ts +6 -4
  249. package/src/SpatializedStatic3DElement.test.ts +99 -0
  250. package/src/SpatializedStatic3DElement.ts +141 -4
  251. package/src/WebMsgCommand.ts +50 -0
  252. package/src/coverage-boost.test.ts +258 -316
  253. package/src/index.ts +28 -12
  254. package/src/install-polyfills.ts +38 -0
  255. package/src/jsbcommand.coverage.test.ts +42 -48
  256. package/src/motion/AnimationObject.test.ts +421 -0
  257. package/src/motion/AnimationObject.ts +208 -0
  258. package/src/motion/compute/index.ts +10 -0
  259. package/src/motion/compute/motionPropertyValues.test.ts +55 -0
  260. package/src/motion/compute/motionPropertyValues.ts +70 -0
  261. package/src/motion/compute/normalize.test.ts +99 -0
  262. package/src/motion/compute/normalize.ts +289 -0
  263. package/src/motion/compute/validate.test.ts +229 -0
  264. package/src/motion/compute/validate.ts +156 -0
  265. package/src/motion/index.ts +2 -0
  266. package/src/motion/native/serializeMotionTimeline.test.ts +61 -0
  267. package/src/motion/native/serializeMotionTimeline.ts +27 -0
  268. package/src/platform-adapter/CommandResultUtils.ts +2 -2
  269. package/src/platform-adapter/createPlatformSync.ts +36 -0
  270. package/src/platform-adapter/index.ts +5 -51
  271. package/src/platform-adapter/interface.ts +35 -23
  272. package/src/platform-adapter/pico-os/PicoOSPlatform.ts +156 -59
  273. package/src/platform-adapter/puppeteer/PuppeteerPlatform.ts +37 -11
  274. package/src/platform-adapter/spatialRequestMetadata.test.ts +175 -0
  275. package/src/platform-adapter/spatialRequestMetadata.ts +48 -0
  276. package/src/platform-adapter/spatialSceneQuery.ts +17 -0
  277. package/src/platform-adapter/vision-os/VisionOSPlatform.ts +60 -24
  278. package/src/platform-runtime.ts +13 -0
  279. package/src/reality/Attachment.ts +2 -2
  280. package/src/reality/entity/SpatialEntity.ts +127 -2
  281. package/src/reality/realityCreator.ts +15 -1
  282. package/src/reality/resource/SpatialTextureResource.ts +16 -0
  283. package/src/reality/resource/index.ts +1 -0
  284. package/src/runtime/WebSpatialRuntimeError.ts +16 -0
  285. package/src/runtime/capability-data.ts +213 -0
  286. package/src/runtime/contract-review.test.ts +44 -0
  287. package/src/runtime/index.ts +17 -0
  288. package/src/runtime/jsbAdapterPlatform.test.ts +36 -0
  289. package/src/runtime/jsbAdapterPlatform.ts +51 -0
  290. package/src/runtime/keys.ts +140 -0
  291. package/src/runtime/semver.ts +33 -0
  292. package/src/runtime/supports.test.ts +346 -0
  293. package/src/runtime/supports.ts +112 -0
  294. package/src/runtime/types.ts +11 -0
  295. package/src/runtime/useAnimation.capability.test.ts +275 -0
  296. package/src/runtime/userAgent.ts +64 -0
  297. package/src/scene-polyfill.manifest.test.ts +312 -9
  298. package/src/scene-polyfill.request-metadata.test.ts +39 -0
  299. package/src/scene-polyfill.test.ts +33 -94
  300. package/src/scene-polyfill.ts +125 -92
  301. package/src/spatial-host.ts +25 -0
  302. package/src/spatial-window-polyfill.ts +18 -2
  303. package/src/transform-roundtrip.test.ts +90 -0
  304. package/src/types/animation.ts +224 -0
  305. package/src/types/{global.d.ts → global.ts} +10 -12
  306. package/src/types/motion/index.ts +10 -0
  307. package/src/types/motion/spatializedElementMotion.ts +49 -0
  308. package/src/types/motion/spatializedMotion.ts +147 -0
  309. package/src/types/motion/spatializedPlayback.ts +17 -0
  310. package/src/types/motion/spatializedVisual.ts +24 -0
  311. package/src/types/types.ts +12 -0
  312. package/src/utils.ts +54 -0
  313. package/tsup.config.ts +61 -36
  314. package/vitest.config.ts +5 -1
  315. package/dist/iife/index.d.ts +0 -1101
  316. package/dist/iife/index.global.js +0 -75
  317. package/dist/iife/index.global.js.map +0 -1
  318. package/src/platform-adapter/android/AndroidPlatform.ts +0 -133
  319. package/src/platform-adapter/ssr/SSRPlatform.ts +0 -43
  320. /package/src/{ssr-polyfill.ts → isSSREnv.ts} +0 -0
@@ -0,0 +1,275 @@
1
+ import { describe, expect, test, vi, beforeEach } from 'vitest'
2
+
3
+ describe('supports("useAnimation")', () => {
4
+ beforeEach(() => {
5
+ vi.resetModules()
6
+ })
7
+
8
+ test('plain browser: supports("useAnimation") is false', async () => {
9
+ vi.stubGlobal('navigator', {
10
+ userAgent:
11
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
12
+ } as Navigator)
13
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
14
+ resetRuntimeCacheForTests()
15
+ expect(supports('useAnimation')).toBe(false)
16
+ })
17
+
18
+ test('visionOS WSAppShell/1.5.0: supports("useAnimation") is false', async () => {
19
+ vi.stubGlobal('navigator', {
20
+ userAgent:
21
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 WSAppShell/1.5.0 WebSpatial/1.5.0',
22
+ } as Navigator)
23
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
24
+ resetRuntimeCacheForTests()
25
+ expect(supports('useAnimation')).toBe(false)
26
+ })
27
+
28
+ test('visionOS WSAppShell/1.6.0: supports("useAnimation") is false', async () => {
29
+ vi.stubGlobal('navigator', {
30
+ userAgent:
31
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 (KHTML, like Gecko) WSAppShell/1.6.0 WebSpatial/1.5.0 Safari/537.36',
32
+ } as Navigator)
33
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
34
+ resetRuntimeCacheForTests()
35
+ expect(supports('useAnimation')).toBe(false)
36
+ })
37
+
38
+ test('visionOS WSAppShell/1.7.0: supports("useAnimation") is false', async () => {
39
+ vi.stubGlobal('navigator', {
40
+ userAgent:
41
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 (KHTML, like Gecko) WSAppShell/1.7.0 WebSpatial/1.5.0 Safari/537.36',
42
+ } as Navigator)
43
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
44
+ resetRuntimeCacheForTests()
45
+ expect(supports('useAnimation')).toBe(false)
46
+ })
47
+
48
+ test('visionOS WSAppShell/1.8.0: supports("useAnimation") is true', async () => {
49
+ vi.stubGlobal('navigator', {
50
+ userAgent:
51
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 (KHTML, like Gecko) WSAppShell/1.8.0 WebSpatial/1.5.0 Safari/537.36',
52
+ } as Navigator)
53
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
54
+ resetRuntimeCacheForTests()
55
+ expect(supports('useAnimation')).toBe(true)
56
+ })
57
+
58
+ test('Puppeteer UA: supports("useAnimation") is true (debug mode enables all)', async () => {
59
+ vi.stubGlobal('navigator', {
60
+ userAgent:
61
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Puppeteer WSAppShell/1.5.0 WebSpatial/1.5.0',
62
+ } as Navigator)
63
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
64
+ resetRuntimeCacheForTests()
65
+ expect(supports('useAnimation')).toBe(true)
66
+ })
67
+
68
+ test('visionOS WS_SHELL_VERSION placeholder: supports("useAnimation") is true (debug mode)', async () => {
69
+ vi.stubGlobal('navigator', {
70
+ userAgent:
71
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 WSAppShell/WS_SHELL_VERSION WebSpatial/1.5.0 Safari/537.36',
72
+ } as Navigator)
73
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
74
+ resetRuntimeCacheForTests()
75
+ expect(supports('useAnimation')).toBe(true)
76
+ })
77
+
78
+ test('picoOS PicoWebApp/0.2.2: supports("useAnimation") is false', async () => {
79
+ vi.stubGlobal('navigator', {
80
+ userAgent:
81
+ 'Mozilla/5.0 (X11; Linux x86_64; unknown OS0.11.0 like Quest) AppleWebKit/537.36 PicoWebApp/0.2.2 (like PicoBrowser) Chrome/138.0 WebSpatial/1.5.0',
82
+ } as Navigator)
83
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
84
+ resetRuntimeCacheForTests()
85
+ expect(supports('useAnimation')).toBe(false)
86
+ })
87
+
88
+ test('picoOS PicoWebApp/0.3.1: supports("useAnimation") is false', async () => {
89
+ vi.stubGlobal('navigator', {
90
+ userAgent:
91
+ 'Mozilla/5.0 (X11; Linux x86_64; unknown OS0.11.0 like Quest) AppleWebKit/537.36 PicoWebApp/0.3.1 (like PicoBrowser) Chrome/138.0 WebSpatial/1.5.0',
92
+ } as Navigator)
93
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
94
+ resetRuntimeCacheForTests()
95
+ expect(supports('useAnimation')).toBe(false)
96
+ })
97
+
98
+ test('picoOS PicoWebApp/0.4.90: supports("useAnimation") is true', async () => {
99
+ vi.stubGlobal('navigator', {
100
+ userAgent:
101
+ 'Mozilla/5.0 (X11; Linux x86_64; unknown OS0.11.0 like Quest) AppleWebKit/537.36 PicoWebApp/0.4.90 (like PicoBrowser) Chrome/138.0 WebSpatial/1.5.0',
102
+ } as Navigator)
103
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
104
+ resetRuntimeCacheForTests()
105
+ expect(supports('useAnimation')).toBe(true)
106
+ })
107
+ })
108
+
109
+ describe('supports("useAnimation", target tokens)', () => {
110
+ beforeEach(() => {
111
+ vi.resetModules()
112
+ })
113
+
114
+ test('plain browser: supports("useAnimation") and target tokens are false', async () => {
115
+ vi.stubGlobal('navigator', {
116
+ userAgent:
117
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/537.36 Chrome/120.0.0.0 Safari/537.36',
118
+ } as Navigator)
119
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
120
+ resetRuntimeCacheForTests()
121
+ expect(supports('useAnimation')).toBe(false)
122
+ expect(supports('useAnimation', ['entity'])).toBe(false)
123
+ expect(supports('useAnimation', ['element'])).toBe(false)
124
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
125
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
126
+ })
127
+
128
+ test('visionOS WSAppShell/1.5.0: supports("useAnimation") and target tokens are false', async () => {
129
+ vi.stubGlobal('navigator', {
130
+ userAgent:
131
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 WSAppShell/1.5.0 WebSpatial/1.5.0',
132
+ } as Navigator)
133
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
134
+ resetRuntimeCacheForTests()
135
+ expect(supports('useAnimation')).toBe(false)
136
+ expect(supports('useAnimation', ['entity'])).toBe(false)
137
+ expect(supports('useAnimation', ['element'])).toBe(false)
138
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
139
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
140
+ })
141
+
142
+ test('visionOS WSAppShell/1.6.0: supports("useAnimation") and target tokens are false', async () => {
143
+ vi.stubGlobal('navigator', {
144
+ userAgent:
145
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 (KHTML, like Gecko) WSAppShell/1.6.0 WebSpatial/1.5.0 Safari/537.36',
146
+ } as Navigator)
147
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
148
+ resetRuntimeCacheForTests()
149
+ expect(supports('useAnimation')).toBe(false)
150
+ expect(supports('useAnimation', ['entity'])).toBe(false)
151
+ expect(supports('useAnimation', ['element'])).toBe(false)
152
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
153
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
154
+ })
155
+
156
+ test('Puppeteer UA: supports("useAnimation") is true while all sub-tokens are false', async () => {
157
+ vi.stubGlobal('navigator', {
158
+ userAgent:
159
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Puppeteer WSAppShell/1.5.0 WebSpatial/1.5.0',
160
+ } as Navigator)
161
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
162
+ resetRuntimeCacheForTests()
163
+ expect(supports('useAnimation')).toBe(true)
164
+ expect(supports('useAnimation', ['entity'])).toBe(false)
165
+ expect(supports('useAnimation', ['element'])).toBe(false)
166
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
167
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
168
+ })
169
+
170
+ test('visionOS WS_SHELL_VERSION placeholder: supports("useAnimation") is true while all sub-tokens are false', async () => {
171
+ vi.stubGlobal('navigator', {
172
+ userAgent:
173
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 WSAppShell/WS_SHELL_VERSION WebSpatial/1.5.0 Safari/537.36',
174
+ } as Navigator)
175
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
176
+ resetRuntimeCacheForTests()
177
+ expect(supports('useAnimation')).toBe(true)
178
+ expect(supports('useAnimation', ['entity'])).toBe(false)
179
+ expect(supports('useAnimation', ['element'])).toBe(false)
180
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
181
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
182
+ })
183
+
184
+ test('picoOS PicoWebApp/0.1.1: supports("useAnimation") and target tokens are false', async () => {
185
+ vi.stubGlobal('navigator', {
186
+ userAgent:
187
+ 'Mozilla/5.0 (X11; Linux x86_64; unknown OS0.11.0 like Quest) AppleWebKit/537.36 PicoWebApp/0.1.1 (like PicoBrowser) Chrome/138.0 WebSpatial/1.5.0',
188
+ } as Navigator)
189
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
190
+ resetRuntimeCacheForTests()
191
+ expect(supports('useAnimation')).toBe(false)
192
+ expect(supports('useAnimation', ['entity'])).toBe(false)
193
+ expect(supports('useAnimation', ['element'])).toBe(false)
194
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
195
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
196
+ })
197
+
198
+ test('picoOS PicoWebApp/0.1.2: supports("useAnimation") and target tokens are false', async () => {
199
+ vi.stubGlobal('navigator', {
200
+ userAgent:
201
+ 'Mozilla/5.0 (X11; Linux x86_64; unknown OS0.11.0 like Quest) AppleWebKit/537.36 PicoWebApp/0.1.2 (like PicoBrowser) Chrome/138.0 WebSpatial/1.5.0',
202
+ } as Navigator)
203
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
204
+ resetRuntimeCacheForTests()
205
+ expect(supports('useAnimation')).toBe(false)
206
+ expect(supports('useAnimation', ['entity'])).toBe(false)
207
+ expect(supports('useAnimation', ['element'])).toBe(false)
208
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
209
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
210
+ })
211
+
212
+ test('picoOS PicoWebApp/0.2.2: supports("useAnimation") and all sub-tokens are false', async () => {
213
+ vi.stubGlobal('navigator', {
214
+ userAgent:
215
+ 'Mozilla/5.0 (X11; Linux x86_64; unknown OS0.11.0 like Quest) AppleWebKit/537.36 PicoWebApp/0.2.2 (like PicoBrowser) Chrome/138.0 WebSpatial/1.5.0',
216
+ } as Navigator)
217
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
218
+ resetRuntimeCacheForTests()
219
+ expect(supports('useAnimation')).toBe(false)
220
+ expect(supports('useAnimation', ['entity'])).toBe(false)
221
+ expect(supports('useAnimation', ['element'])).toBe(false)
222
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
223
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
224
+ })
225
+
226
+ test('visionOS WSAppShell/1.8.0: supports("useAnimation") is true while all sub-tokens are false', async () => {
227
+ vi.stubGlobal('navigator', {
228
+ userAgent:
229
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 (KHTML, like Gecko) WSAppShell/1.8.0 WebSpatial/1.5.0 Safari/537.36',
230
+ } as Navigator)
231
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
232
+ resetRuntimeCacheForTests()
233
+ expect(supports('useAnimation')).toBe(true)
234
+ expect(supports('useAnimation', ['entity'])).toBe(false)
235
+ expect(supports('useAnimation', ['element'])).toBe(false)
236
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
237
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
238
+ })
239
+
240
+ test('visionOS WSAppShell/1.7.0: supports("useAnimation") and all sub-tokens are false', async () => {
241
+ vi.stubGlobal('navigator', {
242
+ userAgent:
243
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7; wv) AppleWebKit/605.1.15 (KHTML, like Gecko) WSAppShell/1.7.0 WebSpatial/1.5.0 Safari/537.36',
244
+ } as Navigator)
245
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
246
+ resetRuntimeCacheForTests()
247
+ expect(supports('useAnimation')).toBe(false)
248
+ expect(supports('useAnimation', ['entity'])).toBe(false)
249
+ expect(supports('useAnimation', ['element'])).toBe(false)
250
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
251
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
252
+ })
253
+ })
254
+
255
+ describe('useAnimation sub-tokens', () => {
256
+ beforeEach(() => {
257
+ vi.resetModules()
258
+ })
259
+
260
+ test('all useAnimation sub-tokens return false', async () => {
261
+ vi.stubGlobal('navigator', {
262
+ userAgent:
263
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Puppeteer WSAppShell/1.5.0 WebSpatial/1.5.0',
264
+ } as Navigator)
265
+ const { supports, resetRuntimeCacheForTests } = await import('./supports')
266
+ resetRuntimeCacheForTests()
267
+ expect(supports('useAnimation')).toBe(true)
268
+ expect(supports('useAnimation', ['entity'])).toBe(false)
269
+ expect(supports('useAnimation', ['element'])).toBe(false)
270
+ expect(supports('useAnimation', ['static3d'])).toBe(false)
271
+ expect(supports('useAnimation', ['dynamic3d'])).toBe(false)
272
+ expect(supports('useAnimation', ['unknown' as any])).toBe(false)
273
+ expect(supports('useAnimation', ['opacity' as any])).toBe(false)
274
+ })
275
+ })
@@ -0,0 +1,64 @@
1
+ import type { WebSpatialRuntimeSnapshot } from './types'
2
+
3
+ /**
4
+ * Parse shell token from UA:
5
+ * - packaged/hybrid runtimes use `WSAppShell/<version>`
6
+ * - browser-mode runtime on Pico OS uses `PicoWebApp/<version>`
7
+ *
8
+ * Keep `WSAppShell` precedence when both appear in one UA.
9
+ */
10
+ export function parseShellToken(ua: string): {
11
+ version: string | null
12
+ source: 'wsapp' | 'picoapp' | null
13
+ } {
14
+ const ws = /\bWSAppShell\/([\w.-]+)/i.exec(ua)
15
+ if (ws?.[1]) return { version: ws[1], source: 'wsapp' }
16
+ const pico = /\bPicoWebApp\/([\w.-]+)/i.exec(ua)
17
+ if (pico?.[1]) return { version: pico[1], source: 'picoapp' }
18
+ return { version: null, source: null }
19
+ }
20
+
21
+ /** Pico runtime: require explicit Pico tokens (do not treat generic `like Quest` VR UAs as picoos). */
22
+ function inferPicoOs(ua: string): boolean {
23
+ return /\bPicoWebApp\//i.test(ua) || /\bPicoBrowser\b/i.test(ua)
24
+ }
25
+
26
+ /** visionOS-class WebView UAs include a Mac OS X platform token; `WSAppShell` alone is not enough. */
27
+ function inferVisionOsFromUa(ua: string): boolean {
28
+ return /Mac OS X/i.test(ua)
29
+ }
30
+
31
+ /**
32
+ * Resolve internal runtime snapshot from `navigator.userAgent`.
33
+ * SSR / no navigator → `{ type: null, shellVersion: null }` (no throw).
34
+ */
35
+ export function computeRuntimeFromUserAgent(
36
+ userAgent: string | undefined,
37
+ ): WebSpatialRuntimeSnapshot {
38
+ if (userAgent === undefined || userAgent === '') {
39
+ return { type: null, shellVersion: null }
40
+ }
41
+ // Must run before the shell-token gate: Puppeteer UAs may omit WSAppShell/PicoWebApp.
42
+ if (userAgent.includes('Puppeteer')) {
43
+ const { version } = parseShellToken(userAgent)
44
+ return { type: 'puppeteer', shellVersion: version }
45
+ }
46
+
47
+ const { version, source } = parseShellToken(userAgent)
48
+ if (!version) {
49
+ return { type: null, shellVersion: null }
50
+ }
51
+
52
+ if (source === 'picoapp' || inferPicoOs(userAgent)) {
53
+ return { type: 'picoos', shellVersion: version }
54
+ }
55
+
56
+ if (source === 'wsapp') {
57
+ if (inferVisionOsFromUa(userAgent)) {
58
+ return { type: 'visionos', shellVersion: version }
59
+ }
60
+ return { type: null, shellVersion: version }
61
+ }
62
+
63
+ return { type: null, shellVersion: version }
64
+ }
@@ -1,15 +1,35 @@
1
- import { describe, it, expect, vi } from 'vitest'
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ const jsbMocks = vi.hoisted(() => ({
4
+ createSpatialSceneCommand: vi.fn().mockImplementation(() => ({
5
+ executeSync: vi.fn().mockReturnValue({
6
+ data: { id: 'scene-1', windowProxy: {} },
7
+ }),
8
+ })),
9
+ focusSceneExecute: vi.fn().mockResolvedValue(undefined),
10
+ }))
11
+
12
+ const spatialHostMocks = vi.hoisted(() => ({
13
+ openSpatialSceneSync: vi.fn().mockReturnValue({
14
+ success: true,
15
+ data: { id: 'scene-1', windowProxy: {} },
16
+ }),
17
+ }))
18
+
19
+ const originalWindowOpen = window.open
20
+
21
+ vi.mock('./spatial-host', () => ({
22
+ openSpatialSceneSync: spatialHostMocks.openSpatialSceneSync,
23
+ }))
2
24
 
3
25
  vi.mock('./JSBCommand', () => {
4
26
  return {
5
- createSpatialSceneCommand: vi.fn().mockImplementation(() => ({
6
- executeSync: vi.fn().mockReturnValue({
7
- data: { id: 'scene-1', windowProxy: {} },
8
- }),
9
- })),
10
- FocusScene: vi.fn().mockImplementation(() => ({
11
- execute: vi.fn().mockResolvedValue(undefined),
12
- })),
27
+ createSpatialSceneCommand: jsbMocks.createSpatialSceneCommand,
28
+ FocusScene: vi.fn().mockImplementation(function () {
29
+ return {
30
+ execute: jsbMocks.focusSceneExecute,
31
+ }
32
+ }),
13
33
  }
14
34
  })
15
35
 
@@ -29,6 +49,16 @@ async function waitTick() {
29
49
  await new Promise(r => setTimeout(r, 0))
30
50
  }
31
51
 
52
+ afterEach(() => {
53
+ window.open = originalWindowOpen
54
+ jsbMocks.createSpatialSceneCommand.mockClear()
55
+ jsbMocks.focusSceneExecute.mockClear()
56
+ spatialHostMocks.openSpatialSceneSync.mockClear()
57
+ document
58
+ .querySelectorAll('link[rel="manifest"]')
59
+ .forEach(node => node.parentElement?.removeChild(node))
60
+ })
61
+
32
62
  describe('setupManifest applies overrides to xr_window_defaults / xr_volume_defaults', () => {
33
63
  it('applies window overrides without affecting volume defaults', async () => {
34
64
  vi.resetModules()
@@ -109,6 +139,279 @@ describe('setupManifest applies overrides to xr_window_defaults / xr_volume_defa
109
139
  cleanup()
110
140
  })
111
141
 
142
+ it('prefers snake_case over camelCase within a layer and still applies overrides by priority', async () => {
143
+ vi.resetModules()
144
+ const cleanup = addDataManifest({
145
+ xr_spatial_scene: {
146
+ defaultSize: { width: '111px', height: '111px' },
147
+ default_size: { width: '222px', height: '333px' },
148
+ overrides: {
149
+ window_scene: {
150
+ defaultSize: { width: '444px' },
151
+ default_size: { width: '555px' },
152
+ },
153
+ },
154
+ },
155
+ })
156
+ const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
157
+ hijackWindowOpen(window)
158
+ await waitTick()
159
+
160
+ let winDefaults: any
161
+ initScene(
162
+ 'w',
163
+ pre => {
164
+ winDefaults = pre
165
+ return pre
166
+ },
167
+ { type: 'window' },
168
+ )
169
+
170
+ let volDefaults: any
171
+ initScene(
172
+ 'v',
173
+ pre => {
174
+ volDefaults = pre
175
+ return pre
176
+ },
177
+ { type: 'volume' },
178
+ )
179
+
180
+ expect(winDefaults).toEqual(
181
+ expect.objectContaining({
182
+ defaultSize: { width: '555px', height: '333px' },
183
+ }),
184
+ )
185
+
186
+ expect(volDefaults).toEqual(
187
+ expect.objectContaining({
188
+ defaultSize: { width: '222px', height: '333px' },
189
+ }),
190
+ )
191
+
192
+ cleanup()
193
+ })
194
+
195
+ it('resolves same-layer aliases recursively so winners own their layer values', async () => {
196
+ vi.resetModules()
197
+ const cleanup = addDataManifest({
198
+ xr_spatial_scene: {
199
+ defaultSize: { width: '111px', height: '111px' },
200
+ default_size: { width: '222px', height: '333px' },
201
+ resizability: {
202
+ minWidth: '100px',
203
+ min_width: '200px',
204
+ maxWidth: '800px',
205
+ },
206
+ },
207
+ })
208
+ const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
209
+ hijackWindowOpen(window)
210
+ await waitTick()
211
+
212
+ let defaults: any
213
+ initScene(
214
+ 'recursive-layer',
215
+ pre => {
216
+ defaults = pre
217
+ return pre
218
+ },
219
+ { type: 'window' },
220
+ )
221
+
222
+ expect(defaults).toEqual(
223
+ expect.objectContaining({
224
+ defaultSize: { width: '222px', height: '333px' },
225
+ resizability: {
226
+ minWidth: '200px',
227
+ maxWidth: '800px',
228
+ },
229
+ }),
230
+ )
231
+
232
+ cleanup()
233
+ })
234
+
235
+ it('supports overrides windowScene/volumeScene and snake_case resizability keys', async () => {
236
+ vi.resetModules()
237
+ const cleanup = addDataManifest({
238
+ xr_spatial_scene: {
239
+ resizability: {
240
+ min_width: '300px',
241
+ min_height: '400px',
242
+ max_width: '1200px',
243
+ max_height: '1300px',
244
+ },
245
+ worldScaling: 'automatic',
246
+ overrides: {
247
+ windowScene: {
248
+ world_scaling: 'dynamic',
249
+ },
250
+ volumeScene: {
251
+ baseplate_visibility: 'hidden',
252
+ },
253
+ },
254
+ },
255
+ })
256
+ const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
257
+ hijackWindowOpen(window)
258
+ await waitTick()
259
+
260
+ let winDefaults: any
261
+ initScene(
262
+ 'w',
263
+ pre => {
264
+ winDefaults = pre
265
+ return pre
266
+ },
267
+ { type: 'window' },
268
+ )
269
+
270
+ let volDefaults: any
271
+ initScene(
272
+ 'v',
273
+ pre => {
274
+ volDefaults = pre
275
+ return pre
276
+ },
277
+ { type: 'volume' },
278
+ )
279
+
280
+ expect(winDefaults).toEqual(
281
+ expect.objectContaining({
282
+ resizability: {
283
+ minWidth: '300px',
284
+ minHeight: '400px',
285
+ maxWidth: '1200px',
286
+ maxHeight: '1300px',
287
+ },
288
+ worldScaling: 'dynamic',
289
+ }),
290
+ )
291
+
292
+ expect(volDefaults).toEqual(
293
+ expect.objectContaining({
294
+ resizability: {
295
+ minWidth: '300px',
296
+ minHeight: '400px',
297
+ maxWidth: '1200px',
298
+ maxHeight: '1300px',
299
+ },
300
+ baseplateVisibility: 'hidden',
301
+ }),
302
+ )
303
+
304
+ cleanup()
305
+ })
306
+
307
+ it('normalizes top-level world_alignment and baseplate_visibility aliases', async () => {
308
+ vi.resetModules()
309
+ const cleanup = addDataManifest({
310
+ xr_spatial_scene: {
311
+ world_alignment: 'gravityAligned',
312
+ baseplate_visibility: 'hidden',
313
+ },
314
+ })
315
+ const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
316
+ hijackWindowOpen(window)
317
+ await waitTick()
318
+
319
+ let winDefaults: any
320
+ initScene(
321
+ 'w-alias',
322
+ pre => {
323
+ winDefaults = pre
324
+ return pre
325
+ },
326
+ { type: 'window' },
327
+ )
328
+
329
+ let volDefaults: any
330
+ initScene(
331
+ 'v-alias',
332
+ pre => {
333
+ volDefaults = pre
334
+ return pre
335
+ },
336
+ { type: 'volume' },
337
+ )
338
+
339
+ expect(winDefaults).toEqual(
340
+ expect.objectContaining({
341
+ worldAlignment: 'gravityAligned',
342
+ baseplateVisibility: 'hidden',
343
+ }),
344
+ )
345
+ expect(volDefaults).toEqual(
346
+ expect.objectContaining({
347
+ worldAlignment: 'gravityAligned',
348
+ baseplateVisibility: 'hidden',
349
+ }),
350
+ )
351
+
352
+ cleanup()
353
+ })
354
+
355
+ it('does not normalize initScene callback return value when chaining', async () => {
356
+ vi.resetModules()
357
+ const cleanup = addDataManifest({
358
+ xr_spatial_scene: {
359
+ default_size: { width: '222px', height: '333px' },
360
+ },
361
+ })
362
+ const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
363
+ hijackWindowOpen(window)
364
+ await waitTick()
365
+
366
+ const firstReturn: any = { default_size: { width: '777px' } }
367
+ const cb1 = vi.fn().mockReturnValue(firstReturn)
368
+ initScene('chain', cb1, { type: 'window' })
369
+
370
+ const cb2 = vi.fn().mockReturnValue({})
371
+ initScene('chain', cb2, { type: 'window' })
372
+
373
+ expect(cb2).toHaveBeenCalledWith(firstReturn)
374
+
375
+ cleanup()
376
+ })
377
+
378
+ it('uses manifest aliases when window.open creates a named scene without initScene', async () => {
379
+ vi.resetModules()
380
+ const cleanup = addDataManifest({
381
+ xr_spatial_scene: {
382
+ default_size: { width: '300px', height: '400px' },
383
+ world_alignment: 'gravityAligned',
384
+ overrides: {
385
+ windowScene: {
386
+ default_size: { width: '500px' },
387
+ resizability: { min_width: '320px' },
388
+ baseplate_visibility: 'visible',
389
+ },
390
+ },
391
+ },
392
+ })
393
+ const { hijackWindowOpen } = await import('./scene-polyfill')
394
+ hijackWindowOpen(window)
395
+ await waitTick()
396
+
397
+ window.open('https://example.com/scene', 'manifest-alias-window')
398
+
399
+ expect(spatialHostMocks.openSpatialSceneSync).toHaveBeenCalledWith(
400
+ 'https://example.com/scene',
401
+ expect.objectContaining({
402
+ type: 'window',
403
+ defaultSize: { width: 500, height: 400 },
404
+ resizability: { minWidth: 320 },
405
+ worldAlignment: 'gravityAligned',
406
+ baseplateVisibility: 'visible',
407
+ }),
408
+ 'manifest-alias-window',
409
+ undefined,
410
+ )
411
+
412
+ cleanup()
413
+ })
414
+
112
415
  it('applies volume overrides without affecting window defaults', async () => {
113
416
  vi.resetModules()
114
417
  const cleanup = addDataManifest({