121 lines
7.0 KiB
JavaScript
121 lines
7.0 KiB
JavaScript
import { loadWasmProbe } from './wasm-probe-runtime.mjs';
|
|
|
|
export const datumCloneIds = ['Clone', 'CompDatums', 'CompSketches', 'CoordinateSystem', 'Line']
|
|
.map(name => `PartDesign:desktop:PartDesign_${name}`);
|
|
|
|
export async function runDatumCloneCases(ids, loadWorker) {
|
|
if (!ids.length) return [];
|
|
const { kernel } = await loadWasmProbe('web/public/kernel', 'FreeCADWebPartDesignRuntimeProbe');
|
|
const worker = await loadWorker(kernel);
|
|
let sequence = 0;
|
|
const router = { request: (_module, command, payload, document) =>
|
|
worker.send(worker.request(`datum-clone-${++sequence}`, command, payload, document)) };
|
|
const results = [];
|
|
for (const capabilityId of ids) {
|
|
const document = `DatumClone${process.pid}_${sequence}`;
|
|
const cases = [];
|
|
const record = (input, actual, checks) => {
|
|
const assertions = Object.entries(checks).map(([name, passed]) => ({ name, passed: Boolean(passed) }));
|
|
cases.push({ input, actual, assertions });
|
|
if (assertions.some(check => !check.passed)) throw new Error('native business assertion failed');
|
|
};
|
|
const native = async (action, args = {}) => {
|
|
const response = await router.request('PartDesign', 'partdesign.document', {
|
|
schema: 'freecad-web-partdesign/document/v1', module: 'PartDesign', document, action, ...args,
|
|
}, document);
|
|
if (response.kind !== 'result') throw new Error(JSON.stringify(response));
|
|
return response.result;
|
|
};
|
|
try {
|
|
await native('create');
|
|
await native('createBody', { object: 'SourceBody' });
|
|
let selected;
|
|
const execute = args => worker.desktop(router, capabilityId.split(':').at(-1), args, document,
|
|
(_document, selection) => { selected = selection; });
|
|
if (capabilityId.endsWith('_Clone')) {
|
|
await native('setSketch', { object: 'Profile', body: 'SourceBody', profileType: 'Rectangle',
|
|
parameters: { Width: 3, Height: 4 }, position: [0, 0, 0] });
|
|
await native('setFeature', { object: 'Pad', body: 'SourceBody', profile: 'Profile', featureType: 'Pad', length: 5 });
|
|
const input = { object: 'Clone', body: 'CloneBody', selection: [{ documentId: document, object: 'Pad' }] };
|
|
const actual = await execute(input);
|
|
const clone = actual.result?.objects?.find(object => object.name === 'Clone');
|
|
record(input, actual, { 'native linked clone': clone?.type === 'PartDesign::FeatureBase' && clone.base === 'Pad'
|
|
&& clone.body === 'CloneBody' && Math.abs(clone.volume - 60) < 1e-7,
|
|
'selection updated': selected?.[0]?.object === 'Clone' });
|
|
const changed = await native('setFeature', { object: 'Pad', body: 'SourceBody', profile: 'Profile', featureType: 'Pad', length: 7 });
|
|
record({ length: 7 }, changed, { 'clone recomputes from source': Math.abs(changed.objects.find(o => o.name === 'Clone').volume - 84) < 1e-7 });
|
|
} else {
|
|
const sketch = capabilityId.endsWith('_CompSketches');
|
|
const input = sketch ? { object: 'Profile', body: 'SourceBody', profileType: 'Rectangle',
|
|
parameters: { Width: 7, Height: 2 }, position: [1, 2, 3] }
|
|
: { object: 'Datum', body: 'SourceBody', origin: [2, -3, 5] };
|
|
const actual = await execute(input);
|
|
const object = actual.result?.objects?.find(o => o.name === input.object);
|
|
const type = sketch ? 'Sketcher::SketchObject' : capabilityId.endsWith('_CompDatums') ? 'PartDesign::Plane'
|
|
: capabilityId.endsWith('_Line') ? 'PartDesign::Line' : 'PartDesign::CoordinateSystem';
|
|
record(input, actual, { 'default child creates native type': object?.type === type,
|
|
'native placement': JSON.stringify(object?.position) === JSON.stringify(input.position ?? input.origin),
|
|
'native body ownership': object?.body === 'SourceBody',
|
|
'selection updated': selected?.[0]?.object === input.object });
|
|
if (capabilityId.endsWith('_Line')) {
|
|
const moved = { ...input, origin: [-4, 7, 11] };
|
|
const updated = await execute(moved);
|
|
const line = updated.result?.objects?.find(o => o.name === input.object);
|
|
record(moved, updated, {
|
|
'same native datum updated': line?.type === 'PartDesign::Line' && line.body === 'SourceBody'
|
|
&& updated.result.objects.filter(o => o.name === input.object).length === 1,
|
|
'updated native placement': JSON.stringify(line?.position) === JSON.stringify(moved.origin),
|
|
});
|
|
const undone = await native('undo');
|
|
record({ action: 'undo' }, undone, {
|
|
'undo restores original placement': JSON.stringify(undone.objects.find(o => o.name === input.object)?.position)
|
|
=== JSON.stringify(input.origin),
|
|
});
|
|
const redone = await native('redo');
|
|
record({ action: 'redo' }, redone, {
|
|
'redo restores edited placement': JSON.stringify(redone.objects.find(o => o.name === input.object)?.position)
|
|
=== JSON.stringify(moved.origin),
|
|
});
|
|
await native('createBody', { object: 'OtherBody' });
|
|
for (const invalid of [
|
|
{ ...moved, body: 'OtherBody' },
|
|
{ ...moved, object: 'SourceBody' },
|
|
{ ...moved, origin: [1, 2] },
|
|
]) {
|
|
const beforeInvalid = await native('snapshot');
|
|
const rejected = await execute(invalid);
|
|
const afterInvalid = await native('snapshot');
|
|
record(invalid, rejected, {
|
|
'invalid datum edit rejected without mutation': rejected.kind === 'error'
|
|
&& JSON.stringify(beforeInvalid) === JSON.stringify(afterInvalid),
|
|
});
|
|
}
|
|
const beforeAttachment = await native('snapshot');
|
|
const attached = { ...input, selection: [{ documentId: document, object: 'Datum' }] };
|
|
const rejectedAttachment = await execute(attached);
|
|
const afterAttachment = await native('snapshot');
|
|
record(attached, rejectedAttachment, {
|
|
'unsupported attachment leaves document unchanged': rejectedAttachment.kind === 'error'
|
|
&& JSON.stringify(beforeAttachment) === JSON.stringify(afterAttachment),
|
|
});
|
|
}
|
|
}
|
|
const before = await native('snapshot');
|
|
const rejected = await execute({ object: 'Rejected', activeTask: true });
|
|
const after = await native('snapshot');
|
|
record({ activeTask: true }, rejected, { 'active task rejected without mutation': rejected.kind === 'error'
|
|
&& JSON.stringify(before) === JSON.stringify(after) });
|
|
await native('undo');
|
|
await native('redo');
|
|
} catch (error) {
|
|
cases.push({ error: String(error), assertions: [{ name: 'business workflow completed', passed: false }] });
|
|
} finally {
|
|
try { await native('close'); } catch (error) {
|
|
cases.push({ error: String(error), assertions: [{ name: 'document closed', passed: false }] });
|
|
}
|
|
}
|
|
results.push({ capabilityId, status: cases.some(c => c.assertions.some(a => !a.passed)) ? 'failed' : 'passed', cases });
|
|
}
|
|
return results;
|
|
}
|