@wandelbots/wandelbots-js-react-components 2.26.0-pr.feature-update-to-react-19.361.ec4e097 → 2.26.0-pr.feature-update-to-react-19.361.edd4b6b
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/dist/components/robots/RobotAnimator.d.ts.map +1 -1
- package/dist/index.cjs +18 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +699 -688
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/robots/RobotAnimator.tsx +57 -41
- package/dist/components/robots/RobotAnimator.test.d.ts +0 -2
- package/dist/components/robots/RobotAnimator.test.d.ts.map +0 -1
- package/src/components/robots/RobotAnimator.test.tsx +0 -349
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.
|
|
3
|
+
"version": "2.26.0-pr.feature-update-to-react-19.361.edd4b6b",
|
|
4
4
|
"description": "React UI toolkit for building applications on top of the Wandelbots platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
DHParameter,
|
|
5
5
|
MotionGroupStateResponse,
|
|
6
6
|
} from "@wandelbots/nova-api/v1"
|
|
7
|
-
import React, {
|
|
7
|
+
import React, { useRef } from "react"
|
|
8
8
|
import type { Group, Object3D } from "three"
|
|
9
9
|
import { useAutorun } from "../utils/hooks"
|
|
10
10
|
import { collectJoints } from "./robotModelLogic"
|
|
@@ -25,56 +25,57 @@ export default function RobotAnimator({
|
|
|
25
25
|
Globals.assign({ frameLoop: "always" })
|
|
26
26
|
const jointValues = useRef<number[]>([])
|
|
27
27
|
const jointObjects = useRef<Object3D[]>([])
|
|
28
|
-
const [isInitialized, setIsInitialized] = useState(false)
|
|
29
28
|
const { invalidate } = useThree()
|
|
30
29
|
|
|
31
30
|
function setGroupRef(group: Group | null) {
|
|
32
|
-
if (!group)
|
|
33
|
-
setIsInitialized(false)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
31
|
+
if (!group) return
|
|
36
32
|
|
|
37
33
|
jointObjects.current = collectJoints(group)
|
|
38
34
|
|
|
39
|
-
//
|
|
35
|
+
// Set initial position only if we have valid joint objects
|
|
40
36
|
if (
|
|
41
37
|
jointObjects.current.length > 0 &&
|
|
42
38
|
jointObjects.current.every((obj) => obj != null)
|
|
43
39
|
) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
// Use requestAnimationFrame to ensure axisValues is ready
|
|
41
|
+
requestAnimationFrame(() => {
|
|
42
|
+
setRotation()
|
|
43
|
+
invalidate()
|
|
44
|
+
})
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
function updateJoints(newJointValues: number[]) {
|
|
51
|
-
if (!isInitialized) return
|
|
52
|
-
|
|
53
49
|
jointValues.current = newJointValues
|
|
54
50
|
setSpring.start(Object.assign({}, jointValues.current) as any)
|
|
55
51
|
}
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
const [axisValues, setSpring] = useSpring(() => {
|
|
54
|
+
const currentJointValues =
|
|
55
|
+
rapidlyChangingMotionState.state.joint_position.joints.filter(
|
|
56
|
+
(item) => item !== undefined,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
...Object.assign({}, currentJointValues),
|
|
61
|
+
onChange: () => {
|
|
65
62
|
setRotation()
|
|
66
63
|
invalidate()
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
onResolve: () => {
|
|
70
|
-
if (isInitialized) {
|
|
64
|
+
},
|
|
65
|
+
onResolve: () => {
|
|
71
66
|
setRotation()
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
})
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
})
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
function setRotation() {
|
|
72
|
+
// Safety check to prevent race condition
|
|
73
|
+
if (
|
|
74
|
+
jointObjects.current.length === 0 ||
|
|
75
|
+
!jointObjects.current.every((obj) => obj != null)
|
|
76
|
+
) {
|
|
77
|
+
return
|
|
78
|
+
}
|
|
78
79
|
|
|
79
80
|
const updatedJointValues = jointObjects.current.map((_, objectIndex) =>
|
|
80
81
|
(axisValues as any)[objectIndex].get(),
|
|
@@ -92,25 +93,40 @@ export default function RobotAnimator({
|
|
|
92
93
|
rotationSign * updatedJointValues[index]! + rotationOffset
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Effect to handle initial rotation when component becomes initialized
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (isInitialized) {
|
|
100
|
-
setRotation()
|
|
101
|
-
invalidate()
|
|
102
|
-
}
|
|
103
|
-
}, [isInitialized, setRotation, invalidate])
|
|
96
|
+
}
|
|
104
97
|
|
|
105
98
|
useAutorun(() => {
|
|
106
|
-
if (!isInitialized) return
|
|
107
|
-
|
|
108
99
|
const newJointValues =
|
|
109
100
|
rapidlyChangingMotionState.state.joint_position.joints.filter(
|
|
110
101
|
(item) => item !== undefined,
|
|
111
102
|
)
|
|
112
103
|
|
|
113
|
-
requestAnimationFrame(() =>
|
|
104
|
+
requestAnimationFrame(() => {
|
|
105
|
+
// Check if spring values are significantly different from target values
|
|
106
|
+
const currentSpringValues = jointObjects.current.map(
|
|
107
|
+
(_, index) => (axisValues as any)[index]?.get() || 0,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
const maxDifference = Math.max(
|
|
111
|
+
...newJointValues.map((newValue, index) =>
|
|
112
|
+
Math.abs(newValue - (currentSpringValues[index] || 0)),
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
// Be more aggressive about detecting "snap back" situations
|
|
117
|
+
// If difference is large (> 0.05 radians ~= 2.9 degrees), reset immediately
|
|
118
|
+
// This handles focus restoration and other cases where animation would be jarring
|
|
119
|
+
if (maxDifference > 0.05) {
|
|
120
|
+
jointValues.current = newJointValues
|
|
121
|
+
setSpring.set(Object.assign({}, jointValues.current) as any)
|
|
122
|
+
// Force immediate rotation update
|
|
123
|
+
setRotation()
|
|
124
|
+
invalidate()
|
|
125
|
+
} else {
|
|
126
|
+
// Normal smooth animation for small changes
|
|
127
|
+
updateJoints(newJointValues)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
114
130
|
})
|
|
115
131
|
|
|
116
132
|
return <group ref={setGroupRef}>{children}</group>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"RobotAnimator.test.d.ts","sourceRoot":"","sources":["../../../src/components/robots/RobotAnimator.test.tsx"],"names":[],"mappings":""}
|
|
@@ -1,349 +0,0 @@
|
|
|
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
|
-
})
|