Files
FreeCADWasmDev/web/tools/test-document-display-persistence.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

61 lines
4.0 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();
function load(name) {
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, ArrayBuffer, Uint8Array })(module.exports, load, module);
modules.set(name, module.exports);
return module.exports;
}
const { PART_DOCUMENT_SCHEMA, handlePartDocument } = load('./part-document-facade.js');
const displayProperties = [{ object: 'Box1', color: '#cc3344', visible: false }];
const payload = { schema: PART_DOCUMENT_SCHEMA, module: 'Part', document: 'Test', action: 'save', path: '/tmp/Test.FCStd', displayProperties };
const response = { ok: true, result: { document: 'Test', path: payload.path, size: 4, objectCount: 1,
objects: [{ name: 'Box1', label: 'Box', type: 'Part::Box', displayColor: '#cc3344', displayVisibility: false }] } };
const signal = () => new AbortController().signal;
const kernel = reply => ({ ccall: () => JSON.stringify(reply), FS: { readFile: () => new Uint8Array([80,75,3,4]) } });
test('save transports native per-object appearance and returns native file bytes', async () => {
const backend = kernel(response);
backend.ccall = (_name,_type,_types,args) => { assert.deepEqual(JSON.parse(args[0]).args, payload); return JSON.stringify(response); };
const result = await handlePartDocument(payload,signal(),backend);
assert.deepEqual([...new Uint8Array(result.bytes)],[80,75,3,4]);
});
test('malformed, duplicate, wrong-operation appearance is rejected before native dispatch', async () => {
const backend = { ccall() { assert.fail('invalid input reached native'); } };
const entries = [null, {}, [{...displayProperties[0],color:'red'}], [{...displayProperties[0],visible:1}],
[{...displayProperties[0],object:'Other/Box'}], [displayProperties[0],displayProperties[0]]];
for(const value of entries) await assert.rejects(handlePartDocument({...payload,displayProperties:value},signal(),backend),/Invalid document display/);
await assert.rejects(handlePartDocument({...payload,action:'snapshot'},signal(),backend),/Invalid document display/);
});
test('a stale kernel cannot silently ignore display properties', async () => {
for(const changes of [{displayColor:undefined},{displayVisibility:undefined},{displayVisibility:true},{displayColor:'#73c6d4'}]) {
const reply = {...response,result:{...response.result,objects:[{...response.result.objects[0],...changes}]}};
await assert.rejects(handlePartDocument(payload,signal(),kernel(reply)),/Saved display properties do not match/);
}
});
test('import cancellation carries an explicit rollback decision to the native owner', async () => {
const payload={schema:PART_DOCUMENT_SCHEMA,module:'Part',document:'Test',action:'finishImport',commitImport:false};
let sent;
const result=await handlePartDocument(payload,signal(),{ccall(_n,_t,_a,args){sent=JSON.parse(args[0]);return JSON.stringify({ok:true,result:{document:'Test',objectCount:0,objects:[]}});}});
assert.deepEqual(sent.args,payload);
assert.equal(result.objectCount,0);
});
test('invalid import decisions and file targets are rejected before native execution',async()=>{
const backend={ccall(){assert.fail('invalid import request reached native');}};
const base={schema:PART_DOCUMENT_SCHEMA,module:'Part',document:'Test'};
for(const fields of [
{action:'finishImport'}, {action:'finishImport',commitImport:'false'},
{action:'snapshot',deferImportCommit:true},
{action:'importShape',object:'Box',path:'/tmp/../other.stl'},
{action:'exportShape',object:'Box',path:'/tmp/model.exe'},
])await assert.rejects(handlePartDocument({...base,...fields},signal(),backend));
});