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-boollist-failure.json'), 'utf8')) const fail = (message) => { throw new Error(`FreeCAD PropertyBoolList failure check failed: ${message}`) } if (report.schemaVersion !== 1 || report.status !== 'pass' || report.baselineId !== 'freecad-1.1.1-property-boollist-failure' || report.freecadVersion !== '1.1.1' || report.gitCommit !== '0108fd4b4850cc46e625b60e53cea7a7bbe69f8d' || report.propertyType !== 'App::PropertyBoolList') fail('baseline is invalid') const writable = report.writable if (writable?.object?.typeId !== 'App::FeatureTest' || writable.property?.typeId !== 'App::PropertyBoolList' || !isDeepStrictEqual(writable.initial, [false])) fail('writable fixture identity changed') const accepted = new Map(writable.acceptedCoercions?.map((entry) => [entry.id, entry]) ?? []) if (accepted.size !== 2 || accepted.get('scalar-integer')?.requested !== '7' || accepted.get('scalar-integer').exception !== null || !isDeepStrictEqual(accepted.get('scalar-integer').after, [true]) || accepted.get('bitstring')?.requested !== "'10201'" || accepted.get('bitstring').exception !== null || !isDeepStrictEqual(accepted.get('bitstring').after, [true, false, false, false, true]) || ![...accepted.values()].every((entry) => entry.objectsPreserved === true && isDeepStrictEqual(entry.beforeObjects, entry.afterObjects))) fail('native accepted coercion partition changed') const expectedFailures = new Map([ ['none', { requested: 'None', message: 'type in list must be bool or int, not NoneType' }], ['float-item', { requested: '[True, 1.5]', message: 'type in list must be bool or int, not float' }], ['string-item', { requested: "[False, 'bad']", message: 'type in list must be bool or int, not str' }], ]) if (writable.failures?.length !== expectedFailures.size) fail('failure case count changed') for (const entry of writable.failures) { const expected = expectedFailures.get(entry.id) if (!expected || entry.requested !== expected.requested || entry.exception?.type !== 'TypeError' || entry.exception.message !== expected.message || entry.valuePreserved !== true || entry.objectsPreserved !== true || entry.polluted !== false || !isDeepStrictEqual(entry.before, [false]) || !isDeepStrictEqual(entry.after, [false]) || !isDeepStrictEqual(entry.beforeObjects, entry.afterObjects)) fail(`${entry.id} did not reject without pollution`) } const disabled = writable.disabled if (disabled?.classification !== 'editor-read-only-and-python-immutable' || !isDeepStrictEqual(disabled.requested, [true, false]) || !isDeepStrictEqual(disabled.status, ['Immutable', 'ReadOnly']) || !isDeepStrictEqual(disabled.editorMode, ['ReadOnly']) || disabled.exception?.type !== 'AttributeError' || disabled.exception.message !== "Object attribute 'BoolList' is read-only" || disabled.valuePreserved !== true || disabled.objectsPreserved !== true || !isDeepStrictEqual(disabled.before, [false]) || !isDeepStrictEqual(disabled.after, [false]) || !isDeepStrictEqual(disabled.restoredStatus, []) || !isDeepStrictEqual(disabled.restoredEditorMode, [])) fail('disabled state did not reject and restore cleanly') const transaction = writable.transaction if (transaction?.undoMode !== 1 || transaction.pendingAfterEdit !== true || transaction.pendingAfterAbort !== false || transaction.activeAfterEdit?.name !== 'property-boollist-cancel' || !(transaction.activeAfterEdit.id > 0) || transaction.activeAfterAbort?.name !== '' || transaction.activeAfterAbort.id !== 0 || transaction.restored !== true || transaction.objectsRestored !== true || !isDeepStrictEqual(transaction.before, [false]) || !isDeepStrictEqual(transaction.edited, [true, false, true]) || !isDeepStrictEqual(transaction.afterAbort, [false]) || !isDeepStrictEqual(transaction.objectsBefore, transaction.objectsAfter)) fail('writable transaction abort did not restore') if (writable.documentIntegrity?.objectsPreserved !== true || !isDeepStrictEqual(writable.documentIntegrity.initialObjects, writable.documentIntegrity.finalObjects)) fail('writable failures polluted the document') const linkTypes = ['App::Link', 'App::LinkGroup', 'App::LinkGroupPython', 'App::LinkPython'] if (report.links?.length !== linkTypes.length || new Set(report.links.map(({ objectTypeId }) => objectTypeId)).size !== linkTypes.length) fail('Link failure inventory changed') for (const typeId of linkTypes) { const entry = report.links.find((candidate) => candidate.objectTypeId === typeId) if (entry?.propertyTypeId !== 'App::PropertyBoolList' || !isDeepStrictEqual(entry.initial, [true, true]) || !isDeepStrictEqual(entry.status, ['Immutable', 'Hidden', 'LockDynamic']) || !isDeepStrictEqual(entry.editorMode, ['Hidden']) || entry.directWrite?.exception?.type !== 'AttributeError' || entry.directWrite.exception.message !== "Object attribute 'VisibilityList' is read-only" || entry.directWrite.preserved !== true || !isDeepStrictEqual(entry.directWrite.after, [true, true])) fail(`${typeId} immutable rejection changed`) if (entry.invalidElement?.name !== 'MissingElement' || entry.invalidElement.result !== -1 || entry.invalidElement.preserved !== true || !isDeepStrictEqual(entry.invalidElement.after, [true, true])) fail(`${typeId} invalid element rejection changed`) const linkTransaction = entry.transaction if (linkTransaction?.hideResult !== 1 || !isDeepStrictEqual(linkTransaction.before, [true, true]) || !isDeepStrictEqual(linkTransaction.edited, [false, true]) || !isDeepStrictEqual(linkTransaction.afterAbort, [true, true]) || linkTransaction.pendingAfterEdit !== true || linkTransaction.pendingAfterAbort !== false || linkTransaction.activeAfterEdit?.name !== 'property-boollist-link-cancel' || !(linkTransaction.activeAfterEdit.id > 0) || linkTransaction.activeAfterAbort?.name !== '' || linkTransaction.activeAfterAbort.id !== 0 || linkTransaction.restored !== true || !isDeepStrictEqual(linkTransaction.stateAfterAbort, ['Touched']) || linkTransaction.statusAfterAbort !== 'Touched' || linkTransaction.recoveryRecomputeResult !== true || !isDeepStrictEqual(linkTransaction.stateAfterRecovery, ['Up-to-date']) || linkTransaction.statusAfterRecovery !== 'Valid' || !isDeepStrictEqual(entry.objectState, ['Up-to-date']) || entry.statusString !== 'Valid' || entry.objectsPreserved !== true || !isDeepStrictEqual(entry.objectsBefore, entry.objectsAfter)) fail(`${typeId} transaction rollback changed`) } if (report.cancellationBoundary?.supported !== false || report.cancellationBoundary.classification !== 'synchronous-property-setter-and-link-extension' || report.cancellationBoundary.reason !== 'no-native-cancel-hook' || report.cancellationBoundary.replacement !== 'abort-active-document-transaction') fail('cancellation boundary is not explicit') console.log(JSON.stringify({ status: 'freecad-property-boollist-failure-pass', acceptedCoercions: [...accepted.values()].map(({ id, requested, after }) => ({ id, requested, after })), failures: writable.failures.map(({ id, exception }) => ({ id, exception })), disabled: { status: disabled.status, editorMode: disabled.editorMode, exception: disabled.exception }, writableTransactionRestored: transaction.restored, linkTransactionsRestored: report.links.every(({ transaction }) => transaction.restored), cancellationBoundary: report.cancellationBoundary }, null, 2))