diagrams-opus-electron/tests/controller.test.ts
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

84 lines
2.5 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { DiagramController } from '@diagram/controller'
// Headless controller (no DOM host / Paper) exercises the graph-level logic.
let ctrl: DiagramController
beforeEach(() => {
ctrl = new DiagramController(null)
})
afterEach(() => {
ctrl.dispose()
})
describe('node lifecycle', () => {
it('adds a node and selects it', () => {
const node = ctrl.addNode('pump', 100, 100)
expect(ctrl.graph.getElements().length).toBe(1)
expect(ctrl.getSelection().map((c) => c.id)).toContain(node.id)
})
it('edits properties through applyValues', () => {
const node = ctrl.addNode('valve', 40, 40)
ctrl.applyValues(node.id as string, { ...(node.get('values') as object), title: 'V-9', opening: 50 })
expect(node.attr('label/text')).toBe('V-9')
expect((node.get('values') as Record<string, unknown>).opening).toBe(50)
})
it('deletes the selection', () => {
const node = ctrl.addNode('tank', 40, 40)
ctrl.select(node.id as string, false)
ctrl.deleteSelected()
expect(ctrl.graph.getElements().length).toBe(0)
})
})
describe('undo / redo', () => {
it('reverts and reapplies an add', () => {
ctrl.addNode('pump', 0, 0)
expect(ctrl.graph.getElements().length).toBe(1)
ctrl.undo()
expect(ctrl.graph.getElements().length).toBe(0)
ctrl.redo()
expect(ctrl.graph.getElements().length).toBe(1)
})
})
describe('copy / paste', () => {
it('duplicates selected nodes', () => {
const node = ctrl.addNode('valve', 60, 60)
ctrl.select(node.id as string, false)
ctrl.duplicateSelected()
expect(ctrl.graph.getElements().length).toBe(2)
})
})
describe('flow', () => {
it('solves and animates a seeded network, then stops', () => {
ctrl.seedSample()
const sol = ctrl.runFlow()
expect(sol.edges.size).toBeGreaterThan(0)
expect(sol.maxMagnitude).toBeGreaterThan(0)
expect(ctrl.isFlowing()).toBe(true)
ctrl.stopFlow()
expect(ctrl.isFlowing()).toBe(false)
})
})
describe('serialization', () => {
it('round-trips the whole diagram including settings', () => {
ctrl.seedSample()
ctrl.setSettings({ jumpover: false, colorScheme: 'colorblind' })
const json = ctrl.toJSON()
const nElements = ctrl.graph.getElements().length
const ctrl2 = new DiagramController(null)
ctrl2.loadJSON(json)
expect(ctrl2.graph.getElements().length).toBe(nElements)
expect(ctrl2.settings.colorScheme).toBe('colorblind')
expect(ctrl2.settings.jumpover).toBe(false)
ctrl2.dispose()
})
})