[feat] sync subgraph node titles with breadcrumb renaming (#4565)

This commit is contained in:
Christian Byrne
2025-07-28 18:00:59 -07:00
committed by GitHub
parent b6922cf386
commit b1436a068b
4 changed files with 403 additions and 19 deletions

View File

@@ -5,10 +5,15 @@ import {
collectAllNodes,
findNodeInHierarchy,
findSubgraphByUuid,
forEachNode,
forEachSubgraphNode,
getLocalNodeIdFromExecutionId,
getNodeByExecutionId,
getNodeByLocatorId,
getRootGraph,
getSubgraphPathFromExecutionId,
mapAllNodes,
mapSubgraphNodes,
parseExecutionId,
traverseSubgraphPath,
triggerCallbackOnAllNodes,
@@ -283,6 +288,141 @@ describe('graphTraversalUtil', () => {
})
})
describe('mapAllNodes', () => {
it('should map over all nodes in a flat graph', () => {
const nodes = [createMockNode(1), createMockNode(2), createMockNode(3)]
const graph = createMockGraph(nodes)
const results = mapAllNodes(graph, (node) => node.id)
expect(results).toEqual([1, 2, 3])
})
it('should map over nodes in subgraphs', () => {
const subNode = createMockNode(100)
const subgraph = createMockSubgraph('sub-uuid', [subNode])
const nodes = [
createMockNode(1),
createMockNode(2, { isSubgraph: true, subgraph })
]
const graph = createMockGraph(nodes)
const results = mapAllNodes(graph, (node) => node.id)
expect(results).toHaveLength(3)
expect(results).toContain(100)
})
it('should exclude undefined results', () => {
const nodes = [createMockNode(1), createMockNode(2), createMockNode(3)]
const graph = createMockGraph(nodes)
const results = mapAllNodes(graph, (node) => {
return Number(node.id) > 1 ? node.id : undefined
})
expect(results).toEqual([2, 3])
})
it('should handle deeply nested structures', () => {
const deepNode = createMockNode(300)
const deepSubgraph = createMockSubgraph('deep-uuid', [deepNode])
const midNode = createMockNode(200)
const midSubgraphNode = createMockNode(201, {
isSubgraph: true,
subgraph: deepSubgraph
})
const midSubgraph = createMockSubgraph('mid-uuid', [
midNode,
midSubgraphNode
])
const nodes = [
createMockNode(1),
createMockNode(2, { isSubgraph: true, subgraph: midSubgraph })
]
const graph = createMockGraph(nodes)
const results = mapAllNodes(graph, (node) => `node-${node.id}`)
expect(results).toHaveLength(5)
expect(results).toContain('node-300')
})
})
describe('forEachNode', () => {
it('should execute function on all nodes in a flat graph', () => {
const nodes = [createMockNode(1), createMockNode(2), createMockNode(3)]
const graph = createMockGraph(nodes)
const visited: number[] = []
forEachNode(graph, (node) => {
visited.push(node.id as number)
})
expect(visited).toHaveLength(3)
expect(visited).toContain(1)
expect(visited).toContain(2)
expect(visited).toContain(3)
})
it('should execute function on nodes in subgraphs', () => {
const subNode = createMockNode(100)
const subgraph = createMockSubgraph('sub-uuid', [subNode])
const nodes = [
createMockNode(1),
createMockNode(2, { isSubgraph: true, subgraph })
]
const graph = createMockGraph(nodes)
const visited: number[] = []
forEachNode(graph, (node) => {
visited.push(node.id as number)
})
expect(visited).toHaveLength(3)
expect(visited).toContain(100)
})
it('should allow node mutations', () => {
const nodes = [createMockNode(1), createMockNode(2), createMockNode(3)]
const graph = createMockGraph(nodes)
// Add a title property to each node
forEachNode(graph, (node) => {
;(node as any).title = `Node ${node.id}`
})
expect(nodes[0]).toHaveProperty('title', 'Node 1')
expect(nodes[1]).toHaveProperty('title', 'Node 2')
expect(nodes[2]).toHaveProperty('title', 'Node 3')
})
it('should handle node type matching for subgraph references', () => {
const subgraphId = 'my-subgraph-123'
const nodes = [
createMockNode(1),
{ ...createMockNode(2), type: subgraphId } as LGraphNode,
createMockNode(3),
{ ...createMockNode(4), type: subgraphId } as LGraphNode
]
const graph = createMockGraph(nodes)
const matchingNodes: number[] = []
forEachNode(graph, (node) => {
if (node.type === subgraphId) {
matchingNodes.push(node.id as number)
}
})
expect(matchingNodes).toEqual([2, 4])
})
})
describe('findNodeInHierarchy', () => {
it('should find node in root graph', () => {
const nodes = [createMockNode(1), createMockNode(2), createMockNode(3)]
@@ -482,5 +622,140 @@ describe('graphTraversalUtil', () => {
expect(found).toBeNull()
})
})
describe('getRootGraph', () => {
it('should return the same graph if it is already root', () => {
const graph = createMockGraph([])
expect(getRootGraph(graph)).toBe(graph)
})
it('should return root graph from subgraph', () => {
const rootGraph = createMockGraph([])
const subgraph = createMockSubgraph('sub-uuid', [])
;(subgraph as any).rootGraph = rootGraph
expect(getRootGraph(subgraph)).toBe(rootGraph)
})
it('should return root graph from deeply nested subgraph', () => {
const rootGraph = createMockGraph([])
const midSubgraph = createMockSubgraph('mid-uuid', [])
const deepSubgraph = createMockSubgraph('deep-uuid', [])
;(midSubgraph as any).rootGraph = rootGraph
;(deepSubgraph as any).rootGraph = midSubgraph
expect(getRootGraph(deepSubgraph)).toBe(rootGraph)
})
})
describe('forEachSubgraphNode', () => {
it('should apply function to all nodes matching subgraph type', () => {
const subgraphId = 'my-subgraph-123'
const nodes = [
createMockNode(1),
{ ...createMockNode(2), type: subgraphId } as LGraphNode,
createMockNode(3),
{ ...createMockNode(4), type: subgraphId } as LGraphNode
]
const graph = createMockGraph(nodes)
const matchingIds: number[] = []
forEachSubgraphNode(graph, subgraphId, (node) => {
matchingIds.push(node.id as number)
})
expect(matchingIds).toEqual([2, 4])
})
it('should work with root graph directly', () => {
const subgraphId = 'target-subgraph'
const rootNodes = [
{ ...createMockNode(1), type: subgraphId } as LGraphNode,
createMockNode(2),
{ ...createMockNode(3), type: subgraphId } as LGraphNode
]
const rootGraph = createMockGraph(rootNodes)
const matchingIds: number[] = []
forEachSubgraphNode(rootGraph, subgraphId, (node) => {
matchingIds.push(node.id as number)
})
expect(matchingIds).toEqual([1, 3])
})
it('should handle null inputs gracefully', () => {
const fn = vi.fn()
forEachSubgraphNode(null, 'id', fn)
forEachSubgraphNode(createMockGraph([]), null, fn)
forEachSubgraphNode(null, null, fn)
expect(fn).not.toHaveBeenCalled()
})
it('should allow node mutations like title updates', () => {
const subgraphId = 'my-subgraph'
const nodes = [
{ ...createMockNode(1), type: subgraphId } as LGraphNode,
{ ...createMockNode(2), type: subgraphId } as LGraphNode,
createMockNode(3)
]
const graph = createMockGraph(nodes)
forEachSubgraphNode(graph, subgraphId, (node) => {
;(node as any).title = 'Updated Title'
})
expect(nodes[0]).toHaveProperty('title', 'Updated Title')
expect(nodes[1]).toHaveProperty('title', 'Updated Title')
expect(nodes[2]).not.toHaveProperty('title', 'Updated Title')
})
})
describe('mapSubgraphNodes', () => {
it('should map over nodes matching subgraph type', () => {
const subgraphId = 'my-subgraph-123'
const nodes = [
createMockNode(1),
{ ...createMockNode(2), type: subgraphId } as LGraphNode,
createMockNode(3),
{ ...createMockNode(4), type: subgraphId } as LGraphNode
]
const graph = createMockGraph(nodes)
const results = mapSubgraphNodes(graph, subgraphId, (node) => node.id)
expect(results).toEqual([2, 4])
})
it('should return empty array for null inputs', () => {
expect(mapSubgraphNodes(null, 'id', (n) => n.id)).toEqual([])
expect(
mapSubgraphNodes(createMockGraph([]), null, (n) => n.id)
).toEqual([])
})
it('should work with complex transformations', () => {
const subgraphId = 'target'
const nodes = [
{ ...createMockNode(1), type: subgraphId } as LGraphNode,
{ ...createMockNode(2), type: 'other' } as LGraphNode,
{ ...createMockNode(3), type: subgraphId } as LGraphNode
]
const graph = createMockGraph(nodes)
const results = mapSubgraphNodes(graph, subgraphId, (node) => ({
id: node.id,
isTarget: true
}))
expect(results).toEqual([
{ id: 1, isTarget: true },
{ id: 3, isTarget: true }
])
})
})
})
})