@webspatial/core-sdk 1.1.0 → 1.2.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.
- package/CHANGELOG.md +10 -0
- package/dist/iife/index.d.ts +29 -32
- package/dist/iife/index.global.js +3 -3
- package/dist/iife/index.global.js.map +1 -1
- package/dist/index.d.ts +29 -32
- package/dist/index.js +146 -69
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/JSBCommand.ts +1 -1
- package/src/SpatialScene.ts +2 -2
- package/src/SpatialSession.ts +7 -4
- package/src/SpatializedElement.ts +13 -53
- package/src/SpatializedStatic3DElement.ts +1 -1
- package/src/WebMsgCommand.ts +8 -3
- package/src/coverage-boost.test.ts +1060 -0
- package/src/jsbcommand.coverage.test.ts +542 -0
- package/src/platform-adapter/index.ts +31 -3
- package/src/platform-adapter/vision-os/VisionOSPlatform.ts +0 -1
- package/src/platform-adapter/xr/XRPlatform.ts +133 -0
- package/src/reality/component/index.ts +1 -1
- package/src/reality/entity/SpatialEntity.ts +2 -14
- package/src/reality/entity/index.ts +1 -1
- package/src/reality/geometry/SpatialBoxGeometry.ts +4 -1
- package/src/reality/geometry/index.ts +1 -1
- package/src/reality/material/index.ts +1 -1
- package/src/reality/resource/index.ts +1 -1
- package/src/scene-polyfill.ts +0 -4
- package/src/types/internal.ts +5 -5
- package/src/types/types.ts +23 -17
|
@@ -0,0 +1,1060 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { composeSRT, parseCornerRadius } from './utils'
|
|
3
|
+
import {
|
|
4
|
+
CommandResultFailure,
|
|
5
|
+
CommandResultSuccess,
|
|
6
|
+
} from './platform-adapter/CommandResultUtils'
|
|
7
|
+
import { SpatialWebEvent } from './SpatialWebEvent'
|
|
8
|
+
import { createSpatialEvent } from './SpatialWebEventCreator'
|
|
9
|
+
import {
|
|
10
|
+
isValidBaseplateVisibilityType,
|
|
11
|
+
isValidSceneUnit,
|
|
12
|
+
isValidSpatialSceneType,
|
|
13
|
+
isValidWorldAlignmentType,
|
|
14
|
+
isValidWorldScalingType,
|
|
15
|
+
} from './types/types'
|
|
16
|
+
|
|
17
|
+
describe('utils', () => {
|
|
18
|
+
it('parseCornerRadius parses px and percent values', () => {
|
|
19
|
+
const computedStyle = {
|
|
20
|
+
getPropertyValue: (prop: string) => {
|
|
21
|
+
if (prop === 'width') return '200px'
|
|
22
|
+
if (prop === 'border-top-left-radius') return '10px'
|
|
23
|
+
if (prop === 'border-top-right-radius') return '5%'
|
|
24
|
+
if (prop === 'border-bottom-left-radius') return ''
|
|
25
|
+
if (prop === 'border-bottom-right-radius') return '20'
|
|
26
|
+
return ''
|
|
27
|
+
},
|
|
28
|
+
} as unknown as CSSStyleDeclaration
|
|
29
|
+
|
|
30
|
+
expect(parseCornerRadius(computedStyle)).toEqual({
|
|
31
|
+
topLeading: 10,
|
|
32
|
+
topTrailing: 10,
|
|
33
|
+
bottomLeading: 0,
|
|
34
|
+
bottomTrailing: 20,
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('composeSRT composes translate and scale in expected order', () => {
|
|
39
|
+
if (!(globalThis as any).DOMMatrix) {
|
|
40
|
+
class DOMMatrixPolyfill {
|
|
41
|
+
private tx = 0
|
|
42
|
+
private ty = 0
|
|
43
|
+
private tz = 0
|
|
44
|
+
private sx = 1
|
|
45
|
+
private sy = 1
|
|
46
|
+
private sz = 1
|
|
47
|
+
|
|
48
|
+
translate(x = 0, y = 0, z = 0) {
|
|
49
|
+
this.tx += x
|
|
50
|
+
this.ty += y
|
|
51
|
+
this.tz += z
|
|
52
|
+
return this
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
rotate() {
|
|
56
|
+
return this
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
scale(x = 1, y = 1, z = 1) {
|
|
60
|
+
this.sx *= x
|
|
61
|
+
this.sy *= y
|
|
62
|
+
this.sz *= z
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
transformPoint(p: { x: number; y: number; z?: number }) {
|
|
67
|
+
return {
|
|
68
|
+
x: p.x * this.sx + this.tx,
|
|
69
|
+
y: p.y * this.sy + this.ty,
|
|
70
|
+
z: (p.z ?? 0) * this.sz + this.tz,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
;(globalThis as any).DOMMatrix = DOMMatrixPolyfill
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const m = composeSRT(
|
|
78
|
+
{ x: 10, y: 20, z: 30 },
|
|
79
|
+
{ x: 0, y: 0, z: 0 },
|
|
80
|
+
{ x: 2, y: 3, z: 4 },
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const origin = m.transformPoint({ x: 0, y: 0, z: 0 })
|
|
84
|
+
expect(origin).toEqual({
|
|
85
|
+
x: 10,
|
|
86
|
+
y: 20,
|
|
87
|
+
z: 30,
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const p = m.transformPoint({ x: 1, y: 1, z: 1 })
|
|
91
|
+
expect(p).toEqual({ x: 12, y: 23, z: 34 })
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('types validators', () => {
|
|
96
|
+
it('validates enum-like string unions', () => {
|
|
97
|
+
expect(isValidBaseplateVisibilityType('automatic')).toBe(true)
|
|
98
|
+
expect(isValidBaseplateVisibilityType('nope')).toBe(false)
|
|
99
|
+
|
|
100
|
+
expect(isValidWorldScalingType('dynamic')).toBe(true)
|
|
101
|
+
expect(isValidWorldScalingType('nope')).toBe(false)
|
|
102
|
+
|
|
103
|
+
expect(isValidWorldAlignmentType('gravityAligned')).toBe(true)
|
|
104
|
+
expect(isValidWorldAlignmentType('nope')).toBe(false)
|
|
105
|
+
|
|
106
|
+
expect(isValidSpatialSceneType('window')).toBe(true)
|
|
107
|
+
expect(isValidSpatialSceneType('nope')).toBe(false)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('validates scene units', () => {
|
|
111
|
+
expect(isValidSceneUnit(0)).toBe(true)
|
|
112
|
+
expect(isValidSceneUnit(-1)).toBe(false)
|
|
113
|
+
expect(isValidSceneUnit('10px')).toBe(true)
|
|
114
|
+
expect(isValidSceneUnit('10m')).toBe(true)
|
|
115
|
+
expect(isValidSceneUnit('10cm')).toBe(false)
|
|
116
|
+
expect(isValidSceneUnit('px')).toBe(true)
|
|
117
|
+
expect(isValidSceneUnit('apx')).toBe(false)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe('CommandResultUtils', () => {
|
|
122
|
+
it('creates success and failure results', () => {
|
|
123
|
+
expect(CommandResultSuccess({ a: 1 })).toEqual({
|
|
124
|
+
success: true,
|
|
125
|
+
data: { a: 1 },
|
|
126
|
+
errorCode: '',
|
|
127
|
+
errorMessage: '',
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
expect(CommandResultFailure('E_TEST', 'bad')).toEqual({
|
|
131
|
+
success: false,
|
|
132
|
+
data: undefined,
|
|
133
|
+
errorCode: 'E_TEST',
|
|
134
|
+
errorMessage: 'bad',
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('SpatialWebEvent', () => {
|
|
140
|
+
it('dispatches to registered receiver', () => {
|
|
141
|
+
SpatialWebEvent.init()
|
|
142
|
+
const cb = vi.fn()
|
|
143
|
+
SpatialWebEvent.addEventReceiver('id1', cb)
|
|
144
|
+
|
|
145
|
+
window.__SpatialWebEvent({ id: 'id1', data: { ok: true } })
|
|
146
|
+
expect(cb).toHaveBeenCalledWith({ ok: true })
|
|
147
|
+
|
|
148
|
+
SpatialWebEvent.removeEventReceiver('id1')
|
|
149
|
+
window.__SpatialWebEvent({ id: 'id1', data: { ok: false } })
|
|
150
|
+
expect(cb).toHaveBeenCalledTimes(1)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
describe('SpatialWebEventCreator', () => {
|
|
155
|
+
it('creates a bubbling custom event with detail', () => {
|
|
156
|
+
const ev = createSpatialEvent('spatialmsg' as any, { x: 1 })
|
|
157
|
+
expect(ev.type).toBe('spatialmsg')
|
|
158
|
+
expect(ev.bubbles).toBe(true)
|
|
159
|
+
expect(ev.cancelable).toBe(false)
|
|
160
|
+
expect(ev.detail).toEqual({ x: 1 })
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('SpatialObject', () => {
|
|
165
|
+
afterEach(() => {
|
|
166
|
+
vi.resetModules()
|
|
167
|
+
vi.clearAllMocks()
|
|
168
|
+
vi.unmock('./JSBCommand')
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('inspect returns data when command succeeds', async () => {
|
|
172
|
+
vi.doMock('./JSBCommand', () => {
|
|
173
|
+
return {
|
|
174
|
+
InspectCommand: vi.fn().mockImplementation(() => ({
|
|
175
|
+
execute: vi.fn().mockResolvedValue({
|
|
176
|
+
success: true,
|
|
177
|
+
data: { a: 1 },
|
|
178
|
+
errorMessage: '',
|
|
179
|
+
}),
|
|
180
|
+
})),
|
|
181
|
+
DestroyCommand: vi.fn().mockImplementation(() => ({
|
|
182
|
+
execute: vi.fn().mockResolvedValue({
|
|
183
|
+
success: true,
|
|
184
|
+
data: undefined,
|
|
185
|
+
errorMessage: '',
|
|
186
|
+
}),
|
|
187
|
+
})),
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const { SpatialObject } = await import('./SpatialObject')
|
|
192
|
+
const obj = new SpatialObject('id')
|
|
193
|
+
await expect(obj.inspect()).resolves.toEqual({ a: 1 })
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('inspect throws when command fails', async () => {
|
|
197
|
+
vi.doMock('./JSBCommand', () => {
|
|
198
|
+
return {
|
|
199
|
+
InspectCommand: vi.fn().mockImplementation(() => ({
|
|
200
|
+
execute: vi.fn().mockResolvedValue({
|
|
201
|
+
success: false,
|
|
202
|
+
data: undefined,
|
|
203
|
+
errorMessage: 'nope',
|
|
204
|
+
}),
|
|
205
|
+
})),
|
|
206
|
+
DestroyCommand: vi.fn().mockImplementation(() => ({
|
|
207
|
+
execute: vi.fn().mockResolvedValue({
|
|
208
|
+
success: true,
|
|
209
|
+
data: undefined,
|
|
210
|
+
errorMessage: '',
|
|
211
|
+
}),
|
|
212
|
+
})),
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const { SpatialObject } = await import('./SpatialObject')
|
|
217
|
+
const obj = new SpatialObject('id')
|
|
218
|
+
await expect(obj.inspect()).rejects.toThrow('nope')
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('destroy calls onDestroy once and is idempotent', async () => {
|
|
222
|
+
const onDestroy = vi.fn()
|
|
223
|
+
vi.doMock('./JSBCommand', () => {
|
|
224
|
+
return {
|
|
225
|
+
InspectCommand: vi.fn().mockImplementation(() => ({
|
|
226
|
+
execute: vi.fn().mockResolvedValue({
|
|
227
|
+
success: true,
|
|
228
|
+
data: undefined,
|
|
229
|
+
errorMessage: '',
|
|
230
|
+
}),
|
|
231
|
+
})),
|
|
232
|
+
DestroyCommand: vi.fn().mockImplementation(() => ({
|
|
233
|
+
execute: vi.fn().mockResolvedValue({
|
|
234
|
+
success: true,
|
|
235
|
+
data: { ok: true },
|
|
236
|
+
errorMessage: '',
|
|
237
|
+
}),
|
|
238
|
+
})),
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const { SpatialObject } = await import('./SpatialObject')
|
|
243
|
+
class TestObject extends SpatialObject {
|
|
244
|
+
protected onDestroy() {
|
|
245
|
+
onDestroy()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const obj = new TestObject('id')
|
|
250
|
+
await expect(obj.destroy()).resolves.toEqual({ ok: true })
|
|
251
|
+
expect(obj.isDestroyed).toBe(true)
|
|
252
|
+
expect(onDestroy).toHaveBeenCalledTimes(1)
|
|
253
|
+
await expect(obj.destroy()).resolves.toBeUndefined()
|
|
254
|
+
expect(onDestroy).toHaveBeenCalledTimes(1)
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
describe('platform adapters', () => {
|
|
259
|
+
afterEach(() => {
|
|
260
|
+
vi.useRealTimers()
|
|
261
|
+
vi.resetModules()
|
|
262
|
+
vi.clearAllMocks()
|
|
263
|
+
vi.unmock('./JSBCommand')
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// it('AndroidPlatform.callJSB resolves from async SpatialWebEvent callback', async () => {
|
|
267
|
+
// const postMessage = vi.fn((rId: string) => {
|
|
268
|
+
// expect(rId.startsWith('rId_')).toBe(true)
|
|
269
|
+
// return ''
|
|
270
|
+
// })
|
|
271
|
+
// ;(window as any).webspatialBridge = { postMessage }
|
|
272
|
+
|
|
273
|
+
// const { AndroidPlatform } = await import(
|
|
274
|
+
// './platform-adapter/android/AndroidPlatform'
|
|
275
|
+
// )
|
|
276
|
+
// const { SpatialWebEvent: SpatialWebEventInstance } = await import(
|
|
277
|
+
// './SpatialWebEvent'
|
|
278
|
+
// )
|
|
279
|
+
// const platform = new AndroidPlatform()
|
|
280
|
+
// const p = platform.callJSB('c', '{"a":1}')
|
|
281
|
+
|
|
282
|
+
// const rId = postMessage.mock.calls[0]?.[0] as string
|
|
283
|
+
// SpatialWebEventInstance.eventReceiver[rId]({
|
|
284
|
+
// success: true,
|
|
285
|
+
// data: { ok: true },
|
|
286
|
+
// })
|
|
287
|
+
|
|
288
|
+
// await expect(p).resolves.toEqual({
|
|
289
|
+
// success: true,
|
|
290
|
+
// data: { ok: true },
|
|
291
|
+
// errorCode: '',
|
|
292
|
+
// errorMessage: '',
|
|
293
|
+
// })
|
|
294
|
+
// })
|
|
295
|
+
|
|
296
|
+
// it('AndroidPlatform.callJSB handles sync bridge response and failures', async () => {
|
|
297
|
+
// ;(window as any).webspatialBridge = {
|
|
298
|
+
// postMessage: vi.fn(() =>
|
|
299
|
+
// JSON.stringify({
|
|
300
|
+
// success: false,
|
|
301
|
+
// data: { code: 'E_SYNC', message: 'bad' },
|
|
302
|
+
// }),
|
|
303
|
+
// ),
|
|
304
|
+
// }
|
|
305
|
+
|
|
306
|
+
// const { AndroidPlatform } = await import(
|
|
307
|
+
// './platform-adapter/android/AndroidPlatform'
|
|
308
|
+
// )
|
|
309
|
+
// const platform = new AndroidPlatform()
|
|
310
|
+
// await expect(platform.callJSB('c', '{}')).resolves.toEqual({
|
|
311
|
+
// success: false,
|
|
312
|
+
// data: undefined,
|
|
313
|
+
// errorCode: 'E_SYNC',
|
|
314
|
+
// errorMessage: 'bad',
|
|
315
|
+
// })
|
|
316
|
+
// })
|
|
317
|
+
|
|
318
|
+
// it('AndroidPlatform.callWebSpatialProtocol polls and returns injected SpatialId', async () => {
|
|
319
|
+
// vi.useFakeTimers()
|
|
320
|
+
|
|
321
|
+
// let canCount = 0
|
|
322
|
+
// vi.doMock('./JSBCommand', () => {
|
|
323
|
+
// return {
|
|
324
|
+
// CheckWebViewCanCreateCommand: vi.fn().mockImplementation(() => ({
|
|
325
|
+
// execute: vi.fn().mockImplementation(() => {
|
|
326
|
+
// canCount += 1
|
|
327
|
+
// return Promise.resolve({
|
|
328
|
+
// success: true,
|
|
329
|
+
// data: { can: canCount >= 2 },
|
|
330
|
+
// errorCode: '',
|
|
331
|
+
// errorMessage: '',
|
|
332
|
+
// })
|
|
333
|
+
// }),
|
|
334
|
+
// })),
|
|
335
|
+
// }
|
|
336
|
+
// })
|
|
337
|
+
|
|
338
|
+
// const windowProxy: any = {}
|
|
339
|
+
// const openFn = vi.fn()
|
|
340
|
+
// ;(window as any).open = vi.fn(() => windowProxy)
|
|
341
|
+
|
|
342
|
+
// setTimeout(() => {
|
|
343
|
+
// windowProxy.open = openFn
|
|
344
|
+
// }, 20)
|
|
345
|
+
|
|
346
|
+
// const { AndroidPlatform } = await import(
|
|
347
|
+
// './platform-adapter/android/AndroidPlatform'
|
|
348
|
+
// )
|
|
349
|
+
// const { SpatialWebEvent: SpatialWebEventInstance } = await import(
|
|
350
|
+
// './SpatialWebEvent'
|
|
351
|
+
// )
|
|
352
|
+
// const platform = new AndroidPlatform()
|
|
353
|
+
// const p = platform.callWebSpatialProtocol('open', 'x=1', '_blank', '')
|
|
354
|
+
|
|
355
|
+
// const receiverIds = Object.keys(SpatialWebEventInstance.eventReceiver)
|
|
356
|
+
// const createdId = receiverIds[0] as string
|
|
357
|
+
// const loadedId = receiverIds[1] as string
|
|
358
|
+
// SpatialWebEventInstance.eventReceiver[createdId]?.({ spatialId: 'temp' })
|
|
359
|
+
// await vi.advanceTimersByTimeAsync(100)
|
|
360
|
+
// SpatialWebEventInstance.eventReceiver[loadedId]?.({
|
|
361
|
+
// spatialId: 'spatial-1',
|
|
362
|
+
// })
|
|
363
|
+
|
|
364
|
+
// await vi.advanceTimersByTimeAsync(200)
|
|
365
|
+
|
|
366
|
+
// const result = await p
|
|
367
|
+
// expect(result.success).toBe(true)
|
|
368
|
+
// expect(result.data.id).toBe('spatial-1')
|
|
369
|
+
// expect(result.data.windowProxy).toBe(windowProxy)
|
|
370
|
+
// const call = openFn.mock.calls[0] as unknown as [string, string | undefined]
|
|
371
|
+
// expect(call?.[0]).toMatch(/^about:blank\?rid=/)
|
|
372
|
+
// expect(call?.[1]).toBe('_self')
|
|
373
|
+
// })
|
|
374
|
+
|
|
375
|
+
it('SSRPlatform returns successful no-op results', async () => {
|
|
376
|
+
const { SSRPlatform } = await import('./platform-adapter/ssr/SSRPlatform')
|
|
377
|
+
const platform = new SSRPlatform()
|
|
378
|
+
|
|
379
|
+
await expect(platform.callJSB('c', '{}')).resolves.toMatchObject({
|
|
380
|
+
success: true,
|
|
381
|
+
})
|
|
382
|
+
await expect(
|
|
383
|
+
platform.callWebSpatialProtocol('s', '', '', ''),
|
|
384
|
+
).resolves.toMatchObject({
|
|
385
|
+
success: true,
|
|
386
|
+
})
|
|
387
|
+
expect(platform.callWebSpatialProtocolSync('s', '', '', '')).toMatchObject({
|
|
388
|
+
success: true,
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('VisionOSPlatform.callJSB returns success and parses failures', async () => {
|
|
393
|
+
;(window as any).webkit = {
|
|
394
|
+
messageHandlers: {
|
|
395
|
+
bridge: {
|
|
396
|
+
postMessage: vi
|
|
397
|
+
.fn()
|
|
398
|
+
.mockResolvedValueOnce({ a: 1 })
|
|
399
|
+
.mockRejectedValueOnce({
|
|
400
|
+
message: JSON.stringify({ code: 'E_VOS', message: 'nope' }),
|
|
401
|
+
}),
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const { VisionOSPlatform } = await import(
|
|
407
|
+
'./platform-adapter/vision-os/VisionOSPlatform'
|
|
408
|
+
)
|
|
409
|
+
const platform = new VisionOSPlatform()
|
|
410
|
+
|
|
411
|
+
await expect(platform.callJSB('c', '{}')).resolves.toEqual({
|
|
412
|
+
success: true,
|
|
413
|
+
data: { a: 1 },
|
|
414
|
+
errorCode: '',
|
|
415
|
+
errorMessage: '',
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
await expect(platform.callJSB('c', '{}')).resolves.toEqual({
|
|
419
|
+
success: false,
|
|
420
|
+
data: undefined,
|
|
421
|
+
errorCode: 'E_VOS',
|
|
422
|
+
errorMessage: 'nope',
|
|
423
|
+
})
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('VisionOSPlatform parses SpatialId from userAgent', async () => {
|
|
427
|
+
const uuid = '12345678-1234-1234-1234-1234567890ab'
|
|
428
|
+
const windowProxy: any = {
|
|
429
|
+
navigator: { userAgent: `x ${uuid} y` },
|
|
430
|
+
}
|
|
431
|
+
;(window as any).open = vi.fn(() => windowProxy)
|
|
432
|
+
;(window as any).webkit = {
|
|
433
|
+
messageHandlers: { bridge: { postMessage: vi.fn() } },
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const { VisionOSPlatform } = await import(
|
|
437
|
+
'./platform-adapter/vision-os/VisionOSPlatform'
|
|
438
|
+
)
|
|
439
|
+
const platform = new VisionOSPlatform()
|
|
440
|
+
const r = await platform.callWebSpatialProtocol('open', 'a=1')
|
|
441
|
+
|
|
442
|
+
expect(r.success).toBe(true)
|
|
443
|
+
expect(r.data.id).toBe(uuid)
|
|
444
|
+
expect(r.data.windowProxy).toBe(windowProxy)
|
|
445
|
+
})
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
describe('geometries', () => {
|
|
449
|
+
it('constructs cone/cylinder/plane/sphere geometries', async () => {
|
|
450
|
+
const { SpatialConeGeometry } = await import(
|
|
451
|
+
'./reality/geometry/SpatialConeGeometry'
|
|
452
|
+
)
|
|
453
|
+
const { SpatialCylinderGeometry } = await import(
|
|
454
|
+
'./reality/geometry/SpatialCylinderGeometry'
|
|
455
|
+
)
|
|
456
|
+
const { SpatialPlaneGeometry } = await import(
|
|
457
|
+
'./reality/geometry/SpatialPlaneGeometry'
|
|
458
|
+
)
|
|
459
|
+
const { SpatialSphereGeometry } = await import(
|
|
460
|
+
'./reality/geometry/SpatialSphereGeometry'
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
const cone = new SpatialConeGeometry('c1', { radius: 1, height: 2 })
|
|
464
|
+
const cylinder = new SpatialCylinderGeometry('cy1', {
|
|
465
|
+
radius: 1,
|
|
466
|
+
height: 2,
|
|
467
|
+
})
|
|
468
|
+
const plane = new SpatialPlaneGeometry('p1', { width: 1 })
|
|
469
|
+
const sphere = new SpatialSphereGeometry('s1', { radius: 1 })
|
|
470
|
+
|
|
471
|
+
expect(SpatialConeGeometry.type).toBe('ConeGeometry')
|
|
472
|
+
expect(SpatialCylinderGeometry.type).toBe('CylinderGeometry')
|
|
473
|
+
expect(SpatialPlaneGeometry.type).toBe('PlaneGeometry')
|
|
474
|
+
expect(SpatialSphereGeometry.type).toBe('SphereGeometry')
|
|
475
|
+
|
|
476
|
+
expect(cone.id).toBe('c1')
|
|
477
|
+
expect(cylinder.id).toBe('cy1')
|
|
478
|
+
expect(plane.id).toBe('p1')
|
|
479
|
+
expect(sphere.id).toBe('s1')
|
|
480
|
+
})
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
describe('spatialWindowPolyfill', () => {
|
|
484
|
+
afterEach(() => {
|
|
485
|
+
vi.resetModules()
|
|
486
|
+
vi.clearAllMocks()
|
|
487
|
+
vi.unmock('./Spatial')
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
it('returns early when not running in SpatialWeb', async () => {
|
|
491
|
+
vi.doMock('./Spatial', () => {
|
|
492
|
+
return {
|
|
493
|
+
Spatial: class {
|
|
494
|
+
runInSpatialWeb() {
|
|
495
|
+
return false
|
|
496
|
+
}
|
|
497
|
+
requestSession() {
|
|
498
|
+
throw new Error('should not be called')
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
}
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
const { spatialWindowPolyfill } = await import('./spatial-window-polyfill')
|
|
505
|
+
await expect(spatialWindowPolyfill()).resolves.toBeUndefined()
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it('updates scene properties and reacts to background material changes', async () => {
|
|
509
|
+
const updateSpatialProperties = vi.fn()
|
|
510
|
+
const mockSession: any = {
|
|
511
|
+
getSpatialScene: () => ({
|
|
512
|
+
updateSpatialProperties,
|
|
513
|
+
}),
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
vi.doMock('./Spatial', () => {
|
|
517
|
+
return {
|
|
518
|
+
Spatial: class {
|
|
519
|
+
runInSpatialWeb() {
|
|
520
|
+
return true
|
|
521
|
+
}
|
|
522
|
+
requestSession() {
|
|
523
|
+
return mockSession
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
}
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
Object.defineProperty(document, 'readyState', {
|
|
530
|
+
value: 'complete',
|
|
531
|
+
configurable: true,
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
document.documentElement.style.width = '200px'
|
|
535
|
+
document.documentElement.style.setProperty(
|
|
536
|
+
'--xr-background-material',
|
|
537
|
+
'translucent',
|
|
538
|
+
)
|
|
539
|
+
document.documentElement.style.setProperty('border-top-left-radius', '10px')
|
|
540
|
+
document.documentElement.style.setProperty('border-top-right-radius', '5%')
|
|
541
|
+
document.documentElement.style.opacity = '0.5'
|
|
542
|
+
|
|
543
|
+
const { spatialWindowPolyfill } = await import('./spatial-window-polyfill')
|
|
544
|
+
await spatialWindowPolyfill()
|
|
545
|
+
|
|
546
|
+
expect(updateSpatialProperties).toHaveBeenCalledWith({
|
|
547
|
+
material: 'translucent',
|
|
548
|
+
})
|
|
549
|
+
expect(updateSpatialProperties).toHaveBeenCalledWith({
|
|
550
|
+
cornerRadius: {
|
|
551
|
+
topLeading: 10,
|
|
552
|
+
topTrailing: 10,
|
|
553
|
+
bottomLeading: 0,
|
|
554
|
+
bottomTrailing: 0,
|
|
555
|
+
},
|
|
556
|
+
})
|
|
557
|
+
expect(updateSpatialProperties).toHaveBeenCalledWith({
|
|
558
|
+
opacity: 0.5,
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
document.documentElement.style.setProperty(
|
|
562
|
+
'--xr-background-material',
|
|
563
|
+
'regular',
|
|
564
|
+
)
|
|
565
|
+
expect(updateSpatialProperties).toHaveBeenCalledWith({
|
|
566
|
+
material: 'regular',
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
document.documentElement.style.removeProperty('--xr-background-material')
|
|
570
|
+
expect(updateSpatialProperties).toHaveBeenCalledWith({
|
|
571
|
+
material: 'none',
|
|
572
|
+
})
|
|
573
|
+
})
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
describe('SpatializedElementCreator', () => {
|
|
577
|
+
afterEach(() => {
|
|
578
|
+
vi.resetModules()
|
|
579
|
+
vi.clearAllMocks()
|
|
580
|
+
vi.unmock('./JSBCommand')
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it('createSpatialized2DElement sets base href and returns element', async () => {
|
|
584
|
+
const windowProxy: any = {
|
|
585
|
+
document: { head: { innerHTML: '' } },
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
vi.doMock('./JSBCommand', () => {
|
|
589
|
+
class OkCommand {
|
|
590
|
+
execute() {
|
|
591
|
+
return Promise.resolve({
|
|
592
|
+
success: true,
|
|
593
|
+
data: undefined,
|
|
594
|
+
errorCode: '',
|
|
595
|
+
errorMessage: '',
|
|
596
|
+
})
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
InspectCommand: OkCommand,
|
|
602
|
+
DestroyCommand: OkCommand,
|
|
603
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
604
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
605
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
606
|
+
UpdateSpatializedStatic3DElementProperties: OkCommand,
|
|
607
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
608
|
+
SetParentForEntityCommand: OkCommand,
|
|
609
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
610
|
+
createSpatialized2DElementCommand: vi.fn().mockImplementation(() => ({
|
|
611
|
+
execute: vi.fn().mockResolvedValue({
|
|
612
|
+
success: true,
|
|
613
|
+
data: { id: 'w1', windowProxy },
|
|
614
|
+
errorCode: '',
|
|
615
|
+
errorMessage: '',
|
|
616
|
+
}),
|
|
617
|
+
})),
|
|
618
|
+
CreateSpatializedStatic3DElementCommand: vi
|
|
619
|
+
.fn()
|
|
620
|
+
.mockImplementation(() => ({
|
|
621
|
+
execute: vi.fn().mockResolvedValue({
|
|
622
|
+
success: true,
|
|
623
|
+
data: { id: 's-default' },
|
|
624
|
+
errorCode: '',
|
|
625
|
+
errorMessage: '',
|
|
626
|
+
}),
|
|
627
|
+
})),
|
|
628
|
+
CreateSpatializedDynamic3DElementCommand: vi
|
|
629
|
+
.fn()
|
|
630
|
+
.mockImplementation(() => ({
|
|
631
|
+
execute: vi.fn().mockResolvedValue({
|
|
632
|
+
success: true,
|
|
633
|
+
data: { id: 'd-default' },
|
|
634
|
+
errorCode: '',
|
|
635
|
+
errorMessage: '',
|
|
636
|
+
}),
|
|
637
|
+
})),
|
|
638
|
+
}
|
|
639
|
+
})
|
|
640
|
+
|
|
641
|
+
const { createSpatialized2DElement } = await import(
|
|
642
|
+
'./SpatializedElementCreator'
|
|
643
|
+
)
|
|
644
|
+
const el = await createSpatialized2DElement()
|
|
645
|
+
|
|
646
|
+
expect(el.id).toBe('w1')
|
|
647
|
+
expect(windowProxy.document.head.innerHTML).toContain('<base href="')
|
|
648
|
+
expect(windowProxy.document.head.innerHTML).toContain(document.baseURI)
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
it('createSpatialized2DElement throws when command fails', async () => {
|
|
652
|
+
vi.doMock('./JSBCommand', () => {
|
|
653
|
+
class OkCommand {
|
|
654
|
+
execute() {
|
|
655
|
+
return Promise.resolve({
|
|
656
|
+
success: true,
|
|
657
|
+
data: undefined,
|
|
658
|
+
errorCode: '',
|
|
659
|
+
errorMessage: '',
|
|
660
|
+
})
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return {
|
|
665
|
+
InspectCommand: OkCommand,
|
|
666
|
+
DestroyCommand: OkCommand,
|
|
667
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
668
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
669
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
670
|
+
UpdateSpatializedStatic3DElementProperties: OkCommand,
|
|
671
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
672
|
+
SetParentForEntityCommand: OkCommand,
|
|
673
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
674
|
+
createSpatialized2DElementCommand: vi.fn().mockImplementation(() => ({
|
|
675
|
+
execute: vi.fn().mockResolvedValue({
|
|
676
|
+
success: false,
|
|
677
|
+
data: undefined,
|
|
678
|
+
errorCode: 'E',
|
|
679
|
+
errorMessage: 'bad',
|
|
680
|
+
}),
|
|
681
|
+
})),
|
|
682
|
+
CreateSpatializedStatic3DElementCommand: vi.fn(),
|
|
683
|
+
CreateSpatializedDynamic3DElementCommand: vi.fn(),
|
|
684
|
+
}
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
const { createSpatialized2DElement } = await import(
|
|
688
|
+
'./SpatializedElementCreator'
|
|
689
|
+
)
|
|
690
|
+
await expect(createSpatialized2DElement()).rejects.toThrow(
|
|
691
|
+
'createSpatialized2DElement failed',
|
|
692
|
+
)
|
|
693
|
+
})
|
|
694
|
+
|
|
695
|
+
it('createSpatializedStatic3DElement returns element and throws on failure', async () => {
|
|
696
|
+
vi.doMock('./JSBCommand', () => {
|
|
697
|
+
class OkCommand {
|
|
698
|
+
execute() {
|
|
699
|
+
return Promise.resolve({
|
|
700
|
+
success: true,
|
|
701
|
+
data: undefined,
|
|
702
|
+
errorCode: '',
|
|
703
|
+
errorMessage: '',
|
|
704
|
+
})
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
InspectCommand: OkCommand,
|
|
710
|
+
DestroyCommand: OkCommand,
|
|
711
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
712
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
713
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
714
|
+
UpdateSpatializedStatic3DElementProperties: OkCommand,
|
|
715
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
716
|
+
SetParentForEntityCommand: OkCommand,
|
|
717
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
718
|
+
createSpatialized2DElementCommand: vi.fn(),
|
|
719
|
+
CreateSpatializedDynamic3DElementCommand: vi.fn(),
|
|
720
|
+
CreateSpatializedStatic3DElementCommand: vi
|
|
721
|
+
.fn()
|
|
722
|
+
.mockImplementationOnce(() => ({
|
|
723
|
+
execute: vi.fn().mockResolvedValue({
|
|
724
|
+
success: true,
|
|
725
|
+
data: { id: 's3' },
|
|
726
|
+
errorCode: '',
|
|
727
|
+
errorMessage: '',
|
|
728
|
+
}),
|
|
729
|
+
}))
|
|
730
|
+
.mockImplementationOnce(() => ({
|
|
731
|
+
execute: vi.fn().mockResolvedValue({
|
|
732
|
+
success: false,
|
|
733
|
+
data: undefined,
|
|
734
|
+
errorCode: 'E',
|
|
735
|
+
errorMessage: 'bad',
|
|
736
|
+
}),
|
|
737
|
+
})),
|
|
738
|
+
}
|
|
739
|
+
})
|
|
740
|
+
|
|
741
|
+
const { createSpatializedStatic3DElement } = await import(
|
|
742
|
+
'./SpatializedElementCreator'
|
|
743
|
+
)
|
|
744
|
+
const ok = await createSpatializedStatic3DElement('u')
|
|
745
|
+
expect(ok.id).toBe('s3')
|
|
746
|
+
|
|
747
|
+
await expect(createSpatializedStatic3DElement('u')).rejects.toThrow(
|
|
748
|
+
'createSpatializedStatic3DElement failed',
|
|
749
|
+
)
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
it('createSpatializedDynamic3DElement returns element and throws on failure', async () => {
|
|
753
|
+
vi.doMock('./JSBCommand', () => {
|
|
754
|
+
class OkCommand {
|
|
755
|
+
execute() {
|
|
756
|
+
return Promise.resolve({
|
|
757
|
+
success: true,
|
|
758
|
+
data: undefined,
|
|
759
|
+
errorCode: '',
|
|
760
|
+
errorMessage: '',
|
|
761
|
+
})
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
InspectCommand: OkCommand,
|
|
767
|
+
DestroyCommand: OkCommand,
|
|
768
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
769
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
770
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
771
|
+
UpdateSpatializedStatic3DElementProperties: OkCommand,
|
|
772
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
773
|
+
SetParentForEntityCommand: OkCommand,
|
|
774
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
775
|
+
createSpatialized2DElementCommand: vi.fn(),
|
|
776
|
+
CreateSpatializedStatic3DElementCommand: vi.fn(),
|
|
777
|
+
CreateSpatializedDynamic3DElementCommand: vi
|
|
778
|
+
.fn()
|
|
779
|
+
.mockImplementationOnce(() => ({
|
|
780
|
+
execute: vi.fn().mockResolvedValue({
|
|
781
|
+
success: true,
|
|
782
|
+
data: { id: 'd3' },
|
|
783
|
+
errorCode: '',
|
|
784
|
+
errorMessage: '',
|
|
785
|
+
}),
|
|
786
|
+
}))
|
|
787
|
+
.mockImplementationOnce(() => ({
|
|
788
|
+
execute: vi.fn().mockResolvedValue({
|
|
789
|
+
success: false,
|
|
790
|
+
data: undefined,
|
|
791
|
+
errorCode: 'E',
|
|
792
|
+
errorMessage: 'bad',
|
|
793
|
+
}),
|
|
794
|
+
})),
|
|
795
|
+
}
|
|
796
|
+
})
|
|
797
|
+
|
|
798
|
+
const { createSpatializedDynamic3DElement } = await import(
|
|
799
|
+
'./SpatializedElementCreator'
|
|
800
|
+
)
|
|
801
|
+
const ok = await createSpatializedDynamic3DElement()
|
|
802
|
+
expect(ok.id).toBe('d3')
|
|
803
|
+
|
|
804
|
+
await expect(createSpatializedDynamic3DElement()).rejects.toThrow(
|
|
805
|
+
'createSpatializedDynamic3DElement failed',
|
|
806
|
+
)
|
|
807
|
+
})
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
describe('SpatializedStatic3DElement', () => {
|
|
811
|
+
afterEach(() => {
|
|
812
|
+
vi.resetModules()
|
|
813
|
+
vi.clearAllMocks()
|
|
814
|
+
vi.unmock('./JSBCommand')
|
|
815
|
+
})
|
|
816
|
+
|
|
817
|
+
it('resets ready when modelURL changes and resolves on load events', async () => {
|
|
818
|
+
const execute = vi.fn().mockResolvedValue({
|
|
819
|
+
success: true,
|
|
820
|
+
data: undefined,
|
|
821
|
+
errorCode: '',
|
|
822
|
+
errorMessage: '',
|
|
823
|
+
})
|
|
824
|
+
vi.doMock('./JSBCommand', () => {
|
|
825
|
+
class OkCommand {
|
|
826
|
+
execute() {
|
|
827
|
+
return Promise.resolve({
|
|
828
|
+
success: true,
|
|
829
|
+
data: undefined,
|
|
830
|
+
errorCode: '',
|
|
831
|
+
errorMessage: '',
|
|
832
|
+
})
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return {
|
|
837
|
+
InspectCommand: OkCommand,
|
|
838
|
+
DestroyCommand: OkCommand,
|
|
839
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
840
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
841
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
842
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
843
|
+
SetParentForEntityCommand: OkCommand,
|
|
844
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
845
|
+
createSpatialized2DElementCommand: vi.fn(),
|
|
846
|
+
CreateSpatializedStatic3DElementCommand: vi.fn(),
|
|
847
|
+
CreateSpatializedDynamic3DElementCommand: vi.fn(),
|
|
848
|
+
UpdateSpatializedStatic3DElementProperties: vi
|
|
849
|
+
.fn()
|
|
850
|
+
.mockImplementation(() => ({ execute })),
|
|
851
|
+
}
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
const { SpatializedStatic3DElement } = await import(
|
|
855
|
+
'./SpatializedStatic3DElement'
|
|
856
|
+
)
|
|
857
|
+
const { SpatialWebMsgType } = await import('./WebMsgCommand')
|
|
858
|
+
|
|
859
|
+
const el = new SpatializedStatic3DElement('m1')
|
|
860
|
+
const onLoad = vi.fn()
|
|
861
|
+
const onFail = vi.fn()
|
|
862
|
+
el.onLoadCallback = onLoad
|
|
863
|
+
el.onLoadFailureCallback = onFail
|
|
864
|
+
|
|
865
|
+
const p1 = el.ready
|
|
866
|
+
await el.updateProperties({ modelURL: 'a.glb' } as any)
|
|
867
|
+
expect(execute).toHaveBeenCalledTimes(1)
|
|
868
|
+
const p2 = el.ready
|
|
869
|
+
expect(p2).not.toBe(p1)
|
|
870
|
+
|
|
871
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloaded } as any)
|
|
872
|
+
await expect(p2).resolves.toBe(true)
|
|
873
|
+
expect(onLoad).toHaveBeenCalledTimes(1)
|
|
874
|
+
|
|
875
|
+
await el.updateProperties({ modelURL: 'b.glb' } as any)
|
|
876
|
+
const p3 = el.ready
|
|
877
|
+
el.onReceiveEvent({ type: SpatialWebMsgType.modelloadfailed } as any)
|
|
878
|
+
await expect(p3).resolves.toBe(false)
|
|
879
|
+
expect(onFail).toHaveBeenCalledTimes(1)
|
|
880
|
+
})
|
|
881
|
+
|
|
882
|
+
it('updateModelTransform passes float64 array to updateProperties', async () => {
|
|
883
|
+
const execute = vi.fn().mockResolvedValue({
|
|
884
|
+
success: true,
|
|
885
|
+
data: undefined,
|
|
886
|
+
errorCode: '',
|
|
887
|
+
errorMessage: '',
|
|
888
|
+
})
|
|
889
|
+
vi.doMock('./JSBCommand', () => {
|
|
890
|
+
class OkCommand {
|
|
891
|
+
execute() {
|
|
892
|
+
return Promise.resolve({
|
|
893
|
+
success: true,
|
|
894
|
+
data: undefined,
|
|
895
|
+
errorCode: '',
|
|
896
|
+
errorMessage: '',
|
|
897
|
+
})
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
return {
|
|
902
|
+
InspectCommand: OkCommand,
|
|
903
|
+
DestroyCommand: OkCommand,
|
|
904
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
905
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
906
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
907
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
908
|
+
SetParentForEntityCommand: OkCommand,
|
|
909
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
910
|
+
createSpatialized2DElementCommand: vi.fn(),
|
|
911
|
+
CreateSpatializedStatic3DElementCommand: vi.fn(),
|
|
912
|
+
CreateSpatializedDynamic3DElementCommand: vi.fn(),
|
|
913
|
+
UpdateSpatializedStatic3DElementProperties: vi
|
|
914
|
+
.fn()
|
|
915
|
+
.mockImplementation(() => ({ execute })),
|
|
916
|
+
}
|
|
917
|
+
})
|
|
918
|
+
|
|
919
|
+
const { SpatializedStatic3DElement } = await import(
|
|
920
|
+
'./SpatializedStatic3DElement'
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
class DOMMatrixWithArray {
|
|
924
|
+
toFloat64Array() {
|
|
925
|
+
return new Float64Array([
|
|
926
|
+
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 10, 20, 30, 1,
|
|
927
|
+
])
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const el = new SpatializedStatic3DElement('m2')
|
|
932
|
+
el.updateModelTransform(new DOMMatrixWithArray() as any)
|
|
933
|
+
expect(execute).toHaveBeenCalledTimes(1)
|
|
934
|
+
})
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
describe('SpatializedDynamic3DElement', () => {
|
|
938
|
+
afterEach(() => {
|
|
939
|
+
vi.resetModules()
|
|
940
|
+
vi.clearAllMocks()
|
|
941
|
+
vi.unmock('./JSBCommand')
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
it('addEntity sets parent, pushes children, and calls SetParentForEntityCommand', async () => {
|
|
945
|
+
const execute = vi.fn().mockResolvedValue({
|
|
946
|
+
success: true,
|
|
947
|
+
data: undefined,
|
|
948
|
+
errorCode: '',
|
|
949
|
+
errorMessage: '',
|
|
950
|
+
})
|
|
951
|
+
vi.doMock('./JSBCommand', () => {
|
|
952
|
+
class OkCommand {
|
|
953
|
+
execute() {
|
|
954
|
+
return Promise.resolve({
|
|
955
|
+
success: true,
|
|
956
|
+
data: undefined,
|
|
957
|
+
errorCode: '',
|
|
958
|
+
errorMessage: '',
|
|
959
|
+
})
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return {
|
|
964
|
+
InspectCommand: OkCommand,
|
|
965
|
+
DestroyCommand: OkCommand,
|
|
966
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
967
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
968
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
969
|
+
UpdateSpatializedStatic3DElementProperties: OkCommand,
|
|
970
|
+
UpdateSpatializedDynamic3DElementProperties: OkCommand,
|
|
971
|
+
createSpatialized2DElementCommand: vi.fn(),
|
|
972
|
+
CreateSpatializedStatic3DElementCommand: vi.fn(),
|
|
973
|
+
CreateSpatializedDynamic3DElementCommand: vi.fn(),
|
|
974
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
975
|
+
SetParentForEntityCommand: vi
|
|
976
|
+
.fn()
|
|
977
|
+
.mockImplementation(() => ({ execute })),
|
|
978
|
+
}
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
const { SpatializedDynamic3DElement } = await import(
|
|
982
|
+
'./SpatializedDynamic3DElement'
|
|
983
|
+
)
|
|
984
|
+
const el = new SpatializedDynamic3DElement('d1')
|
|
985
|
+
|
|
986
|
+
const entity: any = { id: 'e1', parent: undefined }
|
|
987
|
+
await el.addEntity(entity)
|
|
988
|
+
|
|
989
|
+
expect(entity.parent).toBe(el)
|
|
990
|
+
expect(el.children).toContain(entity)
|
|
991
|
+
expect(execute).toHaveBeenCalledTimes(1)
|
|
992
|
+
})
|
|
993
|
+
|
|
994
|
+
it('updateProperties calls UpdateSpatializedDynamic3DElementProperties', async () => {
|
|
995
|
+
const execute = vi.fn().mockResolvedValue({
|
|
996
|
+
success: true,
|
|
997
|
+
data: undefined,
|
|
998
|
+
errorCode: '',
|
|
999
|
+
errorMessage: '',
|
|
1000
|
+
})
|
|
1001
|
+
vi.doMock('./JSBCommand', () => {
|
|
1002
|
+
class OkCommand {
|
|
1003
|
+
execute() {
|
|
1004
|
+
return Promise.resolve({
|
|
1005
|
+
success: true,
|
|
1006
|
+
data: undefined,
|
|
1007
|
+
errorCode: '',
|
|
1008
|
+
errorMessage: '',
|
|
1009
|
+
})
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
return {
|
|
1014
|
+
InspectCommand: OkCommand,
|
|
1015
|
+
DestroyCommand: OkCommand,
|
|
1016
|
+
UpdateSpatializedElementTransform: OkCommand,
|
|
1017
|
+
UpdateSpatialized2DElementProperties: OkCommand,
|
|
1018
|
+
AddSpatializedElementToSpatialized2DElement: OkCommand,
|
|
1019
|
+
UpdateSpatializedStatic3DElementProperties: OkCommand,
|
|
1020
|
+
SetParentForEntityCommand: OkCommand,
|
|
1021
|
+
AddEntityToDynamic3DCommand: OkCommand,
|
|
1022
|
+
createSpatialized2DElementCommand: vi.fn(),
|
|
1023
|
+
CreateSpatializedStatic3DElementCommand: vi.fn(),
|
|
1024
|
+
CreateSpatializedDynamic3DElementCommand: vi.fn(),
|
|
1025
|
+
UpdateSpatializedDynamic3DElementProperties: vi
|
|
1026
|
+
.fn()
|
|
1027
|
+
.mockImplementation(() => ({ execute })),
|
|
1028
|
+
}
|
|
1029
|
+
})
|
|
1030
|
+
|
|
1031
|
+
const { SpatializedDynamic3DElement } = await import(
|
|
1032
|
+
'./SpatializedDynamic3DElement'
|
|
1033
|
+
)
|
|
1034
|
+
const el = new SpatializedDynamic3DElement('d2')
|
|
1035
|
+
await el.updateProperties({ visible: true } as any)
|
|
1036
|
+
expect(execute).toHaveBeenCalledTimes(1)
|
|
1037
|
+
})
|
|
1038
|
+
})
|
|
1039
|
+
|
|
1040
|
+
describe('ssr-polyfill', () => {
|
|
1041
|
+
it('isSSREnv returns false in jsdom', async () => {
|
|
1042
|
+
const { isSSREnv } = await import('./ssr-polyfill')
|
|
1043
|
+
expect(isSSREnv()).toBe(false)
|
|
1044
|
+
})
|
|
1045
|
+
})
|
|
1046
|
+
|
|
1047
|
+
describe('platform-adapter', () => {
|
|
1048
|
+
it('createPlatform returns SSRPlatform in SSR env', async () => {
|
|
1049
|
+
vi.resetModules()
|
|
1050
|
+
vi.doMock('./ssr-polyfill', () => {
|
|
1051
|
+
return { isSSREnv: () => true }
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
const { createPlatform } = await import('./platform-adapter')
|
|
1055
|
+
const p = createPlatform() as any
|
|
1056
|
+
expect(typeof p.callJSB).toBe('function')
|
|
1057
|
+
expect(typeof p.callWebSpatialProtocol).toBe('function')
|
|
1058
|
+
expect(typeof p.callWebSpatialProtocolSync).toBe('function')
|
|
1059
|
+
})
|
|
1060
|
+
})
|