79 lines
5.7 KiB
JavaScript
79 lines
5.7 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { AxisOperationController } from "../app/axis-operation-controller.mjs";
|
|
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
|
|
import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs";
|
|
|
|
const CASE = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini";
|
|
const PROGRAM = "G21 G90\nG0 X0 Y0 Z10 A0 C0\nG1 X10 Y5 Z5 A30 C45 F100\nM2\n";
|
|
const fetchImpl = async (url) => {
|
|
try {
|
|
const body = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url));
|
|
return { ok: true, text: async () => body.toString("utf8") };
|
|
} catch {
|
|
return { ok: false, text: async () => "" };
|
|
}
|
|
};
|
|
|
|
const bundle = await loadSimulationCase({ path: CASE, fetchImpl });
|
|
assert.equal(bundle.status, "ready");
|
|
const core = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) });
|
|
const session = new XyzacSession(core, { sessionId: "xyzac-protocol-react" });
|
|
const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId };
|
|
let seq = 0;
|
|
const send = (kind, operationId, payload = {}) => session.accept({ ...base, kind, seq: ++seq, operation_id: operationId, payload });
|
|
send("HELLO", "hello");
|
|
send("SESSION_ATTACH", "attach");
|
|
assert.equal(send("LOAD_WORKSPACE", "workspace", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.status, "verified");
|
|
|
|
const power = send("POWER", "power-on", { active: true });
|
|
assert.equal(power.kind, "COMMAND_RESULT");
|
|
assert.equal(power.payload.command, "POWER");
|
|
assert.equal(send("HOME", "home-all", { all: true }).payload.wasm_home_limit.state.homed, true);
|
|
const switches = [];
|
|
for (const type of [0, 1, 2, 1]) {
|
|
const response = send("KINEMATICS_SELECT", `select-${type}-${seq}`, { type });
|
|
assert.equal(response.kind, "COMMAND_RESULT");
|
|
assert.equal(response.payload.kinematics.active_type, type);
|
|
assert.equal(response.payload.remap.hal_pin, "motion.switchkins-type");
|
|
switches.push({ type, operation_id: response.operation_id, phase: response.payload.xyzac_ledger?.entries?.at(-1)?.phase ?? "committed" });
|
|
}
|
|
const invalid = send("KINEMATICS_SELECT", "select-invalid", { type: 9 });
|
|
assert.equal(invalid.kind, "ERROR");
|
|
assert.equal(invalid.payload.gate_reason, "xyzac_kinematics_type_invalid");
|
|
|
|
assert.equal(send("PROGRAM_LOAD", "program-load", { source: PROGRAM, path: "xyzac-panel.ngc" }).kind, "COMMAND_RESULT");
|
|
const clear = send("VISUAL_CLEAR", "visual-clear");
|
|
assert.equal(clear.kind, "COMMAND_RESULT");
|
|
assert.equal(clear.payload.visual_only, true);
|
|
assert.deepEqual(clear.payload.before, clear.payload.after);
|
|
assert.equal(send("PROGRAM_RUN", "program-run").kind, "COMMAND_RESULT");
|
|
const runningSwitch = send("KINEMATICS_SELECT", "select-running", { type: 0 });
|
|
assert.equal(runningSwitch.kind, "ERROR");
|
|
assert.equal(runningSwitch.payload.gate_reason, "xyzac_interp_running");
|
|
send("PROGRAM_ABORT", "program-abort");
|
|
|
|
const sent = [];
|
|
const fakeClient = { send: async (kind, payload, operationId) => { sent.push({ kind, payload, operationId }); return { kind: "COMMAND_RESULT", payload: { status: "verified" } }; } };
|
|
const controller = new AxisOperationController(fakeClient);
|
|
const disabledState = { task: { state: "READY", mode: "MANUAL", interp_state: "IDLE" }, xyzac: { coordinates: "XYZAC", joint_count: 5, hal_pin_vars: true, switchkins_net: true, finite_offsets: true, userk_capability: true }, gates: { switchkinsXyzac: { action: "switchkinsXyzac", enabled: false, reason: "worker_gate_disabled" } } };
|
|
const workerBlocked = await controller.execute("KINEMATICS_SELECT", { type: 1 }, { state: disabledState, action: "switchkinsXyzac" });
|
|
assert.equal(workerBlocked.response.kind, "COMMAND_RESULT");
|
|
assert.equal(sent.length, 1);
|
|
const enabledState = { ...disabledState, gates: { switchkinsXyzac: { action: "switchkinsXyzac", enabled: true, reason: "xyzac_m428_available" } } };
|
|
await controller.execute("KINEMATICS_SELECT", { type: 1 }, { state: enabledState, action: "switchkinsXyzac" });
|
|
assert.equal(sent.length, 2);
|
|
assert.deepEqual(controller.exportTrace().map((item) => item.phase), ["pending", "result", "pending", "result"]);
|
|
|
|
const appSource = await readFile(new URL("../react/src/App.tsx", import.meta.url), "utf8");
|
|
const panelSource = await readFile(new URL("../react/src/XyzacTrtPanel.tsx", import.meta.url), "utf8");
|
|
const contractSource = await readFile(new URL("../react/src/xyzac-panel-contract.ts", import.meta.url), "utf8");
|
|
const combined = `${appSource}\n${panelSource}\n${contractSource}`;
|
|
for (const symbol of ["XyzacTrtPanel", "XyzacProgramPane", "XyzacKinematicsControl", "XyzacManualPanel", "XyzacDiagnostics", "KINEMATICS_SELECT", "VISUAL_CLEAR", "POWER", "switchkinsXyzac", "Operation trace", "Alarms / recovery"]) assert.ok(combined.includes(symbol), symbol);
|
|
for (const forbidden of [/_sim_/, /_malloc/, /\bHEAP(?:U|F|I)?\d*\b/, /navigator\.storage/, /\bOPFS\b/i, /postMessage\s*\(/]) assert.equal(forbidden.test(combined), false, String(forbidden));
|
|
assert.ok(appSource.includes("SELECTED_XYZAC_CASE"));
|
|
assert.equal(appSource.includes("3axis-tutorial"), false);
|
|
|
|
console.log(JSON.stringify({ schema: "xyzac-protocol-react-rtcp-09.v1", status: "verified", case: CASE, commands: [power.payload.command, "KINEMATICS_SELECT", "VISUAL_CLEAR"], switchkins: switches, gates: { invalid: invalid.payload.gate_reason, running: runningSwitch.payload.gate_reason, controller: controller.exportTrace().map((item) => item.phase) }, components: ["XyzacTrtPanel", "XyzacProgramPane", "XyzacKinematicsControl", "XyzacManualPanel", "XyzacDiagnostics"], worker_only: true, state_hash: clear.payload.panel_snapshot.state_hash }));
|