* fix(scene-graph): leave a field a component property drives to its instance A component states the default for a field a property reference drives, but the enclosing instance's assignment decides the value. Synchronising copied the default over it, so loading a page into an open document — which synchronises every component it places — reset those layers. A label a navigation item assigned came back as the component's default when its page loaded on its own, while reading the whole archive gave the assigned text. Synchronising now skips a field whose reference names a property an enclosing instance assigns, and carries everything else as before. * fix(scene-graph): read an own assignment, not an inherited key A document-authored component property id can collide with an Object prototype key, and `in` would then report an assignment the instance never made, silently leaving the referenced field unsynchronised. * test(core): cover visibility and swap on an independent page load The fix skips any field a component property reference drives, so the page-scoped test should exercise VISIBLE and INSTANCE_SWAP alongside TEXT rather than leaving the other two to full-document reads. * fix(scene-graph): leave slot content to its own guard #850 added a SLOT_CONTENT reference field, which drives a slot frame's children rather than a scalar field, so the property-driven field map no longer claims to cover every reference field.
85 lines
3.2 KiB
TypeScript
85 lines
3.2 KiB
TypeScript
import { expect, test } from 'bun:test'
|
|
|
|
import { SceneGraph } from '@open-pencil/scene-graph'
|
|
|
|
function graphWithAssignedLabel() {
|
|
const graph = new SceneGraph()
|
|
const page = graph.getPages()[0]
|
|
const component = graph.createNode('COMPONENT', page.id, {
|
|
name: 'NavItem',
|
|
componentPropertyDefinitions: [
|
|
{ id: '207:1', name: 'Label', type: 'TEXT', defaultValue: 'Default' }
|
|
]
|
|
})
|
|
const label = graph.createNode('TEXT', component.id, {
|
|
name: 'Label',
|
|
text: 'Default',
|
|
componentPropertyReferences: [{ propertyId: '207:1', field: 'TEXT' }]
|
|
})
|
|
const instance = graph.createInstance(component.id, page.id)
|
|
return { graph, component, label, instance }
|
|
}
|
|
|
|
/**
|
|
* A component states the default for a field a property drives; the enclosing instance's
|
|
* assignment decides the value. Synchronising must not copy the default over it.
|
|
*/
|
|
test('synchronizing leaves a field the enclosing instance assigns alone', () => {
|
|
const { graph, component, instance } = graphWithAssignedLabel()
|
|
const clone = graph.getChildren(instance.id)[0]
|
|
graph.updateNode(instance.id, { componentPropertyAssignments: { '207:1': 'Assigned' } })
|
|
graph.updateNode(clone.id, { text: 'Assigned' })
|
|
|
|
graph.syncInstances(component.id)
|
|
|
|
expect(graph.getNode(clone.id)?.text).toBe('Assigned')
|
|
})
|
|
|
|
test('synchronizing still carries the default to an instance that assigns nothing', () => {
|
|
const { graph, component, label, instance } = graphWithAssignedLabel()
|
|
const clone = graph.getChildren(instance.id)[0]
|
|
|
|
graph.updateNode(label.id, { text: 'Edited default' })
|
|
graph.syncInstances(component.id)
|
|
|
|
expect(graph.getNode(clone.id)?.text).toBe('Edited default')
|
|
})
|
|
|
|
/** Only the referenced field is left alone; the rest of the component edit still lands. */
|
|
test('synchronizing carries other fields of a property-driven layer', () => {
|
|
const { graph, component, label, instance } = graphWithAssignedLabel()
|
|
const clone = graph.getChildren(instance.id)[0]
|
|
graph.updateNode(instance.id, { componentPropertyAssignments: { '207:1': 'Assigned' } })
|
|
graph.updateNode(clone.id, { text: 'Assigned' })
|
|
|
|
graph.updateNode(label.id, { opacity: 0.5 })
|
|
graph.syncInstances(component.id)
|
|
|
|
expect(graph.getNode(clone.id)?.text).toBe('Assigned')
|
|
expect(graph.getNode(clone.id)?.opacity).toBe(0.5)
|
|
})
|
|
|
|
/** Property IDs are document-authored strings, so one may collide with an Object prototype key. */
|
|
test('a property id shared with an Object prototype key is not read as an assignment', () => {
|
|
const graph = new SceneGraph()
|
|
const page = graph.getPages()[0]
|
|
const component = graph.createNode('COMPONENT', page.id, {
|
|
name: 'NavItem',
|
|
componentPropertyDefinitions: [
|
|
{ id: 'toString', name: 'Label', type: 'TEXT', defaultValue: 'Default' }
|
|
]
|
|
})
|
|
const label = graph.createNode('TEXT', component.id, {
|
|
name: 'Label',
|
|
text: 'Default',
|
|
componentPropertyReferences: [{ propertyId: 'toString', field: 'TEXT' }]
|
|
})
|
|
const instance = graph.createInstance(component.id, page.id)
|
|
const clone = graph.getChildren(instance.id)[0]
|
|
|
|
graph.updateNode(label.id, { text: 'Edited default' })
|
|
graph.syncInstances(component.id)
|
|
|
|
expect(graph.getNode(clone.id)?.text).toBe('Edited default')
|
|
})
|