use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use adaptite::{DiagnosticEvent, NodeKind, Reactor, memo_in, signal_in};
use runite::time::sleep;
use runite::{queue_macrotask, run};
#[test]
fn a_snapshot_lookup_survives_the_consumer_reordering_the_public_node_list() {
let reactor = Reactor::new();
let signals = (0..16)
.map(|i| signal_in(&reactor, u32::try_from(i).unwrap()))
.collect::<Vec<_>>();
for (i, signal) in signals.iter().enumerate() {
for j in 0..=i {
signal.set(u32::try_from(1000 + i * 32 + j).unwrap());
}
}
let target = signals[5].id();
let mut snapshot = reactor.graph_snapshot();
assert_eq!(
snapshot.node(target).map(|node| node.id),
Some(target),
"the lookup works on the snapshot as handed over"
);
snapshot
.nodes
.sort_by_key(|node| std::cmp::Reverse(node.version));
assert!(
snapshot
.nodes
.windows(2)
.any(|pair| pair[0].id > pair[1].id),
"sanity: the sort really did break the id ordering the lookup relied on"
);
let found = snapshot
.node(target)
.expect("a live node must still be found after the caller reorders the list");
assert_eq!(found.id, target);
assert_eq!(found.kind, NodeKind::Signal);
drop(signals);
let snapshot = reactor.graph_snapshot();
assert!(snapshot.node(target).is_none());
}
#[test]
fn a_resource_allocates_five_nodes_and_watch_two() {
let report = Rc::new(RefCell::new(Vec::new()));
queue_macrotask({
let report = Rc::clone(&report);
move || {
let reactor = Reactor::new();
let id = signal_in(&reactor, 1u32);
let before = reactor.graph_stats();
let fetched = reactor.resource(
{
let id = id.clone();
move || id.get()
},
|id| async move { id * 10 },
);
reactor.flush_now();
let after = reactor.graph_stats();
let mut deltas = Vec::new();
for kind in NodeKind::all() {
deltas.push((
kind,
after.live_nodes_of_kind(kind) - before.live_nodes_of_kind(kind),
));
}
report
.borrow_mut()
.push(("resource", deltas, after.live_nodes - before.live_nodes));
let before = reactor.graph_stats();
let watcher = reactor.watch(
{
let id = id.clone();
move || id.get()
},
|_, _| {},
);
reactor.flush_now();
let after = reactor.graph_stats();
let mut deltas = Vec::new();
for kind in NodeKind::all() {
deltas.push((
kind,
after.live_nodes_of_kind(kind) - before.live_nodes_of_kind(kind),
));
}
report
.borrow_mut()
.push(("watch", deltas, after.live_nodes - before.live_nodes));
watcher.leak();
drop(fetched);
drop(runite::spawn(async move {
sleep(Duration::from_millis(5)).await;
}));
}
});
run();
let report = report.borrow();
let resource = report
.iter()
.find(|(what, _, _)| *what == "resource")
.expect("the resource ran");
assert_eq!(
resource.2, 5,
"a resource allocates five nodes, not the four the docs used to claim"
);
let expected = [
(NodeKind::Source, 0),
(NodeKind::Signal, 3),
(NodeKind::Event, 0),
(NodeKind::Thunk, 0),
(NodeKind::Memo, 1),
(NodeKind::Effect, 1),
];
assert_eq!(
resource.1, expected,
"value, loading and refetch-tick signals, the equality-gate memo, and the driving effect"
);
let watch = report
.iter()
.find(|(what, _, _)| *what == "watch")
.expect("the watch ran");
assert_eq!(watch.2, 2);
assert_eq!(
watch.1,
[
(NodeKind::Source, 0),
(NodeKind::Signal, 0),
(NodeKind::Event, 0),
(NodeKind::Thunk, 0),
(NodeKind::Memo, 1),
(NodeKind::Effect, 1),
]
);
}
#[test]
fn an_invalidation_reports_no_flush_because_it_belongs_to_none() {
let reactor = Reactor::new();
let events = Rc::new(RefCell::new(Vec::new()));
let _subscription = reactor.subscribe_diagnostics({
let events = Rc::clone(&events);
move |event| events.borrow_mut().push(event)
});
let source = signal_in(&reactor, 0_u32);
let doubled = memo_in(&reactor, {
let source = source.clone();
move || source.get() * 2
});
let effect = reactor.effect({
let doubled = doubled.clone();
move || {
let _ = doubled.get();
}
});
reactor.flush_now();
let closed_epoch = reactor.graph_stats().flush_epoch;
assert_eq!(reactor.graph_stats().flush_depth, 0, "no flush is open");
events.borrow_mut().clear();
source.set(1);
let marks = events
.borrow()
.iter()
.filter_map(|event| match event {
DiagnosticEvent::ComputedInvalidated { flush_epoch, .. } => {
Some((*flush_epoch, event.flush_epoch()))
}
_ => None,
})
.collect::<Vec<_>>();
assert!(!marks.is_empty(), "the write reached the memo");
for (payload, reported) in marks {
assert_eq!(
payload, closed_epoch,
"the field records the most recently opened flush, which here is one that closed \
before the write"
);
assert_eq!(
reported, None,
"and the accessor declines to call that the flush the mark belongs to"
);
}
reactor.flush_now();
assert_eq!(
reactor.graph_stats().flush_epoch,
closed_epoch + 1,
"the flush that actually drained the write is the *next* epoch, not the recorded one"
);
effect.dispose();
}