@wandelbots/wandelbots-js-react-components 2.26.0-pr.feature-update-to-react-19.361.3244daa → 2.26.0-pr.feature-update-to-react-19.361.ec4e097

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.
@@ -1,2 +1,2 @@
1
- import '@testing-library/jest-dom';
1
+ import "@testing-library/jest-dom";
2
2
  //# sourceMappingURL=setup.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wandelbots/wandelbots-js-react-components",
3
- "version": "2.26.0-pr.feature-update-to-react-19.361.3244daa",
3
+ "version": "2.26.0-pr.feature-update-to-react-19.361.ec4e097",
4
4
  "description": "React UI toolkit for building applications on top of the Wandelbots platform",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -0,0 +1,349 @@
1
+ import { Canvas } from "@react-three/fiber"
2
+ import { act, render, waitFor } from "@testing-library/react"
3
+ import type {
4
+ DHParameter,
5
+ MotionGroupStateResponse,
6
+ } from "@wandelbots/nova-api/v1"
7
+ import React from "react"
8
+ import { Object3D } from "three"
9
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
10
+ import RobotAnimator from "./RobotAnimator"
11
+
12
+ // Mock the dependencies
13
+ vi.mock("../utils/hooks", () => ({
14
+ useAutorun: vi.fn(),
15
+ }))
16
+
17
+ vi.mock("./robotModelLogic", () => ({
18
+ collectJoints: vi.fn(),
19
+ }))
20
+
21
+ vi.mock("@react-spring/three", () => ({
22
+ Globals: {
23
+ assign: vi.fn(),
24
+ },
25
+ useSpring: vi.fn(),
26
+ }))
27
+
28
+ vi.mock("@react-three/fiber", async () => {
29
+ const actual = await vi.importActual("@react-three/fiber")
30
+ return {
31
+ ...actual,
32
+ useThree: () => ({
33
+ invalidate: vi.fn(),
34
+ }),
35
+ }
36
+ })
37
+
38
+ import { useSpring } from "@react-spring/three"
39
+ import { useAutorun } from "../utils/hooks"
40
+ import { collectJoints } from "./robotModelLogic"
41
+
42
+ const mockUseAutorun = useAutorun as any
43
+ const mockCollectJoints = collectJoints as any
44
+ const mockUseSpring = useSpring as any
45
+
46
+ describe("RobotAnimator", () => {
47
+ const mockMotionState: MotionGroupStateResponse = {
48
+ state: {
49
+ joint_position: {
50
+ joints: [0, 0.5, 1, 1.5, 2, 2.5],
51
+ },
52
+ },
53
+ } as any
54
+
55
+ const mockDhParameters: DHParameter[] = [
56
+ { theta: 0, reverse_rotation_direction: false },
57
+ { theta: 0.1, reverse_rotation_direction: true },
58
+ { theta: 0.2, reverse_rotation_direction: false },
59
+ { theta: 0.3, reverse_rotation_direction: true },
60
+ { theta: 0.4, reverse_rotation_direction: false },
61
+ { theta: 0.5, reverse_rotation_direction: true },
62
+ ]
63
+
64
+ let mockJointObjects: Object3D[]
65
+ let mockAxisValues: any
66
+ let mockSetSpring: any
67
+ let mockInvalidate: any
68
+ let mockSpringConfig: any
69
+ let mockAutorunCallback: any
70
+
71
+ beforeEach(() => {
72
+ // Create mock joint objects
73
+ mockJointObjects = Array.from({ length: 6 }, (_, i) => {
74
+ const obj = new Object3D()
75
+ obj.name = `Joint_J${i + 1}`
76
+ obj.rotation.y = 0
77
+ return obj
78
+ })
79
+
80
+ // Mock axis values object that acts like spring values
81
+ mockAxisValues = {}
82
+ for (let i = 0; i < 6; i++) {
83
+ mockAxisValues[i] = {
84
+ get: vi
85
+ .fn()
86
+ .mockReturnValue(mockMotionState.state.joint_position.joints[i]),
87
+ }
88
+ }
89
+
90
+ mockSetSpring = {
91
+ start: vi.fn(),
92
+ }
93
+
94
+ mockInvalidate = vi.fn()
95
+
96
+ // Mock spring config that will be returned by useSpring
97
+ mockSpringConfig = {
98
+ onChange: vi.fn(),
99
+ onResolve: vi.fn(),
100
+ }
101
+
102
+ // Mock collectJoints to return our mock joint objects
103
+ mockCollectJoints.mockReturnValue(mockJointObjects)
104
+
105
+ // Mock useSpring to return our mock values and accept a factory function
106
+ mockUseSpring.mockImplementation((factory: any) => {
107
+ const config = factory()
108
+ mockSpringConfig.onChange = config.onChange
109
+ mockSpringConfig.onResolve = config.onResolve
110
+ return [mockAxisValues, mockSetSpring]
111
+ })
112
+
113
+ // Mock useAutorun to store the callback
114
+ mockUseAutorun.mockImplementation((callback: any) => {
115
+ mockAutorunCallback = callback
116
+ })
117
+ })
118
+
119
+ afterEach(() => {
120
+ vi.clearAllMocks()
121
+ })
122
+
123
+ const renderRobotAnimator = (
124
+ props: Partial<React.ComponentProps<typeof RobotAnimator>> = {},
125
+ ) => {
126
+ const defaultProps = {
127
+ rapidlyChangingMotionState: mockMotionState,
128
+ dhParameters: mockDhParameters,
129
+ children: <mesh />,
130
+ }
131
+
132
+ return render(
133
+ <Canvas>
134
+ <RobotAnimator {...defaultProps} {...props} />
135
+ </Canvas>,
136
+ )
137
+ }
138
+
139
+ it("should render without crashing", () => {
140
+ expect(() => renderRobotAnimator()).not.toThrow()
141
+ })
142
+
143
+ it("should initialize joint objects when group is set", async () => {
144
+ const { container } = renderRobotAnimator()
145
+
146
+ // Wait for the component to render and set up
147
+ await waitFor(() => {
148
+ expect(mockCollectJoints).toHaveBeenCalled()
149
+ })
150
+ })
151
+
152
+ it("should call onRotationChanged when provided", async () => {
153
+ const mockOnRotationChanged = vi.fn()
154
+
155
+ renderRobotAnimator({ onRotationChanged: mockOnRotationChanged })
156
+
157
+ // Wait for initialization
158
+ await waitFor(() => {
159
+ expect(mockCollectJoints).toHaveBeenCalled()
160
+ })
161
+
162
+ // Simulate spring onChange callback
163
+ await act(async () => {
164
+ if (mockSpringConfig.onChange) {
165
+ mockSpringConfig.onChange()
166
+ }
167
+ })
168
+
169
+ expect(mockOnRotationChanged).toHaveBeenCalledWith(
170
+ mockJointObjects,
171
+ expect.arrayContaining([0, 0.5, 1, 1.5, 2, 2.5]),
172
+ )
173
+ })
174
+
175
+ it("should update joint rotations when onRotationChanged is not provided", async () => {
176
+ renderRobotAnimator()
177
+
178
+ // Wait for initialization
179
+ await waitFor(() => {
180
+ expect(mockCollectJoints).toHaveBeenCalled()
181
+ })
182
+
183
+ await act(async () => {
184
+ // Simulate spring animation callback
185
+ if (mockSpringConfig.onChange) {
186
+ mockSpringConfig.onChange()
187
+ }
188
+ })
189
+
190
+ // Check that joint rotations were updated according to DH parameters
191
+ mockJointObjects.forEach((joint, index) => {
192
+ const dhParam = mockDhParameters[index]
193
+ const rotationSign = dhParam.reverse_rotation_direction ? -1 : 1
194
+ const expectedRotation =
195
+ rotationSign * mockMotionState.state.joint_position.joints[index]! +
196
+ dhParam.theta!
197
+
198
+ expect(joint.rotation.y).toBe(expectedRotation)
199
+ })
200
+ })
201
+
202
+ it("should not update rotations when not initialized", async () => {
203
+ // Mock collectJoints to return empty array (not initialized)
204
+ mockCollectJoints.mockReturnValue([])
205
+
206
+ renderRobotAnimator()
207
+
208
+ await act(async () => {
209
+ // Simulate spring animation callback
210
+ if (mockSpringConfig.onChange) {
211
+ mockSpringConfig.onChange()
212
+ }
213
+ })
214
+
215
+ // Verify that joint rotations were not modified
216
+ mockJointObjects.forEach((joint) => {
217
+ expect(joint.rotation.y).toBe(0)
218
+ })
219
+ })
220
+
221
+ it("should handle spring updates correctly", async () => {
222
+ renderRobotAnimator()
223
+
224
+ const newValues = [1, 2, 3, 4, 5, 6]
225
+
226
+ // Wait for initialization
227
+ await waitFor(() => {
228
+ expect(mockCollectJoints).toHaveBeenCalled()
229
+ })
230
+
231
+ await act(async () => {
232
+ // Simulate motion state update by calling the autorun callback
233
+ if (mockAutorunCallback) {
234
+ mockAutorunCallback()
235
+ }
236
+ })
237
+
238
+ expect(mockSetSpring.start).toHaveBeenCalledWith(
239
+ expect.objectContaining({
240
+ 0: 0,
241
+ 1: 0.5,
242
+ 2: 1,
243
+ 3: 1.5,
244
+ 4: 2,
245
+ 5: 2.5,
246
+ }),
247
+ )
248
+ })
249
+
250
+ it("should handle group reference being null", async () => {
251
+ renderRobotAnimator()
252
+
253
+ // This should not throw an error
254
+ await act(async () => {
255
+ // Component should handle null group reference gracefully
256
+ })
257
+
258
+ expect(mockCollectJoints).toHaveBeenCalled()
259
+ })
260
+
261
+ it("should call invalidate when updating rotations", async () => {
262
+ renderRobotAnimator()
263
+
264
+ // Wait for initialization
265
+ await waitFor(() => {
266
+ expect(mockCollectJoints).toHaveBeenCalled()
267
+ })
268
+
269
+ await act(async () => {
270
+ // Simulate spring animation callback
271
+ if (mockSpringConfig.onChange) {
272
+ mockSpringConfig.onChange()
273
+ }
274
+ })
275
+
276
+ // Note: invalidate is called from useThree hook which is mocked
277
+ // The actual invalidate call would happen in the real component
278
+ expect(mockSpringConfig.onChange).toHaveBeenCalled()
279
+ })
280
+
281
+ it("should handle onResolve callback", async () => {
282
+ const mockOnRotationChanged = vi.fn()
283
+ renderRobotAnimator({ onRotationChanged: mockOnRotationChanged })
284
+
285
+ // Wait for initialization
286
+ await waitFor(() => {
287
+ expect(mockCollectJoints).toHaveBeenCalled()
288
+ })
289
+
290
+ await act(async () => {
291
+ // Simulate spring onResolve callback
292
+ if (mockSpringConfig.onResolve) {
293
+ mockSpringConfig.onResolve()
294
+ }
295
+ })
296
+
297
+ expect(mockOnRotationChanged).toHaveBeenCalled()
298
+ })
299
+
300
+ it("should filter out undefined joint values", async () => {
301
+ const motionStateWithUndefined = {
302
+ state: {
303
+ joint_position: {
304
+ joints: [1, undefined, 3, undefined, 5, 6],
305
+ },
306
+ },
307
+ } as any
308
+
309
+ renderRobotAnimator({
310
+ rapidlyChangingMotionState: motionStateWithUndefined,
311
+ })
312
+
313
+ // Wait for initialization
314
+ await waitFor(() => {
315
+ expect(mockCollectJoints).toHaveBeenCalled()
316
+ })
317
+
318
+ await act(async () => {
319
+ if (mockAutorunCallback) {
320
+ mockAutorunCallback()
321
+ }
322
+ })
323
+
324
+ // Should call setSpring.start with filtered values
325
+ expect(mockSetSpring.start).toHaveBeenCalledWith(
326
+ expect.objectContaining({
327
+ 0: 1,
328
+ 1: 3,
329
+ 2: 5,
330
+ 3: 6,
331
+ }),
332
+ )
333
+ })
334
+
335
+ it("should handle empty joint objects gracefully", async () => {
336
+ mockCollectJoints.mockReturnValue([])
337
+
338
+ const { container } = renderRobotAnimator()
339
+
340
+ await act(async () => {
341
+ if (mockAutorunCallback) {
342
+ mockAutorunCallback()
343
+ }
344
+ })
345
+
346
+ // Should not throw and should not call setSpring.start
347
+ expect(mockSetSpring.start).not.toHaveBeenCalled()
348
+ })
349
+ })
@@ -4,7 +4,7 @@ import type {
4
4
  DHParameter,
5
5
  MotionGroupStateResponse,
6
6
  } from "@wandelbots/nova-api/v1"
7
- import React, { useEffect, useRef, useState, useCallback } from "react"
7
+ import React, { useCallback, useEffect, useRef, useState } from "react"
8
8
  import type { Group, Object3D } from "three"
9
9
  import { useAutorun } from "../utils/hooks"
10
10
  import { collectJoints } from "./robotModelLogic"
@@ -1,6 +1,9 @@
1
- import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"
1
+ import type {
2
+ DHParameter,
3
+ MotionGroupStateResponse,
4
+ } from "@wandelbots/nova-api/v1"
2
5
  import { Object3D } from "three"
3
- import type { DHParameter, MotionGroupStateResponse } from "@wandelbots/nova-api/v1"
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
4
7
 
5
8
  // Import the component for testing
6
9
  import RobotAnimator from "./RobotAnimator"
@@ -27,9 +30,9 @@ vi.mock("@react-three/fiber", () => ({
27
30
  }),
28
31
  }))
29
32
 
33
+ import { useSpring } from "@react-spring/three"
30
34
  import { useAutorun } from "../utils/hooks"
31
35
  import { collectJoints } from "./robotModelLogic"
32
- import { useSpring } from "@react-spring/three"
33
36
 
34
37
  const mockUseAutorun = useAutorun as any
35
38
  const mockCollectJoints = collectJoints as any
@@ -72,7 +75,9 @@ describe("RobotAnimator Logic Tests", () => {
72
75
  mockAxisValues = {}
73
76
  for (let i = 0; i < 6; i++) {
74
77
  mockAxisValues[i] = {
75
- get: vi.fn().mockReturnValue(mockMotionState.state.joint_position.joints[i]),
78
+ get: vi
79
+ .fn()
80
+ .mockReturnValue(mockMotionState.state.joint_position.joints[i]),
76
81
  }
77
82
  }
78
83
 
@@ -123,7 +128,7 @@ describe("RobotAnimator Logic Tests", () => {
123
128
  const dhParam = dhParameters[index]
124
129
  const rotationOffset = dhParam.theta || 0
125
130
  const rotationSign = dhParam.reverse_rotation_direction ? -1 : 1
126
-
131
+
127
132
  joint.rotation.y = rotationSign * jointValues[index]! + rotationOffset
128
133
  })
129
134
 
@@ -140,7 +145,7 @@ describe("RobotAnimator Logic Tests", () => {
140
145
  // Test the filtering logic that removes undefined values
141
146
  const inputJoints = [1, undefined, 3, undefined, 5, 6]
142
147
  const filteredJoints = inputJoints.filter((item) => item !== undefined)
143
-
148
+
144
149
  expect(filteredJoints).toEqual([1, 3, 5, 6])
145
150
  expect(filteredJoints.length).toBe(4)
146
151
  })
@@ -148,25 +153,25 @@ describe("RobotAnimator Logic Tests", () => {
148
153
  it("should test DH parameter application", () => {
149
154
  // Test various DH parameter combinations
150
155
  const testCases = [
151
- {
152
- jointValue: 1.0,
156
+ {
157
+ jointValue: 1.0,
153
158
  dhParam: { theta: 0, reverse_rotation_direction: false },
154
- expected: 1.0
159
+ expected: 1.0,
155
160
  },
156
- {
157
- jointValue: 1.0,
161
+ {
162
+ jointValue: 1.0,
158
163
  dhParam: { theta: 0.5, reverse_rotation_direction: false },
159
- expected: 1.5
164
+ expected: 1.5,
160
165
  },
161
- {
162
- jointValue: 1.0,
166
+ {
167
+ jointValue: 1.0,
163
168
  dhParam: { theta: 0, reverse_rotation_direction: true },
164
- expected: -1.0
169
+ expected: -1.0,
165
170
  },
166
- {
167
- jointValue: 1.0,
171
+ {
172
+ jointValue: 1.0,
168
173
  dhParam: { theta: 0.5, reverse_rotation_direction: true },
169
- expected: -0.5
174
+ expected: -0.5,
170
175
  },
171
176
  ]
172
177
 
@@ -174,9 +179,9 @@ describe("RobotAnimator Logic Tests", () => {
174
179
  const joint = new Object3D()
175
180
  const rotationOffset = dhParam.theta || 0
176
181
  const rotationSign = dhParam.reverse_rotation_direction ? -1 : 1
177
-
182
+
178
183
  joint.rotation.y = rotationSign * jointValue + rotationOffset
179
-
184
+
180
185
  expect(joint.rotation.y).toBeCloseTo(expected, 5)
181
186
  })
182
187
  })
@@ -203,10 +208,11 @@ describe("RobotAnimator Logic Tests", () => {
203
208
  it("should test empty joint array handling", () => {
204
209
  // Test behavior when no joints are available
205
210
  const emptyJoints: Object3D[] = []
206
-
211
+
207
212
  // This simulates the isInitialized check in the component
208
- const isInitialized = emptyJoints.length > 0 && emptyJoints.every((obj) => obj != null)
209
-
213
+ const isInitialized =
214
+ emptyJoints.length > 0 && emptyJoints.every((obj) => obj != null)
215
+
210
216
  expect(isInitialized).toBe(false)
211
217
  })
212
218
 
@@ -214,10 +220,12 @@ describe("RobotAnimator Logic Tests", () => {
214
220
  // Test the joint object validation logic
215
221
  const validJoints = [new Object3D(), new Object3D(), new Object3D()]
216
222
  const invalidJoints = [new Object3D(), null, new Object3D()]
217
-
218
- const isValidJointsInitialized = validJoints.length > 0 && validJoints.every((obj) => obj != null)
219
- const isInvalidJointsInitialized = invalidJoints.length > 0 && invalidJoints.every((obj) => obj != null)
220
-
223
+
224
+ const isValidJointsInitialized =
225
+ validJoints.length > 0 && validJoints.every((obj) => obj != null)
226
+ const isInvalidJointsInitialized =
227
+ invalidJoints.length > 0 && invalidJoints.every((obj) => obj != null)
228
+
221
229
  expect(isValidJointsInitialized).toBe(true)
222
230
  expect(isInvalidJointsInitialized).toBe(false)
223
231
  })
@@ -227,7 +235,9 @@ describe("RobotAnimator Logic Tests", () => {
227
235
  expect(mockMotionState.state).toBeDefined()
228
236
  expect(mockMotionState.state.joint_position).toBeDefined()
229
237
  expect(mockMotionState.state.joint_position.joints).toBeDefined()
230
- expect(Array.isArray(mockMotionState.state.joint_position.joints)).toBe(true)
238
+ expect(Array.isArray(mockMotionState.state.joint_position.joints)).toBe(
239
+ true,
240
+ )
231
241
  expect(mockMotionState.state.joint_position.joints.length).toBe(6)
232
242
  })
233
243
 
@@ -245,10 +255,10 @@ describe("RobotAnimator Logic Tests", () => {
245
255
  const mockCallback = vi.fn()
246
256
  const testJoints = [new Object3D(), new Object3D()]
247
257
  const testValues = [1.0, 2.0]
248
-
258
+
249
259
  // Simulate calling the callback
250
260
  mockCallback(testJoints, testValues)
251
-
261
+
252
262
  expect(mockCallback).toHaveBeenCalledWith(testJoints, testValues)
253
263
  expect(mockCallback).toHaveBeenCalledTimes(1)
254
264
  })
@@ -257,29 +267,32 @@ describe("RobotAnimator Logic Tests", () => {
257
267
  // Test the initialization state logic that prevents race conditions
258
268
  let isInitialized = false
259
269
  const joints: Object3D[] = []
260
-
270
+
261
271
  // Simulate setGroupRef logic
262
272
  function simulateSetGroupRef(group: any) {
263
273
  if (!group) {
264
274
  isInitialized = false
265
275
  return
266
276
  }
267
-
277
+
268
278
  // Simulate collectJoints
269
279
  const collectedJoints = group ? [new Object3D(), new Object3D()] : []
270
-
280
+
271
281
  // Only mark as initialized if we have valid joint objects
272
- if (collectedJoints.length > 0 && collectedJoints.every((obj) => obj != null)) {
282
+ if (
283
+ collectedJoints.length > 0 &&
284
+ collectedJoints.every((obj) => obj != null)
285
+ ) {
273
286
  isInitialized = true
274
287
  } else {
275
288
  isInitialized = false
276
289
  }
277
290
  }
278
-
291
+
279
292
  // Test null group
280
293
  simulateSetGroupRef(null)
281
294
  expect(isInitialized).toBe(false)
282
-
295
+
283
296
  // Test valid group
284
297
  simulateSetGroupRef({ type: "Group" })
285
298
  expect(isInitialized).toBe(true)
package/src/test/setup.ts CHANGED
@@ -1,5 +1,5 @@
1
- import '@testing-library/jest-dom'
2
- import { beforeAll, vi } from 'vitest'
1
+ import "@testing-library/jest-dom"
2
+ import { beforeAll, vi } from "vitest"
3
3
 
4
4
  // Mock ResizeObserver
5
5
  global.ResizeObserver = vi.fn().mockImplementation(() => ({
@@ -16,9 +16,9 @@ global.IntersectionObserver = vi.fn().mockImplementation(() => ({
16
16
  }))
17
17
 
18
18
  // Mock matchMedia
19
- Object.defineProperty(window, 'matchMedia', {
19
+ Object.defineProperty(window, "matchMedia", {
20
20
  writable: true,
21
- value: vi.fn().mockImplementation(query => ({
21
+ value: vi.fn().mockImplementation((query) => ({
22
22
  matches: false,
23
23
  media: query,
24
24
  onchange: null,
@@ -95,15 +95,17 @@ const mockWebGLContext = {
95
95
  TRUE: 1,
96
96
  }
97
97
 
98
- HTMLCanvasElement.prototype.getContext = vi.fn().mockImplementation((contextType) => {
99
- if (contextType === 'webgl' || contextType === 'experimental-webgl') {
100
- return mockWebGLContext
101
- }
102
- return null
103
- })
98
+ HTMLCanvasElement.prototype.getContext = vi
99
+ .fn()
100
+ .mockImplementation((contextType) => {
101
+ if (contextType === "webgl" || contextType === "experimental-webgl") {
102
+ return mockWebGLContext
103
+ }
104
+ return null
105
+ })
104
106
 
105
107
  beforeAll(() => {
106
108
  // Suppress console errors in tests
107
- vi.spyOn(console, 'error').mockImplementation(() => {})
108
- vi.spyOn(console, 'warn').mockImplementation(() => {})
109
+ vi.spyOn(console, "error").mockImplementation(() => {})
110
+ vi.spyOn(console, "warn").mockImplementation(() => {})
109
111
  })