218 lines
8.7 KiB
TypeScript
218 lines
8.7 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
|
|
import { createEditor } from '@open-pencil/core/editor'
|
|
|
|
function setupVariants() {
|
|
const editor = createEditor()
|
|
const pageId = editor.state.currentPageId
|
|
const componentSet = editor.graph.createNode('COMPONENT_SET', pageId, {
|
|
name: 'Button',
|
|
componentPropertyDefinitions: [
|
|
{
|
|
id: 'variant:type',
|
|
name: 'Type',
|
|
type: 'VARIANT',
|
|
defaultValue: 'Primary',
|
|
variantOptions: ['Primary', 'Secondary']
|
|
},
|
|
{
|
|
id: 'variant:size',
|
|
name: 'Size',
|
|
type: 'VARIANT',
|
|
defaultValue: 'Small',
|
|
variantOptions: ['Small', 'Large']
|
|
}
|
|
]
|
|
})
|
|
const createVariant = (type: string, size: string, x: number, y: number) =>
|
|
editor.graph.createNode('COMPONENT', componentSet.id, {
|
|
name: `Type=${type}, Size=${size}`,
|
|
x,
|
|
y,
|
|
componentPropertyValues: { Type: type, Size: size }
|
|
})
|
|
const primarySmall = createVariant('Primary', 'Small', 0, 0)
|
|
const primaryLarge = createVariant('Primary', 'Large', 140, 0)
|
|
const secondarySmall = createVariant('Secondary', 'Small', 0, 140)
|
|
return { editor, componentSet, primarySmall, primaryLarge, secondarySmall }
|
|
}
|
|
|
|
describe('variant authoring', () => {
|
|
test('keeps sparse multidimensional transitions exact and reports unavailable combinations', () => {
|
|
const { editor, primarySmall, primaryLarge } = setupVariants()
|
|
const instance = editor.graph.createInstance(primarySmall.id, editor.state.currentPageId)
|
|
if (!instance) throw new Error('Expected instance')
|
|
|
|
expect(editor.getVariantOptionAvailability(instance.id, 'Size')).toEqual([
|
|
{ value: 'Small', available: true },
|
|
{ value: 'Large', available: true }
|
|
])
|
|
expect(
|
|
editor.setInstanceComponentProperty(instance.id, 'variant:size', 'Large')
|
|
).toBeUndefined()
|
|
expect(editor.graph.getNode(instance.id)?.componentId).toBe(primaryLarge.id)
|
|
|
|
expect(editor.getVariantOptionAvailability(instance.id, 'Type')).toEqual([
|
|
{ value: 'Primary', available: true },
|
|
{ value: 'Secondary', available: false }
|
|
])
|
|
expect(editor.switchInstanceVariant(instance.id, 'Type', 'Secondary')).toEqual({
|
|
kind: 'unavailable',
|
|
requested: { Type: 'Secondary', Size: 'Large' }
|
|
})
|
|
expect(editor.graph.getNode(instance.id)?.componentId).toBe(primaryLarge.id)
|
|
})
|
|
|
|
test('renames dimensions and values atomically through undo and redo', () => {
|
|
const { editor, componentSet, primarySmall } = setupVariants()
|
|
|
|
expect(editor.renamePropertyDefinition(componentSet.id, 'variant:size', 'Density')).toBe(true)
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues).toEqual({
|
|
Type: 'Primary',
|
|
Density: 'Small'
|
|
})
|
|
expect(editor.graph.getNode(primarySmall.id)?.name).toBe('Type=Primary, Density=Small')
|
|
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues).toEqual({
|
|
Type: 'Primary',
|
|
Size: 'Small'
|
|
})
|
|
expect(editor.graph.getNode(primarySmall.id)?.name).toBe('Type=Primary, Size=Small')
|
|
|
|
editor.undo.redo()
|
|
expect(editor.renameVariantValue(componentSet.id, 'variant:size', 'Small', 'Compact')).toBe(
|
|
true
|
|
)
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues.Density).toBe('Compact')
|
|
expect(
|
|
editor.graph
|
|
.getNode(componentSet.id)
|
|
?.componentPropertyDefinitions.find((definition) => definition.id === 'variant:size')
|
|
?.variantOptions
|
|
).toEqual(['Compact', 'Large'])
|
|
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues.Density).toBe('Small')
|
|
})
|
|
|
|
test('adds and removes dimensions with complete undo snapshots', () => {
|
|
const { editor, componentSet, primarySmall } = setupVariants()
|
|
const propertyId = editor.addPropertyDefinition(componentSet.id, 'State', 'VARIANT', 'Enabled')
|
|
expect(propertyId).toStartWith('prop:')
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues.State).toBe('Enabled')
|
|
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues).not.toHaveProperty(
|
|
'State'
|
|
)
|
|
editor.undo.redo()
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues.State).toBe('Enabled')
|
|
|
|
if (!propertyId) throw new Error('Expected property ID')
|
|
expect(editor.removePropertyDefinition(componentSet.id, propertyId)).toBe(true)
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues).not.toHaveProperty(
|
|
'State'
|
|
)
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(primarySmall.id)?.componentPropertyValues.State).toBe('Enabled')
|
|
})
|
|
|
|
test('reorders properties and values atomically', () => {
|
|
const { editor, componentSet, primarySmall } = setupVariants()
|
|
|
|
expect(
|
|
editor.reorderPropertyDefinitions(componentSet.id, ['variant:size', 'variant:type'])
|
|
).toBe(true)
|
|
expect(
|
|
editor.graph
|
|
.getNode(componentSet.id)
|
|
?.componentPropertyDefinitions.map((definition) => definition.id)
|
|
).toEqual(['variant:size', 'variant:type'])
|
|
expect(editor.graph.getNode(primarySmall.id)?.name).toBe('Size=Small, Type=Primary')
|
|
|
|
expect(editor.reorderVariantValues(componentSet.id, 'variant:size', ['Large', 'Small'])).toBe(
|
|
true
|
|
)
|
|
expect(
|
|
editor.graph
|
|
.getNode(componentSet.id)
|
|
?.componentPropertyDefinitions.find((definition) => definition.id === 'variant:size')
|
|
).toMatchObject({ defaultValue: 'Large', variantOptions: ['Large', 'Small'] })
|
|
|
|
editor.undo.undo()
|
|
expect(
|
|
editor.graph
|
|
.getNode(componentSet.id)
|
|
?.componentPropertyDefinitions.find((definition) => definition.id === 'variant:size')
|
|
).toMatchObject({ defaultValue: 'Small', variantOptions: ['Small', 'Large'] })
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(primarySmall.id)?.name).toBe('Type=Primary, Size=Small')
|
|
})
|
|
|
|
test('uses top-left defaults and validates missing or duplicate combinations', () => {
|
|
const { editor, componentSet, primarySmall, primaryLarge } = setupVariants()
|
|
const topLeft = editor.graph.getNode(primarySmall.id)
|
|
const topRight = editor.graph.getNode(primaryLarge.id)
|
|
if (!topLeft || !topRight) throw new Error('Expected variants')
|
|
editor.graph.updateNode(topLeft.id, { x: 200, y: 200 })
|
|
editor.graph.updateNode(topRight.id, { x: 0, y: 0 })
|
|
expect(editor.getDefaultVariantForComponentSet(componentSet.id)?.id).toBe(primaryLarge.id)
|
|
|
|
editor.graph.updateNode(primaryLarge.id, {
|
|
componentPropertyValues: { Type: 'Primary', Size: '' }
|
|
})
|
|
expect(editor.validateComponentSet(componentSet.id)).toEqual([
|
|
{
|
|
kind: 'missing-value',
|
|
propertyId: 'variant:size',
|
|
propertyName: 'Size',
|
|
componentIds: [primaryLarge.id]
|
|
}
|
|
])
|
|
editor.graph.updateNode(primaryLarge.id, {
|
|
componentPropertyValues: { Type: 'Primary', Size: 'Small' }
|
|
})
|
|
expect(editor.validateComponentSet(componentSet.id)).toEqual([
|
|
{
|
|
kind: 'duplicate-combination',
|
|
values: { Type: 'Primary', Size: 'Small' },
|
|
componentIds: [primarySmall.id, primaryLarge.id]
|
|
}
|
|
])
|
|
})
|
|
test('edits combinations, diagnoses conflicts, and duplicates or removes variants', () => {
|
|
const { editor, componentSet, primarySmall, primaryLarge } = setupVariants()
|
|
|
|
expect(editor.setVariantPropertyValue(primaryLarge.id, 'variant:size', 'Small')).toEqual({
|
|
kind: 'conflict',
|
|
componentIds: [primaryLarge.id, primarySmall.id]
|
|
})
|
|
expect(editor.getComponentSetVariantConflicts(componentSet.id)).toEqual([])
|
|
expect(editor.graph.getNode(primaryLarge.id)?.componentPropertyValues.Size).toBe('Large')
|
|
|
|
expect(editor.setVariantPropertyValue(primaryLarge.id, 'variant:size', 'Medium')).toEqual({
|
|
kind: 'changed'
|
|
})
|
|
expect(editor.graph.getNode(primaryLarge.id)?.componentPropertyValues.Size).toBe('Medium')
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(primaryLarge.id)?.componentPropertyValues.Size).toBe('Large')
|
|
|
|
const duplicateId = editor.duplicateVariant(primarySmall.id)
|
|
expect(duplicateId).toBeString()
|
|
expect(editor.getComponentSetVariantConflicts(componentSet.id)).toHaveLength(1)
|
|
editor.undo.undo()
|
|
expect(duplicateId ? editor.graph.getNode(duplicateId) : undefined).toBeUndefined()
|
|
editor.undo.redo()
|
|
expect(duplicateId ? editor.graph.getNode(duplicateId)?.parentId : undefined).toBe(
|
|
componentSet.id
|
|
)
|
|
|
|
if (!duplicateId) throw new Error('Expected duplicate')
|
|
expect(editor.removeVariant(duplicateId)).toBe(true)
|
|
expect(editor.graph.getNode(duplicateId)).toBeUndefined()
|
|
editor.undo.undo()
|
|
expect(editor.graph.getNode(duplicateId)?.parentId).toBe(componentSet.id)
|
|
})
|
|
})
|