110 lines
3.5 KiB
TypeScript
110 lines
3.5 KiB
TypeScript
|
|
import { describe, expect, test } from 'bun:test'
|
||
|
|
|
||
|
|
import { SceneGraph } from '@open-pencil/scene-graph'
|
||
|
|
|
||
|
|
import { createComponentSyncScheduler } from '#core/editor/component-sync'
|
||
|
|
import { computeAllLayouts } from '#core/layout'
|
||
|
|
|
||
|
|
function createGraph() {
|
||
|
|
const graph = new SceneGraph()
|
||
|
|
const componentPage = graph.getPages()[0]
|
||
|
|
const instancePage = graph.addPage('Instances')
|
||
|
|
const unrelatedPage = graph.addPage('Unrelated')
|
||
|
|
|
||
|
|
const component = graph.createNode('COMPONENT', componentPage.id, {
|
||
|
|
name: 'Button',
|
||
|
|
width: 200,
|
||
|
|
height: 40,
|
||
|
|
layoutMode: 'HORIZONTAL',
|
||
|
|
primaryAxisSizing: 'FIXED',
|
||
|
|
counterAxisSizing: 'FIXED',
|
||
|
|
itemSpacing: 8,
|
||
|
|
paddingLeft: 16
|
||
|
|
})
|
||
|
|
const label = graph.createNode('TEXT', component.id, { name: 'Label', text: 'Label', x: 0, y: 0 })
|
||
|
|
const instance = graph.createInstance(component.id, instancePage.id, { x: 0, y: 0 })
|
||
|
|
const unrelated = graph.createNode('FRAME', unrelatedPage.id, {
|
||
|
|
name: 'Dashboard',
|
||
|
|
width: 300,
|
||
|
|
height: 200,
|
||
|
|
layoutMode: 'VERTICAL',
|
||
|
|
itemSpacing: 4
|
||
|
|
})
|
||
|
|
graph.createNode('TEXT', unrelated.id, { name: 'Title', text: 'Title', x: 0, y: 0 })
|
||
|
|
|
||
|
|
return {
|
||
|
|
graph,
|
||
|
|
componentPage,
|
||
|
|
instancePage,
|
||
|
|
unrelatedPage,
|
||
|
|
component,
|
||
|
|
label,
|
||
|
|
instance,
|
||
|
|
unrelated
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('component sync layout scope', () => {
|
||
|
|
test('recomputes only the pages that changed, including cross-page instances', async () => {
|
||
|
|
const { graph, componentPage, instancePage, unrelatedPage, label } = createGraph()
|
||
|
|
const scopes: (string | undefined)[] = []
|
||
|
|
const { scheduleComponentSync } = createComponentSyncScheduler(
|
||
|
|
() => graph,
|
||
|
|
() => undefined,
|
||
|
|
(innerGraph, scopeId) => {
|
||
|
|
scopes.push(scopeId)
|
||
|
|
computeAllLayouts(innerGraph, scopeId)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
graph.updateNode(label.id, { text: 'Renamed label' })
|
||
|
|
scheduleComponentSync(label.id)
|
||
|
|
|
||
|
|
await Promise.resolve()
|
||
|
|
expect([...scopes].sort()).toEqual([componentPage.id, instancePage.id].sort())
|
||
|
|
expect(scopes).not.toContain(unrelatedPage.id)
|
||
|
|
expect(scopes).not.toContain(undefined)
|
||
|
|
})
|
||
|
|
|
||
|
|
test('an edit outside any component does no layout work', async () => {
|
||
|
|
const { graph, unrelated } = createGraph()
|
||
|
|
const scopes: (string | undefined)[] = []
|
||
|
|
const { scheduleComponentSync } = createComponentSyncScheduler(
|
||
|
|
() => graph,
|
||
|
|
() => undefined,
|
||
|
|
(innerGraph, scopeId) => {
|
||
|
|
scopes.push(scopeId)
|
||
|
|
computeAllLayouts(innerGraph, scopeId)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
graph.updateNode(unrelated.id, { width: 400 })
|
||
|
|
scheduleComponentSync(unrelated.id)
|
||
|
|
|
||
|
|
await Promise.resolve()
|
||
|
|
expect(scopes).toEqual([])
|
||
|
|
})
|
||
|
|
|
||
|
|
test('a cross-page instance still receives the component layout', async () => {
|
||
|
|
const { graph, instance, component } = createGraph()
|
||
|
|
const { scheduleComponentSync } = createComponentSyncScheduler(
|
||
|
|
() => graph,
|
||
|
|
() => undefined,
|
||
|
|
computeAllLayouts
|
||
|
|
)
|
||
|
|
|
||
|
|
// Widen the component's padding, which moves its label; the instance must follow.
|
||
|
|
const label = graph.getChildren(component.id)[0]
|
||
|
|
if (!label) throw new Error('Expected a component child')
|
||
|
|
graph.updateNode(label.id, { text: 'A much longer label' })
|
||
|
|
scheduleComponentSync(label.id)
|
||
|
|
|
||
|
|
await Promise.resolve()
|
||
|
|
const instanceLabel = graph.getChildren(instance.id)[0]
|
||
|
|
const componentLabel = graph.getChildren(component.id)[0]
|
||
|
|
expect(instanceLabel).toBeDefined()
|
||
|
|
expect(instanceLabel?.x).toBe(componentLabel?.x)
|
||
|
|
expect(instanceLabel?.width).toBe(componentLabel?.width)
|
||
|
|
})
|
||
|
|
})
|