@shopware-ag/dive 1.6.4 → 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopware-ag/dive",
3
- "version": "1.6.4",
3
+ "version": "1.7.0",
4
4
  "description": "Shopware Spatial Framework",
5
5
  "type": "module",
6
6
  "main": "./build/dive.cjs",
@@ -137,6 +137,7 @@ jest.mock('../controls/OrbitControls.ts', () => {
137
137
  id: undefined,
138
138
  }
139
139
  this.removeFromParent = jest.fn();
140
+ this.Dispose = jest.fn();
140
141
  return this;
141
142
  });
142
143
  });
@@ -243,6 +244,11 @@ describe('dive/DIVE', () => {
243
244
  expect(dive.Communication).toBeDefined();
244
245
  });
245
246
 
247
+ it('should have Info', () => {
248
+ const dive = new DIVE();
249
+ expect(dive.Info).toBeDefined();
250
+ });
251
+
246
252
  it('should resize', () => {
247
253
  const dive = new DIVE();
248
254
  expect(() => dive.OnResize(800, 600)).not.toThrow();
@@ -41,10 +41,16 @@ export default class DIVECommunication {
41
41
  private static __instances: DIVECommunication[] = [];
42
42
 
43
43
  public static get(id: string): DIVECommunication | undefined {
44
+ const fromComID = this.__instances.find((instance) => instance.id === id);
45
+ if (fromComID) return fromComID;
44
46
  return this.__instances.find((instance) => Array.from(instance.registered.values()).find((object) => object.id === id));
45
47
  }
46
48
 
47
- private id: string;
49
+ private _id: string;
50
+ public get id(): string {
51
+ return this._id;
52
+ }
53
+
48
54
  private renderer: DIVERenderer;
49
55
  private scene: DIVEScene;
50
56
  private controller: DIVEOrbitControls;
@@ -65,7 +71,7 @@ export default class DIVECommunication {
65
71
  private listeners: Map<keyof Actions, EventListener<keyof Actions>[]> = new Map();
66
72
 
67
73
  constructor(renderer: DIVERenderer, scene: DIVEScene, controls: DIVEOrbitControls, toolbox: DIVEToolbox) {
68
- this.id = generateUUID();
74
+ this._id = generateUUID();
69
75
  this.renderer = renderer;
70
76
  this.scene = scene;
71
77
  this.controller = controls;
@@ -186,10 +186,19 @@ describe('dive/communication/DIVECommunication', () => {
186
186
 
187
187
  it('should instantiate', () => {
188
188
  expect(testCom).toBeDefined();
189
- expect(() => DIVECommunication.get('id')).not.toThrow();
190
189
  expect(DIVECommunication['__instances']).toHaveLength(1);
191
190
  });
192
191
 
192
+ it('should get instance', () => {
193
+ expect(testCom).toBeDefined();
194
+ expect(DIVECommunication.get(testCom.id)).toBeDefined();
195
+
196
+ testCom.PerformAction('ADD_OBJECT', {
197
+ id: 'someID',
198
+ } as COMModel);
199
+ expect(DIVECommunication.get('someID')).toBeDefined();
200
+ });
201
+
193
202
  it('should destroy instance', () => {
194
203
  expect(testCom).toBeDefined();
195
204
  testCom.DestroyInstance();
@@ -670,6 +679,14 @@ describe('dive/communication/DIVECommunication', () => {
670
679
  expect(visibility).toBe(false);
671
680
  });
672
681
 
682
+ it('should perform action USE_TOOL', () => {
683
+ let success = testCom.PerformAction('USE_TOOL', { tool: 'select' });
684
+ expect(success).toBe(true);
685
+
686
+ success = testCom.PerformAction('USE_TOOL', { tool: 'none' });
687
+ expect(success).toBe(true);
688
+ });
689
+
673
690
  it('should perform action MODEL_LOADED', () => {
674
691
  const payload = {
675
692
  entityType: "model",
@@ -37,6 +37,8 @@ export default class DIVEOrbitControls extends OrbitControls {
37
37
  public object: DIVEPerspectiveCamera;
38
38
  public domElement: HTMLCanvasElement;
39
39
 
40
+ private _removePreRenderCallback: () => void = () => { };
41
+
40
42
  constructor(camera: DIVEPerspectiveCamera, renderer: DIVERenderer, animationSystem: DIVEAnimationSystem, settings: DIVEOrbitControlsSettings = DIVEOrbitControlsDefaultSettings) {
41
43
  super(camera, renderer.domElement);
42
44
 
@@ -46,14 +48,23 @@ export default class DIVEOrbitControls extends OrbitControls {
46
48
 
47
49
  this.object = camera;
48
50
 
49
- renderer.AddPreRenderCallback(() => {
51
+ const id = renderer.AddPreRenderCallback(() => {
50
52
  this.preRenderCallback();
51
53
  });
52
54
 
55
+ this._removePreRenderCallback = () => {
56
+ renderer.RemovePreRenderCallback(id);
57
+ }
58
+
53
59
  this.enableDamping = settings.enableDamping;
54
60
  this.dampingFactor = settings.dampingFactor;
55
61
  }
56
62
 
63
+ public Dispose(): void {
64
+ this._removePreRenderCallback();
65
+ this.dispose();
66
+ }
67
+
57
68
  public ComputeEncompassingView(bb: Box3): { position: Vector3Like, target: Vector3Like } {
58
69
  const center = bb.getCenter(new Vector3());
59
70
  const size = bb.getSize(new Vector3());
@@ -152,6 +152,11 @@ describe('dive/controls/DIVEOrbitControls', () => {
152
152
  expect(controller).toBeDefined();
153
153
  });
154
154
 
155
+ it('should dispose', () => {
156
+ const controller = new DIVEOrbitControls(mockCamera, mockRenderer, mockAnimSystem);
157
+ expect(() => controller.Dispose()).not.toThrow();
158
+ });
159
+
155
160
  it('should compute encompassing view', () => {
156
161
  const controller = new DIVEOrbitControls(mockCamera, mockRenderer, mockAnimSystem);
157
162
  expect(() => controller.ComputeEncompassingView(new Box3())).not.toThrow();
package/src/dive.ts CHANGED
@@ -12,6 +12,7 @@ import type { Actions } from './com/actions/index.ts';
12
12
  import type { COMPov, COMLight, COMModel, COMEntity } from './com/types.ts';
13
13
  import { DIVEMath } from './math/index.ts';
14
14
  import { generateUUID } from "three/src/math/MathUtils";
15
+ import { DIVEInfo } from "./info/Info.ts";
15
16
 
16
17
  export type DIVESettings = {
17
18
  autoResize: boolean;
@@ -145,6 +146,10 @@ export default class DIVE {
145
146
  return this.renderer.domElement;
146
147
  }
147
148
 
149
+ public get Info(): DIVEInfo {
150
+ return DIVEInfo;
151
+ }
152
+
148
153
  // setters
149
154
  public set Settings(settings: Partial<DIVESettings>) {
150
155
  const settingsDelta = getObjectDelta(this._settings, settings);
@@ -229,6 +234,7 @@ export default class DIVE {
229
234
  public Dispose(): void {
230
235
  this.removeResizeObserver();
231
236
  this.renderer.Dispose();
237
+ this.orbitControls.Dispose();
232
238
  this.axisCamera?.Dispose();
233
239
  this.animationSystem.Dispose();
234
240
  this.toolbox.Dispose();
@@ -0,0 +1,114 @@
1
+ export class DIVEInfo {
2
+ private static _supportsWebXR: boolean | null = null;
3
+
4
+ /**
5
+ *
6
+ * @returns The system the user is using. Possible values are "Android", "iOS", "Windows", "MacOS", "Linux" or "Unknown".
7
+ */
8
+ public static GetSystem(): string {
9
+ const platform = navigator.platform;
10
+ if (/Android/.test(navigator.userAgent)) {
11
+ return "Android";
12
+ } else if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
13
+ return "iOS";
14
+ } else if (platform.startsWith("Win")) {
15
+ return "Windows";
16
+ } else if (platform.startsWith("Mac")) {
17
+ return "MacOS";
18
+ } else if (platform.startsWith("Linux")) {
19
+ return "Linux";
20
+ } else {
21
+ return "Unknown";
22
+ }
23
+ }
24
+
25
+ /**
26
+ * @returns A promise that resolves to a boolean indicating whether the user's device supports WebXR.
27
+ */
28
+ public static async GetSupportsWebXR(): Promise<boolean> {
29
+ if (this._supportsWebXR !== null) {
30
+ return this._supportsWebXR;
31
+ }
32
+
33
+ if (!navigator.xr) {
34
+ this._supportsWebXR = false;
35
+ return this._supportsWebXR;
36
+ }
37
+ // Check if immersive-vr session mode is supported
38
+ try {
39
+ const supported = await navigator.xr.isSessionSupported('immersive-ar');
40
+ this._supportsWebXR = supported;
41
+ } catch (error) {
42
+ this._supportsWebXR = false;
43
+ }
44
+ return this._supportsWebXR;
45
+ }
46
+
47
+ /**
48
+ * @returns A boolean indicating whether the user's device supports AR Quick Look.
49
+ */
50
+ public static GetSupportsARQuickLook(): boolean {
51
+ const a = document.createElement("a");
52
+ if (a.relList.supports("ar")) {
53
+ return true;
54
+ }
55
+
56
+ // fallback check
57
+ const userAgent = navigator.userAgent;
58
+
59
+ // Check if the device is running iOS
60
+ const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !(window as unknown as Window & { MSStream?: string }).MSStream;
61
+ if (!isIOS) {
62
+ return false;
63
+ }
64
+
65
+ // Extract iOS version
66
+ const match = userAgent.match(/OS (\d+)_/);
67
+ if (!match || match.length < 2) {
68
+ return false;
69
+ }
70
+ const iOSVersion = parseInt(match[1], 10);
71
+
72
+ // Minimum iOS version for QuickLook support
73
+ const minQuickLookVersion = 12;
74
+
75
+ // Check if the iOS version is supported
76
+ if (iOSVersion < minQuickLookVersion) {
77
+ return false;
78
+ }
79
+
80
+ // Check for supported browser
81
+ const isSupportedBrowser = /^((?!chrome|android).)*safari|CriOS|FxiOS/i.test(userAgent);
82
+ if (isSupportedBrowser) {
83
+ return true;
84
+ }
85
+
86
+ // Default to false if none of the conditions are met
87
+ return false;
88
+ }
89
+
90
+ /**
91
+ * @returns A boolean indicating whether the user's device is a mobile device.
92
+ */
93
+ public static get isMobile(): boolean {
94
+ return this.GetSystem() === "Android" || this.GetSystem() === "iOS";
95
+ }
96
+
97
+ /**
98
+ * @returns A boolean indicating whether the user's device is a desktop device.
99
+ */
100
+ public static get isDesktop(): boolean {
101
+ return !this.isMobile;
102
+ }
103
+
104
+ /**
105
+ * @returns A promise that resolves to a boolean indicating whether the user's device is capable of AR.
106
+ */
107
+ public static async GetIsARCapable(): Promise<boolean> {
108
+ if (this.GetSupportsARQuickLook()) {
109
+ return true;
110
+ }
111
+
112
+ return await this.GetSupportsWebXR();
113
+ }
114
+ }
@@ -0,0 +1,267 @@
1
+ import { DIVEInfo } from '../Info';
2
+
3
+ const mockNavigator = (navigator: any) => {
4
+ Object.defineProperty(global, 'navigator', {
5
+ value: navigator,
6
+ writable: true
7
+ });
8
+ };
9
+
10
+ describe('dive/info/DIVEInfo', () => {
11
+ beforeEach(() => {
12
+ DIVEInfo['_supportsWebXR'] = null;
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ it('should get system: Windows', () => {
17
+ mockNavigator({
18
+ platform: 'Win64'
19
+ });
20
+ expect(DIVEInfo.GetSystem()).toBe('Windows');
21
+ });
22
+
23
+ it('should get system: MacOS', () => {
24
+ mockNavigator({
25
+ platform: 'MacIntel'
26
+ });
27
+ expect(DIVEInfo.GetSystem()).toBe('MacOS');
28
+ });
29
+
30
+ it('should get system: Linux', () => {
31
+ mockNavigator({
32
+ platform: 'Linux'
33
+ });
34
+ expect(DIVEInfo.GetSystem()).toBe('Linux');
35
+ });
36
+
37
+ it('should get system: Android', () => {
38
+ mockNavigator({
39
+ userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
40
+ platform: 'Linux'
41
+ });
42
+ expect(DIVEInfo.GetSystem()).toBe('Android');
43
+ });
44
+
45
+ it('should get system: iOS', () => {
46
+ mockNavigator({
47
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0_1 like Mac OS X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3',
48
+ platform: 'iPhone'
49
+ });
50
+ expect(DIVEInfo.GetSystem()).toBe('iOS');
51
+ });
52
+
53
+ it('should get system: Unknown', () => {
54
+ mockNavigator({
55
+ platform: 'Unknown'
56
+ });
57
+ expect(DIVEInfo.GetSystem()).toBe('Unknown');
58
+ });
59
+
60
+ it('should support webXR', async () => {
61
+ DIVEInfo['_supportsWebXR'] = null;
62
+ mockNavigator({
63
+ xr: {
64
+ isSessionSupported: jest.fn().mockResolvedValue(true),
65
+ }
66
+ });
67
+ const supports = await DIVEInfo.GetSupportsWebXR();
68
+ expect(supports).toBe(true);
69
+ });
70
+
71
+ it('should not support webXR (xr undefined)', async () => {
72
+ DIVEInfo['_supportsWebXR'] = null;
73
+ mockNavigator({
74
+ xr: undefined
75
+ });
76
+ const supports = await DIVEInfo.GetSupportsWebXR();
77
+ expect(supports).toBe(false);
78
+ });
79
+
80
+ it('should not support webXR', async () => {
81
+ DIVEInfo['_supportsWebXR'] = null;
82
+ mockNavigator({
83
+ xr: {
84
+ isSessionSupported: jest.fn().mockResolvedValue(false),
85
+ }
86
+ });
87
+ const supports = await DIVEInfo.GetSupportsWebXR();
88
+ expect(supports).toBe(false);
89
+ });
90
+
91
+ it('should not support webXR on error', async () => {
92
+ DIVEInfo['_supportsWebXR'] = null;
93
+ mockNavigator({
94
+ xr: {
95
+ isSessionSupported: jest.fn().mockRejectedValue('error'),
96
+ }
97
+ });
98
+ const supports = await DIVEInfo.GetSupportsWebXR();
99
+ expect(supports).toBe(false);
100
+ });
101
+
102
+ it('should return cached value', async () => {
103
+ DIVEInfo['_supportsWebXR'] = true;
104
+ mockNavigator({
105
+ xr: {
106
+ isSessionSupported: jest.fn().mockRejectedValue('error'),
107
+ }
108
+ });
109
+ const supports = await DIVEInfo.GetSupportsWebXR();
110
+ expect(supports).toBe(true);
111
+ });
112
+
113
+ it('should return cached value (false)', async () => {
114
+ DIVEInfo['_supportsWebXR'] = false;
115
+ mockNavigator({
116
+ xr: {
117
+ isSessionSupported: jest.fn().mockRejectedValue('error'),
118
+ }
119
+ });
120
+ const supports = await DIVEInfo.GetSupportsWebXR();
121
+ expect(supports).toBe(false);
122
+ });
123
+
124
+ it('should support ARQuickLook with feature detection', () => {
125
+ mockNavigator({
126
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.'
127
+ });
128
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => true } } as unknown as HTMLAnchorElement);
129
+ const supports = DIVEInfo.GetSupportsARQuickLook();
130
+ expect(supports).toBe(true);
131
+ });
132
+
133
+ it('should support ARQuickLook (iPhone, iOS 15, Safari)', () => {
134
+ mockNavigator({
135
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Mobile/15E148 Safari/604.'
136
+ });
137
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
138
+ const supports = DIVEInfo.GetSupportsARQuickLook();
139
+ expect(supports).toBe(true);
140
+ });
141
+
142
+ it('should support ARQuickLook (iPhone, iOS 17, Google)', () => {
143
+ mockNavigator({
144
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) GSA/277.0.555192628 Mobile/15E148 Safari/604.'
145
+ });
146
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
147
+ const supports = DIVEInfo.GetSupportsARQuickLook();
148
+ expect(supports).toBe(true);
149
+ });
150
+
151
+ it('should support ARQuickLook (iPhone, iOS 17, Chrome)', () => {
152
+ mockNavigator({
153
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/126.0.6478.153 Mobile/15E148 Safari/604.'
154
+ });
155
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
156
+ const supports = DIVEInfo.GetSupportsARQuickLook();
157
+ expect(supports).toBe(true);
158
+ });
159
+
160
+ it('should support ARQuickLook (iPhone, iOS 17, Chrome)', () => {
161
+ mockNavigator({
162
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 CriOS/604.'
163
+ });
164
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
165
+ const supports = DIVEInfo.GetSupportsARQuickLook();
166
+ expect(supports).toBe(true);
167
+ });
168
+
169
+ it('should support ARQuickLook (iPhone, iOS 17, Firefox)', () => {
170
+ mockNavigator({
171
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 FxiOS/604.'
172
+ });
173
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
174
+ const supports = DIVEInfo.GetSupportsARQuickLook();
175
+ expect(supports).toBe(true);
176
+ });
177
+
178
+ it('should not support ARQuickLook (Android)', () => {
179
+ mockNavigator({
180
+ userAgent: 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Mobile Safari/537.3'
181
+ });
182
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
183
+ const supports = DIVEInfo.GetSupportsARQuickLook();
184
+ expect(supports).toBe(false);
185
+ });
186
+
187
+ it('should not support ARQuickLook (iPhone, no iOS version)', () => {
188
+ mockNavigator({
189
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 Safari/604.'
190
+ });
191
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
192
+ const supports = DIVEInfo.GetSupportsARQuickLook();
193
+ expect(supports).toBe(false);
194
+ });
195
+
196
+ it('should not support ARQuickLook (iPhone, iOS 17, no browser)', () => {
197
+ mockNavigator({
198
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X)'
199
+ });
200
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
201
+ const supports = DIVEInfo.GetSupportsARQuickLook();
202
+ expect(supports).toBe(false);
203
+ });
204
+
205
+ it('should not support ARQuickLook (iPhone, iOS <12, Safari)', () => {
206
+ mockNavigator({
207
+ userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_1 like Mac OS X) AppleWebKit/605.1.11 (KHTML, like Gecko) Version/11.1 Mobile/11E148 Safari/604.'
208
+ });
209
+ jest.spyOn(document, 'createElement').mockReturnValue({ relList: { supports: () => false } } as unknown as HTMLAnchorElement);
210
+ const supports = DIVEInfo.GetSupportsARQuickLook();
211
+ expect(supports).toBe(false);
212
+ });
213
+
214
+ it('should be mobile (iOS)', () => {
215
+ jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('iOS');
216
+ expect(DIVEInfo.isMobile).toBe(true);
217
+ expect(DIVEInfo.isDesktop).toBe(false);
218
+ });
219
+
220
+ it('should be mobile (Android)', () => {
221
+ jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Android');
222
+ expect(DIVEInfo.isMobile).toBe(true);
223
+ expect(DIVEInfo.isDesktop).toBe(false);
224
+ });
225
+
226
+ it('should be desktop (Windows)', () => {
227
+ jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Windows');
228
+ expect(DIVEInfo.isMobile).toBe(false);
229
+ expect(DIVEInfo.isDesktop).toBe(true);
230
+ });
231
+
232
+ it('should be desktop (MacOS)', () => {
233
+ jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('MacOS');
234
+ expect(DIVEInfo.isMobile).toBe(false);
235
+ expect(DIVEInfo.isDesktop).toBe(true);
236
+ });
237
+
238
+ it('should be desktop (Linux)', () => {
239
+ jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Linux');
240
+ expect(DIVEInfo.isMobile).toBe(false);
241
+ expect(DIVEInfo.isDesktop).toBe(true);
242
+ });
243
+
244
+ it('should be desktop (Unknown)', () => {
245
+ jest.spyOn(DIVEInfo, 'GetSystem').mockReturnValue('Unknown');
246
+ expect(DIVEInfo.isMobile).toBe(false);
247
+ expect(DIVEInfo.isDesktop).toBe(true);
248
+ });
249
+
250
+ it('should be capable of AR (ARQuickLook)', async () => {
251
+ jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(true);
252
+ jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(false);
253
+ expect(await DIVEInfo.GetIsARCapable()).toBe(true);
254
+ });
255
+
256
+ it('should be capable of AR (WebXR)', async () => {
257
+ jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
258
+ jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(true);
259
+ expect(await DIVEInfo.GetIsARCapable()).toBe(true);
260
+ });
261
+
262
+ it('should not be capable of AR', async () => {
263
+ jest.spyOn(DIVEInfo, 'GetSupportsARQuickLook').mockReturnValue(false);
264
+ jest.spyOn(DIVEInfo, 'GetSupportsWebXR').mockResolvedValue(false);
265
+ expect(await DIVEInfo.GetIsARCapable()).toBe(false);
266
+ });
267
+ });