@shopware-ag/dive 1.17.2 → 1.18.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 (40) hide show
  1. package/README.md +56 -22
  2. package/build/dive.cjs +11 -7
  3. package/build/dive.cjs.map +1 -1
  4. package/build/dive.d.cts +3 -4
  5. package/build/dive.d.ts +3 -4
  6. package/build/dive.js +9 -5
  7. package/build/dive.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/__test__/DIVE.test.ts +0 -40
  10. package/src/animation/__test__/AnimationSystem.test.ts +0 -7
  11. package/src/ar/arquicklook/__test__/ARQuickLook.test.ts +0 -140
  12. package/src/ar/sceneviewer/__test__/SceneViewer.test.ts +0 -140
  13. package/src/axiscamera/__test__/AxisCamera.test.ts +0 -76
  14. package/src/com/__test__/Communication.test.ts +0 -6
  15. package/src/controls/__test__/OrbitControls.test.ts +0 -87
  16. package/src/dive.ts +3 -3
  17. package/src/exporters/usdz/__test__/USDZExporter.test.ts +57 -0
  18. package/src/group/Group.ts +6 -1
  19. package/src/group/__test__/Group.test.ts +6 -3
  20. package/src/io/gltf/__test__/GLTFIO.test.ts +0 -77
  21. package/src/light/PointLight.ts +1 -1
  22. package/src/light/__test__/AmbientLight.test.ts +0 -24
  23. package/src/light/__test__/PointLight.test.ts +0 -61
  24. package/src/light/__test__/SceneLight.test.ts +0 -89
  25. package/src/loadingmanager/LoadingManager.ts +2 -1
  26. package/src/loadingmanager/__test__/LoadingManager.test.ts +0 -30
  27. package/src/math/degToRad/__test__/degToRad.test.ts +0 -7
  28. package/src/math/radToDeg/__test__/radToDeg.test.ts +0 -7
  29. package/src/model/Model.ts +1 -1
  30. package/src/model/__test__/Model.test.ts +5 -155
  31. package/src/node/__test__/Node.test.ts +0 -149
  32. package/src/primitive/__test__/Primitive.test.ts +6 -199
  33. package/src/primitive/floor/__test__/Floor.test.ts +0 -3
  34. package/src/renderer/__test__/Renderer.test.ts +16 -46
  35. package/src/scene/__test__/Scene.test.ts +6 -16
  36. package/src/scene/root/Root.ts +4 -4
  37. package/src/scene/root/__test__/Root.test.ts +6 -188
  38. package/src/toolbox/__test__/BaseTool.test.ts +34 -38
  39. package/src/toolbox/select/__test__/SelectTool.test.ts +16 -89
  40. package/src/toolbox/transform/__test__/TransformTool.test.ts +14 -82
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopware-ag/dive",
3
- "version": "1.17.2",
3
+ "version": "1.18.0",
4
4
  "description": "Shopware Spatial Framework",
5
5
  "type": "module",
6
6
  "main": "./build/dive.cjs",
@@ -1,45 +1,5 @@
1
1
  import DIVE, { DIVESettings } from '../dive.ts';
2
2
 
3
- jest.mock('three', () => {
4
- return {
5
- Vector4: jest.fn(),
6
- WebGLRenderer: jest.fn(function () {
7
- this.domElement = {
8
- clientWidth: 800,
9
- clientHeight: 600,
10
- style: {
11
- position: 'absolute',
12
- },
13
- };
14
- this.domElement.parentElement = this.domElement;
15
- this.debug = {
16
- checkShaderErrors: true,
17
- };
18
- this.setSize = jest.fn();
19
- this.setPixelRatio = jest.fn();
20
- this.render = jest.fn();
21
- this.setAnimationLoop = jest.fn();
22
- this.shadowMap = {
23
- enabled: false,
24
- };
25
- return this;
26
- }),
27
- MathUtils: {
28
- generateUUID: () => {
29
- return 'test_uuid';
30
- },
31
- },
32
- };
33
- });
34
-
35
- jest.mock('three/src/math/MathUtils', () => {
36
- return {
37
- generateUUID: () => {
38
- return 'test_uuid';
39
- },
40
- };
41
- });
42
-
43
3
  jest.mock('../com/Communication.ts', () => {
44
4
  return {
45
5
  DIVECommunication: jest.fn(function () {
@@ -1,13 +1,6 @@
1
1
  import { DIVERenderer } from '../../renderer/Renderer';
2
2
  import { DIVEAnimationSystem } from '../AnimationSystem';
3
3
 
4
- jest.mock('@tweenjs/tween.js', () => {
5
- return {
6
- Tween: jest.fn(() => {}),
7
- update: jest.fn(),
8
- };
9
- });
10
-
11
4
  const mockRenderer = {
12
5
  render: jest.fn(),
13
6
  OnResize: jest.fn(),
@@ -3,146 +3,6 @@ import { DIVEScene } from '../../../scene/Scene';
3
3
  import { DIVEAROptions } from '../../AR';
4
4
  import { DIVEARQuickLook } from '../ARQuickLook';
5
5
 
6
- jest.mock('three', () => {
7
- return {
8
- Vector3: jest.fn(function (
9
- x: number = 0,
10
- y: number = 0,
11
- z: number = 0,
12
- ) {
13
- this.x = x;
14
- this.y = y;
15
- this.z = z;
16
- this.copy = (vec3: Vector3) => {
17
- this.x = vec3.x;
18
- this.y = vec3.y;
19
- this.z = vec3.z;
20
- return this;
21
- };
22
- this.set = (x: number, y: number, z: number) => {
23
- this.x = x;
24
- this.y = y;
25
- this.z = z;
26
- return this;
27
- };
28
- this.multiply = (vec3: Vector3) => {
29
- this.x *= vec3.x;
30
- this.y *= vec3.y;
31
- this.z *= vec3.z;
32
- return this;
33
- };
34
- this.clone = () => {
35
- return new Vector3(this.x, this.y, this.z);
36
- };
37
- this.setY = (y: number) => {
38
- this.y = y;
39
- return this;
40
- };
41
- this.add = (vec3: Vector3) => {
42
- this.x += vec3.x;
43
- this.y += vec3.y;
44
- this.z += vec3.z;
45
- return this;
46
- };
47
- this.sub = (vec3: Vector3) => {
48
- this.x -= vec3.x;
49
- this.y -= vec3.y;
50
- this.z -= vec3.z;
51
- return this;
52
- };
53
- return this;
54
- }),
55
- Euler: jest.fn(function () {
56
- this.set = jest.fn();
57
- return this;
58
- }),
59
- Object3D: jest.fn(function () {
60
- this.clear = jest.fn();
61
- this.color = {};
62
- this.intensity = 0;
63
- this.layers = {
64
- mask: 0,
65
- };
66
- this.shadow = {
67
- radius: 0,
68
- mapSize: { width: 0, height: 0 },
69
- bias: 0,
70
- camera: {
71
- near: 0,
72
- far: 0,
73
- fov: 0,
74
- },
75
- };
76
- this.add = jest.fn();
77
- this.sub = jest.fn();
78
- this.children = [
79
- {
80
- visible: true,
81
- material: {
82
- color: {},
83
- },
84
- },
85
- ];
86
- this.userData = {};
87
- this.position = new Vector3();
88
- this.rotation = new Euler();
89
- this.scale = new Vector3(1, 1, 1);
90
- this.localToWorld = (vec3: Vector3) => {
91
- return vec3;
92
- };
93
- this.mesh = new Mesh();
94
- this.traverse = jest.fn((callback) => {
95
- callback(this.children[0]);
96
- });
97
- this.getWorldPosition = jest.fn(() => {
98
- return new Vector3();
99
- });
100
- return this;
101
- }),
102
- Box3: jest.fn(function () {
103
- this.min = new Vector3(Infinity, Infinity, Infinity);
104
- this.max = new Vector3(-Infinity, -Infinity, -Infinity);
105
- this.getCenter = jest.fn(() => {
106
- return new Vector3(0, 0, 0);
107
- });
108
- this.expandByObject = jest.fn();
109
-
110
- return this;
111
- }),
112
- Mesh: jest.fn(function () {
113
- this.geometry = {
114
- computeBoundingBox: jest.fn(),
115
- boundingBox: new Box3(),
116
- };
117
- this.material = {};
118
- this.castShadow = true;
119
- this.receiveShadow = true;
120
- this.layers = {
121
- mask: 0,
122
- };
123
- this.updateWorldMatrix = jest.fn();
124
- this.traverse = jest.fn();
125
- this.removeFromParent = jest.fn();
126
- this.localToWorld = (vec3: Vector3) => {
127
- return vec3;
128
- };
129
- return this;
130
- }),
131
- MeshStandardMaterial: jest.fn(function () {
132
- this.color = new Color();
133
- this.roughness = 1;
134
- this.roughnessMap = undefined;
135
- this.metalness = 0;
136
- this.metalnessMap = undefined;
137
- return this;
138
- }),
139
- Color: jest.fn(function () {
140
- this.set = jest.fn();
141
- return this;
142
- }),
143
- };
144
- });
145
-
146
6
  jest.mock('../../../exporters/usdz/USDZExporter', () => {
147
7
  return {
148
8
  DIVEUSDZExporter: jest.fn().mockImplementation(() => {
@@ -21,146 +21,6 @@ jest.mock('../../../scene/Scene', () => {
21
21
  };
22
22
  });
23
23
 
24
- jest.mock('three', () => {
25
- return {
26
- Vector3: jest.fn(function (
27
- x: number = 0,
28
- y: number = 0,
29
- z: number = 0,
30
- ) {
31
- this.x = x;
32
- this.y = y;
33
- this.z = z;
34
- this.copy = (vec3: Vector3) => {
35
- this.x = vec3.x;
36
- this.y = vec3.y;
37
- this.z = vec3.z;
38
- return this;
39
- };
40
- this.set = (x: number, y: number, z: number) => {
41
- this.x = x;
42
- this.y = y;
43
- this.z = z;
44
- return this;
45
- };
46
- this.multiply = (vec3: Vector3) => {
47
- this.x *= vec3.x;
48
- this.y *= vec3.y;
49
- this.z *= vec3.z;
50
- return this;
51
- };
52
- this.clone = () => {
53
- return new Vector3(this.x, this.y, this.z);
54
- };
55
- this.setY = (y: number) => {
56
- this.y = y;
57
- return this;
58
- };
59
- this.add = (vec3: Vector3) => {
60
- this.x += vec3.x;
61
- this.y += vec3.y;
62
- this.z += vec3.z;
63
- return this;
64
- };
65
- this.sub = (vec3: Vector3) => {
66
- this.x -= vec3.x;
67
- this.y -= vec3.y;
68
- this.z -= vec3.z;
69
- return this;
70
- };
71
- return this;
72
- }),
73
- Euler: jest.fn(function () {
74
- this.set = jest.fn();
75
- return this;
76
- }),
77
- Object3D: jest.fn(function () {
78
- this.clear = jest.fn();
79
- this.color = {};
80
- this.intensity = 0;
81
- this.layers = {
82
- mask: 0,
83
- };
84
- this.shadow = {
85
- radius: 0,
86
- mapSize: { width: 0, height: 0 },
87
- bias: 0,
88
- camera: {
89
- near: 0,
90
- far: 0,
91
- fov: 0,
92
- },
93
- };
94
- this.add = jest.fn();
95
- this.sub = jest.fn();
96
- this.children = [
97
- {
98
- visible: true,
99
- material: {
100
- color: {},
101
- },
102
- },
103
- ];
104
- this.userData = {};
105
- this.position = new Vector3();
106
- this.rotation = new Euler();
107
- this.scale = new Vector3(1, 1, 1);
108
- this.localToWorld = (vec3: Vector3) => {
109
- return vec3;
110
- };
111
- this.mesh = new Mesh();
112
- this.traverse = jest.fn((callback) => {
113
- callback(this.children[0]);
114
- });
115
- this.getWorldPosition = jest.fn(() => {
116
- return new Vector3();
117
- });
118
- return this;
119
- }),
120
- Box3: jest.fn(function () {
121
- this.min = new Vector3(Infinity, Infinity, Infinity);
122
- this.max = new Vector3(-Infinity, -Infinity, -Infinity);
123
- this.getCenter = jest.fn(() => {
124
- return new Vector3(0, 0, 0);
125
- });
126
- this.expandByObject = jest.fn();
127
-
128
- return this;
129
- }),
130
- Mesh: jest.fn(function () {
131
- this.geometry = {
132
- computeBoundingBox: jest.fn(),
133
- boundingBox: new Box3(),
134
- };
135
- this.material = {};
136
- this.castShadow = true;
137
- this.receiveShadow = true;
138
- this.layers = {
139
- mask: 0,
140
- };
141
- this.updateWorldMatrix = jest.fn();
142
- this.traverse = jest.fn();
143
- this.removeFromParent = jest.fn();
144
- this.localToWorld = (vec3: Vector3) => {
145
- return vec3;
146
- };
147
- return this;
148
- }),
149
- MeshStandardMaterial: jest.fn(function () {
150
- this.color = new Color();
151
- this.roughness = 1;
152
- this.roughnessMap = undefined;
153
- this.metalness = 0;
154
- this.metalnessMap = undefined;
155
- return this;
156
- }),
157
- Color: jest.fn(function () {
158
- this.set = jest.fn();
159
- return this;
160
- }),
161
- };
162
- });
163
-
164
24
  jest.mock('../../../exporters/usdz/USDZExporter', () => {
165
25
  return {
166
26
  DIVEUSDZExporter: jest.fn().mockImplementation(() => {
@@ -4,82 +4,6 @@ import { DIVERenderer } from '../../renderer/Renderer';
4
4
  import { DIVEScene } from '../../scene/Scene';
5
5
  import DIVEOrbitControls from '../../controls/OrbitControls';
6
6
 
7
- jest.mock('three', () => {
8
- return {
9
- Vector4: jest.fn(),
10
- Color: jest.fn(function () {
11
- this.getHexString = jest.fn().mockReturnValue('ffffff');
12
- return this;
13
- }),
14
- Matrix4: jest.fn(function () {
15
- this.extractRotation = jest.fn(() => {
16
- return this;
17
- });
18
- this.invert = jest.fn(() => {
19
- return this;
20
- });
21
- // prettier-multiline-arrays-next-line-pattern: 4
22
- this.elements = [
23
- 1, 0, 0, 0,
24
- 0, 1, 0, 0,
25
- 0, 0, 1, 0,
26
- 0, 0, 0, 1,
27
- ];
28
- return this;
29
- }),
30
- OrthographicCamera: jest.fn(function () {
31
- this.isObject3D = true;
32
- this.parent = null;
33
- this.dispatchEvent = jest.fn();
34
- this.layers = {
35
- mask: 0,
36
- };
37
- this.position = {
38
- set: jest.fn(),
39
- };
40
- this.add = jest.fn();
41
- return this;
42
- }),
43
- AxesHelper: jest.fn(function () {
44
- this.isObject3D = true;
45
- this.parent = null;
46
- this.dispatchEvent = jest.fn();
47
- this.layers = {
48
- mask: 0,
49
- };
50
- this.position = {
51
- set: jest.fn(),
52
- };
53
- this.add = jest.fn();
54
- this.material = {
55
- depthTest: false,
56
- };
57
- this.setColors = jest.fn();
58
- this.rotation = {
59
- setFromRotationMatrix: jest.fn(),
60
- };
61
- return this;
62
- }),
63
- };
64
- });
65
-
66
- jest.mock('three-spritetext', () => {
67
- return jest.fn(() => {
68
- return {
69
- isObject3D: true,
70
- parent: null,
71
- dispatchEvent: jest.fn(),
72
- layers: {
73
- mask: 0,
74
- },
75
- position: {
76
- set: jest.fn(),
77
- },
78
- removeFromParent: jest.fn(),
79
- };
80
- });
81
- });
82
-
83
7
  const mockRenderer = {
84
8
  render: jest.fn(),
85
9
  OnResize: jest.fn(),
@@ -36,12 +36,6 @@ import {
36
36
  } from '../types';
37
37
  import { type DIVESceneObject } from '../../types';
38
38
 
39
- jest.mock('three/src/math/MathUtils', () => {
40
- return {
41
- generateUUID: jest.fn().mockReturnValue('uuid'),
42
- };
43
- });
44
-
45
39
  jest.mock('../../mediacreator/MediaCreator', () => {
46
40
  return {
47
41
  DIVEMediaCreator: jest.fn(function () {
@@ -5,56 +5,6 @@ import { Box3 } from 'three';
5
5
  import { DIVEAnimationSystem } from '../../animation/AnimationSystem';
6
6
  import { Tween } from '@tweenjs/tween.js';
7
7
 
8
- jest.mock('@tweenjs/tween.js', () => {
9
- return {
10
- Tween: jest.fn(() => {}),
11
- update: jest.fn(),
12
- };
13
- });
14
-
15
- jest.mock('three/examples/jsm/controls/OrbitControls', () => {
16
- return {
17
- OrbitControls: jest.fn(function () {
18
- this.enableDamping = true;
19
- this.dampingFactor = 0.25;
20
- this.enableZoom = true;
21
- this.enablePan = true;
22
- this.minPolarAngle = 0;
23
- this.maxPolarAngle = Math.PI;
24
- this.minDistance = 0;
25
- this.maxDistance = Infinity;
26
- this.rotateSpeed = 0.5;
27
- this.panSpeed = 0.5;
28
- this.zoomSpeed = 0.5;
29
- this.keyPanSpeed = 0.5;
30
- this.screenSpacePanning = true;
31
- this.autoRotate = false;
32
- this.autoRotateSpeed = 2.0;
33
- this.enableKeys = true;
34
- this.keys = {
35
- LEFT: 37,
36
- UP: 38,
37
- RIGHT: 39,
38
- BOTTOM: 40,
39
- };
40
- this.mouseButtons = {
41
- LEFT: 0,
42
- MIDDLE: 1,
43
- RIGHT: 2,
44
- };
45
- this.update = jest.fn();
46
- this.dispose = jest.fn();
47
- this.getDistance = jest.fn();
48
- this.target = {
49
- clone: jest.fn(),
50
- set: jest.fn(),
51
- copy: jest.fn(),
52
- };
53
- return this;
54
- }),
55
- };
56
- });
57
-
58
8
  jest.mock('../../renderer/Renderer', () => {
59
9
  return jest.fn(function () {
60
10
  this.domElement = {
@@ -83,43 +33,6 @@ jest.mock('../../animation/AnimationSystem', () => {
83
33
  };
84
34
  });
85
35
 
86
- jest.mock('@tweenjs/tween.js', () => {
87
- return {
88
- Easing: {
89
- Quadratic: {
90
- In: jest.fn(),
91
- Out: jest.fn(),
92
- InOut: jest.fn(),
93
- },
94
- },
95
- Tween: jest.fn(() => {
96
- const instance: object = {
97
- easing: () => {
98
- return instance;
99
- },
100
- to: () => {
101
- return instance;
102
- },
103
- start: () => {
104
- return instance;
105
- },
106
- stop: () => {
107
- return instance;
108
- },
109
- onComplete: (callback: () => typeof instance) => {
110
- callback();
111
- return instance;
112
- },
113
- onUpdate: (callback: () => typeof instance) => {
114
- callback();
115
- return instance;
116
- },
117
- };
118
- return instance;
119
- }),
120
- };
121
- });
122
-
123
36
  const moveToPos = { x: 10, y: 0, z: 0 };
124
37
  const moveToQuat = { x: 0, y: 0, z: 0 };
125
38
  const moveToDuration = 1000;
package/src/dive.ts CHANGED
@@ -17,7 +17,7 @@ import { DIVECommunication } from './com/Communication.ts';
17
17
  import { DIVEAnimationSystem } from './animation/AnimationSystem.ts';
18
18
  import DIVEAxisCamera from './axiscamera/AxisCamera.ts';
19
19
  import { getObjectDelta } from './helper/getObjectDelta/getObjectDelta.ts';
20
- import { generateUUID } from 'three/src/math/MathUtils';
20
+ import { MathUtils } from 'three';
21
21
  import { DIVEInfo } from './info/Info.ts';
22
22
  import pkgjson from '../package.json';
23
23
 
@@ -74,7 +74,7 @@ export default class DIVE {
74
74
  });
75
75
 
76
76
  // generate scene light id
77
- const lightid = generateUUID();
77
+ const lightid = MathUtils.generateUUID();
78
78
 
79
79
  // add scene light
80
80
  dive.Communication.PerformAction('ADD_OBJECT', {
@@ -89,7 +89,7 @@ export default class DIVE {
89
89
  });
90
90
 
91
91
  // generate model id
92
- const modelid = generateUUID();
92
+ const modelid = MathUtils.generateUUID();
93
93
 
94
94
  // add loaded listener
95
95
  dive.Communication.Subscribe('MODEL_LOADED', (data) => {
@@ -0,0 +1,57 @@
1
+ import { Object3D } from 'three';
2
+ import { DIVEUSDZExporter, DIVEUSDZExporterOptions } from '../USDZExporter';
3
+
4
+ describe('DIVEUSDZExporter', () => {
5
+ let exporter: DIVEUSDZExporter;
6
+ let scene: Object3D;
7
+
8
+ beforeEach(() => {
9
+ exporter = new DIVEUSDZExporter();
10
+ scene = new Object3D();
11
+ });
12
+
13
+ test('should create an instance of DIVEUSDZExporter', () => {
14
+ expect(exporter).toBeInstanceOf(DIVEUSDZExporter);
15
+ });
16
+
17
+ test('should call parse method with scene and options', async () => {
18
+ const options: DIVEUSDZExporterOptions = {
19
+ ar: {
20
+ anchoring: { type: 'plane' },
21
+ planeAnchoring: { alignment: 'horizontal' },
22
+ },
23
+ };
24
+
25
+ const result = await exporter.parse(scene, options);
26
+ expect(result).toBeInstanceOf(Uint8Array);
27
+ });
28
+
29
+ test('should call parse method without options', async () => {
30
+ const result = await exporter.parse(scene);
31
+ expect(result).toBeInstanceOf(Uint8Array);
32
+ });
33
+
34
+ test('should handle parse method with different anchoring types', async () => {
35
+ const options: DIVEUSDZExporterOptions = {
36
+ ar: {
37
+ anchoring: { type: 'image' },
38
+ planeAnchoring: { alignment: 'vertical' },
39
+ },
40
+ };
41
+
42
+ const result = await exporter.parse(scene, options);
43
+ expect(result).toBeInstanceOf(Uint8Array);
44
+ });
45
+
46
+ test('should handle parse method with different plane anchoring alignments', async () => {
47
+ const options: DIVEUSDZExporterOptions = {
48
+ ar: {
49
+ anchoring: { type: 'face' },
50
+ planeAnchoring: { alignment: 'any' },
51
+ },
52
+ };
53
+
54
+ const result = await exporter.parse(scene, options);
55
+ expect(result).toBeInstanceOf(Uint8Array);
56
+ });
57
+ });
@@ -54,12 +54,17 @@ export class DIVEGroup extends DIVENode {
54
54
  }
55
55
 
56
56
  public attach(object: DIVESceneObject): this {
57
+ // Check if the object is already a member
58
+ if (this._members.includes(object)) {
59
+ return this;
60
+ }
61
+
57
62
  // create a line to the new object
58
63
  const line = this.createLine();
59
64
  this.add(line);
60
65
  this._lines.push(line);
61
66
 
62
- // attach (insted of add) object to keep it's world position
67
+ // attach (instead of add) object to keep its world position
63
68
  super.attach(object);
64
69
  this._members.push(object);
65
70
 
@@ -1,4 +1,4 @@
1
- import { type Vector3Like } from 'three';
1
+ import { Object3D, type Vector3Like } from 'three';
2
2
  import { DIVECommunication } from '../../com/Communication';
3
3
  import { type DIVENode } from '../../node/Node';
4
4
  import { DIVEGroup } from '../Group';
@@ -21,6 +21,8 @@ jest.spyOn(DIVECommunication, 'get').mockReturnValue({
21
21
 
22
22
  let group: DIVEGroup;
23
23
 
24
+ Object3D.prototype.attach = jest.fn();
25
+
24
26
  describe('dive/group/DIVEGroup', () => {
25
27
  beforeEach(() => {
26
28
  group = new DIVEGroup();
@@ -38,8 +40,9 @@ describe('dive/group/DIVEGroup', () => {
38
40
  const mockObject = new DIVEGroup();
39
41
 
40
42
  expect(() => group.attach(mockObject)).not.toThrow();
41
- expect(group.children).toContain(mockObject);
42
- expect(group.members).toContain(mockObject);
43
+ expect(group.members).toContainEqual(
44
+ expect.objectContaining(mockObject),
45
+ );
43
46
 
44
47
  jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
45
48
  expect(() => group.attach(mockObject)).not.toThrow();