Files
FreeCADWasmDev/tools/web-cross-workbench-native-case.mjs

103 lines
5.0 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createHash } from 'node:crypto';
import { loadWasmProbe } from './wasm-probe-runtime.mjs';
// Run in a child process so a synchronous native stall has a hard deadline.
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const directory = process.argv[2];
const report = { status: 'failed', stages: [], calls: [], assertions: [] };
const started = performance.now();
let kernel;
const persist = () => fs.writeFileSync(path.join(directory, 'independent-native.json'), JSON.stringify(report, null, 2));
const stage = name => {
report.stages.push({ name, enteredMs: performance.now() - started });
persist();
};
const done = () => { report.stages.at(-1).completedMs = performance.now() - started; persist(); };
const call = (document, action, details = {}, reject = false) => {
const args = { ...details, document, action, includeMesh: true };
stage(`${document}:${action}`);
const response = JSON.parse(kernel.ccall('fc_web_dispatch_json', 'string', ['string'], [JSON.stringify({ cmd: 'partdesign.document', args })]));
report.calls.push({ args, response });
done();
assert.equal(response.ok, !reject, JSON.stringify(response));
if (reject) { assert.ok(response.error); return response; }
assert.equal(response.result.document, document);
return response.result;
};
const profile = width => ({ object: 'Sketch1', body: 'Body1', profileType: 'Rectangle', parameters: { Width: width, Height: 8 }, position: [0, 0, 0] });
const check = (snapshot, width) => {
assert.equal(snapshot.objectCount, snapshot.objects.length);
const body = snapshot.objects.find(object => object.name === 'Body1');
const sketch = snapshot.objects.find(object => object.name === 'Sketch1');
const pad = snapshot.objects.find(object => object.name === 'Pad1');
assert.equal(body.type, 'PartDesign::Body');
assert.equal(body.tip, 'Pad1');
assert.ok(body.group.includes('Sketch1') && body.group.includes('Pad1'));
assert.equal(sketch.type, 'Sketcher::SketchObject');
assert.equal(sketch.body, 'Body1');
assert.equal(sketch.geometryCount, 4);
assert.ok(sketch.constraintCount >= 8);
assert.equal(sketch.dof, 0);
assert.equal(sketch.fullyConstrained, true);
assert.deepEqual(sketch.parameters, { Width: width, Height: 8 });
assert.equal(pad.type, 'PartDesign::Pad');
assert.equal(pad.body, 'Body1');
assert.equal(pad.profile, 'Sketch1');
assert.equal(pad.length, 5);
assert.ok(Math.abs(pad.volume - width * 8 * 5) < 1e-7);
assert.ok(pad.mesh.triangleCount > 0 && pad.mesh.positions.length > 0);
};
try {
stage('load-and-initialize-wasm');
({ kernel } = await loadWasmProbe(path.join(root, 'web/public/kernel'), 'FreeCADWebPartDesignRuntimeProbe'));
done();
const primary = 'CrossNative', other = 'CrossOther', restored = 'CrossRestored';
for (const [document, width] of [[primary, 12], [other, 7]]) {
call(document, 'create');
call(document, 'createBody', { object: 'Body1' });
call(document, 'setSketch', profile(width));
check(call(document, 'setFeature', { object: 'Pad1', body: 'Body1', profile: 'Sketch1', featureType: 'Pad', length: 5, reversed: false }), width);
}
const unrelated = call(other, 'snapshot');
const baseline = call(primary, 'snapshot');
const edited = call(primary, 'setSketch', profile(14));
check(edited, 14);
assert.equal(edited.undoCount, baseline.undoCount + 1);
check(call(primary, 'undo'), 12);
const undone = call(primary, 'snapshot');
assert.equal(undone.redoCount, 1);
call(primary, 'setSketch', profile(-1), true);
assert.deepEqual(call(primary, 'snapshot'), undone, 'Rejected native edit must preserve geometry AND pending redo');
check(call(primary, 'redo'), 14);
assert.deepEqual(call(other, 'snapshot'), unrelated, 'Same-name objects in the second document remain untouched');
report.assertions.push('Constrained sketch and dependent Pad recompute; undo/redo and rejected edit preserve both documents');
const filename = '/tmp/CrossNative.FCStd';
call(primary, 'save', { path: filename });
const bytes = kernel.FS.readFile(filename);
assert.ok(bytes.length > 1000);
assert.deepEqual(Array.from(bytes.slice(0, 2)), [80, 75]);
fs.writeFileSync(path.join(directory, 'independent-native.FCStd'), bytes);
report.file = { byteLength: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') };
call(primary, 'close');
check(call(restored, 'open', { path: filename }), 14);
check(call(restored, 'setSketch', profile(15)), 15);
check(call(restored, 'undo'), 14);
check(call(restored, 'redo'), 15);
assert.deepEqual(call(other, 'snapshot'), unrelated);
report.assertions.push('Actual FCStd restores Body/Tip/profile/constraints and permits fresh edit, undo and redo');
call(restored, 'close');
call(other, 'close');
report.status = 'passed';
} catch (error) {
report.error = String(error.stack ?? error);
process.exitCode = 1;
} finally {
stage('cleanup');
kernel?.PThread?.terminateAllThreads();
done();
}