222 lines
8.4 KiB
TypeScript
222 lines
8.4 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import {
|
|
createLibraryRevision,
|
|
createLibraryUpdatePreview,
|
|
materializeLibraryAsset
|
|
} from '@open-pencil/core/library'
|
|
import { SceneGraph } from '@open-pencil/scene-graph'
|
|
|
|
function source(label: string) {
|
|
const graph = new SceneGraph()
|
|
const set = graph.createNode('COMPONENT_SET', graph.getPages()[0].id, {
|
|
name: 'Button',
|
|
componentKey: 'button'
|
|
})
|
|
const component = graph.createNode('COMPONENT', set.id, {
|
|
name: 'Size=Small',
|
|
componentPropertyValues: { Size: 'Small' }
|
|
})
|
|
graph.createNode('TEXT', component.id, {
|
|
name: 'Label',
|
|
text: label,
|
|
componentPropertyReferences: [{ propertyId: 'label', field: 'TEXT' }]
|
|
})
|
|
graph.updateNode(set.id, {
|
|
componentPropertyDefinitions: [
|
|
{ id: 'label', name: 'Label', type: 'TEXT', defaultValue: label }
|
|
]
|
|
})
|
|
return graph
|
|
}
|
|
|
|
function propertySource(kind: 'VISIBLE' | 'INSTANCE_SWAP', value: string) {
|
|
const graph = new SceneGraph()
|
|
const page = graph.getPages()[0]
|
|
const alternate = graph.createNode('COMPONENT', page.id, {
|
|
name: 'Alternate',
|
|
componentKey: 'alternate',
|
|
width: 40,
|
|
height: 40
|
|
})
|
|
const component = graph.createNode('COMPONENT', page.id, {
|
|
name: 'Card',
|
|
componentKey: 'card',
|
|
componentPropertyDefinitions: [
|
|
{
|
|
id: 'property',
|
|
name: 'Property',
|
|
type: kind === 'VISIBLE' ? 'BOOLEAN' : 'INSTANCE_SWAP',
|
|
defaultValue: value
|
|
}
|
|
]
|
|
})
|
|
if (kind === 'VISIBLE') {
|
|
graph.createNode('RECTANGLE', component.id, {
|
|
name: 'Optional',
|
|
visible: true,
|
|
componentPropertyReferences: [{ propertyId: 'property', field: 'VISIBLE' }]
|
|
})
|
|
} else {
|
|
graph.createNode('INSTANCE', component.id, {
|
|
name: 'Nested',
|
|
componentId: alternate.id,
|
|
componentPropertyReferences: [{ propertyId: 'property', field: 'INSTANCE_SWAP' }]
|
|
})
|
|
}
|
|
return graph
|
|
}
|
|
|
|
async function revisions(firstGraph: SceneGraph, latestGraph: SceneGraph, assetKey: string) {
|
|
const first = await createLibraryRevision({
|
|
libraryId: 'library',
|
|
name: 'Library',
|
|
graph: firstGraph
|
|
})
|
|
const latest = await createLibraryRevision({
|
|
libraryId: 'library',
|
|
name: 'Library',
|
|
graph: latestGraph,
|
|
previousRevisionId: first.manifest.revisionId
|
|
})
|
|
const consumer = new SceneGraph()
|
|
const definition = materializeLibraryAsset(consumer, first, assetKey)
|
|
return { latest, consumer, definition }
|
|
}
|
|
|
|
describe('library update preview', () => {
|
|
test('builds an isolated exact-variant comparison without mutating the consumer', async () => {
|
|
const first = await createLibraryRevision({
|
|
libraryId: 'library',
|
|
name: 'Library',
|
|
graph: source('Old')
|
|
})
|
|
const latest = await createLibraryRevision({
|
|
libraryId: 'library',
|
|
name: 'Library',
|
|
graph: source('New'),
|
|
previousRevisionId: first.manifest.revisionId
|
|
})
|
|
const consumer = new SceneGraph()
|
|
const definition = materializeLibraryAsset(consumer, first, 'button')
|
|
const instance = consumer.createNode('INSTANCE', consumer.getPages()[0].id, {
|
|
componentId: definition.componentId,
|
|
componentPropertyAssignments: { label: 'Custom' }
|
|
})
|
|
const beforeNodes = [...consumer.nodes]
|
|
const beforeBindings = [...consumer.enabledLibraries]
|
|
|
|
const preview = createLibraryUpdatePreview(consumer, instance.id, latest)
|
|
|
|
expect(preview.fallback).toBe(false)
|
|
expect(preview.graph.getNode(preview.currentNodeId)?.type).toBe('INSTANCE')
|
|
expect(preview.graph.getNode(preview.updatedNodeId)?.type).toBe('INSTANCE')
|
|
const currentComponentId = preview.graph.getNode(preview.currentNodeId)?.componentId
|
|
const updatedComponentId = preview.graph.getNode(preview.updatedNodeId)?.componentId
|
|
expect(
|
|
currentComponentId ? preview.graph.getNode(currentComponentId)?.componentPropertyValues : null
|
|
).toEqual({ Size: 'Small' })
|
|
expect(
|
|
updatedComponentId ? preview.graph.getNode(updatedComponentId)?.componentPropertyValues : null
|
|
).toEqual({ Size: 'Small' })
|
|
expect(
|
|
preview.graph.getChildren(preview.currentNodeId).find((node) => node.type === 'TEXT')?.text
|
|
).toBe('Custom')
|
|
expect(
|
|
preview.graph.getChildren(preview.updatedNodeId).find((node) => node.type === 'TEXT')?.text
|
|
).toBe('Custom')
|
|
expect([...consumer.nodes]).toEqual(beforeNodes)
|
|
expect([...consumer.enabledLibraries]).toEqual(beforeBindings)
|
|
})
|
|
|
|
test('reapplies boolean visibility properties to both previews', async () => {
|
|
const { latest, consumer, definition } = await revisions(
|
|
propertySource('VISIBLE', 'true'),
|
|
propertySource('VISIBLE', 'true'),
|
|
'card'
|
|
)
|
|
const instance = consumer.createNode('INSTANCE', consumer.getPages()[0].id, {
|
|
componentId: definition.componentId,
|
|
componentPropertyAssignments: { property: 'false' }
|
|
})
|
|
const preview = createLibraryUpdatePreview(consumer, instance.id, latest)
|
|
for (const id of [preview.currentNodeId, preview.updatedNodeId]) {
|
|
expect(preview.graph.getChildren(id)[0]?.visible).toBe(false)
|
|
}
|
|
})
|
|
|
|
test('reapplies nested instance swaps using preview-local definitions', async () => {
|
|
const firstGraph = propertySource('INSTANCE_SWAP', 'alternate')
|
|
const latestGraph = propertySource('INSTANCE_SWAP', 'alternate')
|
|
latestGraph.createNode('COMPONENT', latestGraph.getPages()[0].id, {
|
|
name: 'Replacement',
|
|
componentKey: 'replacement'
|
|
})
|
|
const { latest, consumer, definition } = await revisions(firstGraph, latestGraph, 'card')
|
|
const instance = consumer.createNode('INSTANCE', consumer.getPages()[0].id, {
|
|
componentId: definition.componentId,
|
|
componentPropertyAssignments: { property: 'replacement' }
|
|
})
|
|
const preview = createLibraryUpdatePreview(consumer, instance.id, latest)
|
|
const nested = preview.graph
|
|
.getChildren(preview.updatedNodeId)
|
|
.find((node) => node.type === 'INSTANCE')
|
|
expect(nested?.componentId ? preview.graph.getNode(nested.componentId)?.name : null).toBe(
|
|
'Replacement'
|
|
)
|
|
})
|
|
|
|
test('copies current-side nested definitions and remaps their instances', async () => {
|
|
const firstGraph = propertySource('INSTANCE_SWAP', 'alternate')
|
|
const latestGraph = propertySource('INSTANCE_SWAP', 'alternate')
|
|
const { latest, consumer, definition } = await revisions(firstGraph, latestGraph, 'card')
|
|
const instance = consumer.createNode('INSTANCE', consumer.getPages()[0].id, {
|
|
componentId: definition.componentId
|
|
})
|
|
|
|
const preview = createLibraryUpdatePreview(consumer, instance.id, latest)
|
|
const currentNested = preview.graph
|
|
.getChildren(preview.currentNodeId)
|
|
.find((node) => node.type === 'INSTANCE')
|
|
const currentDefinitionNode = currentNested?.componentId
|
|
? preview.graph.getNode(currentNested.componentId)
|
|
: null
|
|
const currentDependency = currentDefinitionNode?.componentId
|
|
? preview.graph.getNode(currentDefinitionNode.componentId)
|
|
: null
|
|
|
|
expect(currentDefinitionNode?.name).toBe('Nested')
|
|
expect(currentDependency?.name).toBe('Alternate')
|
|
expect(currentDependency?.parentId).toBe(preview.graph.getPages()[0].id)
|
|
expect(consumer.getNode(currentNested?.componentId ?? '')).toBeUndefined()
|
|
})
|
|
|
|
test('reports top-left fallback and reapplies compatible assignments', async () => {
|
|
const firstGraph = source('Old')
|
|
const latestGraph = source('Fallback')
|
|
const set = [...latestGraph.getAllNodes()].find((node) => node.type === 'COMPONENT_SET')
|
|
const variant = set ? latestGraph.getChildren(set.id)[0] : null
|
|
if (!variant) throw new Error('Latest variant missing')
|
|
latestGraph.updateNode(variant.id, {
|
|
name: 'Size=Large',
|
|
componentPropertyValues: { Size: 'Large' },
|
|
x: 0,
|
|
y: 0
|
|
})
|
|
const { latest, consumer, definition } = await revisions(firstGraph, latestGraph, 'button')
|
|
const instance = consumer.createNode('INSTANCE', consumer.getPages()[0].id, {
|
|
componentId: definition.componentId,
|
|
componentPropertyAssignments: { label: 'Custom fallback' }
|
|
})
|
|
const preview = createLibraryUpdatePreview(consumer, instance.id, latest)
|
|
expect(preview.fallback).toBe(true)
|
|
const componentId = preview.graph.getNode(preview.updatedNodeId)?.componentId
|
|
expect(
|
|
componentId ? preview.graph.getNode(componentId)?.componentPropertyValues : null
|
|
).toEqual({ Size: 'Large' })
|
|
expect(
|
|
preview.graph.getChildren(preview.updatedNodeId).find((node) => node.type === 'TEXT')?.text
|
|
).toBe('Custom fallback')
|
|
})
|
|
})
|