import { test } from 'node:test' import assert from 'node:assert/strict' import { unzipSync } from 'fflate' import { createWebCadFacade } from '../src/facade/mockFacade' import { decodeFcstdPropertyValue, serializeFcstdMetadataArchive } from '../src/facade/fcstd' import type { ColorValue, DocumentSnapshot, FacadeEvent, ObjectPropertySnapshot } from '../src/facade/types' const hosts: Array<[string, string, string, ColorValue]> = [ ['FeatureTestColor', 'App::FeatureTest', 'Colour', [0, 0, 0, 1]], ['FeatureTestExceptionColor', 'App::FeatureTestException', 'Colour', [0, 0, 0, 1]], ['PartColor', 'App::Part', 'Color', [1, 1, 1, 0]], ['AssemblyLinkColor', 'Assembly::AssemblyLink', 'Color', [1, 1, 1, 0]], ['AssemblyObjectColor', 'Assembly::AssemblyObject', 'Color', [1, 1, 1, 0]], ['AnnotationColor', 'TechDraw::DrawViewAnnotation', 'TextColor', [0, 0, 0, 1]], ['DraftColor', 'TechDraw::DrawViewDraft', 'Color', [0, 0, 0, 1]], ['SpreadsheetColor', 'TechDraw::DrawViewSpreadsheet', 'TextColor', [0, 0, 0, 1]], ] const colorProperty = (name: string, value: ColorValue): ObjectPropertySnapshot => ({ name, label: name, group: 'Color', scope: 'data', type: 'App::PropertyColor', value: [...value] as ColorValue, recompute: true }) const fixture = (): DocumentSnapshot => ({ id: 'property-color-facade', label: 'PropertyColor Facade', version: 1, dirty: false, readOnly: false, units: 'mm', tree: hosts.map(([id]) => ({ id, label: id, type: 'feature', state: 'valid' })), objects: hosts.map(([id, typeId, propertyName, value], index) => ({ id, typeId, properties: [ colorProperty(propertyName, value), ...(index === 0 ? [{ name: 'LineColor', label: 'Line color', group: 'Appearance', scope: 'view' as const, type: 'App::PropertyColor' as const, value: '#123456' }] : []), ], })), dependencies: [], recompute: { generation: 0, status: 'idle', objectStates: Object.fromEntries(hosts.map(([id]) => [id, 'up-to-date'])), dirtyObjects: [], order: [], errors: [] }, }) const valueOf = (facade: ReturnType, objectId: string, propertyName: string) => facade.app.document.getObject(objectId)?.properties.find((property) => property.name === propertyName)?.value test('App::PropertyColor data values use four RGBA channels while view values retain #RRGGBB', () => { const facade = createWebCadFacade({ initialDocument: fixture(), initialSelectedObjectIds: ['FeatureTestColor'], runtimeMode: 'mock' }) const events: FacadeEvent[] = [] facade.subscribe((event) => { if (event.type === 'property.before-change' || event.type === 'property.changed' || event.type === 'transaction.committed') events.push(event) }) const target: ColorValue = [17 / 255, 34 / 255, 51 / 255, 68 / 255] facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'Colour', value: target }) target[0] = 1 assert.deepEqual(valueOf(facade, 'FeatureTestColor', 'Colour'), [17 / 255, 34 / 255, 51 / 255, 68 / 255]) const exposed = valueOf(facade, 'FeatureTestColor', 'Colour') as number[] exposed[1] = 1 assert.deepEqual(valueOf(facade, 'FeatureTestColor', 'Colour'), [17 / 255, 34 / 255, 51 / 255, 68 / 255]) assert.equal(facade.app.document.getActive().version, 2) assert.equal(facade.app.document.getActive().recompute?.objectStates.FeatureTestColor, 'touched') assert.deepEqual(events.map((event) => event.type), ['property.before-change', 'property.changed', 'transaction.committed']) facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'Colour', value: [2, -1, 0.5, 1.5] }) assert.deepEqual(valueOf(facade, 'FeatureTestColor', 'Colour'), [2, -1, 0.5, 1.5]) facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'LineColor', value: '#abcdef' }) assert.equal(valueOf(facade, 'FeatureTestColor', 'LineColor'), '#abcdef') const stable = JSON.stringify(facade.app.document.getActive()) assert.throws(() => facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'Colour', value: '#112233' }), /four finite RGBA channels/) assert.throws(() => facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'Colour', value: [0, 0, 0] }), /four finite RGBA channels/) assert.throws(() => facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'Colour', value: [0, 0, 0, Number.NaN] }), /four finite RGBA channels/) assert.throws(() => facade.app.document.setProperty({ objectId: 'FeatureTestColor', propertyName: 'LineColor', value: [0, 0, 0, 1] }), /#RRGGBB color/) assert.equal(JSON.stringify(facade.app.document.getActive()), stable) }) test('App::PropertyColor serializes all eight native hosts with packed RRGGBBAA values', () => { const document = fixture() ;(document.objects[0].properties[0].value as ColorValue) = [17 / 255, 34 / 255, 51 / 255, 68 / 255] const archive = serializeFcstdMetadataArchive(document) const documentXml = new TextDecoder().decode(unzipSync(archive)['Document.xml']) const guiXml = new TextDecoder().decode(unzipSync(archive)['GuiDocument.xml']) assert.equal((documentXml.match(/type="App::PropertyColor"/g) ?? []).length, 8) assert.match(documentXml, /<\/Property>/) assert.match(documentXml, /<\/Property>/) assert.match(documentXml, /<\/Property>/) assert.doesNotMatch(documentXml, /]+(?:group|doc|attr|ro|hide)=/) assert.match(guiXml, /<\/Property>/) const inspection = createWebCadFacade({ initialDocument: document, runtimeMode: 'mock' }).project.fcstd.inspect(archive) assert.equal(inspection.objects.length, 8) assert.ok(inspection.objects.every((object) => object.support === 'recognized')) const summary = inspection.objects[0].properties.find((property) => property.name === 'Colour') assert.deepEqual({ typeId: summary?.typeId, element: summary?.element, value: summary?.value }, { typeId: 'App::PropertyColor', element: 'PropertyColor', value: '287454020' }) assert.deepEqual(decodeFcstdPropertyValue(summary!), { value: [17 / 255, 34 / 255, 51 / 255, 68 / 255], decoded: true }) assert.equal(decodeFcstdPropertyValue({ name: 'Colour', typeId: 'App::PropertyColor', element: 'PropertyColor', value: '4294967296' }).decoded, false) assert.equal(decodeFcstdPropertyValue({ name: 'Colour', typeId: 'App::PropertyColor', element: 'String', value: '287454020' }).decoded, false) const target: ColorValue = [51 / 255, 102 / 255, 153 / 255, 204 / 255] const rewritten = createWebCadFacade({ runtimeMode: 'mock' }).project.fcstd.rewriteColor(archive, { objectName: 'FeatureTestColor', propertyName: 'Colour', value: target, expectedValue: [17 / 255, 34 / 255, 51 / 255, 68 / 255], }) const rewrittenProperty = createWebCadFacade({ runtimeMode: 'mock' }).project.fcstd.inspect(rewritten).objects[0].properties.find((property) => property.name === 'Colour') assert.equal(rewrittenProperty?.value, '862362060') assert.deepEqual(decodeFcstdPropertyValue(rewrittenProperty!), { value: target, decoded: true }) assert.throws(() => createWebCadFacade({ runtimeMode: 'mock' }).project.fcstd.rewriteColor(archive, { objectName: 'FeatureTestColor', propertyName: 'Colour', value: target, expectedValue: [0, 0, 0, 1] }), /does not match expectedValue/) assert.throws(() => createWebCadFacade({ runtimeMode: 'mock' }).project.fcstd.rewriteColor(archive, { objectName: 'FeatureTestColor', propertyName: 'Colour', value: [0, 0, 0, Number.NaN] }), /four finite RGBA channels/) })