//! Dropping a wire onto an input that already has one. //! //! The editor added a second wire. An input takes one, so the graph became ambiguous: //! `diagnostics` reported an error and the LOWER wire id won — the old wire kept feeding //! the node and the one just drawn did nothing. `Graph::connect_replacing` is what the drop //! means: the new connection takes the input over. use flowrs_core::graph::diagnostics::{Severity, check}; use flowrs_core::{Graph, NodeId, WireId, WireMode}; /// 7 and 6 feed a Multiply, which feeds a PrintLn: the editor's starting graph. fn seed() -> (Graph, [NodeId; 4]) { let mut g = Graph::new(); let a = g.add_node("IntLiteral", 60.0, 60.0); let b = g.add_node("IntLiteral", 60.0, 180.0); let mul = g.add_node("Multiply", 320.0, 120.0); let print = g.add_node("PrintLn", 580.0, 120.0); g.add_wire(a, "value", mul, "a", WireMode::Copy).unwrap(); g.add_wire(b, "value", mul, "b", WireMode::Copy).unwrap(); g.add_wire(mul, "result", print, "input", WireMode::Move) .unwrap(); (g, [a, b, mul, print]) } fn feeding(g: &Graph, node: NodeId, port: &str) -> Vec<(NodeId, String)> { let mut v: Vec<_> = g .wires .values() .filter(|w| w.to_node == node && w.to_port == port) .map(|w| (w.from_node, w.from_port.clone())) .collect(); v.sort_by_key(|(n, p)| (n.0, p.clone())); v } fn ambiguous(g: &Graph) -> bool { check(g) .iter() .any(|d| d.severity == Severity::Error && d.message.contains("is fed by")) } #[test] fn a_drop_onto_an_occupied_input_replaces_its_wire() { let (mut g, [a, b, mul, _]) = seed(); let (id, displaced) = g .connect_replacing(b, "value", mul, "a", WireMode::Borrow) .expect("both endpoints exist"); assert_eq!(feeding(&g, mul, "a"), vec![(b, "value".to_string())]); assert_eq!(g.wires.len(), 3, "a replacement, not an addition"); assert!(!ambiguous(&g), "the input must not be left doubly fed"); assert_eq!(g.wires[&id].mode, WireMode::Borrow); // The displaced wire is the undo record: it is the one that was there, intact. assert_eq!(displaced.len(), 1); assert_eq!(displaced[0].from_node, a); assert_eq!(displaced[0].mode, WireMode::Copy); } #[test] fn a_drop_onto_a_free_input_just_connects() { let (mut g, [a, _, _, _]) = seed(); let sink = g.add_node("PrintLn", 580.0, 300.0); let (id, displaced) = g .connect_replacing(a, "value", sink, "input", WireMode::Borrow) .unwrap(); assert!(displaced.is_empty()); assert_eq!(g.wires.len(), 4); assert_eq!(g.wires[&id].to_node, sink); // The output still feeds what it fed before: an output fans out, only inputs are // exclusive. assert_eq!(g.wires.values().filter(|w| w.from_node == a).count(), 2); } #[test] fn redrawing_the_same_connection_keeps_its_wire_and_mode() { let (mut g, [a, _, mul, _]) = seed(); let before: WireId = g .wires .values() .find(|w| w.to_node == mul && w.to_port == "a") .unwrap() .id; let (id, displaced) = g .connect_replacing(a, "value", mul, "a", WireMode::Borrow) .unwrap(); assert_eq!(id, before, "the existing wire is kept, not re-made"); assert!(displaced.is_empty()); assert_eq!( g.wires[&id].mode, WireMode::Copy, "and its mode is not reset" ); assert_eq!(g.wires.len(), 3); } #[test] fn an_input_already_doubly_fed_is_left_with_exactly_the_new_wire() { // Graphs saved before this exist with ambiguous inputs. Drawing onto one should // resolve it, not displace one of the two and leave the other. let (mut g, [_, b, mul, _]) = seed(); g.add_wire(b, "value", mul, "a", WireMode::Copy).unwrap(); assert!(ambiguous(&g)); let c = g.add_node("IntLiteral", 60.0, 300.0); let (_, displaced) = g .connect_replacing(c, "value", mul, "a", WireMode::Borrow) .unwrap(); assert_eq!(displaced.len(), 2); assert_eq!(feeding(&g, mul, "a"), vec![(c, "value".to_string())]); assert!(!ambiguous(&g)); } #[test] fn a_missing_endpoint_changes_nothing() { let (mut g, [a, _, mul, _]) = seed(); let ghost = NodeId(999); assert!( g.connect_replacing(ghost, "value", mul, "a", WireMode::Borrow) .is_err() ); assert!( g.connect_replacing(a, "value", ghost, "a", WireMode::Borrow) .is_err() ); // In particular the existing wire was not disconnected on the way to the error. assert_eq!(feeding(&g, mul, "a"), vec![(a, "value".to_string())]); assert_eq!(g.wires.len(), 3); }