fix(canvas): make collapse reachable + persist for every node type
Three connected bugs in PR #158's collapse feature: 1. Toggle wired to the wrong component The chevron was on GroupRectNode and computed children via React Flow parentId. But in this codebase parentId is set by createGroup() on type=group nodes, not on groupRect zones — zones are decorative rectangles. Result: childrenCount was always 0 on every zone and the button never rendered, so the feature was unreachable from the UI. Fix: - Add the same chevron toggle to GroupNode (the actual container). parentId children are already known there, so the existing BFS in computeCollapseInfo hides them when data.collapsed flips. - For GroupRectNode, switch childrenCount to spatial containment so drawn zones also work: hit-test other nodes' bbox centres against the zone bbox. 2. Visibility filter ignored spatial zones Extend computeCollapseInfo with a second pass that hides every node whose centre lies inside a collapsed groupRect, plus the parentId subtrees of those nodes (so a Proxmox host inside a collapsed zone takes its VMs/LXCs with it). Edge rewiring routes vanished endpoints to the same visible zone via a unified hiddenBy map populated by both passes. 3. Save dropped data.collapsed for every type except groupRect The DevTools payload was the smoking gun: for a type=group node the serializer wrote custom_colors: {show_border: true} with no collapsed key, so the backend stored a stale false on every save. Only the groupRect branch of serializeNode/deserializeApiNode stashed and hoisted the flag. Move the stash + hoist to the general branch too (backend's custom_colors is dict[str, Any] so no schema change). Tests: 11 new cases for spatial containment + GroupNode toggle UI, and 4 round-trip cases for collapse on non-groupRect types.
This commit is contained in:
@@ -81,3 +81,71 @@ describe('canvasSerializer — groupRect collapse', () => {
|
||||
expect(back.data.collapsed).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('canvasSerializer — collapse on non-groupRect node types', () => {
|
||||
it('stashes data.collapsed into custom_colors for a group container', () => {
|
||||
const rf: Node<NodeData> = {
|
||||
id: 'g1',
|
||||
type: 'group',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Container',
|
||||
type: 'group',
|
||||
status: 'unknown',
|
||||
services: [],
|
||||
custom_colors: { show_border: true },
|
||||
collapsed: true,
|
||||
},
|
||||
}
|
||||
const api = serializeNode(rf) as Record<string, unknown>
|
||||
const cc = api.custom_colors as Record<string, unknown>
|
||||
expect(cc.collapsed).toBe(true)
|
||||
// Existing custom_colors keys are preserved alongside the stash.
|
||||
expect(cc.show_border).toBe(true)
|
||||
})
|
||||
|
||||
it('leaves custom_colors null when neither flag nor colors are set', () => {
|
||||
const rf: Node<NodeData> = {
|
||||
id: 's1',
|
||||
type: 'server',
|
||||
position: { x: 0, y: 0 },
|
||||
data: { label: 'Server', type: 'server', status: 'online', services: [] },
|
||||
}
|
||||
const api = serializeNode(rf) as Record<string, unknown>
|
||||
expect(api.custom_colors).toBeNull()
|
||||
})
|
||||
|
||||
it('hoists custom_colors.collapsed to data.collapsed for a group container', () => {
|
||||
const apiNode: ApiNode = {
|
||||
id: 'g1',
|
||||
type: 'group',
|
||||
label: 'Container',
|
||||
pos_x: 0,
|
||||
pos_y: 0,
|
||||
status: 'unknown',
|
||||
services: [],
|
||||
custom_colors: { show_border: true, collapsed: true },
|
||||
}
|
||||
const rf = deserializeApiNode(apiNode, new Map())
|
||||
expect(rf.data.collapsed).toBe(true)
|
||||
})
|
||||
|
||||
it('round-trips collapse on a group container', () => {
|
||||
const rf: Node<NodeData> = {
|
||||
id: 'g1',
|
||||
type: 'group',
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
label: 'Container',
|
||||
type: 'group',
|
||||
status: 'unknown',
|
||||
services: [],
|
||||
custom_colors: { show_border: true },
|
||||
collapsed: true,
|
||||
},
|
||||
}
|
||||
const api = serializeNode(rf) as unknown as ApiNode
|
||||
const back = deserializeApiNode(api, new Map())
|
||||
expect(back.data.collapsed).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user