use fenestra_core::{Element, Fonts, FrameState, Theme, build_frame, by, col, text};
use kurbo::Point;
const COUNT: usize = 400;
fn fixed() -> Element<()> {
col().w(200.0).h(120.0).children([col()
.h(120.0)
.scroll_y()
.id("fixed")
.virtual_rows(COUNT, 24.0, |i| {
col().shrink0().children([text(format!("row {i}"))])
})])
}
fn variable() -> Element<()> {
variable_with_id("var")
}
fn variable_with_id(id: &str) -> Element<()> {
col()
.w(200.0)
.h(120.0)
.children([col()
.h(120.0)
.scroll_y()
.id(id)
.virtual_rows_variable(COUNT, 24.0, |i| {
let h = if i.is_multiple_of(2) { 16.0 } else { 48.0 };
col().h(h).shrink0().children([text(format!("row {i}"))])
})])
}
fn build(view: &Element<()>, fonts: &mut Fonts, state: &mut FrameState) -> fenestra_core::Frame {
build_frame(view, &Theme::light(), fonts, state, (200.0, 120.0), 1.0)
}
#[test]
fn virtual_heights_are_gc_d_when_the_container_leaves() {
let mut fonts = Fonts::embedded();
let mut state = FrameState::new();
let frame_a = build(&variable_with_id("a"), &mut fonts, &mut state);
let id_a = frame_a.get(&by::id("a")).id;
drop(frame_a);
assert!(
state.has_virtual_heights(id_a),
"container 'a' recorded a height index while present"
);
let frame_b = build(&variable_with_id("b"), &mut fonts, &mut state);
let id_b = frame_b.get(&by::id("b")).id;
assert!(
state.has_virtual_heights(id_b),
"container 'b' recorded a height index"
);
assert!(
!state.has_virtual_heights(id_a),
"container 'a' left the tree; its height index must be GC'd, not leaked"
);
}
fn realized(frame: &fenestra_core::Frame) -> Vec<(usize, kurbo::Rect)> {
(0..COUNT)
.filter_map(|i| {
frame
.query(&by::id(format!("v{i}")))
.map(|node| (i, node.rect))
})
.collect()
}
#[test]
fn fixed_rows_realize_only_the_window() {
let mut fonts = Fonts::embedded();
let mut state = FrameState::new();
let frame = build(&fixed(), &mut fonts, &mut state);
let list = frame.scrollable_at(Point::new(20.0, 20.0)).expect("list");
assert!(frame.query(&by::label("row 0")).is_some());
assert!(
frame.query(&by::label("row 399")).is_none(),
"the far end is not realized"
);
drop(frame);
state.scroll_to(list, 1.0e9);
let frame = build(&fixed(), &mut fonts, &mut state);
assert!(frame.query(&by::label("row 399")).is_some());
assert!(frame.query(&by::label("row 0")).is_none());
let last = frame.get(&by::id("v399")).rect;
let list_rect = frame.get(&by::id("fixed")).rect;
assert!(
(last.y1 - list_rect.y1).abs() < 0.5,
"fixed math is exact: row bottom {} vs viewport bottom {}",
last.y1,
list_rect.y1
);
}
#[test]
fn variable_rows_converge_on_measured_heights() {
let mut fonts = Fonts::embedded();
let mut state = FrameState::new();
let frame = build(&variable(), &mut fonts, &mut state);
let list = frame.scrollable_at(Point::new(20.0, 20.0)).expect("list");
drop(frame);
for _ in 0..6 {
state.scroll_to(list, 1.0e9);
let _ = build(&variable(), &mut fonts, &mut state);
let _ = build(&variable(), &mut fonts, &mut state);
}
state.scroll_to(list, 1.0e9);
let _ = build(&variable(), &mut fonts, &mut state);
let frame = build(&variable(), &mut fonts, &mut state);
let rows = realized(&frame);
assert!(rows.len() >= 8, "a realized window exists");
let (last_index, last) = *rows.last().expect("rows");
assert_eq!(last_index, COUNT - 1, "the true last row is realized");
let list_rect = frame.get(&by::id("var")).rect;
assert!(
(last.y1 - list_rect.y1).abs() < 0.5,
"true bottom once measured: row bottom {} vs viewport bottom {}",
last.y1,
list_rect.y1
);
for (i, rect) in &rows {
let want = if i.is_multiple_of(2) { 16.0 } else { 48.0 };
assert!(
(rect.height() - want).abs() < 0.01,
"row {i} is {} tall, wanted {want}",
rect.height()
);
}
for pair in rows.windows(2) {
assert!(
(pair[1].1.y0 - pair[0].1.y1).abs() < 0.01,
"rows {} and {} do not abut: {:?} then {:?}",
pair[0].0,
pair[1].0,
pair[0].1,
pair[1].1
);
}
}