use std::{cell::RefCell, rc::Rc};
use crate::{self as vertigo, dom};
use crate::{
JsJson, JsJsonSerialize, Value,
dev::command::{CommandForWasm, DriverDomCommand},
driver_module::{
api::{api_callbacks, api_command_wasm},
driver::get_driver,
get_driver_dom,
},
exports::mount,
reactive::on_after_transaction,
transaction,
};
fn mounted_labels() -> [Value<String>; 3] {
let labels = ["a", "b", "c"].map(|label| Value::new(label.to_string()));
let [a, b, c] = labels.clone();
mount(move || {
dom! {
<html>
<head />
<body>
<p>{a}</p>
<p>{b}</p>
<p>{c}</p>
</body>
</html>
}
});
labels
}
fn batches_during(run: impl FnOnce()) -> Vec<Vec<DriverDomCommand>> {
let pending: Rc<RefCell<Vec<DriverDomCommand>>> = Rc::new(RefCell::new(Vec::new()));
let batches: Rc<RefCell<Vec<Vec<DriverDomCommand>>>> = Rc::new(RefCell::new(Vec::new()));
let _tee = get_driver_dom().inspect_command({
let pending = pending.clone();
move |command| pending.borrow_mut().push(command)
});
let _cut = on_after_transaction({
let pending = pending.clone();
let batches = batches.clone();
move || {
let batch = pending.borrow_mut().drain(..).collect::<Vec<_>>();
if !batch.is_empty() {
batches.borrow_mut().push(batch);
}
}
});
run();
let tail = pending.borrow_mut().drain(..).collect::<Vec<_>>();
if !tail.is_empty() {
batches.borrow_mut().push(tail);
}
batches.borrow().clone()
}
fn dispatch_event(handler: impl Fn(JsJson) -> JsJson + 'static) {
let (callback_id, _registration) = api_callbacks().register(handler);
let event = CommandForWasm::CallbackCall {
callback_id,
value: JsJson::Null,
};
api_command_wasm().command_from_js(event.to_json());
}
fn batches_for_event(
handler: impl Fn(&[Value<String>; 3]) + 'static,
) -> Vec<Vec<DriverDomCommand>> {
let labels = mounted_labels();
let batches = batches_during(|| {
dispatch_event(move |_| {
handler(&labels);
JsJson::Null
})
});
drop(get_driver().take_root());
batches
}
fn text_updates(batch: &[DriverDomCommand]) -> usize {
batch
.iter()
.filter(|command| matches!(command, DriverDomCommand::UpdateText { .. }))
.count()
}
#[test]
fn an_event_setting_several_values_reaches_the_dom_as_one_batch() {
let batches = batches_for_event(|labels| {
for (i, label) in labels.iter().enumerate() {
label.set(format!("new {i}"));
}
});
assert_eq!(batches.len(), 1, "one event, one batch: {batches:?}");
assert_eq!(text_updates(&batches[0]), 3);
}
#[test]
fn a_transaction_of_the_apps_own_nests_in_the_events() {
let batches = batches_for_event(|labels| {
transaction(|_| {
labels[0].set("x".to_string());
labels[1].set("y".to_string());
});
labels[2].set("z".to_string());
});
assert_eq!(batches.len(), 1, "{batches:?}");
assert_eq!(text_updates(&batches[0]), 3);
}
#[test]
fn an_event_that_changes_nothing_sends_nothing() {
let batches = batches_for_event(|_| {});
assert!(batches.is_empty(), "{batches:?}");
}
#[test]
fn the_callbacks_of_an_animation_frame_reach_the_dom_as_one_batch() {
let labels = mounted_labels();
let _callbacks = labels
.clone()
.map(|label| get_driver().on_animation_frame(move |time| label.set(format!("{time}"))));
let batches = batches_during(|| {
let frame = CommandForWasm::AnimationFrame { time: 16.0 };
api_command_wasm().command_from_js(frame.to_json());
});
drop(get_driver().take_root());
assert_eq!(batches.len(), 1, "one frame, one batch: {batches:?}");
assert_eq!(text_updates(&batches[0]), 3);
}