use std::cell::Cell;
use std::rc::Rc;
use abstracttui::reactive::create_root;
use abstracttui::ui::Callback;
#[test]
fn callback_clones_share_one_closure() {
let hits = Rc::new(Cell::new(0));
let h = hits.clone();
let cb: Callback<i32> = Callback::new(move |n| h.set(h.get() + n));
let a = cb.clone();
let b = cb.clone();
a.call(1);
b.call(10);
cb.call(100);
assert_eq!(
hits.get(),
111,
"all clones must drive the same closure state"
);
}
#[test]
fn noop_callback_is_inert() {
let cb: Callback<&str> = Callback::noop();
cb.call("ignored");
cb.call("still ignored");
let def: Callback<()> = Callback::default();
def.call(());
}
#[test]
fn callback_outlives_its_creating_scope() {
let sink = Rc::new(Cell::new(0));
let mut escaped: Option<Callback<i32>> = None;
let (root, ()) = create_root(|_cx| {
let inner = sink.clone();
escaped = Some(Callback::new(move |n| inner.set(inner.get() + n)));
});
root.dispose();
let cb = escaped.expect("callback escaped the scope");
cb.call(7);
cb.call(35);
assert_eq!(sink.get(), 42, "callback must survive its scope's disposal");
}
#[test]
fn callback_touching_disposed_signal_is_a_controlled_panic_not_ub() {
use abstracttui::reactive::flush_effects;
let mut sig_setter: Option<Callback<i32>> = None;
let (root, ()) = create_root(|cx| {
let value = cx.signal(0i32);
sig_setter = Some(Callback::new(move |n| value.set(n)));
});
let cb = sig_setter.expect("setter escaped");
root.dispose();
flush_effects();
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| cb.call(5)));
assert!(
result.is_err(),
"touching a disposed signal must fail loudly (documented panic), not silently proceed"
);
let (root2, out) = create_root(|cx| {
let v = cx.signal(99i32);
v.get_untracked()
});
assert_eq!(
out, 99,
"runtime remains consistent after a caught use-after-dispose"
);
root2.dispose();
}
#[test]
fn dropping_all_clones_drops_captured_state_once() {
let drops = Rc::new(Cell::new(0));
struct DropCounter(Rc<Cell<u32>>);
impl Drop for DropCounter {
fn drop(&mut self) {
self.0.set(self.0.get() + 1);
}
}
let counter = DropCounter(drops.clone());
let cb: Callback<()> = Callback::new(move |_| {
let _ = &counter;
});
let clone1 = cb.clone();
let clone2 = cb.clone();
assert_eq!(drops.get(), 0, "no drop while clones live");
drop(cb);
drop(clone1);
assert_eq!(drops.get(), 0, "still alive while one clone remains");
drop(clone2);
assert_eq!(
drops.get(),
1,
"captured state drops exactly once when the last clone dies"
);
}