mod common;
use graphrefly_core::{HandleId, Message};
use graphrefly_graph::Graph;
use std::sync::{Arc, Mutex};
fn h(n: u64) -> HandleId {
HandleId::new(n)
}
#[test]
fn describe_reactive_pushes_initial_snapshot() {
let g = Graph::new("test", common::binding());
g.state("a", Some(h(1))).unwrap();
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let _handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
let snaps = snapshots.lock().unwrap();
assert_eq!(snaps.len(), 1);
assert!(snaps[0].nodes.contains_key("a"));
}
#[test]
fn describe_reactive_fires_on_new_node() {
let g = Graph::new("test", common::binding());
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let _handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
assert_eq!(snapshots.lock().unwrap().len(), 1);
g.state("a", Some(h(1))).unwrap();
let snaps = snapshots.lock().unwrap();
assert_eq!(snaps.len(), 2);
assert!(snaps[0].nodes.is_empty());
assert!(snaps[1].nodes.contains_key("a"));
}
#[test]
fn describe_reactive_fires_on_remove() {
let g = Graph::new("test", common::binding());
g.state("a", Some(h(1))).unwrap();
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let _handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
assert_eq!(snapshots.lock().unwrap().len(), 1);
g.remove("a").unwrap();
let snaps = snapshots.lock().unwrap();
assert_eq!(snaps.len(), 2);
assert!(snaps[0].nodes.contains_key("a"));
assert!(!snaps[1].nodes.contains_key("a"));
}
#[test]
fn describe_reactive_stops_on_drop() {
let g = Graph::new("test", common::binding());
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
g.state("a", Some(h(1))).unwrap();
assert_eq!(snapshots.lock().unwrap().len(), 2);
drop(handle);
g.state("b", Some(h(2))).unwrap();
assert_eq!(snapshots.lock().unwrap().len(), 2);
}
#[test]
fn describe_reactive_accumulates_nodes() {
let g = Graph::new("test", common::binding());
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let _handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
g.state("a", Some(h(1))).unwrap();
g.state("b", Some(h(2))).unwrap();
let snaps = snapshots.lock().unwrap();
assert_eq!(snaps.len(), 3);
assert_eq!(snaps[0].nodes.len(), 0);
assert_eq!(snaps[1].nodes.len(), 1);
assert_eq!(snaps[2].nodes.len(), 2);
}
#[test]
fn observe_all_reactive_subscribes_current_nodes() {
let g = Graph::new("test", common::binding());
g.state("a", Some(h(10))).unwrap();
let events = Arc::new(Mutex::new(Vec::<(String, Vec<Message>)>::new()));
let events_clone = events.clone();
let mut obs = g.observe_all_reactive();
let count = obs.subscribe(move |name: &str, msgs: &[Message]| {
events_clone
.lock()
.unwrap()
.push((name.to_string(), msgs.to_vec()));
});
assert_eq!(count, 1);
let evts = events.lock().unwrap();
assert!(!evts.is_empty());
assert_eq!(evts[0].0, "a");
}
#[test]
fn observe_all_reactive_auto_subscribes_late_node() {
let g = Graph::new("test", common::binding());
let events = Arc::new(Mutex::new(Vec::<(String, Vec<Message>)>::new()));
let events_clone = events.clone();
let mut obs = g.observe_all_reactive();
let count = obs.subscribe(move |name: &str, msgs: &[Message]| {
events_clone
.lock()
.unwrap()
.push((name.to_string(), msgs.to_vec()));
});
assert_eq!(count, 0);
g.state("late", Some(h(42))).unwrap();
let evts = events.lock().unwrap();
let late_events: Vec<_> = evts.iter().filter(|(name, _)| name == "late").collect();
assert!(
!late_events.is_empty(),
"expected auto-subscribe to fire for late-added node"
);
}
#[test]
fn observe_all_reactive_stops_on_drop() {
let g = Graph::new("test", common::binding());
let events = Arc::new(Mutex::new(Vec::<(String, Vec<Message>)>::new()));
let events_clone = events.clone();
let mut obs = g.observe_all_reactive();
obs.subscribe(move |name: &str, msgs: &[Message]| {
events_clone
.lock()
.unwrap()
.push((name.to_string(), msgs.to_vec()));
});
g.state("x", Some(h(1))).unwrap();
let count_before = events.lock().unwrap().len();
drop(obs);
g.state("y", Some(h(2))).unwrap();
let count_after = events.lock().unwrap().len();
assert_eq!(
count_after, count_before,
"no events should arrive after dropping reactive handle"
);
}
#[test]
fn describe_reactive_fires_on_mount_new() {
let g = Graph::new("root", common::binding());
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let _handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
assert_eq!(snapshots.lock().unwrap().len(), 1);
g.mount_new("child").unwrap();
let snaps = snapshots.lock().unwrap();
assert_eq!(snaps.len(), 2);
assert!(snaps[1].subgraphs.iter().any(|s| s == "child"));
}
#[test]
fn describe_reactive_fires_on_unmount() {
let g = Graph::new("root", common::binding());
g.mount_new("child").unwrap();
let snapshots = Arc::new(Mutex::new(Vec::new()));
let snapshots_clone = snapshots.clone();
let _handle = g.describe_reactive(Arc::new(move |output| {
snapshots_clone.lock().unwrap().push(output.clone());
}));
assert_eq!(snapshots.lock().unwrap().len(), 1);
assert!(snapshots.lock().unwrap()[0]
.subgraphs
.iter()
.any(|s| s == "child"));
g.unmount("child").unwrap();
let snaps = snapshots.lock().unwrap();
assert_eq!(snaps.len(), 2);
assert!(!snaps[1].subgraphs.iter().any(|s| s == "child"));
}
#[test]
fn observe_all_reactive_handles_late_mount() {
let g = Graph::new("root", common::binding());
let events = Arc::new(Mutex::new(Vec::<(String, Vec<Message>)>::new()));
let events_clone = events.clone();
let mut obs = g.observe_all_reactive();
obs.subscribe(move |name: &str, msgs: &[Message]| {
events_clone
.lock()
.unwrap()
.push((name.to_string(), msgs.to_vec()));
});
let child = g.mount_new("child").unwrap();
child.state("inside", Some(h(99))).unwrap();
g.state("parent_node", Some(h(1))).unwrap();
let evts = events.lock().unwrap();
assert!(
evts.iter().any(|(name, _)| name == "parent_node"),
"expected parent_node event after late add"
);
}
#[test]
#[should_panic(expected = "single-shot")]
fn observe_all_reactive_subscribe_twice_panics() {
let g = Graph::new("root", common::binding());
let mut obs = g.observe_all_reactive();
obs.subscribe(|_, _| {});
obs.subscribe(|_, _| {});
}