Ilya Ashikhmin e70ec822c9 test: cover domain, engine and controller scenarios; add headless mode
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>
2026-07-02 23:31:12 +02:00

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')
})
})