flowrs-engine/crates/flowrs-core/tests/connect_replacing.rs
flowRS fd958c0547 flowRS engine, cut from flowRS 3451b065
Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:04:39 -05:00

128 lines
4.6 KiB
Rust

//! 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);
}