Add Vitest suites for relations, properties, catalogue, symbols, edge styles, flow solver, settings, JointJS shapes/serialization and the controller lifecycle (add/connect/edit/delete, undo-redo, copy-paste, flow, save-load). Introduce headless DiagramController (Paper-less) so the controller is testable outside a browser, and fix the flow direction sign. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import { dia, shapes } from '@joint/core'
|
|
import {
|
|
PipelineNode,
|
|
createNode,
|
|
createLink,
|
|
applyNodeValues,
|
|
applyEdgeValues,
|
|
portMedium,
|
|
cellValues
|
|
} from '@diagram/shapes'
|
|
import { canConnectMedia, type MediumId } from '@diagram/relations'
|
|
|
|
const cellNamespace = { ...shapes, pipeline: { Node: PipelineNode } }
|
|
const makeGraph = (): dia.Graph => new dia.Graph({}, { cellNamespace })
|
|
|
|
describe('node creation', () => {
|
|
it('creates a pump node with its ports and medium metadata', () => {
|
|
const node = createNode('pump', 100, 100)
|
|
expect(node.get('kind')).toBe('node')
|
|
expect(node.get('symbolId')).toBe('pump')
|
|
expect(node.getPorts().map((p) => p.id).sort()).toEqual(['in', 'out'])
|
|
expect(portMedium(node, 'in')).toBe('water')
|
|
})
|
|
|
|
it('applies title, size and rotation from values', () => {
|
|
const node = createNode('valve', 0, 0)
|
|
applyNodeValues(node, { ...cellValues(node), title: 'V-101', width: 90, height: 60, rotation: 90 })
|
|
expect(node.attr('label/text')).toBe('V-101')
|
|
expect(node.size()).toEqual({ width: 90, height: 60 })
|
|
expect(node.angle()).toBe(90)
|
|
})
|
|
|
|
it('hides the label when showLabel is no', () => {
|
|
const node = createNode('tank', 0, 0)
|
|
applyNodeValues(node, { ...cellValues(node), showLabel: 'no', title: 'T' })
|
|
expect(node.attr('label/text')).toBe('')
|
|
})
|
|
})
|
|
|
|
describe('connection rule (port media)', () => {
|
|
it('permits compatible ports and blocks incompatible ones', () => {
|
|
const pump = createNode('pump', 0, 0)
|
|
const sensor = createNode('sensor', 0, 0)
|
|
const water = portMedium(pump, 'out') as MediumId
|
|
const signal = portMedium(sensor, 'sig') as MediumId
|
|
expect(canConnectMedia(water, water)).toBe(true)
|
|
expect(canConnectMedia(water, signal)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('edge creation', () => {
|
|
it('creates a styled link and applies markers', () => {
|
|
const link = createLink({ id: 'a', port: 'out' }, { id: 'b', port: 'in' })
|
|
expect(link.get('kind')).toBe('edge')
|
|
applyEdgeValues(link, { ...cellValues(link), color: '#ff0000', headMarker: 'diamond', strokeWidth: 5 })
|
|
expect(link.attr('line/stroke')).toBe('#ff0000')
|
|
expect(link.attr('line/strokeWidth')).toBe(5)
|
|
expect(link.attr('line/targetMarker')).toMatchObject({ type: 'path' })
|
|
})
|
|
})
|
|
|
|
describe('serialization round-trip', () => {
|
|
it('preserves nodes, links, ports and values', () => {
|
|
const g1 = makeGraph()
|
|
const a = createNode('reservoir', 40, 40)
|
|
const b = createNode('outlet', 300, 40)
|
|
a.addTo(g1)
|
|
b.addTo(g1)
|
|
const link = createLink({ id: a.id as string, port: 'out' }, { id: b.id as string, port: 'in' })
|
|
link.addTo(g1)
|
|
applyNodeValues(a, { ...cellValues(a), title: 'Main reservoir' })
|
|
|
|
const json = JSON.stringify(g1.toJSON())
|
|
|
|
const g2 = makeGraph()
|
|
g2.fromJSON(JSON.parse(json))
|
|
expect(g2.getElements().length).toBe(2)
|
|
expect(g2.getLinks().length).toBe(1)
|
|
const restored = g2.getCell(a.id) as dia.Element
|
|
expect((restored.get('values') as Record<string, unknown>).title).toBe('Main reservoir')
|
|
expect(restored.getPort('out')).toBeTruthy()
|
|
expect(portMedium(restored, 'out')).toBe('water')
|
|
})
|
|
})
|