@wandelbots/wandelbots-js-react-components 5.4.0 → 5.4.1
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/jogging/JoggingPanel.test.d.ts +2 -0
- package/dist/components/jogging/JoggingPanel.test.d.ts.map +1 -0
- package/dist/components/jogging/__fixtures__/motionStreamMockData.d.ts +11 -0
- package/dist/components/jogging/__fixtures__/motionStreamMockData.d.ts.map +1 -0
- package/dist/core.cjs.js +1 -1
- package/dist/core.es.js +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/lib/JoggerConnection.d.ts +5 -3
- package/dist/lib/JoggerConnection.d.ts.map +1 -1
- package/dist/{theming-BCLKcKOU.cjs → theming-BJ6pB6jG.cjs} +18 -18
- package/dist/{theming-BCLKcKOU.cjs.map → theming-BJ6pB6jG.cjs.map} +1 -1
- package/dist/{theming-BnAhDNbi.js → theming-LwkvEF-K.js} +548 -543
- package/dist/{theming-BnAhDNbi.js.map → theming-LwkvEF-K.js.map} +1 -1
- package/package.json +1 -1
- package/src/components/jogging/JoggingPanel.test.tsx +653 -0
- package/src/components/jogging/__fixtures__/motionStreamMockData.ts +137 -0
- package/src/lib/JoggerConnection.ts +24 -10
package/package.json
CHANGED
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
import { render, screen, within } from "@testing-library/react"
|
|
2
|
+
import userEvent from "@testing-library/user-event"
|
|
3
|
+
import { describe, expect, it, vi, beforeEach } from "vitest"
|
|
4
|
+
import { JointTypeEnum } from "@wandelbots/nova-js/v2"
|
|
5
|
+
import type {
|
|
6
|
+
CoordinateSystem,
|
|
7
|
+
MotionGroupDescription,
|
|
8
|
+
MotionGroupState,
|
|
9
|
+
RobotTcp,
|
|
10
|
+
DHParameter,
|
|
11
|
+
} from "@wandelbots/nova-js/v2"
|
|
12
|
+
import { JoggingStore } from "./JoggingStore"
|
|
13
|
+
import { JoggingJointTab } from "./JoggingJointTab"
|
|
14
|
+
import { JoggingCartesianTab } from "./JoggingCartesianTab"
|
|
15
|
+
import { I18nextProvider } from "react-i18next"
|
|
16
|
+
import { i18n } from "../../i18n/config"
|
|
17
|
+
import { JoggerConnection } from "../../lib/JoggerConnection"
|
|
18
|
+
import type { JoggerConnection as JoggerConnectionType } from "../../lib/JoggerConnection"
|
|
19
|
+
import type { MotionStreamConnection } from "../../lib/MotionStreamConnection"
|
|
20
|
+
import {
|
|
21
|
+
ur5eMotionGroupState,
|
|
22
|
+
ur5eDescription,
|
|
23
|
+
turnMockMotionGroupState,
|
|
24
|
+
turnMockDescription,
|
|
25
|
+
linearAxisMotionGroupState,
|
|
26
|
+
linearAxisDescription,
|
|
27
|
+
} from "./__fixtures__/motionStreamMockData"
|
|
28
|
+
|
|
29
|
+
// ---------- helpers ----------
|
|
30
|
+
|
|
31
|
+
function createMockMotionStream(
|
|
32
|
+
motionGroupState: MotionGroupState,
|
|
33
|
+
description: MotionGroupDescription,
|
|
34
|
+
tcpOverride?: string,
|
|
35
|
+
): MotionStreamConnection {
|
|
36
|
+
const jointPositions = motionGroupState.joint_position as number[]
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
nova: {} as any,
|
|
40
|
+
controller: { controller: motionGroupState.controller } as any,
|
|
41
|
+
motionGroup: {
|
|
42
|
+
...motionGroupState,
|
|
43
|
+
tcp: tcpOverride,
|
|
44
|
+
} as unknown as MotionGroupState,
|
|
45
|
+
description,
|
|
46
|
+
initialMotionState: motionGroupState,
|
|
47
|
+
motionStateSocket: {
|
|
48
|
+
addEventListener: vi.fn(),
|
|
49
|
+
changeUrl: vi.fn(),
|
|
50
|
+
close: vi.fn(),
|
|
51
|
+
} as any,
|
|
52
|
+
rapidlyChangingMotionState: { ...motionGroupState },
|
|
53
|
+
motionGroupId: motionGroupState.motion_group,
|
|
54
|
+
controllerId: motionGroupState.controller,
|
|
55
|
+
joints: jointPositions.map((_: number, i: number) => ({ index: i })),
|
|
56
|
+
dispose: vi.fn(),
|
|
57
|
+
} as unknown as MotionStreamConnection
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createMockJogger(
|
|
61
|
+
motionStream: MotionStreamConnection,
|
|
62
|
+
): JoggerConnectionType {
|
|
63
|
+
return {
|
|
64
|
+
nova: motionStream.nova,
|
|
65
|
+
motionStream,
|
|
66
|
+
motionGroupId: motionStream.motionGroupId,
|
|
67
|
+
mode: "off" as const,
|
|
68
|
+
tcp: "Flange",
|
|
69
|
+
orientation: "coordsys" as const,
|
|
70
|
+
numJoints: motionStream.joints.length,
|
|
71
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
72
|
+
dispose: vi.fn().mockResolvedValue(undefined),
|
|
73
|
+
rotateJoints: vi.fn().mockResolvedValue(undefined),
|
|
74
|
+
translateTCP: vi.fn().mockResolvedValue(undefined),
|
|
75
|
+
rotateTCP: vi.fn().mockResolvedValue(undefined),
|
|
76
|
+
setOptions: vi.fn(),
|
|
77
|
+
setJoggingMode: vi.fn().mockResolvedValue(undefined),
|
|
78
|
+
initializeJoggingWebsocket: vi.fn().mockResolvedValue(undefined),
|
|
79
|
+
onBlocked: undefined,
|
|
80
|
+
} as unknown as JoggerConnectionType
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Create a JoggingStore from real captured motion group state data */
|
|
84
|
+
function createJoggingStore(opts: {
|
|
85
|
+
motionGroupState: MotionGroupState
|
|
86
|
+
description: MotionGroupDescription
|
|
87
|
+
inverseSolver?: string | null | undefined
|
|
88
|
+
}): JoggingStore {
|
|
89
|
+
const motionStream = createMockMotionStream(opts.motionGroupState, opts.description)
|
|
90
|
+
const jogger = createMockJogger(motionStream)
|
|
91
|
+
|
|
92
|
+
const coordSystems: CoordinateSystem[] = [
|
|
93
|
+
{ coordinate_system: "world", name: "World" } as CoordinateSystem,
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
const tcps: RobotTcp[] = [
|
|
97
|
+
{
|
|
98
|
+
id: "Flange",
|
|
99
|
+
readable_name: "Flange",
|
|
100
|
+
position: [0, 0, 0],
|
|
101
|
+
orientation: [0, 0, 0],
|
|
102
|
+
} as unknown as RobotTcp,
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
return new JoggingStore(
|
|
106
|
+
jogger,
|
|
107
|
+
coordSystems,
|
|
108
|
+
opts.description,
|
|
109
|
+
tcps,
|
|
110
|
+
opts.inverseSolver !== undefined ? opts.inverseSolver : "some-solver",
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Shorthand: create a UR5e-based store (6 revolute joints) with optional overrides */
|
|
115
|
+
function createUr5eStore(inverseSolver?: string | null | undefined) {
|
|
116
|
+
return createJoggingStore({
|
|
117
|
+
motionGroupState: ur5eMotionGroupState,
|
|
118
|
+
description: ur5eDescription,
|
|
119
|
+
inverseSolver,
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderWithI18n(ui: React.ReactElement) {
|
|
124
|
+
return render(<I18nextProvider i18n={i18n}>{ui}</I18nextProvider>)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ---------- tests ----------
|
|
128
|
+
|
|
129
|
+
describe("JoggingPanel", () => {
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
localStorage.clear()
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
// ---- Tab visibility (cartesian tab shown / hidden) ----
|
|
135
|
+
|
|
136
|
+
describe("Cartesian tab visibility", () => {
|
|
137
|
+
it("shows both Cartesian and Joint tabs when inverseSolver is defined (string)", () => {
|
|
138
|
+
const store = createUr5eStore("some-ik-solver")
|
|
139
|
+
expect(store.tabs).toHaveLength(2)
|
|
140
|
+
expect(store.tabs.map((t) => t.id)).toContain("cartesian")
|
|
141
|
+
expect(store.tabs.map((t) => t.id)).toContain("joint")
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it("shows both Cartesian and Joint tabs when inverseSolver is undefined (could not be loaded)", () => {
|
|
145
|
+
const store = createUr5eStore(undefined)
|
|
146
|
+
expect(store.tabs).toHaveLength(2)
|
|
147
|
+
expect(store.tabs.map((t) => t.id)).toContain("cartesian")
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it("hides Cartesian tab when inverseSolver is null (explicitly no solver)", () => {
|
|
151
|
+
const store = createUr5eStore(null)
|
|
152
|
+
expect(store.tabs).toHaveLength(1)
|
|
153
|
+
expect(store.tabs[0]!.id).toBe("joint")
|
|
154
|
+
expect(store.tabs.map((t) => t.id)).not.toContain("cartesian")
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it("defaults to cartesian tab when solver is available", () => {
|
|
158
|
+
const store = createUr5eStore("solver")
|
|
159
|
+
expect(store.currentTab.id).toBe("cartesian")
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it("defaults to joint tab when solver is null", () => {
|
|
163
|
+
const store = createUr5eStore(null)
|
|
164
|
+
expect(store.currentTab.id).toBe("joint")
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
// ---- 6-joint revolute motion group (UR5e) ----
|
|
169
|
+
|
|
170
|
+
describe("6-joint revolute motion group (UR5e)", () => {
|
|
171
|
+
let store: JoggingStore
|
|
172
|
+
|
|
173
|
+
beforeEach(() => {
|
|
174
|
+
store = createJoggingStore({
|
|
175
|
+
motionGroupState: ur5eMotionGroupState,
|
|
176
|
+
description: ur5eDescription,
|
|
177
|
+
inverseSolver: "ik-solver",
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it("has the correct joint type", () => {
|
|
182
|
+
expect(store.jointType).toBe(JointTypeEnum.RevoluteJoint)
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it("displays 6 joint controls in the joint tab", () => {
|
|
186
|
+
renderWithI18n(
|
|
187
|
+
<JoggingJointTab store={store}>
|
|
188
|
+
<></>
|
|
189
|
+
</JoggingJointTab>,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
const wrapper = screen.getByTestId(
|
|
193
|
+
"jogging-joint-value-controls-wrapper",
|
|
194
|
+
)
|
|
195
|
+
for (let i = 0; i < 6; i++) {
|
|
196
|
+
expect(
|
|
197
|
+
within(wrapper).getByTestId(`jogging-joint-value-control-${i}`),
|
|
198
|
+
).toBeInTheDocument()
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("renders the joint tab", () => {
|
|
203
|
+
renderWithI18n(
|
|
204
|
+
<JoggingJointTab store={store}>
|
|
205
|
+
<></>
|
|
206
|
+
</JoggingJointTab>,
|
|
207
|
+
)
|
|
208
|
+
expect(screen.getByTestId("jogging-joint-tab")).toBeInTheDocument()
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it("renders the cartesian tab with translate/rotate toggle", () => {
|
|
212
|
+
renderWithI18n(
|
|
213
|
+
<JoggingCartesianTab store={store}>
|
|
214
|
+
<></>
|
|
215
|
+
</JoggingCartesianTab>,
|
|
216
|
+
)
|
|
217
|
+
expect(
|
|
218
|
+
screen.getByTestId("jogging-cartesian-tab"),
|
|
219
|
+
).toBeInTheDocument()
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it("shows X, Y, Z axis controls in cartesian tab", () => {
|
|
223
|
+
renderWithI18n(
|
|
224
|
+
<JoggingCartesianTab store={store}>
|
|
225
|
+
<></>
|
|
226
|
+
</JoggingCartesianTab>,
|
|
227
|
+
)
|
|
228
|
+
for (const axis of ["x", "y", "z"]) {
|
|
229
|
+
expect(
|
|
230
|
+
screen.getByTestId(`jogging-cartesian-axis-control-${axis}`),
|
|
231
|
+
).toBeInTheDocument()
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it("has both cartesian and joint tabs available", () => {
|
|
236
|
+
expect(store.tabs).toHaveLength(2)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// ---- Prismatic motion group (linear axis) ----
|
|
241
|
+
|
|
242
|
+
describe("Prismatic motion group (linear axis)", () => {
|
|
243
|
+
let store: JoggingStore
|
|
244
|
+
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
store = createJoggingStore({
|
|
247
|
+
motionGroupState: linearAxisMotionGroupState,
|
|
248
|
+
description: linearAxisDescription,
|
|
249
|
+
inverseSolver: null,
|
|
250
|
+
})
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it("has the correct joint type", () => {
|
|
254
|
+
expect(store.jointType).toBe(JointTypeEnum.PrismaticJoint)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it("only shows joint tab (no cartesian) when solver is null", () => {
|
|
258
|
+
expect(store.tabs).toHaveLength(1)
|
|
259
|
+
expect(store.tabs[0]!.id).toBe("joint")
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("uses mm/s as velocity unit for prismatic joints", () => {
|
|
263
|
+
const useDegree =
|
|
264
|
+
store.jointType === JointTypeEnum.RevoluteJoint
|
|
265
|
+
expect(useDegree).toBe(false)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it("renders 1 joint control for single-axis prismatic group", () => {
|
|
269
|
+
renderWithI18n(
|
|
270
|
+
<JoggingJointTab store={store}>
|
|
271
|
+
<></>
|
|
272
|
+
</JoggingJointTab>,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
const wrapper = screen.getByTestId(
|
|
276
|
+
"jogging-joint-value-controls-wrapper",
|
|
277
|
+
)
|
|
278
|
+
expect(
|
|
279
|
+
within(wrapper).getByTestId("jogging-joint-value-control-0"),
|
|
280
|
+
).toBeInTheDocument()
|
|
281
|
+
expect(
|
|
282
|
+
within(wrapper).queryByTestId("jogging-joint-value-control-1"),
|
|
283
|
+
).not.toBeInTheDocument()
|
|
284
|
+
})
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
// ---- 2-joint motion group (turn mock) ----
|
|
288
|
+
|
|
289
|
+
describe("2-joint motion group (turn mock)", () => {
|
|
290
|
+
let store: JoggingStore
|
|
291
|
+
|
|
292
|
+
beforeEach(() => {
|
|
293
|
+
store = createJoggingStore({
|
|
294
|
+
motionGroupState: turnMockMotionGroupState,
|
|
295
|
+
description: turnMockDescription,
|
|
296
|
+
inverseSolver: "solver-2j",
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
it("has the correct joint type", () => {
|
|
301
|
+
expect(store.jointType).toBe(JointTypeEnum.RevoluteJoint)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it("displays exactly 2 joint controls", () => {
|
|
305
|
+
renderWithI18n(
|
|
306
|
+
<JoggingJointTab store={store}>
|
|
307
|
+
<></>
|
|
308
|
+
</JoggingJointTab>,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
const wrapper = screen.getByTestId(
|
|
312
|
+
"jogging-joint-value-controls-wrapper",
|
|
313
|
+
)
|
|
314
|
+
expect(
|
|
315
|
+
within(wrapper).getByTestId("jogging-joint-value-control-0"),
|
|
316
|
+
).toBeInTheDocument()
|
|
317
|
+
expect(
|
|
318
|
+
within(wrapper).getByTestId("jogging-joint-value-control-1"),
|
|
319
|
+
).toBeInTheDocument()
|
|
320
|
+
expect(
|
|
321
|
+
within(wrapper).queryByTestId("jogging-joint-value-control-2"),
|
|
322
|
+
).not.toBeInTheDocument()
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it("has cartesian tab available when solver is present", () => {
|
|
326
|
+
expect(store.tabs.map((t) => t.id)).toContain("cartesian")
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
// ---- Locking / Unlocking ----
|
|
331
|
+
|
|
332
|
+
describe("Locking", () => {
|
|
333
|
+
it("starts unlocked", () => {
|
|
334
|
+
const store = createUr5eStore()
|
|
335
|
+
expect(store.isLocked).toBe(false)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
it("can be locked and unlocked via external lock", () => {
|
|
339
|
+
const store = createUr5eStore()
|
|
340
|
+
store.lock("external")
|
|
341
|
+
expect(store.isLocked).toBe(true)
|
|
342
|
+
|
|
343
|
+
store.unlock("external")
|
|
344
|
+
expect(store.isLocked).toBe(false)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it("supports multiple simultaneous locks", () => {
|
|
348
|
+
const store = createUr5eStore()
|
|
349
|
+
store.lock("lock-a")
|
|
350
|
+
store.lock("lock-b")
|
|
351
|
+
expect(store.isLocked).toBe(true)
|
|
352
|
+
|
|
353
|
+
store.unlock("lock-a")
|
|
354
|
+
expect(store.isLocked).toBe(true) // still locked by lock-b
|
|
355
|
+
|
|
356
|
+
store.unlock("lock-b")
|
|
357
|
+
expect(store.isLocked).toBe(false)
|
|
358
|
+
})
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
// ---- Blocking ----
|
|
362
|
+
|
|
363
|
+
describe("Blocking", () => {
|
|
364
|
+
it("starts unblocked", () => {
|
|
365
|
+
const store = createUr5eStore()
|
|
366
|
+
expect(store.blocked).toBe(false)
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
it("can be blocked and unblocked", () => {
|
|
370
|
+
const store = createUr5eStore()
|
|
371
|
+
store.block()
|
|
372
|
+
expect(store.blocked).toBe(true)
|
|
373
|
+
|
|
374
|
+
store.unblock()
|
|
375
|
+
expect(store.blocked).toBe(false)
|
|
376
|
+
})
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// ---- Tab switching ----
|
|
380
|
+
|
|
381
|
+
describe("Tab switching", () => {
|
|
382
|
+
it("can switch from cartesian to joint tab", () => {
|
|
383
|
+
const store = createUr5eStore("solver")
|
|
384
|
+
expect(store.currentTab.id).toBe("cartesian")
|
|
385
|
+
|
|
386
|
+
store.onTabChange({} as React.SyntheticEvent, 1)
|
|
387
|
+
expect(store.currentTab.id).toBe("joint")
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
it("can switch back to cartesian tab", () => {
|
|
391
|
+
const store = createUr5eStore("solver")
|
|
392
|
+
store.onTabChange({} as React.SyntheticEvent, 1)
|
|
393
|
+
expect(store.currentTab.id).toBe("joint")
|
|
394
|
+
|
|
395
|
+
store.onTabChange({} as React.SyntheticEvent, 0)
|
|
396
|
+
expect(store.currentTab.id).toBe("cartesian")
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
it("falls back to first tab when selected tab is invalid", () => {
|
|
400
|
+
const store = createUr5eStore(null)
|
|
401
|
+
store.selectedTabId = "cartesian" as any
|
|
402
|
+
expect(store.currentTab.id).toBe("joint")
|
|
403
|
+
})
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
// ---- Velocity settings ----
|
|
407
|
+
|
|
408
|
+
describe("Velocity settings", () => {
|
|
409
|
+
it("has default translation velocity", () => {
|
|
410
|
+
const store = createUr5eStore()
|
|
411
|
+
expect(store.translationVelocityMmPerSec).toBe(10)
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
it("has default rotation velocity", () => {
|
|
415
|
+
const store = createUr5eStore()
|
|
416
|
+
expect(store.rotationVelocityDegPerSec).toBe(1)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it("converts rotation velocity to radians correctly", () => {
|
|
420
|
+
const store = createUr5eStore()
|
|
421
|
+
store.rotationVelocityDegPerSec = 180
|
|
422
|
+
expect(store.rotationVelocityRadsPerSec).toBeCloseTo(Math.PI)
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
it("updates translation velocity via slider setter", () => {
|
|
426
|
+
const store = createUr5eStore()
|
|
427
|
+
store.setVelocityFromSlider(50, false)
|
|
428
|
+
expect(store.translationVelocityMmPerSec).toBe(50)
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it("updates rotation velocity via slider setter", () => {
|
|
432
|
+
const store = createUr5eStore()
|
|
433
|
+
store.setVelocityFromSlider(30, true)
|
|
434
|
+
expect(store.rotationVelocityDegPerSec).toBe(30)
|
|
435
|
+
})
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
// ---- Cartesian motion type ----
|
|
439
|
+
|
|
440
|
+
describe("Cartesian motion type", () => {
|
|
441
|
+
it("defaults to translate", () => {
|
|
442
|
+
const store = createUr5eStore()
|
|
443
|
+
expect(store.selectedCartesianMotionType).toBe("translate")
|
|
444
|
+
})
|
|
445
|
+
|
|
446
|
+
it("can switch to rotate", () => {
|
|
447
|
+
const store = createUr5eStore()
|
|
448
|
+
store.setSelectedCartesianMotionType("rotate")
|
|
449
|
+
expect(store.selectedCartesianMotionType).toBe("rotate")
|
|
450
|
+
})
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
// ---- Orientation & coordinate system ----
|
|
454
|
+
|
|
455
|
+
describe("Orientation and coordinate system", () => {
|
|
456
|
+
it("defaults to coordsys orientation", () => {
|
|
457
|
+
const store = createUr5eStore()
|
|
458
|
+
expect(store.selectedOrientation).toBe("coordsys")
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
it("can switch to tool orientation", () => {
|
|
462
|
+
const store = createUr5eStore()
|
|
463
|
+
store.setSelectedOrientation("tool")
|
|
464
|
+
expect(store.selectedOrientation).toBe("tool")
|
|
465
|
+
expect(store.activeCoordSystemId).toBe("tool")
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it("uses selected coordinate system when not in tool orientation", () => {
|
|
469
|
+
const store = createUr5eStore()
|
|
470
|
+
expect(store.activeCoordSystemId).toBe("world")
|
|
471
|
+
})
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
// ---- Increment options ----
|
|
475
|
+
|
|
476
|
+
describe("Increment options", () => {
|
|
477
|
+
it("defaults to continuous jogging", () => {
|
|
478
|
+
const store = createUr5eStore()
|
|
479
|
+
expect(store.selectedIncrementId).toBe("continuous")
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
it("has no active discrete increment when continuous", () => {
|
|
483
|
+
const store = createUr5eStore()
|
|
484
|
+
expect(store.activeDiscreteIncrement).toBeUndefined()
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it("has active discrete increment when a discrete option is selected", () => {
|
|
488
|
+
const store = createUr5eStore()
|
|
489
|
+
store.setSelectedIncrementId("1")
|
|
490
|
+
expect(store.activeDiscreteIncrement).toBeDefined()
|
|
491
|
+
expect(store.activeDiscreteIncrement?.mm).toBe(1)
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
it("disables discrete increment when in tool orientation", () => {
|
|
495
|
+
const store = createUr5eStore()
|
|
496
|
+
store.setSelectedIncrementId("1")
|
|
497
|
+
store.setSelectedOrientation("tool")
|
|
498
|
+
expect(store.activeDiscreteIncrement).toBeUndefined()
|
|
499
|
+
})
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
// ---- Joint legends ----
|
|
503
|
+
|
|
504
|
+
describe("Joint legends", () => {
|
|
505
|
+
it("shows joint labels when showJointsLegend is enabled", () => {
|
|
506
|
+
const store = createJoggingStore({
|
|
507
|
+
motionGroupState: turnMockMotionGroupState,
|
|
508
|
+
description: turnMockDescription,
|
|
509
|
+
})
|
|
510
|
+
store.showJointsLegend = true
|
|
511
|
+
|
|
512
|
+
renderWithI18n(
|
|
513
|
+
<JoggingJointTab store={store}>
|
|
514
|
+
<></>
|
|
515
|
+
</JoggingJointTab>,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
expect(screen.getByText("G1")).toBeInTheDocument()
|
|
519
|
+
expect(screen.getByText("G2")).toBeInTheDocument()
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it("hides joint labels when showJointsLegend is disabled", () => {
|
|
523
|
+
const store = createJoggingStore({
|
|
524
|
+
motionGroupState: turnMockMotionGroupState,
|
|
525
|
+
description: turnMockDescription,
|
|
526
|
+
})
|
|
527
|
+
store.showJointsLegend = false
|
|
528
|
+
|
|
529
|
+
renderWithI18n(
|
|
530
|
+
<JoggingJointTab store={store}>
|
|
531
|
+
<></>
|
|
532
|
+
</JoggingJointTab>,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
expect(screen.queryByText("G1")).not.toBeInTheDocument()
|
|
536
|
+
})
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
// ---- Dispose ----
|
|
540
|
+
|
|
541
|
+
describe("Dispose", () => {
|
|
542
|
+
it("disposes the jogger connection when store is disposed", () => {
|
|
543
|
+
const motionStream = createMockMotionStream(ur5eMotionGroupState, ur5eDescription)
|
|
544
|
+
const jogger = createMockJogger(motionStream)
|
|
545
|
+
|
|
546
|
+
const store = new JoggingStore(
|
|
547
|
+
jogger,
|
|
548
|
+
[{ coordinate_system: "world", name: "World" } as CoordinateSystem],
|
|
549
|
+
ur5eDescription,
|
|
550
|
+
[],
|
|
551
|
+
"solver",
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
store.dispose()
|
|
555
|
+
expect(jogger.dispose).toHaveBeenCalled()
|
|
556
|
+
})
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
// ---- Velocity unit conversions ----
|
|
560
|
+
|
|
561
|
+
describe("Velocity unit conversions (mm/deg)", () => {
|
|
562
|
+
it("rotationVelocityRadsPerSec converts degrees to radians correctly", () => {
|
|
563
|
+
const store = createUr5eStore("solver")
|
|
564
|
+
|
|
565
|
+
// Default is 1 °/s
|
|
566
|
+
expect(store.rotationVelocityRadsPerSec).toBeCloseTo(Math.PI / 180)
|
|
567
|
+
|
|
568
|
+
store.rotationVelocityDegPerSec = 180
|
|
569
|
+
expect(store.rotationVelocityRadsPerSec).toBeCloseTo(Math.PI)
|
|
570
|
+
|
|
571
|
+
store.rotationVelocityDegPerSec = 90
|
|
572
|
+
expect(store.rotationVelocityRadsPerSec).toBeCloseTo(Math.PI / 2)
|
|
573
|
+
})
|
|
574
|
+
|
|
575
|
+
it("velocityInDisplayUnits returns deg/s when useDegree=true, mm/s when false", () => {
|
|
576
|
+
const store = createUr5eStore("solver")
|
|
577
|
+
|
|
578
|
+
store.translationVelocityMmPerSec = 42
|
|
579
|
+
store.rotationVelocityDegPerSec = 15
|
|
580
|
+
|
|
581
|
+
expect(store.velocityInDisplayUnits(false)).toBe(42)
|
|
582
|
+
expect(store.velocityInDisplayUnits(true)).toBe(15)
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it("setVelocityFromSlider updates the correct velocity field", () => {
|
|
586
|
+
const store = createUr5eStore("solver")
|
|
587
|
+
|
|
588
|
+
store.setVelocityFromSlider(50, false)
|
|
589
|
+
expect(store.translationVelocityMmPerSec).toBe(50)
|
|
590
|
+
expect(store.rotationVelocityDegPerSec).toBe(1)
|
|
591
|
+
|
|
592
|
+
store.setVelocityFromSlider(30, true)
|
|
593
|
+
expect(store.rotationVelocityDegPerSec).toBe(30)
|
|
594
|
+
expect(store.translationVelocityMmPerSec).toBe(50)
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
it("minVelocityInDisplayUnits and maxVelocityInDisplayUnits return correct bounds", () => {
|
|
598
|
+
const store = createUr5eStore("solver")
|
|
599
|
+
|
|
600
|
+
expect(store.minVelocityInDisplayUnits(false)).toBe(store.minTranslationVelocityMmPerSec)
|
|
601
|
+
expect(store.maxVelocityInDisplayUnits(false)).toBe(store.maxTranslationVelocityMmPerSec)
|
|
602
|
+
expect(store.minVelocityInDisplayUnits(true)).toBe(store.minRotationVelocityDegPerSec)
|
|
603
|
+
expect(store.maxVelocityInDisplayUnits(true)).toBe(store.maxRotationVelocityDegPerSec)
|
|
604
|
+
})
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
// ---- JoggerConnection TCP resolution ----
|
|
608
|
+
|
|
609
|
+
describe("JoggerConnection TCP resolution", () => {
|
|
610
|
+
it("6-joint revolute defaults to 'Flange' TCP from motionGroup.tcp", () => {
|
|
611
|
+
const ms = createMockMotionStream(ur5eMotionGroupState, ur5eDescription, "Flange")
|
|
612
|
+
const jogger = new JoggerConnection(ms)
|
|
613
|
+
expect(jogger.tcp).toBe("Flange")
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
it("1-joint prismatic without motionGroup.tcp gets undefined (NO_TCP)", () => {
|
|
617
|
+
const ms = createMockMotionStream(linearAxisMotionGroupState, linearAxisDescription)
|
|
618
|
+
const jogger = new JoggerConnection(ms)
|
|
619
|
+
expect(jogger.tcp).toBeUndefined()
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
it("1-joint prismatic with motionGroup.tcp keeps tcp", () => {
|
|
623
|
+
const ms = createMockMotionStream(linearAxisMotionGroupState, linearAxisDescription, "PrismaticTCP")
|
|
624
|
+
const jogger = new JoggerConnection(ms)
|
|
625
|
+
expect(jogger.tcp).toBe("PrismaticTCP")
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
it("2-joint revolute without motionGroup.tcp gets undefined (NO_TCP)", () => {
|
|
629
|
+
const ms = createMockMotionStream(turnMockMotionGroupState, turnMockDescription)
|
|
630
|
+
const jogger = new JoggerConnection(ms)
|
|
631
|
+
expect(jogger.tcp).toBeUndefined()
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
it("explicit tcp option overrides any default", () => {
|
|
635
|
+
const ms = createMockMotionStream(linearAxisMotionGroupState, linearAxisDescription)
|
|
636
|
+
const jogger = new JoggerConnection(ms, { tcp: "MyCustomTool" })
|
|
637
|
+
expect(jogger.tcp).toBe("MyCustomTool")
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it("6-joint revolute with explicit tcp option uses the provided value", () => {
|
|
641
|
+
const ms = createMockMotionStream(ur5eMotionGroupState, ur5eDescription, "Flange")
|
|
642
|
+
const jogger = new JoggerConnection(ms, { tcp: "GripperTCP" })
|
|
643
|
+
expect(jogger.tcp).toBe("GripperTCP")
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
it("6-joint revolute with no motionGroup.tcp falls back to DEFAULT_TCP", () => {
|
|
647
|
+
const ms = createMockMotionStream(ur5eMotionGroupState, ur5eDescription)
|
|
648
|
+
const jogger = new JoggerConnection(ms)
|
|
649
|
+
expect(jogger.tcp).toBe("Flange")
|
|
650
|
+
})
|
|
651
|
+
})
|
|
652
|
+
})
|
|
653
|
+
|