use abstracttui::base::Rect;
use abstracttui::layout::{Display, LayoutTree, Style, Track};
use abstracttui::testing::Rng;
fn overlaps(a: Rect, b: Rect) -> bool {
a.x.max(b.x) < a.right().min(b.right()) && a.y.max(b.y) < a.bottom().min(b.bottom())
}
#[test]
fn grid_columns_tile_width_exactly() {
let mut rng = Rng::new(0x0062_17D0);
for _ in 0..400 {
let ncols = 1 + rng.below(6);
let gap = rng.below(4) as i32;
let w = 4 + rng.below(120) as i32;
let h = 4 + rng.below(20) as i32;
let mut cols = Vec::new();
for _ in 0..ncols {
if rng.below(3) == 0 {
cols.push(Track::Cells(1 + rng.below(10) as i32));
} else {
cols.push(Track::Fr(0.5 + rng.below(30) as f32 / 10.0));
}
}
let has_fr = cols.iter().any(|t| matches!(t, Track::Fr(_)));
let mut tree = LayoutTree::new();
let root = tree.add(Style::default().grid(cols.clone(), vec![]).gap(gap));
let mut ids = Vec::new();
for _ in 0..ncols {
let id = tree.add(Style::default());
tree.add_child(root, id);
ids.push(id);
}
let container = Rect::new(0, 0, w, h);
abstracttui::layout::solve(&mut tree, root, container);
let rects: Vec<Rect> = ids.iter().map(|&id| tree.rect(id)).collect();
for i in 0..rects.len() {
for j in i + 1..rects.len() {
assert!(
!overlaps(rects[i], rects[j]),
"grid cells overlap: {:?} {:?}",
rects[i],
rects[j]
);
}
}
let fixed: i32 = cols
.iter()
.map(|t| match t {
Track::Cells(c) => (*c).max(0),
Track::Percent(_) | Track::Auto | Track::Fr(_) => 0,
})
.sum();
let gaps_total = gap * (ncols as i32 - 1).max(0);
let fits = w >= fixed + gaps_total;
if has_fr && fits {
let widths: i32 = rects.iter().map(|r| r.w).sum();
assert_eq!(
widths + gaps_total,
w,
"fr columns must tile width exactly: cols={cols:?} w={w} gap={gap}"
);
let mut sorted = rects.clone();
sorted.sort_by_key(|r| r.x);
for pair in sorted.windows(2) {
assert_eq!(pair[1].x - pair[0].right(), gap, "wrong inter-column gap");
}
}
if fits {
for r in &rects {
assert!(
r.x >= 0 && r.y >= 0 && r.right() <= w && r.bottom() <= h,
"grid cell escapes container while fitting: {r:?} in {container:?}"
);
}
}
}
}
#[test]
fn grid_col_span_covers_tracks_plus_internal_gaps() {
let gap = 2;
let mut tree = LayoutTree::new();
let root = tree.add(
Style::default()
.grid(
vec![Track::Cells(10), Track::Cells(10), Track::Cells(10)],
vec![],
)
.gap(gap),
);
let wide = tree.add(Style::default().col_span(2));
let solo = tree.add(Style::default());
tree.add_child(root, wide);
tree.add_child(root, solo);
abstracttui::layout::solve(&mut tree, root, Rect::new(0, 0, 34, 4));
let rw = tree.rect(wide);
assert_eq!(rw.w, 22, "col_span(2) over 10+gap(2)+10 = 22, got {rw:?}");
assert_eq!(rw.x, 0);
let rs = tree.rect(solo);
assert_eq!(rs.x, 24, "solo should start at track 3 (10+2+10+2)");
assert_eq!(rs.w, 10);
}
#[test]
fn grid_zero_tracks_is_one_full_width_column() {
let mut tree = LayoutTree::new();
let root = tree.add(Style::default().grid(vec![], vec![]));
let a = tree.add(Style::default().h(2));
let b = tree.add(Style::default().h(2));
tree.add_child(root, a);
tree.add_child(root, b);
abstracttui::layout::solve(&mut tree, root, Rect::new(0, 0, 30, 10));
let (ra, rb) = (tree.rect(a), tree.rect(b));
assert_eq!(ra.w, 30, "single implicit column spans full width");
assert_eq!(rb.w, 30);
assert!(
!overlaps(ra, rb),
"stacked rows must not overlap: {ra:?} {rb:?}"
);
assert!(
ra.bottom() <= rb.y || rb.bottom() <= ra.y,
"rows must be vertically disjoint"
);
}
#[test]
fn grid_overwide_span_clamps_safely() {
let mut tree = LayoutTree::new();
let root = tree.add(Style::default().grid(vec![Track::Fr(1.0), Track::Fr(1.0)], vec![]));
let huge = tree.add(Style::default().col_span(99));
tree.add_child(root, huge);
abstracttui::layout::solve(&mut tree, root, Rect::new(0, 0, 20, 4));
let r = tree.rect(huge);
assert!(
r.right() <= 20,
"over-span must clamp within the container: {r:?}"
);
assert!(r.w > 0, "clamped span still has width");
}
#[test]
fn grid_solve_is_deterministic() {
let build = || {
let mut tree = LayoutTree::new();
let root = tree.add(
Style::default()
.grid(
vec![Track::Fr(1.0), Track::Cells(6), Track::Fr(2.0)],
vec![Track::Cells(3)],
)
.gap(1)
.cross_gap(1),
);
let ids: Vec<_> = (0..6)
.map(|i| {
let s = if i % 2 == 0 {
Style::default()
} else {
Style::default().col_span(2)
};
let id = tree.add(s);
tree.add_child(root, id);
id
})
.collect();
abstracttui::layout::solve(&mut tree, root, Rect::new(0, 0, 41, 13));
ids.iter().map(|&id| tree.rect(id)).collect::<Vec<_>>()
};
assert_eq!(build(), build(), "grid layout must be deterministic");
let _ = Display::Flex;
}