@@ -501,6 +501,177 @@ def test_reactflow_events_and_selection() -> None:
501501 assert flow .edges [0 ]["data" ]["weight" ] == 0.25
502502
503503
504+ def test_handle_msg_edge_added_emits_once () -> None :
505+ """Frontend connect sends edge_added; Python must not double-emit (add_edge already emits)."""
506+ flow = ReactFlow (
507+ nodes = [
508+ {"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}},
509+ {"id" : "n2" , "position" : {"x" : 1 , "y" : 1 }, "data" : {}},
510+ ],
511+ edges = [],
512+ )
513+ events : list [dict ] = []
514+ flow .on ("edge_added" , events .append )
515+ flow ._handle_msg (
516+ {
517+ "type" : "edge_added" ,
518+ "edge" : {"id" : "n1->n2" , "source" : "n1" , "target" : "n2" },
519+ },
520+ )
521+ assert len (events ) == 1
522+ assert events [0 ]["type" ] == "edge_added"
523+ assert events [0 ]["edge" ]["id" ] == "n1->n2"
524+ assert len (flow .edges ) == 1
525+
526+
527+ def test_handle_msg_node_moved_emits_once () -> None :
528+ flow = ReactFlow (nodes = [{"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}}])
529+ events : list [dict ] = []
530+ flow .on ("node_moved" , events .append )
531+ flow ._handle_msg ({"type" : "node_moved" , "node_id" : "n1" , "position" : {"x" : 10 , "y" : 20 }})
532+ assert len (events ) == 1
533+ assert events [0 ]["type" ] == "node_moved"
534+ assert events [0 ]["node_id" ] == "n1"
535+ assert events [0 ]["position" ] == {"x" : 10 , "y" : 20 }
536+ assert flow .nodes [0 ]["position" ] == {"x" : 10 , "y" : 20 }
537+
538+
539+ def test_handle_msg_selection_changed_emits_once () -> None :
540+ flow = ReactFlow (
541+ nodes = [
542+ {"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}},
543+ {"id" : "n2" , "position" : {"x" : 1 , "y" : 1 }, "data" : {}},
544+ ],
545+ edges = [{"id" : "e1" , "source" : "n1" , "target" : "n2" , "data" : {}}],
546+ )
547+ events : list [dict ] = []
548+ flow .on ("selection_changed" , events .append )
549+ flow ._handle_msg ({"type" : "selection_changed" , "nodes" : ["n1" ], "edges" : ["e1" ]})
550+ assert len (events ) == 1
551+ assert events [0 ]["type" ] == "selection_changed"
552+ assert flow .selection == {"nodes" : ["n1" ], "edges" : ["e1" ]}
553+
554+
555+ def test_handle_msg_node_clicked_emits_once () -> None :
556+ flow = ReactFlow (nodes = [{"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}}])
557+ events : list [dict ] = []
558+ flow .on ("node_clicked" , events .append )
559+ flow ._handle_msg ({"type" : "node_clicked" , "node_id" : "n1" , "button" : 0 })
560+ assert len (events ) == 1
561+ assert events [0 ]["type" ] == "node_clicked"
562+ assert events [0 ]["node_id" ] == "n1"
563+
564+
565+ def test_handle_msg_sync_emits_once () -> None :
566+ flow = ReactFlow ()
567+ events : list [dict ] = []
568+ flow .on ("sync" , events .append )
569+ msg = {
570+ "type" : "sync" ,
571+ "nodes" : [
572+ {
573+ "id" : "n1" ,
574+ "position" : {"x" : 0 , "y" : 0 },
575+ "type" : "panel" ,
576+ "data" : {},
577+ "selected" : False ,
578+ }
579+ ],
580+ "edges" : [],
581+ }
582+ flow ._handle_msg (msg )
583+ assert len (events ) == 1
584+ assert events [0 ]["type" ] == "sync"
585+ assert [n ["id" ] for n in flow .nodes ] == ["n1" ]
586+
587+
588+ def test_handle_msg_node_deleted_emits_once_for_single_node () -> None :
589+ """remove_node already emits; _handle_msg must not emit a duplicate batch message."""
590+ flow = ReactFlow (
591+ nodes = [
592+ {"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}},
593+ {"id" : "n2" , "position" : {"x" : 1 , "y" : 1 }, "data" : {}},
594+ ],
595+ edges = [{"id" : "e1" , "source" : "n1" , "target" : "n2" , "data" : {}}],
596+ )
597+ events : list [dict ] = []
598+ flow .on ("node_deleted" , events .append )
599+ flow ._handle_msg (
600+ {
601+ "type" : "node_deleted" ,
602+ "node_id" : "n1" ,
603+ "node_ids" : ["n1" ],
604+ "deleted_edges" : ["e1" ],
605+ },
606+ )
607+ assert len (events ) == 1
608+ assert events [0 ]["type" ] == "node_deleted"
609+ assert events [0 ]["node_id" ] == "n1"
610+ assert events [0 ]["deleted_edges" ] == ["e1" ]
611+ assert [n ["id" ] for n in flow .nodes ] == ["n2" ]
612+ assert flow .edges == []
613+
614+
615+ def test_handle_msg_node_deleted_one_event_per_node_when_batch () -> None :
616+ flow = ReactFlow (
617+ nodes = [
618+ {"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}},
619+ {"id" : "n2" , "position" : {"x" : 1 , "y" : 1 }, "data" : {}},
620+ ],
621+ edges = [],
622+ )
623+ events : list [dict ] = []
624+ flow .on ("node_deleted" , events .append )
625+ flow ._handle_msg (
626+ {
627+ "type" : "node_deleted" ,
628+ "node_id" : None ,
629+ "node_ids" : ["n1" , "n2" ],
630+ "deleted_edges" : [],
631+ },
632+ )
633+ assert len (events ) == 2
634+ assert {e ["node_id" ] for e in events } == {"n1" , "n2" }
635+ assert flow .nodes == []
636+
637+
638+ def test_handle_msg_edge_deleted_emits_once () -> None :
639+ """remove_edge already emits; _handle_msg must not emit a duplicate batch message."""
640+ flow = ReactFlow (
641+ nodes = [
642+ {"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}},
643+ {"id" : "n2" , "position" : {"x" : 1 , "y" : 1 }, "data" : {}},
644+ ],
645+ edges = [{"id" : "e1" , "source" : "n1" , "target" : "n2" , "data" : {}}],
646+ )
647+ events : list [dict ] = []
648+ flow .on ("edge_deleted" , events .append )
649+ flow ._handle_msg ({"type" : "edge_deleted" , "edge_id" : "e1" , "edge_ids" : ["e1" ]})
650+ assert len (events ) == 1
651+ assert events [0 ]["type" ] == "edge_deleted"
652+ assert events [0 ]["edge_id" ] == "e1"
653+ assert flow .edges == []
654+
655+
656+ def test_handle_msg_edge_deleted_one_event_per_edge_when_batch () -> None :
657+ flow = ReactFlow (
658+ nodes = [
659+ {"id" : "n1" , "position" : {"x" : 0 , "y" : 0 }, "data" : {}},
660+ {"id" : "n2" , "position" : {"x" : 1 , "y" : 1 }, "data" : {}},
661+ ],
662+ edges = [
663+ {"id" : "e1" , "source" : "n1" , "target" : "n2" , "data" : {}},
664+ {"id" : "e2" , "source" : "n2" , "target" : "n1" , "data" : {}},
665+ ],
666+ )
667+ events : list [dict ] = []
668+ flow .on ("edge_deleted" , events .append )
669+ flow ._handle_msg ({"type" : "edge_deleted" , "edge_id" : None , "edge_ids" : ["e1" , "e2" ]})
670+ assert len (events ) == 2
671+ assert {e ["edge_id" ] for e in events } == {"e1" , "e2" }
672+ assert flow .edges == []
673+
674+
504675@nx_available
505676def test_reactflow_to_networkx () -> None :
506677 flow = ReactFlow ()
0 commit comments