Files
Web_FreeCAD_Bitbybit/tests/propertyColorFacade.test.ts
wangdequan d11566403d
Some checks failed
real-verification / chrome (push) Has been cancelled
real-verification / freecad-oracle (push) Has been cancelled
real-verification / wasm (push) Has been cancelled
feat: close ordered pairs and property codec batches
2026-08-17 04:58:46 -04:00

108 lines
7.7 KiB
TypeScript

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<typeof createWebCadFacade>, 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 name="Colour" type="App::PropertyColor"><PropertyColor value="287454020"\/><\/Property>/)
assert.match(documentXml, /<Property name="Color" type="App::PropertyColor"><PropertyColor value="4294967040"\/><\/Property>/)
assert.match(documentXml, /<Property name="TextColor" type="App::PropertyColor"><PropertyColor value="255"\/><\/Property>/)
assert.doesNotMatch(documentXml, /<Property name="(?:Colour|Color|TextColor)"[^>]+(?:group|doc|attr|ro|hide)=/)
assert.match(guiXml, /<Property name="LineColor" type="App::PropertyColor" status="1"><PropertyColor value="305420031"\/><\/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/)
})