import { isDeepStrictEqual } from 'node:util' import { readFile } from 'node:fs/promises' import { resolve } from 'node:path' const root = resolve(new URL('..', import.meta.url).pathname) const report = JSON.parse(await readFile(resolve(root, 'config/freecad-property-linksublistglobal-failure.json'), 'utf8')) const fail = (message) => { throw new Error(`FreeCAD PropertyLinkSubListGlobal failure check failed: ${message}`) } if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-linksublistglobal-failure' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyLinkSubListGlobal' || report.caseCount !== 1) fail('baseline is invalid') const entry = report.case if (!entry || entry.objectTypeId !== 'PartDesign::ShapeBinder' || entry.objectName !== 'ShapeBinderProbe' || entry.propertyName !== 'Support') fail('fixture identity changed') const initial = entry.initial if (initial?.propertyTypeId !== report.propertyType || !isDeepStrictEqual(initial.propertyStatus, []) || !isDeepStrictEqual(initial.editorMode, []) || !isDeepStrictEqual(initial.value, [{ object: 'SourceBox', subElements: ['Face1', 'Face2'] }, { object: 'SecondBox', subElements: ['Face1'] }]) || initial.statusString !== 'Valid' || !isDeepStrictEqual(initial.ownerState, ['Up-to-date'])) fail('initial global link state is invalid') const assertGeometryPreserved = (snapshot, label) => { if (!isDeepStrictEqual(snapshot.objectSet, initial.objectSet) || !isDeepStrictEqual(snapshot.targetShapeHashes, initial.targetShapeHashes)) fail(`${label} polluted objects or source Shapes`) } const expectedInputs = new Map([ ['scalar', { exception: ['TypeError', 'Expects sequence of items of type DocObj, (DocObj,SubName), or (DocObj, (SubName,...))'], value: initial.value, preserved: true }], ['singlePairShorthand', { exception: null, value: [{ object: 'SourceBox', subElements: ['Face1'] }], preserved: false }], ['stringObject', { exception: null, value: [], preserved: false }], ['integerObject', { exception: null, value: [], preserved: false }], ]) if (entry.inputs?.length !== expectedInputs.size) fail('invalid-input case count changed') for (const input of entry.inputs) { const expected = expectedInputs.get(input.id) if (!expected || !isDeepStrictEqual(input.before, initial) || !isDeepStrictEqual(input.after.value, expected.value) || input.valuePreserved !== expected.preserved || (expected.exception === null ? input.exception !== null : input.exception?.type !== expected.exception[0] || input.exception.message !== expected.exception[1])) fail(`${input.id} classification changed`) assertGeometryPreserved(input.after, input.id) } if (!entry.crossDocument?.exception || entry.crossDocument.valuePreserved !== true || entry.crossDocument.documentPreserved !== true || !isDeepStrictEqual(entry.crossDocument.before, initial) || !isDeepStrictEqual(entry.crossDocument.after, initial)) fail('cross-document boundary is not atomic') assertGeometryPreserved(entry.crossDocument.after, 'cross-document') if (entry.readOnly?.exception !== null || entry.readOnly.pythonBypassesEditorReadOnly !== true || !isDeepStrictEqual(entry.readOnly.after.value, [{ object: 'SecondBox', subElements: ['Face2'] }]) || !isDeepStrictEqual(entry.readOnly.after.propertyStatus, ['ReadOnly'])) fail('ReadOnly Python boundary changed') if (entry.immutable?.exception?.type !== 'AttributeError' || entry.immutable.exception.message !== "Object attribute 'Support' is read-only" || entry.immutable.pythonBypassesImmutable !== false || !isDeepStrictEqual(entry.immutable.after.value, initial.value) || !isDeepStrictEqual(entry.immutable.after.propertyStatus, ['Immutable'])) fail('Immutable Python boundary changed') const transaction = entry.transaction if (transaction?.undoMode !== 1 || transaction.pendingAfterEdit !== true || transaction.pendingAfterAbort !== false || transaction.activeAfterEdit?.name !== 'property-linksublistglobal-cancel' || transaction.activeAfterAbort?.id !== 0 || !isDeepStrictEqual(transaction.before, transaction.afterAbort) || transaction.restored !== true || transaction.recomputeAfterAbort !== true) fail('transaction cancel and recovery changed') if (report.cancellationBoundary?.supported !== false || report.cancellationBoundary.classification !== 'synchronous-property-setter' || report.cancellationBoundary.replacement !== 'abort-active-document-transaction') fail('cancellation boundary is incomplete') if (entry.documentIntegrity?.objectsPreserved !== true || !isDeepStrictEqual(entry.documentIntegrity.initialObjects, entry.documentIntegrity.finalObjects)) fail('document object set was polluted') console.log(JSON.stringify({ status: 'freecad-property-linksublistglobal-failure-pass', inputs: entry.inputs.map(({ id, exception, after }) => ({ id, exception, normalizedValue: after.value })), crossDocument: entry.crossDocument.exception, readOnlyPythonAccepted: entry.readOnly.pythonBypassesEditorReadOnly, immutablePythonAccepted: entry.immutable.pythonBypassesImmutable, transactionRestored: transaction.restored, cancellationBoundary: report.cancellationBoundary }, null, 2))