import { describe, expect, it } from "vitest"; import { createKdlWorkerRuntime } from "../../src/kdl/runtime.js"; import { dispatchKdlRpcRequest } from "../../src/kdl/workerRpc.js"; import type { CycleTimeResult, PathPlanResult, TrajectoryResult } from "../../src/kdl/types.js"; function pose(x: number, y: number, z: number) { return { position: [x, y, z] as [number, number, number], quaternion: [0, 0, 0, 1] as [number, number, number, number] }; } function trajectory(overrides: Partial = {}): TrajectoryResult { return { ok: true, motion: "MOVEJ", duration: 1, sampleTime: 0.5, events: [], diagnostics: [], points: [ { index: 0, time: 0, dt: 0, s: 0, sd: 0, sdd: 0, joints: [0, 0], jointVelocity: [0, 0], jointAcceleration: [0, 0], flange: pose(0, 0, 0), tcp: pose(0, 0, 0), motion: "MOVEJ", segmentId: "s1", diagnostics: [] }, { index: 1, time: 1, dt: 1, s: 1, sd: 0, sdd: 0, joints: [1, 2], jointVelocity: [0, 0], jointAcceleration: [0, 0], flange: pose(1, 0, 0), tcp: pose(1, 2, 0), motion: "MOVEJ", segmentId: "s1", diagnostics: [] } ], ...overrides }; } async function createRuntime() { const runtime = createKdlWorkerRuntime(); await dispatchKdlRpcRequest(runtime, { id: 1, method: "init", payload: [{}] }); return runtime; } describe("cycle-time, resample, and diagnostics utilities", () => { it("estimates cycle time for a trajectory and a path plan", async () => { const runtime = await createRuntime(); const singleResponse = await dispatchKdlRpcRequest(runtime, { id: 2, method: "estimateCycleTime", payload: [trajectory()] }); expect(singleResponse.result).toMatchObject({ ok: true, motionTime: 1, totalTime: 1, segmentTimes: [ { segmentId: "s1", motion: "MOVEJ", duration: 1 } ], diagnostics: [] }); const path: PathPlanResult = { ok: true, duration: 3, segments: [ trajectory(), trajectory({ motion: "MOVEL", duration: 2, points: trajectory().points.map((point) => ({ ...point, motion: "MOVEL", segmentId: "s2" })) }) ], points: [], diagnostics: [] }; const pathResponse = await dispatchKdlRpcRequest(runtime, { id: 3, method: "estimateCycleTime", payload: [path] }); expect(pathResponse.result).toMatchObject({ ok: true, motionTime: 3, totalTime: 3, segmentTimes: [ { segmentId: "s1", motion: "MOVEJ", duration: 1 }, { segmentId: "s2", motion: "MOVEL", duration: 2 } ] }); }); it("resamples a trajectory with stable time and point ordering", async () => { const runtime = await createRuntime(); const response = await dispatchKdlRpcRequest(runtime, { id: 4, method: "resampleTrajectory", payload: [trajectory(), 0.25] }); expect(response.ok).toBe(true); const result = response.result as TrajectoryResult; expect(result.sampleTime).toBe(0.25); expect(result.points.map((point) => point.time)).toEqual([0, 0.25, 0.5, 0.75, 1]); expect(result.points.map((point) => point.index)).toEqual([0, 1, 2, 3, 4]); expect(result.points[2]?.joints).toEqual([0.5, 1]); expect(result.points[2]?.tcp.position).toEqual([0.5, 1, 0]); expect(result.diagnostics).toContainEqual( expect.objectContaining({ severity: "info", code: "KDL_TRAJECTORY_RESAMPLED" }) ); }); it("keeps structured diagnostics for warning and error cases", async () => { const runtime = await createRuntime(); const emptyResponse = await dispatchKdlRpcRequest(runtime, { id: 5, method: "resampleTrajectory", payload: [ trajectory({ points: [] }), 0.1 ] }); expect(emptyResponse.result).toMatchObject({ diagnostics: [ { severity: "warning", code: "KDL_RESAMPLE_EMPTY_TRAJECTORY" } ] }); const invalidResponse = await dispatchKdlRpcRequest(runtime, { id: 6, method: "resampleTrajectory", payload: [trajectory(), 0] }); expect(invalidResponse).toMatchObject({ ok: false, error: { code: "KDL_INVALID_SAMPLE_TIME", diagnostics: [ { severity: "error", code: "KDL_INVALID_SAMPLE_TIME" } ] } }); }); });