Files
FreeCADWasmDev/web/tools/test-techdraw-desktop-adapter.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

86 lines
6.3 KiB
JavaScript

import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import test from "node:test";
import ts from "typescript";
const modules = new Map();
const routeIndex = [];
function load(name) {
if (name === "./module-registry.generated.js") return { getWebModuleRoute: module => ({ commands: [`${module.toLowerCase()}.pythonFamilyBatch`] }) };
if (modules.has(name)) return modules.get(name);
const source = fs.readFileSync(new URL(`../src/${name.replace(/^\.\//, "").replace(/\.js$/, ".ts")}`, import.meta.url), "utf8");
const code = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 } }).outputText;
const module = { exports: {} };
vm.runInNewContext("(function(exports,require,module){" + code + "})", { AbortController, fetch: async () => ({ ok: true, json: async () => ({ schema: "freecad-web-desktop-command-routes/v1", records: routeIndex }) }) })(module.exports, load, module);
modules.set(name, module.exports); return module.exports;
}
const adapter = load("./techdraw-desktop-adapter.js");
const { DesktopCommandDispatcher } = load("./desktop-command-dispatcher.js");
const record = command => ({ id: `TechDraw:desktop:${command}`, command, module: "TechDraw" });
const prepare = (command, args) => adapter.prepareTechDrawCommand(record(command), args, "Current");
const plain = value => JSON.parse(JSON.stringify(value));
test("section task exclusion, ownership and cancellation contracts are preserved", () => {
for (const name of ["TechDraw_ComplexSection", "TechDraw_SectionView", "TechDraw_SectionGroup"])
assert.equal(prepare(name, { activeTask: true }).response.error.code, "task_in_progress");
assert.equal(prepare("TechDraw_Image", { taskActive: "yes" }).response.error.code, "invalid_request");
assert.equal(prepare("TechDraw_Image", { document: "Other" }).response.error.code, "invalid_request");
assert.equal(prepare("TechDraw_Image", { action: "cancel" }).args.document, "Current");
});
test("image, projection and projected subelement selections retain their native inputs", () => {
const selection = [{ documentId: "Current", object: "View", subelements: ["Face1"] }];
assert.equal(prepare("TechDraw_Image", { selection }).args.owner, "View");
assert.deepEqual(plain(prepare("TechDraw_Hatch", { selection }).args.selection), ["Face1"]);
assert.equal(prepare("TechDraw_Hatch", { selection }).args.view, "View");
assert.equal(prepare("TechDraw_View", { selection }).response.error.code, "implementation_required");
assert.deepEqual(plain(prepare("TechDraw_View", { selection: [{ documentId: "Current", object: "Box" }] }).args.sources), ["Box"]);
assert.equal(prepare("TechDraw_Image", { selection: [{ documentId: "Other", object: "View" }] }).response.error.code, "invalid_request");
});
test("stack selection is deduplicated and command container policy stays native", () => {
assert.deepEqual(plain(prepare("TechDraw_StackTop", { selection: ["View", "View"] }).args.selection), ["View"]);
assert.equal(adapter.techDrawRoutePolicy("TechDraw_SectionGroup").familyRoute, true);
assert.equal(adapter.techDrawRoutePolicy("TechDraw_StackTop").nativeContainer, true);
assert.equal(adapter.techDrawRoutePolicy("Unrelated").nativeContainer, false);
});
test("native errors and cancellation remain visible while exposed results continue", () => {
const item = record("TechDraw_Image");
const response = status => ({ kind: "result", result: { schema: "freecad-web-techdraw/python-family-batch/v1", records: [{ apiId: item.id, status, code: "bad_image", message: "Native error" }] } });
const cancelled = response("cancelled");
assert.equal(adapter.validateTechDrawResponse(item, cancelled, item.id), cancelled);
assert.equal(adapter.validateTechDrawResponse(item, response("error"), item.id).error.code, "bad_image");
assert.equal(adapter.validateTechDrawResponse(item, response("exposed"), item.id), null);
});
test("the common dispatcher invokes the module adapter without changing other modules", async () => {
const calls = [];
const router = { request: async (...args) => { calls.push(args); return { kind: "result", result: { schema: "freecad-web-techdraw/python-family-batch/v1", records: [{ apiId: "TechDraw:desktop:TechDraw_Image", status: "exposed" }] } }; } };
const dispatcher = new DesktopCommandDispatcher(router);
await dispatcher.execute(record("TechDraw_Image"), { selection: [{ documentId: "Current", object: "View" }] }, "Current");
assert.equal(calls[0][2].records[0].args.owner, "View");
assert.equal(calls[0][2].records[0].args.document, "Current");
await dispatcher.execute({ id: "Mesh:desktop:Inspect", module: "Mesh", command: "Inspect" }, { value: 4 }, "MeshDoc");
assert.deepEqual(plain(calls[1][2].records[0].args), { value: 4, documentId: "MeshDoc" });
});
test("Part adapter preserves native selection delivery and original error envelopes", async () => {
const item = { id: "Part:desktop:Part_BoxSelection", module: "Part", command: "Part_BoxSelection" };
let native = { apiId: item.id, status: "exposed", result: { documentId: "Current", selection: [{ documentId: "Current", object: "Box" }] } };
const changes = [], router = { request: async () => ({ kind: "result", result: { records: [native] } }) };
const dispatcher = new DesktopCommandDispatcher(router);
const execute = () => dispatcher.execute(item, {}, "Current", (document, selection) => changes.push({ document, selection }));
assert.equal((await execute()).kind, "result");
assert.deepEqual(plain(changes), [{ document: "Current", selection: [{ documentId: "Current", object: "Box" }] }]);
native = { ...native, status: "restricted", code: "native_rejected", message: "Original native failure" };
assert.equal((await execute()).error.code, "native_rejected"); assert.equal(changes.length, 1);
native = { ...native, status: "exposed", implementationBoundary: "Missing native operation" };
assert.equal((await execute()).kind, "error"); assert.equal(changes.length, 1);
native = { apiId: "Part:desktop:Other", status: "exposed" };
assert.equal((await execute()).error.code, "native_protocol_error");
native = { apiId: item.id, status: "exposed", result: { documentId: "Current", selection: [{ documentId: "Other", object: "Box" }] } };
await execute(); assert.equal(changes.length, 1);
});