@webspatial/core-sdk 1.5.0 → 1.6.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 +29 -0
- package/dist/iife/index.d.ts +164 -12
- package/dist/iife/index.global.js +11 -11
- package/dist/iife/index.global.js.map +1 -1
- package/dist/index.d.ts +164 -12
- package/dist/index.js +415 -117
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/JSBCommand.ts +7 -2
- package/src/Spatial.ts +1 -1
- package/src/SpatialSession.ts +4 -2
- package/src/SpatializedElement.ts +10 -10
- package/src/SpatializedElementCreator.ts +5 -2
- package/src/SpatializedStatic3DElement.test.ts +15 -1
- package/src/SpatializedStatic3DElement.ts +167 -8
- package/src/WebMsgCommand.ts +22 -0
- package/src/scene-polyfill.manifest.test.ts +402 -0
- package/src/scene-polyfill.test.ts +168 -18
- package/src/scene-polyfill.ts +290 -46
- package/src/types/global.d.ts +3 -0
- package/src/types/types.ts +71 -0
- package/src/utils.ts +21 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
vi.mock('./JSBCommand', () => {
|
|
4
|
+
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
|
+
})),
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
function addDataManifest(manifest: any) {
|
|
17
|
+
const link = document.createElement('link')
|
|
18
|
+
link.rel = 'manifest'
|
|
19
|
+
const json = JSON.stringify(manifest)
|
|
20
|
+
link.href = 'data:application/manifest+json,' + encodeURIComponent(json)
|
|
21
|
+
document.head.appendChild(link)
|
|
22
|
+
return () => {
|
|
23
|
+
document.head.removeChild(link)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function waitTick() {
|
|
28
|
+
await Promise.resolve()
|
|
29
|
+
await new Promise(r => setTimeout(r, 0))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe('setupManifest applies overrides to xr_window_defaults / xr_volume_defaults', () => {
|
|
33
|
+
it('applies window overrides without affecting volume defaults', async () => {
|
|
34
|
+
vi.resetModules()
|
|
35
|
+
const cleanup = addDataManifest({
|
|
36
|
+
xr_spatial_scene: {
|
|
37
|
+
defaultSize: { width: '200px', height: '300px' },
|
|
38
|
+
resizability: { minWidth: '300px', minHeight: '400px' },
|
|
39
|
+
worldScaling: 'dynamic',
|
|
40
|
+
worldAlignment: 'gravityAligned',
|
|
41
|
+
baseplateVisibility: 'visible',
|
|
42
|
+
overrides: {
|
|
43
|
+
window_scene: {
|
|
44
|
+
defaultSize: { width: '1000px' },
|
|
45
|
+
resizability: { minWidth: '500px', maxWidth: '1200px' },
|
|
46
|
+
worldAlignment: 'automatic',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
|
|
52
|
+
hijackWindowOpen(window)
|
|
53
|
+
await waitTick()
|
|
54
|
+
|
|
55
|
+
let winDefaults: any
|
|
56
|
+
initScene(
|
|
57
|
+
'w',
|
|
58
|
+
pre => {
|
|
59
|
+
winDefaults = pre
|
|
60
|
+
return pre
|
|
61
|
+
},
|
|
62
|
+
{ type: 'window' },
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
let volDefaults: any
|
|
66
|
+
initScene(
|
|
67
|
+
'v',
|
|
68
|
+
pre => {
|
|
69
|
+
volDefaults = pre
|
|
70
|
+
return pre
|
|
71
|
+
},
|
|
72
|
+
{ type: 'volume' },
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
// window uses overrides + formatted to px
|
|
76
|
+
expect(winDefaults).toEqual(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
defaultSize: { width: '1000px', height: '300px' },
|
|
79
|
+
resizability: expect.objectContaining({
|
|
80
|
+
minWidth: '500px',
|
|
81
|
+
minHeight: '400px',
|
|
82
|
+
maxWidth: '1200px',
|
|
83
|
+
}),
|
|
84
|
+
worldAlignment: 'automatic',
|
|
85
|
+
worldScaling: 'dynamic',
|
|
86
|
+
baseplateVisibility: 'visible',
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
// volume remains from top-level only (no volume override) and formats:
|
|
91
|
+
// - pre passed to initScene is the raw manifest values (unformatted)
|
|
92
|
+
expect(volDefaults).toEqual(
|
|
93
|
+
expect.objectContaining({
|
|
94
|
+
defaultSize: {
|
|
95
|
+
width: '200px',
|
|
96
|
+
height: '300px',
|
|
97
|
+
// depth may be omitted in manifest; defaults keep only provided keys
|
|
98
|
+
},
|
|
99
|
+
resizability: expect.objectContaining({
|
|
100
|
+
minWidth: '300px',
|
|
101
|
+
minHeight: '400px',
|
|
102
|
+
}),
|
|
103
|
+
worldAlignment: 'gravityAligned',
|
|
104
|
+
worldScaling: 'dynamic',
|
|
105
|
+
baseplateVisibility: 'visible',
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
cleanup()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('applies volume overrides without affecting window defaults', async () => {
|
|
113
|
+
vi.resetModules()
|
|
114
|
+
const cleanup = addDataManifest({
|
|
115
|
+
xr_spatial_scene: {
|
|
116
|
+
defaultSize: { width: '120px', height: '240px' },
|
|
117
|
+
resizability: { minWidth: '200px', minHeight: '300px' },
|
|
118
|
+
overrides: {
|
|
119
|
+
volume_scene: {
|
|
120
|
+
defaultSize: { width: '2m', height: '2m', depth: '2m' },
|
|
121
|
+
resizability: { minWidth: '1m' },
|
|
122
|
+
baseplateVisibility: 'automatic',
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
|
|
128
|
+
hijackWindowOpen(window)
|
|
129
|
+
await waitTick()
|
|
130
|
+
|
|
131
|
+
let volDefaults: any
|
|
132
|
+
initScene(
|
|
133
|
+
'v',
|
|
134
|
+
pre => {
|
|
135
|
+
volDefaults = pre
|
|
136
|
+
return pre
|
|
137
|
+
},
|
|
138
|
+
{ type: 'volume' },
|
|
139
|
+
)
|
|
140
|
+
let winDefaults: any
|
|
141
|
+
initScene(
|
|
142
|
+
'w',
|
|
143
|
+
pre => {
|
|
144
|
+
winDefaults = pre
|
|
145
|
+
return pre
|
|
146
|
+
},
|
|
147
|
+
{ type: 'window' },
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// volume uses overrides + formatting (m for defaultSize, px for resizability)
|
|
151
|
+
expect(volDefaults).toEqual(
|
|
152
|
+
expect.objectContaining({
|
|
153
|
+
defaultSize: { width: '2m', height: '2m', depth: '2m' },
|
|
154
|
+
resizability: expect.objectContaining({
|
|
155
|
+
minWidth: '1m',
|
|
156
|
+
}),
|
|
157
|
+
baseplateVisibility: 'automatic',
|
|
158
|
+
}),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
// window remains from top-level only (no window override)
|
|
162
|
+
expect(winDefaults).toEqual(
|
|
163
|
+
expect.objectContaining({
|
|
164
|
+
defaultSize: { width: '120px', height: '240px' },
|
|
165
|
+
resizability: expect.objectContaining({
|
|
166
|
+
minWidth: '200px',
|
|
167
|
+
minHeight: '300px',
|
|
168
|
+
}),
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
cleanup()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('applies provided mixed-case defaults and empty volume override; volume pre is formatted as expected', async () => {
|
|
176
|
+
vi.resetModules()
|
|
177
|
+
const cleanup = addDataManifest({
|
|
178
|
+
xr_spatial_scene: {
|
|
179
|
+
default_size: {
|
|
180
|
+
width: '1024px',
|
|
181
|
+
height: '1024px',
|
|
182
|
+
depth: '55px',
|
|
183
|
+
},
|
|
184
|
+
resizability: {
|
|
185
|
+
minWidth: '1024px',
|
|
186
|
+
minHeight: '1024px',
|
|
187
|
+
maxWidth: '2000px',
|
|
188
|
+
maxHeight: '2000px',
|
|
189
|
+
},
|
|
190
|
+
worldScaling: 'automatic',
|
|
191
|
+
worldAlignment: 'automatic',
|
|
192
|
+
baseplateVisibility: 'visible',
|
|
193
|
+
overrides: {
|
|
194
|
+
window_scene: {
|
|
195
|
+
default_size: {
|
|
196
|
+
width: '500px',
|
|
197
|
+
height: '500px',
|
|
198
|
+
depth: '55px',
|
|
199
|
+
},
|
|
200
|
+
resizability: {
|
|
201
|
+
minWidth: '500px',
|
|
202
|
+
minHeight: '500px',
|
|
203
|
+
maxWidth: '1000px',
|
|
204
|
+
maxHeight: '1000px',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
volume_scene: {},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
|
|
212
|
+
hijackWindowOpen(window)
|
|
213
|
+
await waitTick()
|
|
214
|
+
|
|
215
|
+
const pxToM = (px: number) => px / 1360
|
|
216
|
+
|
|
217
|
+
let volDefaults: any
|
|
218
|
+
initScene(
|
|
219
|
+
'sa',
|
|
220
|
+
pre => {
|
|
221
|
+
volDefaults = pre
|
|
222
|
+
return { ...pre }
|
|
223
|
+
},
|
|
224
|
+
{ type: 'volume' },
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
expect(volDefaults).toEqual(
|
|
228
|
+
expect.objectContaining({
|
|
229
|
+
defaultSize: {
|
|
230
|
+
width: '1024px',
|
|
231
|
+
height: '1024px',
|
|
232
|
+
depth: '55px',
|
|
233
|
+
},
|
|
234
|
+
resizability: expect.objectContaining({
|
|
235
|
+
minWidth: '1024px',
|
|
236
|
+
minHeight: '1024px',
|
|
237
|
+
maxWidth: '2000px',
|
|
238
|
+
maxHeight: '2000px',
|
|
239
|
+
}),
|
|
240
|
+
worldScaling: 'automatic',
|
|
241
|
+
worldAlignment: 'automatic',
|
|
242
|
+
baseplateVisibility: 'visible',
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// Snapshot current 'sa' internal config before cleanup
|
|
247
|
+
const { __getSceneConfigSnapshotForTest } = await import('./scene-polyfill')
|
|
248
|
+
const snap = __getSceneConfigSnapshotForTest('sa')
|
|
249
|
+
expect(snap).toEqual(
|
|
250
|
+
expect.objectContaining({
|
|
251
|
+
type: 'volume',
|
|
252
|
+
defaultSize: {
|
|
253
|
+
width: pxToM(1024),
|
|
254
|
+
height: pxToM(1024),
|
|
255
|
+
depth: pxToM(55),
|
|
256
|
+
},
|
|
257
|
+
resizability: expect.objectContaining({
|
|
258
|
+
minWidth: 1024,
|
|
259
|
+
minHeight: 1024,
|
|
260
|
+
maxWidth: 2000,
|
|
261
|
+
maxHeight: 2000,
|
|
262
|
+
}),
|
|
263
|
+
}),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
cleanup()
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
describe('manifest error paths and empty configs', () => {
|
|
271
|
+
function addInvalidDataManifest() {
|
|
272
|
+
const link = document.createElement('link')
|
|
273
|
+
link.rel = 'manifest'
|
|
274
|
+
// Invalid JSON payload to force parse failure in getPWAManifest
|
|
275
|
+
link.href = 'data:application/manifest+json,INVALID_JSON'
|
|
276
|
+
document.head.appendChild(link)
|
|
277
|
+
return () => {
|
|
278
|
+
document.head.removeChild(link)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
it('falls back to built-in defaults when no manifest link is present', async () => {
|
|
283
|
+
vi.resetModules()
|
|
284
|
+
const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
|
|
285
|
+
hijackWindowOpen(window)
|
|
286
|
+
await waitTick()
|
|
287
|
+
|
|
288
|
+
let winPre: any
|
|
289
|
+
initScene(
|
|
290
|
+
'no-manifest-win',
|
|
291
|
+
pre => {
|
|
292
|
+
winPre = pre
|
|
293
|
+
return pre
|
|
294
|
+
},
|
|
295
|
+
{ type: 'window' },
|
|
296
|
+
)
|
|
297
|
+
let volPre: any
|
|
298
|
+
initScene(
|
|
299
|
+
'no-manifest-vol',
|
|
300
|
+
pre => {
|
|
301
|
+
volPre = pre
|
|
302
|
+
return pre
|
|
303
|
+
},
|
|
304
|
+
{ type: 'volume' },
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
// Window defaults: numbers in px domain for width/height
|
|
308
|
+
expect(winPre).toEqual(
|
|
309
|
+
expect.objectContaining({
|
|
310
|
+
defaultSize: { width: 1280, height: 720 },
|
|
311
|
+
}),
|
|
312
|
+
)
|
|
313
|
+
// Volume defaults: strings in meters for width/height/depth
|
|
314
|
+
expect(volPre).toEqual(
|
|
315
|
+
expect.objectContaining({
|
|
316
|
+
defaultSize: { width: '0.94m', height: '0.94m', depth: '0.94m' },
|
|
317
|
+
}),
|
|
318
|
+
)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('falls back to built-in defaults when manifest parsing fails', async () => {
|
|
322
|
+
vi.resetModules()
|
|
323
|
+
const cleanup = addInvalidDataManifest()
|
|
324
|
+
const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
|
|
325
|
+
hijackWindowOpen(window)
|
|
326
|
+
await waitTick()
|
|
327
|
+
|
|
328
|
+
let winPre: any
|
|
329
|
+
initScene(
|
|
330
|
+
'bad-manifest-win',
|
|
331
|
+
pre => {
|
|
332
|
+
winPre = pre
|
|
333
|
+
return pre
|
|
334
|
+
},
|
|
335
|
+
{ type: 'window' },
|
|
336
|
+
)
|
|
337
|
+
let volPre: any
|
|
338
|
+
initScene(
|
|
339
|
+
'bad-manifest-vol',
|
|
340
|
+
pre => {
|
|
341
|
+
volPre = pre
|
|
342
|
+
return pre
|
|
343
|
+
},
|
|
344
|
+
{ type: 'volume' },
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
expect(winPre).toEqual(
|
|
348
|
+
expect.objectContaining({
|
|
349
|
+
defaultSize: { width: 1280, height: 720 },
|
|
350
|
+
}),
|
|
351
|
+
)
|
|
352
|
+
expect(volPre).toEqual(
|
|
353
|
+
expect.objectContaining({
|
|
354
|
+
defaultSize: { width: '0.94m', height: '0.94m', depth: '0.94m' },
|
|
355
|
+
}),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
cleanup()
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('ignores empty xr_spatial_scene object and preserves built-in defaults', async () => {
|
|
362
|
+
vi.resetModules()
|
|
363
|
+
const cleanup = addDataManifest({
|
|
364
|
+
xr_spatial_scene: {},
|
|
365
|
+
})
|
|
366
|
+
const { hijackWindowOpen, initScene } = await import('./scene-polyfill')
|
|
367
|
+
hijackWindowOpen(window)
|
|
368
|
+
await waitTick()
|
|
369
|
+
|
|
370
|
+
let winPre: any
|
|
371
|
+
initScene(
|
|
372
|
+
'empty-xr-win',
|
|
373
|
+
pre => {
|
|
374
|
+
winPre = pre
|
|
375
|
+
return pre
|
|
376
|
+
},
|
|
377
|
+
{ type: 'window' },
|
|
378
|
+
)
|
|
379
|
+
let volPre: any
|
|
380
|
+
initScene(
|
|
381
|
+
'empty-xr-vol',
|
|
382
|
+
pre => {
|
|
383
|
+
volPre = pre
|
|
384
|
+
return pre
|
|
385
|
+
},
|
|
386
|
+
{ type: 'volume' },
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
expect(winPre).toEqual(
|
|
390
|
+
expect.objectContaining({
|
|
391
|
+
defaultSize: { width: 1280, height: 720 },
|
|
392
|
+
}),
|
|
393
|
+
)
|
|
394
|
+
expect(volPre).toEqual(
|
|
395
|
+
expect.objectContaining({
|
|
396
|
+
defaultSize: { width: '0.94m', height: '0.94m', depth: '0.94m' },
|
|
397
|
+
}),
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
cleanup()
|
|
401
|
+
})
|
|
402
|
+
})
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { describe, expect, test, vi, beforeEach, afterEach, it } from 'vitest'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
formatSceneConfig,
|
|
4
|
+
initScene,
|
|
5
|
+
injectSceneHook,
|
|
6
|
+
__getSceneConfigSnapshotForTest,
|
|
7
|
+
hijackWindowATag,
|
|
8
|
+
} from './scene-polyfill'
|
|
3
9
|
import { SpatialSceneCreationOptions } from './types/types'
|
|
10
|
+
import { pointToPhysical } from './physicalMetrics'
|
|
4
11
|
|
|
5
12
|
describe('test formatSceneConfig in window', () => {
|
|
6
13
|
test('should format window with no unit', () => {
|
|
@@ -79,6 +86,23 @@ describe('test formatSceneConfig in window', () => {
|
|
|
79
86
|
])
|
|
80
87
|
})
|
|
81
88
|
|
|
89
|
+
test('window mixed units: cm invalid and numbers treated as px', () => {
|
|
90
|
+
const config = {
|
|
91
|
+
defaultSize: {
|
|
92
|
+
width: '10cm',
|
|
93
|
+
height: 800,
|
|
94
|
+
},
|
|
95
|
+
} satisfies SpatialSceneCreationOptions
|
|
96
|
+
const [formatted, errors] = formatSceneConfig(config, 'window')
|
|
97
|
+
expect(errors).toEqual(['defaultSize.width'])
|
|
98
|
+
expect(formatted.defaultSize).toEqual(
|
|
99
|
+
expect.objectContaining({
|
|
100
|
+
height: 800,
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
expect((formatted.defaultSize as any).width).toBeUndefined()
|
|
104
|
+
})
|
|
105
|
+
|
|
82
106
|
test('should format window with meter', () => {
|
|
83
107
|
const config = {
|
|
84
108
|
defaultSize: {
|
|
@@ -106,6 +130,27 @@ describe('test formatSceneConfig in window', () => {
|
|
|
106
130
|
})
|
|
107
131
|
})
|
|
108
132
|
|
|
133
|
+
describe('formatSceneConfig invalid unit (mixed)', () => {
|
|
134
|
+
test('volume defaultSize width cm invalid while numbers convert', () => {
|
|
135
|
+
const config = {
|
|
136
|
+
defaultSize: {
|
|
137
|
+
width: '10cm',
|
|
138
|
+
height: 1000,
|
|
139
|
+
depth: 100,
|
|
140
|
+
},
|
|
141
|
+
} satisfies SpatialSceneCreationOptions
|
|
142
|
+
const [formattedConfig, errors] = formatSceneConfig(config, 'volume')
|
|
143
|
+
expect(errors).toEqual(['defaultSize.width'])
|
|
144
|
+
expect(formattedConfig.defaultSize).toEqual(
|
|
145
|
+
expect.objectContaining({
|
|
146
|
+
height: pointToPhysical(1000),
|
|
147
|
+
depth: pointToPhysical(100),
|
|
148
|
+
}),
|
|
149
|
+
)
|
|
150
|
+
expect((formattedConfig.defaultSize as any).width).toBeUndefined()
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
109
154
|
describe('test formatSceneConfig in volume', () => {
|
|
110
155
|
test('should format volume with no unit', () => {
|
|
111
156
|
const config = {
|
|
@@ -123,15 +168,15 @@ describe('test formatSceneConfig in volume', () => {
|
|
|
123
168
|
} satisfies SpatialSceneCreationOptions
|
|
124
169
|
const [formattedConfig] = formatSceneConfig(config, 'volume')
|
|
125
170
|
expect(formattedConfig.defaultSize).toEqual({
|
|
126
|
-
width: 1,
|
|
127
|
-
height: 1,
|
|
128
|
-
depth: 1,
|
|
171
|
+
width: pointToPhysical(1),
|
|
172
|
+
height: pointToPhysical(1),
|
|
173
|
+
depth: pointToPhysical(1),
|
|
129
174
|
})
|
|
130
175
|
expect(formattedConfig.resizability).toEqual({
|
|
131
|
-
minWidth:
|
|
132
|
-
minHeight:
|
|
133
|
-
maxWidth:
|
|
134
|
-
maxHeight:
|
|
176
|
+
minWidth: 1,
|
|
177
|
+
minHeight: 1,
|
|
178
|
+
maxWidth: 1,
|
|
179
|
+
maxHeight: 1,
|
|
135
180
|
})
|
|
136
181
|
})
|
|
137
182
|
|
|
@@ -316,7 +361,7 @@ describe('injectScenePolyfill should call xrCurrentSceneDefaults and update scen
|
|
|
316
361
|
|
|
317
362
|
expect(mockFn).toHaveBeenCalledWith(
|
|
318
363
|
expect.objectContaining({
|
|
319
|
-
defaultSize: { width: 0.
|
|
364
|
+
defaultSize: { width: '0.94m', height: '0.94m', depth: '0.94m' },
|
|
320
365
|
}),
|
|
321
366
|
)
|
|
322
367
|
|
|
@@ -324,12 +369,16 @@ describe('injectScenePolyfill should call xrCurrentSceneDefaults and update scen
|
|
|
324
369
|
const { UpdateSceneConfig } = await import('./JSBCommand')
|
|
325
370
|
expect(UpdateSceneConfig).toHaveBeenCalledWith({
|
|
326
371
|
type: 'volume',
|
|
327
|
-
defaultSize: {
|
|
372
|
+
defaultSize: {
|
|
373
|
+
width: pointToPhysical(1),
|
|
374
|
+
height: pointToPhysical(1),
|
|
375
|
+
depth: pointToPhysical(1),
|
|
376
|
+
},
|
|
328
377
|
resizability: {
|
|
329
|
-
minWidth: 0.5
|
|
330
|
-
minHeight: 1
|
|
331
|
-
maxWidth: 0.5
|
|
332
|
-
maxHeight: 1
|
|
378
|
+
minWidth: 0.5,
|
|
379
|
+
minHeight: 1,
|
|
380
|
+
maxWidth: 0.5,
|
|
381
|
+
maxHeight: 1,
|
|
333
382
|
},
|
|
334
383
|
})
|
|
335
384
|
})
|
|
@@ -341,7 +390,7 @@ describe('initScene should receive defaultScene config by type', () => {
|
|
|
341
390
|
.fn()
|
|
342
391
|
.mockResolvedValue({ defaultSize: { width: 800, height: 600 } })
|
|
343
392
|
|
|
344
|
-
initScene('sa', mockFn)
|
|
393
|
+
initScene('sa-no-type', mockFn)
|
|
345
394
|
|
|
346
395
|
expect(mockFn).toHaveBeenCalledWith(
|
|
347
396
|
expect.objectContaining({ defaultSize: { width: 1280, height: 720 } }),
|
|
@@ -353,7 +402,7 @@ describe('initScene should receive defaultScene config by type', () => {
|
|
|
353
402
|
.fn()
|
|
354
403
|
.mockResolvedValue({ defaultSize: { width: 800, height: 600 } })
|
|
355
404
|
|
|
356
|
-
initScene('sa', mockFn, { type: 'window' })
|
|
405
|
+
initScene('sa-window', mockFn, { type: 'window' })
|
|
357
406
|
|
|
358
407
|
expect(mockFn).toHaveBeenCalledWith(
|
|
359
408
|
expect.objectContaining({ defaultSize: { width: 1280, height: 720 } }),
|
|
@@ -365,12 +414,113 @@ describe('initScene should receive defaultScene config by type', () => {
|
|
|
365
414
|
.fn()
|
|
366
415
|
.mockResolvedValue({ defaultSize: { width: 800, height: 600 } })
|
|
367
416
|
|
|
368
|
-
initScene('sa', mockFn, { type: 'volume' })
|
|
417
|
+
initScene('sa-volume', mockFn, { type: 'volume' })
|
|
369
418
|
|
|
370
419
|
expect(mockFn).toHaveBeenCalledWith(
|
|
371
420
|
expect.objectContaining({
|
|
372
|
-
defaultSize: { width: 0.
|
|
421
|
+
defaultSize: { width: '0.94m', height: '0.94m', depth: '0.94m' },
|
|
373
422
|
}),
|
|
374
423
|
)
|
|
375
424
|
})
|
|
425
|
+
|
|
426
|
+
it('with volume type and merge pre', () => {
|
|
427
|
+
const cb = vi.fn().mockImplementation(pre => ({
|
|
428
|
+
...pre,
|
|
429
|
+
defaultSize: { ...(pre.defaultSize as any), depth: '0.1m' },
|
|
430
|
+
}))
|
|
431
|
+
|
|
432
|
+
// pre-snapshot should be empty before initScene writes configMap
|
|
433
|
+
const preSnap = __getSceneConfigSnapshotForTest('sa')
|
|
434
|
+
expect(preSnap).toBeUndefined()
|
|
435
|
+
|
|
436
|
+
initScene('sa', cb, { type: 'volume' })
|
|
437
|
+
|
|
438
|
+
expect(cb).toHaveBeenCalledWith(
|
|
439
|
+
expect.objectContaining({
|
|
440
|
+
defaultSize: { width: '0.94m', height: '0.94m', depth: '0.94m' },
|
|
441
|
+
}),
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
const snap = __getSceneConfigSnapshotForTest('sa')
|
|
445
|
+
expect(snap).toEqual(
|
|
446
|
+
expect.objectContaining({
|
|
447
|
+
type: 'volume',
|
|
448
|
+
defaultSize: { width: 0.94, height: 0.94, depth: 0.1 },
|
|
449
|
+
}),
|
|
450
|
+
)
|
|
451
|
+
})
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
describe('initScene callback chaining', () => {
|
|
455
|
+
it('passes previous return value as next pre argument', () => {
|
|
456
|
+
const firstReturn = { defaultSize: { width: 1000, height: 1000 } }
|
|
457
|
+
const cb1 = vi.fn().mockReturnValue(firstReturn)
|
|
458
|
+
initScene('sa-chain', cb1)
|
|
459
|
+
expect(cb1).toHaveBeenCalledWith(
|
|
460
|
+
expect.objectContaining({ defaultSize: { width: 1280, height: 720 } }),
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
const cb2 = vi.fn().mockReturnValue({})
|
|
464
|
+
initScene('sa-chain', cb2)
|
|
465
|
+
expect(cb2).toHaveBeenCalledWith(firstReturn)
|
|
466
|
+
})
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
describe('hijackWindowATag', () => {
|
|
470
|
+
afterEach(() => {
|
|
471
|
+
document.body.innerHTML = ''
|
|
472
|
+
document.onclick = null
|
|
473
|
+
vi.restoreAllMocks()
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
it('handles clicks on nested elements inside anchor tags', () => {
|
|
477
|
+
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
|
|
478
|
+
hijackWindowATag(window)
|
|
479
|
+
|
|
480
|
+
const anchor = document.createElement('a')
|
|
481
|
+
anchor.href = 'https://example.com/detail'
|
|
482
|
+
anchor.target = '_blank'
|
|
483
|
+
|
|
484
|
+
const image = document.createElement('img')
|
|
485
|
+
anchor.appendChild(image)
|
|
486
|
+
document.body.appendChild(anchor)
|
|
487
|
+
|
|
488
|
+
image.dispatchEvent(
|
|
489
|
+
new MouseEvent('click', { bubbles: true, cancelable: true }),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
expect(openSpy).toHaveBeenCalledWith('https://example.com/detail', '_blank')
|
|
493
|
+
})
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
describe('hijackWindowATag – defaultPrevented', () => {
|
|
497
|
+
afterEach(() => {
|
|
498
|
+
document.body.innerHTML = ''
|
|
499
|
+
document.onclick = null
|
|
500
|
+
vi.restoreAllMocks()
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
it('does not open a new window when the click was already preventDefault-ed', () => {
|
|
504
|
+
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
|
|
505
|
+
hijackWindowATag(window)
|
|
506
|
+
|
|
507
|
+
const anchor = document.createElement('a')
|
|
508
|
+
anchor.href = 'https://example.com/detail'
|
|
509
|
+
anchor.target = '_blank'
|
|
510
|
+
|
|
511
|
+
const span = document.createElement('span')
|
|
512
|
+
anchor.appendChild(span)
|
|
513
|
+
document.body.appendChild(anchor)
|
|
514
|
+
|
|
515
|
+
// Simulate an app-level handler that cancels the click before the polyfill sees it.
|
|
516
|
+
span.addEventListener('click', ev => {
|
|
517
|
+
ev.preventDefault()
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
span.dispatchEvent(
|
|
521
|
+
new MouseEvent('click', { bubbles: true, cancelable: true }),
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
expect(openSpy).not.toHaveBeenCalled()
|
|
525
|
+
})
|
|
376
526
|
})
|