diagrams-fable-tauri/tests/graph.test.ts
2026-07-02 23:22:21 +02:00

93 lines
3.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { PipelineGraph, parseDocument, serializeDocument } from '../src/model/graph';
function sampleGraph(): PipelineGraph {
const g = new PipelineGraph();
g.addNode({ id: 'src', typeId: 'waterSource', x: 0, y: 0, angle: 0 });
g.addNode({ id: 'pump', typeId: 'pump', x: 200, y: 0, angle: 0 });
g.addNode({ id: 'home', typeId: 'consumer', x: 400, y: 0, angle: 0 });
g.addEdge({
id: 'e1',
sourceNodeId: 'src',
sourcePortId: 'out',
targetNodeId: 'pump',
targetPortId: 'in',
});
g.addEdge({
id: 'e2',
sourceNodeId: 'pump',
sourcePortId: 'out',
targetNodeId: 'home',
targetPortId: 'waterIn',
});
return g;
}
describe('PipelineGraph', () => {
it('adds nodes with type defaults merged into props', () => {
const g = sampleGraph();
const src = g.nodes.get('src')!;
expect(src.props.supply).toBe(50); // waterSource default
expect(src.props.title).toBe('Water source');
});
it('rejects unknown node types, ports and duplicate ids', () => {
const g = sampleGraph();
expect(() => g.addNode({ id: 'x', typeId: 'nope', x: 0, y: 0, angle: 0 })).toThrow(/Unknown node type/);
expect(() => g.addNode({ id: 'src', typeId: 'pump', x: 0, y: 0, angle: 0 })).toThrow(/Duplicate/);
expect(() =>
g.addEdge({ id: 'e3', sourceNodeId: 'src', sourcePortId: 'nope', targetNodeId: 'pump', targetPortId: 'in' }),
).toThrow(/Unknown source port/);
});
it('rejects edges between incompatible ports', () => {
const g = sampleGraph();
g.addNode({ id: 'ps', typeId: 'powerSupply', x: 0, y: 200, angle: 0 });
expect(() =>
g.addEdge({ id: 'bad', sourceNodeId: 'ps', sourcePortId: 'out', targetNodeId: 'home', targetPortId: 'waterIn' }),
).toThrow(/Incompatible relations/);
// power → consumer power inlet is fine
expect(() =>
g.addEdge({ id: 'ok', sourceNodeId: 'ps', sourcePortId: 'out', targetNodeId: 'home', targetPortId: 'powerIn' }),
).not.toThrow();
});
it('derives edge relation from the source port', () => {
const g = sampleGraph();
expect(g.edges.get('e1')!.relation).toBe('water');
});
it('removing a node cascades to connected edges', () => {
const g = sampleGraph();
g.removeNode('pump');
expect(g.nodes.has('pump')).toBe(false);
expect(g.edges.size).toBe(0);
});
it('edgesOf returns incident edges', () => {
const g = sampleGraph();
expect(g.edgesOf('pump').map((e) => e.id).sort()).toEqual(['e1', 'e2']);
expect(g.edgesOf('home').map((e) => e.id)).toEqual(['e2']);
});
it('survives a serialization round-trip', () => {
const g = sampleGraph();
g.edges.get('e1')!.vertices = [{ x: 100, y: 50 }];
g.nodes.get('pump')!.props.head = 45;
const json = serializeDocument(g.toDocument());
const restored = PipelineGraph.fromDocument(parseDocument(json));
expect(restored.nodes.size).toBe(3);
expect(restored.edges.size).toBe(2);
expect(restored.nodes.get('pump')!.props.head).toBe(45);
expect(restored.edges.get('e1')!.vertices).toEqual([{ x: 100, y: 50 }]);
expect(restored.edges.get('e2')!.relation).toBe('water');
});
it('parseDocument rejects malformed payloads', () => {
expect(() => parseDocument('{"foo": 1}')).toThrow(/Invalid diagram document/);
expect(() => parseDocument('not json')).toThrow();
});
});