fix: cluster edges render on left/right handles from approve flow
Cluster edges created via the pending -> approve path rendered on the top
handle instead of left/right, because the edge and its endpoints lost their
handle information on the way to the canvas.
- Approve resolver (scan.py) now returns each edge's type + source/target
handle. Handle IDs are the bare slot-0 side names ('right'/'left'), the
canonical stored form React Flow resolves to the correct side; a '-t' target
id fails to resolve and falls back to the top handle.
- Frontend injectAutoEdges no longer hardcodes iot/bottom/top-t. It injects
each edge with its real type + handles and bumps the referenced nodes'
left/right handle counts (which default to 0) so the cluster endpoints exist.
Logic extracted to a pure, tested util (applyAutoEdges).
- clusterEdges direct-import path uses the bare 'left' target to match.
Tests: new autoEdges unit tests; updated backend handle assertions.
ha-relevant: maybe
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { applyAutoEdges, handleSide, type AutoEdge } from '../autoEdges'
|
||||
import type { Edge, Node } from '@xyflow/react'
|
||||
import type { EdgeData, NodeData } from '@/types'
|
||||
|
||||
function node(id: string, data: Partial<NodeData> = {}): Node<NodeData> {
|
||||
return {
|
||||
id,
|
||||
position: { x: 0, y: 0 },
|
||||
data: { label: id, type: 'proxmox', status: 'online', services: [], ...data },
|
||||
}
|
||||
}
|
||||
|
||||
describe('handleSide', () => {
|
||||
it('maps handle ids (with -t / slot suffixes) to their side', () => {
|
||||
expect(handleSide('left-t')).toBe('left')
|
||||
expect(handleSide('right')).toBe('right')
|
||||
expect(handleSide('bottom-2')).toBe('bottom')
|
||||
expect(handleSide('top')).toBe('top')
|
||||
expect(handleSide(null)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyAutoEdges', () => {
|
||||
it('injects a cluster edge with its handles and grants left/right handles', () => {
|
||||
const nodes = [node('a'), node('b')]
|
||||
const auto: AutoEdge[] = [
|
||||
{ id: 'e1', source: 'a', target: 'b', type: 'cluster', source_handle: 'right', target_handle: 'left' },
|
||||
]
|
||||
const res = applyAutoEdges(nodes, [], auto)
|
||||
|
||||
// Edge keeps its cluster type + right→left handles (not the iot default).
|
||||
expect(res.edges).toHaveLength(1)
|
||||
expect(res.edges[0]).toMatchObject({ type: 'cluster', sourceHandle: 'right', targetHandle: 'left' })
|
||||
// Source node gained a right handle; target node gained a left handle.
|
||||
expect(res.nodes.find((n) => n.id === 'a')!.data.right_handles).toBe(1)
|
||||
expect(res.nodes.find((n) => n.id === 'b')!.data.left_handles).toBe(1)
|
||||
})
|
||||
|
||||
it('does not lower an existing higher handle count', () => {
|
||||
const nodes = [node('a', { right_handles: 3 }), node('b')]
|
||||
const auto: AutoEdge[] = [
|
||||
{ id: 'e1', source: 'a', target: 'b', type: 'cluster', source_handle: 'right', target_handle: 'left' },
|
||||
]
|
||||
const res = applyAutoEdges(nodes, [], auto)
|
||||
expect(res.nodes.find((n) => n.id === 'a')!.data.right_handles).toBe(3)
|
||||
})
|
||||
|
||||
it('defaults to an iot bottom→top edge and bumps no handles', () => {
|
||||
const nodes = [node('a'), node('b')]
|
||||
const auto: AutoEdge[] = [{ id: 'e1', source: 'a', target: 'b' }]
|
||||
const res = applyAutoEdges(nodes, [], auto)
|
||||
expect(res.edges[0]).toMatchObject({ type: 'iot', sourceHandle: 'bottom', targetHandle: 'top' })
|
||||
// top/bottom always exist — nodes are returned untouched.
|
||||
expect(res.nodes).toBe(nodes)
|
||||
})
|
||||
|
||||
it('appends to existing edges rather than replacing them', () => {
|
||||
const existing = [{ id: 'x', source: 'a', target: 'b' }] as Edge<EdgeData>[]
|
||||
const res = applyAutoEdges([node('a'), node('b')], existing, [
|
||||
{ id: 'e1', source: 'a', target: 'b', type: 'iot' },
|
||||
])
|
||||
expect(res.edges.map((e) => e.id)).toEqual(['x', 'e1'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user