From b842e4f474e9a8fcae8d877771d4d811023b4dbe Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Fri, 18 Sep 2026 14:09:00 -0400 Subject: [PATCH] Pin decided-box warm/cold failures --- tests/cases/unsettled.rs | 174 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/tests/cases/unsettled.rs b/tests/cases/unsettled.rs index 2182559..c2f766f 100644 --- a/tests/cases/unsettled.rs +++ b/tests/cases/unsettled.rs @@ -14,6 +14,180 @@ use iris::harness::Harness; use iris::prelude::*; use iris::random::Branch; +fn assert_same_regions( + warm: &Harness, + warm_ids: &[WidgetId], + cold: &Harness, + cold_ids: &[WidgetId], +) { + let mut wrong = Vec::new(); + for (i, (&w, &c)) in warm_ids.iter().zip(cold_ids).enumerate() { + let (got, want) = (warm.region(&w), cold.region(&c)); + if got != want { + wrong.push(format!("widget {i}: warm {got:?} cold {want:?}")); + } + } + assert!(wrong.is_empty(), "{}", wrong.join("\n")); +} + +/// Ten widgets, shrunk from seed 2 at depth 5. The stack is as tall as its +/// first child, so its other children belong in that one-line box. A cold +/// layout used to keep the span's answer from the larger measuring box while +/// a repaint asked it in the stack's final box. +fn plant_stack_in_its_sizing_childs_box(h: &mut Harness) -> Vec { + let sizing = wtext("one line, overflowing whatever it is given") + .size(16) + .wrap(false) + .add(&mut h.rsc); + let filler = rect(Color::CYAN.alpha(252)).add(&mut h.rsc); + let plain = wtext("one line, overflowing whatever it is given") + .size(16) + .wrap(false) + .add(&mut h.rsc); + let span = (filler, plain).span(Dir::DOWN).add(&mut h.rsc); + let pad = Pad { + padding: Padding::ZERO, + inner: span.add_strong(&mut h.rsc), + } + .add(&mut h.rsc); + let probe = rect(Color::RED).add(&mut h.rsc); + let wide = rect(Color::YELLOW.alpha(252)).add(&mut h.rsc); + let narrow = rect(Color::RED).add(&mut h.rsc); + let branch = Branch { + probe: probe.add_strong(&mut h.rsc), + wide: wide.add_strong(&mut h.rsc), + narrow: narrow.add_strong(&mut h.rsc), + threshold: 55.0, + } + .add(&mut h.rsc); + let stack = Stack { + children: vec![ + sizing.add_strong(&mut h.rsc), + pad.add_strong(&mut h.rsc), + branch.add_strong(&mut h.rsc), + ], + size: StackSize::Child(0), + } + .add(&mut h.rsc); + h.rsc + .widgets_mut() + .set_size_rules(stack.id(), Some(LayoutLen::LEFTOVER), None); + h.set_root(stack); + vec![ + sizing.id(), + filler.id(), + plain.id(), + span.id(), + pad.id(), + probe.id(), + wide.id(), + narrow.id(), + branch.id(), + stack.id(), + ] +} + +#[test] +fn repainting_a_stack_uses_the_box_its_sizing_child_decided() { + let mut warm = Harness::new((900, 1200)); + let ids = plant_stack_in_its_sizing_childs_box(&mut warm); + for &id in &ids { + warm.rsc.widgets_mut().get_dyn_mut(id); + } + warm.frame(); + + let mut cold = Harness::new((900, 1200)); + let cold_ids = plant_stack_in_its_sizing_childs_box(&mut cold); + + assert_same_regions(&warm, &ids, &cold, &cold_ids); +} + +/// Ten widgets, shrunk from seed 108 at depth 5. The nested reverse spans +/// evaluate the branch in successively narrower boxes. The answer from the +/// final, decided box must be the one retained after every span is reordered. +fn plant_branch_in_nested_reverse_spans( + h: &mut Harness, + reordered: bool, +) -> (Vec, [WeakWidget; 3]) { + let pair = |first: StrongWidget, second: StrongWidget| match reordered { + true => vec![second, first], + false => vec![first, second], + }; + let probe = rect(Color::RED.alpha(63)).add(&mut h.rsc); + let wide = rect(Color::RED).add(&mut h.rsc); + let narrow = wtext(PARAGRAPH).size(16).wrap(true).add(&mut h.rsc); + let branch = Branch { + probe: probe.add_strong(&mut h.rsc), + wide: wide.add_strong(&mut h.rsc), + narrow: narrow.add_strong(&mut h.rsc), + threshold: 483.0, + } + .add(&mut h.rsc); + let wrapped = wtext(PARAGRAPH).size(16).wrap(true).add(&mut h.rsc); + let down = Span { + children: pair( + branch.add_strong(&mut h.rsc), + wrapped.add_strong(&mut h.rsc), + ), + dir: Dir::DOWN, + gap: Px::ZERO, + } + .add(&mut h.rsc); + let inner_filler = rect(Color::CYAN.alpha(63)).add(&mut h.rsc); + let inner = Span { + children: pair( + down.add_strong(&mut h.rsc), + inner_filler.add_strong(&mut h.rsc), + ), + dir: Dir::LEFT, + gap: Px::ZERO, + } + .height(LayoutLen::rel(1.0)) + .add(&mut h.rsc); + let outer_filler = rect(Color::GREEN.alpha(63)).add(&mut h.rsc); + let outer = Span { + children: pair( + inner.add_strong(&mut h.rsc), + outer_filler.add_strong(&mut h.rsc), + ), + dir: Dir::LEFT, + gap: Px::ZERO, + } + .height(LayoutLen::rel(1.0)) + .add(&mut h.rsc); + h.set_root(outer); + ( + vec![ + probe.id(), + wide.id(), + narrow.id(), + branch.id(), + wrapped.id(), + down.id(), + inner_filler.id(), + inner.id(), + outer_filler.id(), + outer.id(), + ], + [down, inner, outer], + ) +} + +#[test] +fn reordering_nested_spans_keeps_the_answer_from_the_decided_box() { + let mut warm = Harness::new((900, 1200)); + let (ids, spans) = plant_branch_in_nested_reverse_spans(&mut warm, false); + for span in spans { + warm.rsc[span].children.rotate_left(1); + } + warm.frame(); + + let mut cold = Harness::new((900, 1200)); + let (cold_ids, _) = plant_branch_in_nested_reverse_spans(&mut cold, true); + + assert_same_regions(&warm, &ids, &cold, &cold_ids); +} + /// Six widgets, shrunk from a 402-widget tree the fuzzer found. Nothing about /// the tree changes -- every widget is marked for redraw and the frame is /// taken again -- so no box may move, and a warm frame has to land where a