Files
figcraft/crates/layout/src/tests.rs
T
Dylan M. Taylor 9475a815da fix(layout): end the fill distribution loop when no child can freeze (#1926)
Each iteration froze the children whose clamping passed a 1e-9 tolerance,
but it compared the aggregate violation against the same tolerance: several
clampings each under it could add up past it, so no child froze, the
pending set never shrank and the loop never ended. An iteration that
freezes nothing now settles the remaining children as clamped.
2026-11-03 22:25:33 -05:00

471 lines
20 KiB
Rust

use super::*;
use figcraft_doc::{AutoLayout, Document, NodeKind};
fn setup(mode: LayoutMode) -> (Document, NodeId, Vec<NodeId>) {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 300.0, 100.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout { mode, gap: 10.0, padding: [5.0, 5.0, 5.0, 5.0], ..Default::default() });
let a = d.create(f, NodeKind::Rectangle, "A", 0.0, 0.0, 50.0, 20.0).unwrap();
let b = d.create(f, NodeKind::Rectangle, "B", 0.0, 0.0, 30.0, 40.0).unwrap();
(d, f, vec![a, b])
}
pub(super) fn pos(d: &Document, id: NodeId) -> (f64, f64, f64, f64) {
let n = d.node(id).unwrap();
let c = n.transform.as_coeffs();
(c[4], c[5], n.width, n.height)
}
#[test]
fn horizontal_stack() {
let (mut d, f, k) = setup(LayoutMode::Horizontal);
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(pos(&d, k[0]), (5.0, 5.0, 50.0, 20.0));
assert_eq!(pos(&d, k[1]), (65.0, 5.0, 30.0, 40.0));
}
#[test]
fn hug_container() {
let (mut d, f, _) = setup(LayoutMode::Horizontal);
let n = d.get_mut(f).unwrap();
n.child_layout.sizing_h = Sizing::Hug;
n.child_layout.sizing_v = Sizing::Hug;
layout_frame(&mut d, f, &NoMeasure);
let n = d.node(f).unwrap();
assert_eq!((n.width, n.height), (5.0 + 50.0 + 10.0 + 30.0 + 5.0, 50.0));
}
#[test]
fn vertical_center_alignment() {
let (mut d, f, k) = setup(LayoutMode::Vertical);
d.get_mut(f).unwrap().layout.as_mut().unwrap().cross_align = CrossAlign::Center;
layout_frame(&mut d, f, &NoMeasure);
// Inner width 290; A (50 wide) centred → x = 5 + 120.
assert_eq!(pos(&d, k[0]).0, 125.0);
assert_eq!(pos(&d, k[1]).1, 5.0 + 20.0 + 10.0);
}
#[test]
fn fill_children_share_space() {
let (mut d, f, k) = setup(LayoutMode::Horizontal);
for c in &k {
d.get_mut(*c).unwrap().child_layout.sizing_h = Sizing::Fill;
}
d.get_mut(k[1]).unwrap().child_layout.sizing_v = Sizing::Fill;
layout_frame(&mut d, f, &NoMeasure);
// Inner 290 - gap 10 = 280 → 140 each.
assert_eq!(pos(&d, k[0]).2, 140.0);
assert_eq!(pos(&d, k[1]).0, 5.0 + 140.0 + 10.0);
assert_eq!(pos(&d, k[1]).3, 90.0);
// Max width on one.
d.get_mut(k[0]).unwrap().child_layout.max_w = Some(100.0);
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(pos(&d, k[0]).2, 100.0);
assert_eq!(pos(&d, k[1]).2, 180.0);
}
#[test]
fn space_between_and_absolute() {
let (mut d, f, k) = setup(LayoutMode::Horizontal);
d.get_mut(f).unwrap().layout.as_mut().unwrap().main_align = MainAlign::SpaceBetween;
let abs = d.create(f, NodeKind::Rectangle, "Abs", 7.0, 7.0, 10.0, 10.0).unwrap();
d.get_mut(abs).unwrap().child_layout.absolute = true;
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(pos(&d, k[1]).0, 300.0 - 5.0 - 30.0);
assert_eq!(pos(&d, abs).0, 7.0);
}
#[test]
fn wrap_into_rows() {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 100.0, 100.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout { wrap: true, gap: 10.0, cross_gap: 5.0, padding: [0.0; 4], ..Default::default() });
d.get_mut(f).unwrap().child_layout.sizing_v = Sizing::Hug;
let ids: Vec<NodeId> = (0..5).map(|i| d.create(f, NodeKind::Rectangle, &format!("R{i}"), 0.0, 0.0, 40.0, 20.0).unwrap()).collect();
layout_frame(&mut d, f, &NoMeasure);
// Two per row (40+10+40 = 90 ≤ 100).
assert_eq!(pos(&d, ids[2]), (0.0, 25.0, 40.0, 20.0));
assert_eq!(d.node(f).unwrap().height, 20.0 * 3.0 + 5.0 * 2.0);
}
#[test]
fn grid_mode() {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 210.0, 110.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout {
mode: LayoutMode::Grid,
grid_columns: 2,
grid_rows: 2,
gap: 10.0,
cross_gap: 10.0,
padding: [0.0; 4],
..Default::default()
});
let ids: Vec<NodeId> = (0..4).map(|i| d.create(f, NodeKind::Rectangle, &format!("R{i}"), 0.0, 0.0, 10.0, 10.0).unwrap()).collect();
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(pos(&d, ids[3]).0, 110.0);
assert_eq!(pos(&d, ids[3]).1, 60.0);
}
#[test]
fn nested_hug_propagates_with_layout_page() {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let outer = d.create(p, NodeKind::Frame, "Outer", 0.0, 0.0, 10.0, 10.0).unwrap();
d.get_mut(outer).unwrap().layout = Some(AutoLayout { padding: [0.0; 4], ..Default::default() });
d.get_mut(outer).unwrap().child_layout.sizing_h = Sizing::Hug;
let inner = d.create(outer, NodeKind::Frame, "Inner", 0.0, 0.0, 10.0, 10.0).unwrap();
d.get_mut(inner).unwrap().layout = Some(AutoLayout { padding: [0.0; 4], gap: 0.0, ..Default::default() });
d.get_mut(inner).unwrap().child_layout.sizing_h = Sizing::Hug;
d.create(inner, NodeKind::Rectangle, "R", 0.0, 0.0, 77.0, 10.0).unwrap();
layout_page(&mut d, p, &NoMeasure);
assert_eq!(d.node(inner).unwrap().width, 77.0);
assert_eq!(d.node(outer).unwrap().width, 77.0);
}
#[test]
fn constraints_on_resize() {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 100.0, 100.0).unwrap();
let r = d.create(f, NodeKind::Rectangle, "R", 80.0, 10.0, 10.0, 10.0).unwrap();
d.get_mut(r).unwrap().constraints = figcraft_doc::Constraints { horizontal: HConstraint::Right, vertical: VConstraint::TopBottom };
let c = d.create(f, NodeKind::Rectangle, "C", 45.0, 45.0, 10.0, 10.0).unwrap();
d.get_mut(c).unwrap().constraints = figcraft_doc::Constraints { horizontal: HConstraint::Center, vertical: VConstraint::Scale };
set_size(&mut d, f, 200.0, 200.0);
apply_constraints(&mut d, f, 100.0, 100.0);
assert_eq!(pos(&d, r), (180.0, 10.0, 10.0, 110.0));
assert_eq!(pos(&d, c), (95.0, 90.0, 10.0, 20.0));
}
#[test]
fn grid_lines_columns_stretch() {
let g = LayoutGrid { kind: GridKind::Columns, count: 4, gutter: 20.0, offset: 10.0, ..LayoutGrid::default() };
let r = grid_lines(&g, 400.0, 100.0);
assert_eq!(r.len(), 4);
assert_eq!(r[0].x0, 10.0);
assert!((r[3].x1 - 390.0).abs() < 1e-9);
}
#[test]
fn hostile_values() {
let (mut d, f, _) = setup(LayoutMode::Horizontal);
let n = d.get_mut(f).unwrap();
n.layout.as_mut().unwrap().gap = f64::NAN;
n.layout.as_mut().unwrap().padding = [f64::INFINITY, -1.0, f64::NAN, 1e300];
n.layout.as_mut().unwrap().grid_columns = 0;
layout_frame(&mut d, f, &NoMeasure);
d.get_mut(f).unwrap().layout.as_mut().unwrap().mode = LayoutMode::Grid;
layout_frame(&mut d, f, &NoMeasure);
let _ = grid_lines(&LayoutGrid { size: 0.0, ..Default::default() }, 1e9, 1e9);
}
/// A fake measurer: each text has a natural (unwrapped) size and a first baseline; wrapping at a
/// narrower width adds lines of the natural height.
pub(super) struct FakeMeasure(pub(super) std::collections::HashMap<NodeId, (f64, f64, f64)>);
impl Measure for FakeMeasure {
fn text_height(&self, _: &Document, id: NodeId, width: f64) -> Option<f64> {
let (w, h, _) = self.0.get(&id).copied()?;
Some(h * (w / width.max(1.0)).ceil().max(1.0))
}
fn text_size(&self, _: &Document, id: NodeId) -> Option<(f64, f64)> {
self.0.get(&id).map(|(w, h, _)| (*w, *h))
}
fn text_baseline(&self, _: &Document, id: NodeId) -> Option<f64> {
self.0.get(&id).map(|t| t.2)
}
}
pub(super) fn text(d: &mut Document, parent: NodeId, name: &str) -> NodeId {
d.create(parent, NodeKind::Text(figcraft_doc::TextData::new(name)), name, 0.0, 0.0, 10.0, 10.0).unwrap()
}
#[test]
fn min_max_clamp_fixed_children_and_frame() {
let (mut d, f, k) = setup(LayoutMode::Horizontal);
d.get_mut(k[0]).unwrap().child_layout.min_w = Some(80.0);
d.get_mut(k[1]).unwrap().child_layout.max_h = Some(25.0);
d.get_mut(f).unwrap().child_layout.max_w = Some(200.0);
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(pos(&d, k[0]).2, 80.0);
assert_eq!(pos(&d, k[1]), (95.0, 5.0, 30.0, 25.0));
// A fixed frame wider than its max width is clamped too.
assert_eq!(d.node(f).unwrap().width, 200.0);
}
#[test]
fn min_max_on_hug_and_fill() {
let (mut d, f, _) = setup(LayoutMode::Horizontal);
{
let n = d.get_mut(f).unwrap();
n.child_layout.sizing_h = Sizing::Hug;
n.child_layout.min_w = Some(200.0);
}
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(d.node(f).unwrap().width, 200.0);
{
let n = d.get_mut(f).unwrap();
n.child_layout.min_w = None;
n.child_layout.max_w = Some(60.0);
}
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(d.node(f).unwrap().width, 60.0);
// A fill child with a min width wider than its share keeps the min.
let (mut d, f, k) = setup(LayoutMode::Horizontal);
for c in &k {
d.get_mut(*c).unwrap().child_layout.sizing_h = Sizing::Fill;
}
d.get_mut(k[0]).unwrap().child_layout.min_w = Some(200.0);
layout_frame(&mut d, f, &NoMeasure);
assert_eq!(pos(&d, k[0]).2, 200.0);
assert_eq!(pos(&d, k[1]).2, 80.0);
}
#[test]
fn min_over_max_and_junk_limits() {
let (mut d, f, k) = setup(LayoutMode::Vertical);
{
let cl = &mut d.get_mut(k[0]).unwrap().child_layout;
cl.min_w = Some(90.0);
cl.max_w = Some(40.0);
cl.min_h = Some(f64::NAN);
cl.max_h = Some(f64::NEG_INFINITY);
}
layout_frame(&mut d, f, &NoMeasure);
// Min wins over a smaller max; non-finite limits are ignored.
assert_eq!(pos(&d, k[0]).2, 90.0);
assert_eq!(pos(&d, k[0]).3, 20.0);
assert_eq!(clamp_size(-5.0, None, None), 0.0);
assert_eq!(clamp_size(f64::NAN, Some(3.0), None), 3.0);
}
#[test]
fn hug_text_wraps_at_max_width() {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 10.0, 10.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout { padding: [0.0; 4], ..Default::default() });
let t = text(&mut d, f, "Long");
d.get_mut(t).unwrap().child_layout.sizing_h = Sizing::Hug;
let m = FakeMeasure([(t, (200.0, 20.0, 15.0))].into_iter().collect());
layout_frame(&mut d, f, &m);
assert_eq!(pos(&d, t).2, 200.0);
d.get_mut(t).unwrap().child_layout.max_w = Some(80.0);
layout_frame(&mut d, f, &m);
let n = d.node(t).unwrap();
assert_eq!((n.width, n.height), (80.0, 60.0));
assert!(matches!(&n.kind, NodeKind::Text(tx) if tx.auto_resize == TextAutoResize::Height));
// Without the max it hugs its natural width again.
d.get_mut(t).unwrap().child_layout.max_w = None;
layout_frame(&mut d, f, &m);
let n = d.node(t).unwrap();
assert_eq!((n.width, n.height), (200.0, 20.0));
assert!(matches!(&n.kind, NodeKind::Text(tx) if tx.auto_resize == TextAutoResize::WidthAndHeight));
}
#[test]
fn absolute_children_follow_constraints() {
let (mut d, f, _) = setup(LayoutMode::Horizontal);
let abs = d.create(f, NodeKind::Rectangle, "Badge", 280.0, 4.0, 10.0, 10.0).unwrap();
{
let n = d.get_mut(abs).unwrap();
n.child_layout.absolute = true;
n.constraints = figcraft_doc::Constraints { horizontal: HConstraint::Right, vertical: VConstraint::Top };
}
d.get_mut(f).unwrap().child_layout.sizing_h = Sizing::Hug;
layout_frame(&mut d, f, &NoMeasure);
// Hugging shrank the frame 300 → 100: the badge keeps its distance from the right edge.
assert_eq!(d.node(f).unwrap().width, 100.0);
assert_eq!(pos(&d, abs), (80.0, 4.0, 10.0, 10.0));
assert!(!flow_children(&d, f).contains(&abs));
// A plain resize of an auto layout frame moves only absolute children by their constraints.
let first = pos(&d, d.node(f).unwrap().children[0]);
set_size(&mut d, f, 150.0, 100.0);
apply_constraints(&mut d, f, 100.0, 100.0);
assert_eq!(pos(&d, abs).0, 130.0);
assert_eq!(pos(&d, d.node(f).unwrap().children[0]), first);
}
#[test]
fn baseline_alignment_horizontal() {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 10.0, 10.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout { padding: [5.0; 4], gap: 0.0, cross_align: CrossAlign::Baseline, ..Default::default() });
d.get_mut(f).unwrap().child_layout.sizing_v = Sizing::Hug;
let small = text(&mut d, f, "small");
let big = text(&mut d, f, "big");
let rect = d.create(f, NodeKind::Rectangle, "R", 0.0, 0.0, 10.0, 20.0).unwrap();
let m = FakeMeasure([(small, (30.0, 14.0, 10.0)), (big, (60.0, 40.0, 30.0))].into_iter().collect());
layout_frame(&mut d, f, &m);
// Baselines line up at 5 + 30; the rectangle (no baseline) sits at the top.
assert_eq!(pos(&d, small).1, 25.0);
assert_eq!(pos(&d, big).1, 5.0);
assert_eq!(pos(&d, rect).1, 5.0);
// Hug height covers the shifted small text: max(20 + 14, 40) + padding.
assert_eq!(d.node(f).unwrap().height, 50.0);
// Nested auto layout frames align on their first child's baseline.
let outer = d.create(p, NodeKind::Frame, "O", 0.0, 0.0, 400.0, 100.0).unwrap();
d.get_mut(outer).unwrap().layout = Some(AutoLayout { padding: [0.0; 4], gap: 0.0, cross_align: CrossAlign::Baseline, ..Default::default() });
d.reparent(f, outer, None).unwrap();
let t2 = text(&mut d, outer, "t2");
let m = FakeMeasure([(small, (30.0, 14.0, 10.0)), (big, (60.0, 40.0, 30.0)), (t2, (20.0, 60.0, 50.0))].into_iter().collect());
layout_page(&mut d, p, &m);
// Frame baseline = 5 + 30 = 35; t2's is 50 → the frame moves down 15.
assert_eq!(pos(&d, f).1, 15.0);
assert_eq!(pos(&d, t2).1, 0.0);
// Vertical layouts read baseline as left.
d.get_mut(outer).unwrap().layout.as_mut().unwrap().mode = LayoutMode::Vertical;
layout_frame(&mut d, outer, &m);
assert_eq!(pos(&d, f).0, 0.0);
assert_eq!(pos(&d, t2).0, 0.0);
}
fn grid_frame(cols: u32, w: f64, h: f64) -> (Document, NodeId) {
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "G", 0.0, 0.0, w, h).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout {
mode: LayoutMode::Grid,
grid_columns: cols,
grid_rows: 1,
gap: 10.0,
cross_gap: 10.0,
padding: [0.0; 4],
..Default::default()
});
(d, f)
}
#[test]
fn grid_tracks_fixed_hug_fr() {
use figcraft_doc::GridTrack;
let (mut d, f) = grid_frame(4, 400.0, 100.0);
d.get_mut(f).unwrap().layout.as_mut().unwrap().column_tracks =
vec![GridTrack::fixed(50.0), GridTrack::hug(), GridTrack::fill(1.0), GridTrack::fill(2.0)];
let ids: Vec<NodeId> = (0..4).map(|i| d.create(f, NodeKind::Rectangle, &format!("R{i}"), 0.0, 0.0, 30.0 + i as f64, 20.0).unwrap()).collect();
for c in &ids {
d.get_mut(*c).unwrap().child_layout.sizing_h = Sizing::Fill;
}
// The hug column fits its (fixed) child.
d.get_mut(ids[1]).unwrap().child_layout.sizing_h = Sizing::Fixed;
layout_frame(&mut d, f, &NoMeasure);
let g = grid_geometry(&d, f).unwrap();
// 400 - 3 gaps (30) - 50 - 31 = 289 free → 1fr 96.33, 2fr 192.67.
assert_eq!(g.columns[0], (0.0, 50.0));
assert_eq!(g.columns[1], (60.0, 91.0));
assert!((g.columns[2].1 - g.columns[2].0 - 289.0 / 3.0).abs() < 1e-9);
assert!((g.columns[3].1 - 400.0).abs() < 1e-9);
assert_eq!(pos(&d, ids[0]).2, 50.0);
assert!((pos(&d, ids[3]).2 - 289.0 * 2.0 / 3.0).abs() < 1e-9);
// The only row fills the height.
assert_eq!(g.rows, vec![(0.0, 100.0)]);
}
#[test]
fn grid_cells_spans_and_anchors() {
let (mut d, f) = grid_frame(3, 320.0, 200.0);
let ids: Vec<NodeId> = (0..4).map(|i| d.create(f, NodeKind::Rectangle, &format!("R{i}"), 0.0, 0.0, 10.0, 10.0).unwrap()).collect();
// R0 spans two columns and fills them; R3 is anchored at column 0, row 2.
{
let cl = &mut d.get_mut(ids[0]).unwrap().child_layout;
cl.grid_column_span = 2;
cl.sizing_h = Sizing::Fill;
}
{
let cl = &mut d.get_mut(ids[3]).unwrap().child_layout;
cl.grid_column = Some(0);
cl.grid_row = Some(2);
}
layout_frame(&mut d, f, &NoMeasure);
let g = grid_geometry(&d, f).unwrap();
let cell = |id: NodeId| g.cells.iter().find(|c| c.id == id).copied().unwrap();
assert_eq!((cell(ids[0]).column, cell(ids[0]).row, cell(ids[0]).column_span), (0, 0, 2));
assert_eq!((cell(ids[1]).column, cell(ids[1]).row), (2, 0));
assert_eq!((cell(ids[2]).column, cell(ids[2]).row), (0, 1));
assert_eq!((cell(ids[3]).column, cell(ids[3]).row), (0, 2));
assert_eq!(g.rows.len(), 3);
// Columns are 100 wide: the spanning child covers 100 + 10 + 100.
assert_eq!(pos(&d, ids[0]).2, 210.0);
assert_eq!(pos(&d, ids[1]).0, 220.0);
assert_eq!(pos(&d, ids[3]).1, 140.0);
}
#[test]
fn grid_hug_with_span() {
use figcraft_doc::GridTrack;
let (mut d, f) = grid_frame(2, 10.0, 10.0);
{
let n = d.get_mut(f).unwrap();
n.child_layout.sizing_h = Sizing::Hug;
n.child_layout.sizing_v = Sizing::Hug;
n.layout.as_mut().unwrap().column_tracks = vec![GridTrack::hug(), GridTrack::fixed(20.0)];
}
d.create(f, NodeKind::Rectangle, "A", 0.0, 0.0, 40.0, 10.0).unwrap();
let wide = d.create(f, NodeKind::Rectangle, "W", 0.0, 0.0, 100.0, 30.0).unwrap();
d.get_mut(wide).unwrap().child_layout.grid_column_span = 2;
layout_frame(&mut d, f, &NoMeasure);
// Column 0 grows so the span fits: 100 - 10 gap - 20 fixed = 70.
let n = d.node(f).unwrap();
assert_eq!((n.width, n.height), (100.0, 10.0 + 10.0 + 30.0));
assert_eq!(pos(&d, wide).1, 20.0);
}
#[test]
fn grid_hostile_values() {
use figcraft_doc::GridTrack;
let (mut d, f) = grid_frame(0, f64::NAN, -5.0);
{
let l = d.get_mut(f).unwrap().layout.as_mut().unwrap();
l.column_tracks = vec![GridTrack { sizing: Sizing::Fixed, value: f64::NAN }, GridTrack::fill(f64::INFINITY), GridTrack::fill(-3.0)];
l.row_tracks = vec![GridTrack::fixed(1e300)];
l.grid_rows = u32::MAX;
}
let r = d.create(f, NodeKind::Rectangle, "R", 0.0, 0.0, 10.0, 10.0).unwrap();
{
let cl = &mut d.get_mut(r).unwrap().child_layout;
cl.grid_column = Some(u32::MAX);
cl.grid_row = Some(u32::MAX);
cl.grid_column_span = u32::MAX;
cl.grid_row_span = u32::MAX;
cl.sizing_h = Sizing::Fill;
}
for i in 0..20 {
let c = d.create(f, NodeKind::Rectangle, &format!("K{i}"), 0.0, 0.0, 10.0, 10.0).unwrap();
d.get_mut(c).unwrap().child_layout.grid_column_span = 3;
}
layout_frame(&mut d, f, &NoMeasure);
let g = grid_geometry(&d, f).unwrap();
assert!(g.columns.len() == 1 && g.rows.len() <= 1000);
assert!(g.width.is_finite() && g.height.is_finite());
for id in d.node(f).unwrap().children.clone() {
let (x, y, w, h) = pos(&d, id);
assert!(x.is_finite() && y.is_finite() && w.is_finite() && h.is_finite());
}
}
#[test]
fn fill_children_whose_tiny_clampings_add_up_past_the_tolerance_still_settle() {
// Three fill children sharing 300 px, each with a minimum 5e-10 over its 100 px share: no
// single clamping passes the 1e-9 tolerance, but together they do, so no child froze and the
// distribution loop went round forever without changing anything.
let mut d = Document::new("t");
let p = d.first_page().unwrap();
let f = d.create(p, NodeKind::Frame, "F", 0.0, 0.0, 300.0, 100.0).unwrap();
d.get_mut(f).unwrap().layout = Some(AutoLayout { mode: LayoutMode::Horizontal, gap: 0.0, padding: [0.0; 4], ..Default::default() });
let kids: Vec<NodeId> = (0..3).map(|i| d.create(f, NodeKind::Rectangle, "K", 0.0, 0.0, 10.0, 20.0 + i as f64).unwrap()).collect();
for k in &kids {
let n = d.get_mut(*k).unwrap();
n.child_layout.sizing_h = Sizing::Fill;
n.child_layout.min_w = Some(100.0 + 5e-10);
}
layout_frame(&mut d, f, &NoMeasure);
for k in &kids {
assert!((pos(&d, *k).2 - 100.0).abs() < 1e-6, "{:?}", pos(&d, *k));
}
}