use super::*;
use cranpose_core::NodeId;
use cranpose_foundation::lazy::{remember_lazy_list_state, LazyListScope, LazyListState};
use cranpose_ui_graphics::Size as ViewportSize;
use std::cell::RefCell;
use std::rc::Rc;
const ROWS: usize = 40;
const ROW_HEIGHT: f32 = 48.0;
const VIEWPORT: ViewportSize = ViewportSize {
width: 320.0,
height: 200.0,
};
fn measure(composition: &mut TestComposition, root: NodeId) {
let handle = composition.runtime_handle();
let mut applier = composition.applier_mut();
applier.set_runtime_handle(handle);
measure_layout(&mut applier, root, VIEWPORT).expect("layout measurement");
applier.clear_runtime_handle();
}
#[composable]
#[allow(non_snake_case)]
fn ParkedRow(index: usize, nested: bool) {
let body = move || {
if index == 0 {
cranpose_core::LaunchedEffectAsync!(0u32, move |_scope| {
Box::pin(async move {
std::future::pending::<()>().await;
})
});
}
Text(
format!("row {index}"),
Modifier::empty().height(ROW_HEIGHT),
TextStyle::default(),
);
};
match nested {
true => {
SwipeToDismiss(
Modifier::empty().fill_max_width(),
SwipeToDismissSpec::default(),
|| {},
body,
);
}
false => body(),
}
}
fn task_counts_across_reuse(nested: bool, scroll_back: bool) -> Vec<usize> {
let held: Rc<RefCell<Option<LazyListState>>> = Rc::new(RefCell::new(None));
let keeper = Rc::clone(&held);
let mut composition = run_test_composition(move || {
let list_state = remember_lazy_list_state();
*keeper.borrow_mut() = Some(list_state);
LazyColumn(
Modifier::empty().fill_max_size(),
list_state,
LazyColumnSpec::default(),
move |scope| {
scope.items(
ROWS,
Some(|index: usize| index as u64),
None::<fn(usize) -> u64>,
move |index| ParkedRow(index, nested),
);
},
);
});
let root = composition.root().expect("root node");
measure(&mut composition, root);
composition.runtime_handle().drain_ui();
let mut counts = vec![composition.runtime_handle().debug_stats().tasks_len];
let list_state = (*held.borrow()).expect("list state");
list_state.scroll_to_item(ROWS - 4, 0.0);
measure(&mut composition, root);
composition.runtime_handle().drain_ui();
counts.push(composition.runtime_handle().debug_stats().tasks_len);
if scroll_back {
list_state.scroll_to_item(0, 0.0);
measure(&mut composition, root);
composition.runtime_handle().drain_ui();
counts.push(composition.runtime_handle().debug_stats().tasks_len);
}
counts
}
#[test]
fn a_recycled_lazy_row_stops_its_parked_effect() {
assert_eq!(
task_counts_across_reuse(false, false),
vec![1, 0],
"the row's effect must run on screen and be cancelled off screen"
);
}
#[test]
fn a_recycled_lazy_row_stops_the_effect_of_a_widget_one_subcompose_deeper() {
assert_eq!(
task_counts_across_reuse(true, false),
vec![1, 0],
"the SwipeToDismiss effect must run on screen and be cancelled off screen"
);
}
#[test]
fn a_recycled_lazy_row_restarts_its_effect_when_it_returns() {
assert_eq!(
(
task_counts_across_reuse(false, true),
task_counts_across_reuse(true, true),
),
(vec![1, 0, 1], vec![1, 0, 1]),
"reusing a row must restart its nested effect exactly once"
);
}