use std::cell::{Cell, RefCell};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::rc::Rc;
use adaptite::{Reactor, event_in, memo_by_in, memo_in, on_in, signal_in};
use runite::{queue_macrotask, run};
#[test]
fn a_panic_inside_a_macrotask_does_not_reach_the_test_harness() {
let started = Rc::new(Cell::new(false));
let finished = Rc::new(Cell::new(false));
queue_macrotask({
let started = Rc::clone(&started);
let finished = Rc::clone(&finished);
move || {
started.set(true);
assert_eq!(1, 2, "deliberately false, and deliberately never observed");
finished.set(true);
}
});
run();
assert!(started.get(), "the macrotask must actually have run");
assert!(
!finished.get(),
"the macrotask must have died at the false assertion — if it did not, this test is not \
measuring what it claims"
);
}
#[test]
fn an_effect_recovers_after_a_dependency_verification_panics() {
let reactor = Reactor::new();
let seen = Rc::new(RefCell::new(Vec::new()));
let source = signal_in(&reactor, 1i32);
let doubled = memo_in(&reactor, {
let source = source.clone();
move || {
let value = source.get();
assert!(value != 13, "unlucky number");
value * 2
}
});
let effect = reactor.effect({
let doubled = doubled.clone();
let seen = Rc::clone(&seen);
move || seen.borrow_mut().push(doubled.get())
});
reactor.flush_now();
assert_eq!(&*seen.borrow(), &[2]);
source.set(13);
let result = catch_unwind(AssertUnwindSafe(|| reactor.flush_now()));
assert!(result.is_err(), "verification should propagate the panic");
source.set(7);
reactor.flush_now();
assert_eq!(
&*seen.borrow(),
&[2, 14],
"a panicking verification must not wedge the effect"
);
effect.dispose();
}
#[test]
fn comparator_reads_do_not_become_the_reading_effects_dependencies() {
let reactor = Reactor::new();
let runs = Rc::new(Cell::new(0usize));
let tuning = signal_in(&reactor, 0i32);
let source = signal_in(&reactor, 1i32);
let value = memo_by_in(
&reactor,
{
let tuning = tuning.clone();
move |old: &i32, new: &i32| {
let _ = tuning.get();
old == new
}
},
{
let source = source.clone();
move || source.get()
},
);
let effect = reactor.effect({
let value = value.clone();
let source = source.clone();
let runs = Rc::clone(&runs);
move || {
runs.set(runs.get() + 1);
let _ = source.get();
let _ = value.get();
}
});
reactor.flush_now();
assert_eq!(runs.get(), 1);
source.set(2);
reactor.flush_now();
assert_eq!(runs.get(), 2);
tuning.set(99);
reactor.flush_now();
assert_eq!(runs.get(), 2, "comparator reads must not be tracked");
effect.dispose();
}
#[test]
fn event_handlers_do_not_subscribe_to_what_they_read() {
let reactor = Reactor::new();
let drains = Rc::new(Cell::new(0usize));
let event = event_in::<usize>(&reactor);
let unrelated = signal_in(&reactor, 0usize);
let subscription = on_in(&reactor, &event, {
let drains = Rc::clone(&drains);
let unrelated = unrelated.clone();
move |_value| {
let _ = unrelated.get();
drains.set(drains.get() + 1);
}
});
event.emit(1);
reactor.flush_now();
assert_eq!(drains.get(), 1);
unrelated.set(99);
reactor.flush_now();
assert_eq!(drains.get(), 1, "handler reads must not be tracked");
event.emit(2);
reactor.flush_now();
assert_eq!(drains.get(), 2, "and delivery still works afterwards");
drop(subscription);
}
#[test]
fn watch_is_equality_gated_on_its_source_and_its_handler_is_untracked() {
let reactor = Reactor::new();
let runs = Rc::new(Cell::new(0usize));
let numerator = signal_in(&reactor, 4i32);
let side_input = signal_in(&reactor, 0i32);
let handle = reactor.watch(
{
let numerator = numerator.clone();
move || numerator.get() % 2
},
{
let runs = Rc::clone(&runs);
let side_input = side_input.clone();
move |_new: &i32, _old: Option<&i32>| {
let _ = side_input.get();
runs.set(runs.get() + 1);
}
},
);
reactor.flush_now();
assert_eq!(runs.get(), 1, "immediate first invocation");
numerator.set(6);
reactor.flush_now();
assert_eq!(
runs.get(),
1,
"an unchanged source must not fire the handler"
);
side_input.set(99);
reactor.flush_now();
assert_eq!(runs.get(), 1, "handler reads must not be tracked");
numerator.set(7);
reactor.flush_now();
assert_eq!(runs.get(), 2, "a changed source must fire the handler");
handle.dispose();
}