@@ -221,6 +221,230 @@ describe('AgentflowContext - deleteNode', () => {
221221 expect ( result . current . state . edges ) . toHaveLength ( 1 )
222222 expect ( result . current . state . edges [ 0 ] . id ) . toBe ( 'edge-3-4' )
223223 } )
224+
225+ it ( 'should clean up connected input values when node is deleted' , ( ) => {
226+ const initialFlow : FlowData = {
227+ nodes : [
228+ makeFlowNode ( 'agent_0' , {
229+ data : {
230+ id : 'agent_0' ,
231+ name : 'agent' ,
232+ label : 'Agent' ,
233+ outputAnchors : [ { id : 'agent_0-output-0' , name : 'output' , label : 'Output' , type : 'Agent' } ]
234+ }
235+ } ) ,
236+ makeFlowNode ( 'tool_0' , {
237+ data : {
238+ id : 'tool_0' ,
239+ name : 'tool' ,
240+ label : 'Tool' ,
241+ inputs : [ { id : 'tool_0-input-agent-Agent' , name : 'agent' , label : 'Agent' , type : 'Agent' } ] ,
242+ inputAnchors : [ { id : 'tool_0-input-agent-Agent' , name : 'agent' , label : 'Agent' , type : 'Agent' } ] ,
243+ inputValues : {
244+ agent : '{{agent_0.data.instance}}' ,
245+ apiKey : 'sk-1234'
246+ } ,
247+ outputAnchors : [ ]
248+ }
249+ } )
250+ ] ,
251+ edges : [
252+ makeEdge ( 'agent_0' , 'tool_0' , {
253+ id : 'edge-agent-tool' ,
254+ sourceHandle : 'agent_0-output-0' ,
255+ targetHandle : 'tool_0-input-agent-Agent'
256+ } )
257+ ]
258+ }
259+
260+ const { result } = renderHook ( ( ) => useAgentflowContext ( ) , {
261+ wrapper : createWrapper ( initialFlow )
262+ } )
263+
264+ // Delete agent_0
265+ act ( ( ) => {
266+ result . current . deleteNode ( 'agent_0' )
267+ } )
268+
269+ // tool_0 should still exist
270+ expect ( result . current . state . nodes ) . toHaveLength ( 1 )
271+ const tool = result . current . state . nodes . find ( ( n ) => n . id === 'tool_0' )
272+ expect ( tool ) . toBeDefined ( )
273+
274+ // The agent input should be cleared, but apiKey should be preserved
275+ expect ( tool ?. data . inputValues ?. agent ) . toBe ( '' )
276+ expect ( tool ?. data . inputValues ?. apiKey ) . toBe ( 'sk-1234' )
277+
278+ // Edge should be removed
279+ expect ( result . current . state . edges ) . toHaveLength ( 0 )
280+ } )
281+
282+ it ( 'should clean up list input values when node is deleted' , ( ) => {
283+ const initialFlow : FlowData = {
284+ nodes : [
285+ makeFlowNode ( 'tool_0' , {
286+ data : {
287+ id : 'tool_0' ,
288+ name : 'tool' ,
289+ label : 'Tool 1' ,
290+ outputAnchors : [ { id : 'tool_0-output-0' , name : 'output' , label : 'Output' , type : 'Tool' } ]
291+ }
292+ } ) ,
293+ makeFlowNode ( 'tool_1' , {
294+ data : {
295+ id : 'tool_1' ,
296+ name : 'tool' ,
297+ label : 'Tool 2' ,
298+ outputAnchors : [ { id : 'tool_1-output-0' , name : 'output' , label : 'Output' , type : 'Tool' } ]
299+ }
300+ } ) ,
301+ makeFlowNode ( 'agent_0' , {
302+ data : {
303+ id : 'agent_0' ,
304+ name : 'agent' ,
305+ label : 'Agent' ,
306+ inputs : [ { id : 'agent_0-input-tools-Tool' , name : 'tools' , label : 'Tools' , type : 'Tool' } ] ,
307+ inputAnchors : [ { id : 'agent_0-input-tools-Tool' , name : 'tools' , label : 'Tools' , type : 'Tool' , list : true } as any ] ,
308+ inputValues : {
309+ tools : [ '{{tool_0.data.instance}}' , '{{tool_1.data.instance}}' ]
310+ } ,
311+ outputAnchors : [ ]
312+ }
313+ } )
314+ ] ,
315+ edges : [
316+ makeEdge ( 'tool_0' , 'agent_0' , {
317+ id : 'edge-tool0-agent' ,
318+ sourceHandle : 'tool_0-output-0' ,
319+ targetHandle : 'agent_0-input-tools-Tool'
320+ } ) ,
321+ makeEdge ( 'tool_1' , 'agent_0' , {
322+ id : 'edge-tool1-agent' ,
323+ sourceHandle : 'tool_1-output-0' ,
324+ targetHandle : 'agent_0-input-tools-Tool'
325+ } )
326+ ]
327+ }
328+
329+ const { result } = renderHook ( ( ) => useAgentflowContext ( ) , {
330+ wrapper : createWrapper ( initialFlow )
331+ } )
332+
333+ // Delete tool_0
334+ act ( ( ) => {
335+ result . current . deleteNode ( 'tool_0' )
336+ } )
337+
338+ // agent_0 should still exist with tool_1 reference
339+ expect ( result . current . state . nodes ) . toHaveLength ( 2 )
340+ const agent = result . current . state . nodes . find ( ( n ) => n . id === 'agent_0' )
341+ expect ( agent ) . toBeDefined ( )
342+
343+ // The tools list should only contain tool_1
344+ expect ( agent ?. data . inputValues ?. tools ) . toEqual ( [ '{{tool_1.data.instance}}' ] )
345+
346+ // Only one edge should remain
347+ expect ( result . current . state . edges ) . toHaveLength ( 1 )
348+ expect ( result . current . state . edges [ 0 ] . id ) . toBe ( 'edge-tool1-agent' )
349+ } )
350+
351+ it ( 'should delete parent node and all descendant nodes' , ( ) => {
352+ const initialFlow : FlowData = {
353+ nodes : [
354+ makeFlowNode ( 'parent' , {
355+ data : { id : 'parent' , name : 'stickyNote' , label : 'Parent' , outputAnchors : [ ] }
356+ } ) ,
357+ makeFlowNode ( 'child-1' , {
358+ parentNode : 'parent' ,
359+ data : { id : 'child-1' , name : 'agent' , label : 'Child 1' , outputAnchors : [ ] }
360+ } ) ,
361+ makeFlowNode ( 'child-2' , {
362+ parentNode : 'parent' ,
363+ data : { id : 'child-2' , name : 'tool' , label : 'Child 2' , outputAnchors : [ ] }
364+ } ) ,
365+ makeFlowNode ( 'grandchild' , {
366+ parentNode : 'child-1' ,
367+ data : { id : 'grandchild' , name : 'agent' , label : 'Grandchild' , outputAnchors : [ ] }
368+ } ) ,
369+ makeFlowNode ( 'unrelated' , {
370+ data : { id : 'unrelated' , name : 'agent' , label : 'Unrelated' , outputAnchors : [ ] }
371+ } )
372+ ] ,
373+ edges : [ ]
374+ }
375+
376+ const { result } = renderHook ( ( ) => useAgentflowContext ( ) , {
377+ wrapper : createWrapper ( initialFlow )
378+ } )
379+
380+ // Delete parent node
381+ act ( ( ) => {
382+ result . current . deleteNode ( 'parent' )
383+ } )
384+
385+ // Should only have the unrelated node left
386+ expect ( result . current . state . nodes ) . toHaveLength ( 1 )
387+ expect ( result . current . state . nodes [ 0 ] . id ) . toBe ( 'unrelated' )
388+ } )
389+
390+ it ( 'should clean up inputs for all descendant nodes before deletion' , ( ) => {
391+ const initialFlow : FlowData = {
392+ nodes : [
393+ makeFlowNode ( 'parent' , {
394+ data : { id : 'parent' , name : 'stickyNote' , label : 'Parent' , outputAnchors : [ ] }
395+ } ) ,
396+ makeFlowNode ( 'child' , {
397+ parentNode : 'parent' ,
398+ data : {
399+ id : 'child' ,
400+ name : 'agent' ,
401+ label : 'Child' ,
402+ outputAnchors : [ { id : 'child-output-0' , name : 'output' , label : 'Output' , type : 'Agent' } ]
403+ }
404+ } ) ,
405+ makeFlowNode ( 'target' , {
406+ data : {
407+ id : 'target' ,
408+ name : 'tool' ,
409+ label : 'Target' ,
410+ inputs : [ { id : 'target-input-agent-Agent' , name : 'agent' , label : 'Agent' , type : 'Agent' } ] ,
411+ inputAnchors : [ { id : 'target-input-agent-Agent' , name : 'agent' , label : 'Agent' , type : 'Agent' } ] ,
412+ inputValues : {
413+ agent : '{{child.data.instance}}'
414+ } ,
415+ outputAnchors : [ ]
416+ }
417+ } )
418+ ] ,
419+ edges : [
420+ makeEdge ( 'child' , 'target' , {
421+ id : 'edge-child-target' ,
422+ sourceHandle : 'child-output-0' ,
423+ targetHandle : 'target-input-agent-Agent'
424+ } )
425+ ]
426+ }
427+
428+ const { result } = renderHook ( ( ) => useAgentflowContext ( ) , {
429+ wrapper : createWrapper ( initialFlow )
430+ } )
431+
432+ // Delete parent (which should also delete child)
433+ act ( ( ) => {
434+ result . current . deleteNode ( 'parent' )
435+ } )
436+
437+ // Should only have target node left
438+ expect ( result . current . state . nodes ) . toHaveLength ( 1 )
439+ const target = result . current . state . nodes [ 0 ]
440+ expect ( target . id ) . toBe ( 'target' )
441+
442+ // The agent input should be cleared
443+ expect ( target . data . inputValues ?. agent ) . toBe ( '' )
444+
445+ // Edge should be removed
446+ expect ( result . current . state . edges ) . toHaveLength ( 0 )
447+ } )
224448} )
225449
226450describe ( 'AgentflowContext - duplicateNode' , ( ) => {
0 commit comments