Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/flex/wrap_test.rs
StratoSDK / crates / strato-ui-core / src / elements / flex / wrap_test.rs
1use pathfinder_geometry::rect::RectF;
2use pathfinder_geometry::vector::vec2f;
3 
4use crate::elements::ConstrainedBox;
5use crate::elements::Container;
6use crate::elements::Empty;
7use crate::elements::ParentElement;
8use crate::elements::Rect;
9use crate::platform::WindowStyle;
10use crate::Entity;
11use crate::View;
12use crate::WindowId;
13use crate::{App, TypedActionView};
14 
15use super::*;
16 
17struct TestRootView {
18 parent_size: Vector2F,
19 children_sizes: Vec<Vector2F>,
20 axis: Axis,
21 run_spacing: f32,
22}
23 
24impl TestRootView {
25 pub fn new(
26 parent_size: Vector2F,
27 children_sizes: Vec<Vector2F>,
28 axis: Axis,
29 run_spacing: f32,
30 ) -> Self {
31 Self {
32 parent_size,
33 children_sizes,
34 axis,
35 run_spacing,
36 }
37 }
38}
39 
40impl Entity for TestRootView {
41 type Event = ();
42}
43 
44impl View for TestRootView {
45 fn ui_name() -> &'static str {
46 "Wrap::tests::TestRootView"
47 }
48 
49 fn render(&self, _: &AppContext) -> Box<dyn Element> {
50 let mut wrap = Wrap::new(self.axis).with_run_spacing(self.run_spacing);
51 wrap.extend(self.children_sizes.iter().map(|size| {
52 ConstrainedBox::new(Rect::new().finish())
53 .with_height(size.y())
54 .with_width(size.x())
55 .finish()
56 }));
57 
58 ConstrainedBox::new(wrap.finish())
59 .with_width(self.parent_size.x())
60 .with_height(self.parent_size.y())
61 .finish()
62 }
63}
64 
65impl TypedActionView for TestRootView {
66 type Action = ();
67}
68 
69type RenderFn = dyn Fn(&AppContext) -> Box<dyn Element> + 'static;
70 
71struct TestDynamicView {
72 render: Box<RenderFn>,
73}
74 
75impl TestDynamicView {
76 fn new(render: impl Fn(&AppContext) -> Box<dyn Element> + 'static) -> Self {
77 Self {
78 render: Box::new(render),
79 }
80 }
81}
82 
83impl Entity for TestDynamicView {
84 type Event = ();
85}
86 
87impl View for TestDynamicView {
88 fn ui_name() -> &'static str {
89 "Wrap::tests::TestDynamicView"
90 }
91 
92 fn render(&self, app: &AppContext) -> Box<dyn Element> {
93 (self.render)(app)
94 }
95}
96 
97impl TypedActionView for TestDynamicView {
98 type Action = ();
99}
100 
101/// Asserts that the bounds of all the painted rects match that of `rects`.
102fn assert_bounds_of_rects(
103 app: &mut App,
104 window_id: WindowId,
105 rects: impl IntoIterator<Item = RectF>,
106) {
107 let presenter_ref = app
108 .presenter(window_id)
109 .expect("Test window should have a presenter since first frame is rendered.");
110 let presenter = presenter_ref.borrow();
111 let scene = presenter
112 .scene()
113 .expect("Presenter should have rendered a scene after the test_view was updated.");
114 
115 let actual_rects = scene
116 .layers()
117 .flat_map(|layer| layer.rects.iter())
118 .map(|rect| rect.bounds);
119 
120 itertools::assert_equal(actual_rects, rects);
121}
122 
123#[test]
124fn test_row_wraps_across_runs() {
125 App::test((), |mut app| async move {
126 let child_size = vec2f(100., 100.);
127 let app = &mut app;
128 
129 // Attempt to render 4 100x100 rects into a 250x250 box. This should result in the first
130 // two rects rendered in a row, followed by a 10px horizontal spacing, followed by the
131 // next two rects rendered in a row.
132 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
133 TestRootView::new(
134 vec2f(250., 250.),
135 vec![child_size; 4],
136 Axis::Horizontal,
137 10.,
138 )
139 });
140 test_view.update(app, |_, ctx| {
141 ctx.notify();
142 });
143 
144 assert_bounds_of_rects(
145 app,
146 window_id,
147 [
148 RectF::new(vec2f(0., 0.), child_size),
149 RectF::new(vec2f(100., 0.), child_size),
150 RectF::new(vec2f(0., 110.), child_size),
151 RectF::new(vec2f(100., 110.), child_size),
152 ],
153 );
154 })
155}
156 
157#[test]
158fn test_column_wraps_across_runs() {
159 App::test((), |mut app| async move {
160 let child_size = vec2f(100., 100.);
161 let app = &mut app;
162 
163 // Attempt to render 4 100x100 rects into a 250x250 box. This should result in the first
164 // two rects rendered in a column, followed by a 10px vertical spacing, followed by the
165 // next two rects rendered in a column.
166 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
167 TestRootView::new(vec2f(250., 250.), vec![child_size; 4], Axis::Vertical, 10.)
168 });
169 test_view.update(app, |_, ctx| {
170 ctx.notify();
171 });
172 
173 assert_bounds_of_rects(
174 app,
175 window_id,
176 [
177 RectF::new(vec2f(0., 0.), child_size),
178 RectF::new(vec2f(0., 100.), child_size),
179 RectF::new(vec2f(110., 0.), child_size),
180 RectF::new(vec2f(110., 100.), child_size),
181 ],
182 );
183 })
184}
185 
186/// Tests that elements within a `Wrap` are not rendered if they can't be fit within the max
187/// size constraint along the cross axis.
188#[test]
189fn test_wrap_with_too_many_elements() {
190 App::test((), |mut app| async move {
191 let child_size = vec2f(100., 100.);
192 let app = &mut app;
193 
194 // Attempt to render 10 100x100 rects into a 250x250 box. This should result in the
195 // first two rects rendered in a row, followed by a 10px horizontal spacing, followed by
196 // the next two rects rendered in a row. Only four of the ten rects can fit in the
197 // parent box.
198 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
199 TestRootView::new(vec2f(250., 250.), vec![child_size; 10], Axis::Vertical, 10.)
200 });
201 test_view.update(app, |_, ctx| {
202 ctx.notify();
203 });
204 
205 // Only 4 elements are painted--even though there were 10 initial elements passed to the
206 // wrap.
207 assert_bounds_of_rects(
208 app,
209 window_id,
210 [
211 RectF::new(vec2f(0., 0.), child_size),
212 RectF::new(vec2f(0., 100.), child_size),
213 RectF::new(vec2f(110., 0.), child_size),
214 RectF::new(vec2f(110., 100.), child_size),
215 ],
216 );
217 })
218}
219 
220/// Tests that when the first item exceeds the max size constraint, it is clamped for layout
221/// purposes and clipped during paint. Subsequent items that fit are still laid out.
222#[test]
223fn test_wrap_first_element_exceeds_size_constraint() {
224 App::test((), |mut app| async move {
225 let child_size = vec2f(100., 100.);
226 let mut children = vec![vec2f(500., 500.)];
227 children.extend(vec![child_size; 2]);
228 
229 let app = &mut app;
230 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
231 TestRootView::new(vec2f(250., 250.), children, Axis::Vertical, 10.)
232 });
233 test_view.update(app, |_, ctx| {
234 ctx.notify();
235 });
236 
237 // The first oversized element is clamped to 250x250 for layout, filling the
238 // entire space. Subsequent items can't fit on the cross axis.
239 // The child is laid out at 250x500 (cross axis is clamped by ConstrainedBox
240 // to 250, main axis stays 500), then clamped to 250x250 for run placement.
241 assert_bounds_of_rects(
242 app,
243 window_id,
244 [RectF::new(vec2f(0., 0.), vec2f(250., 500.))],
245 );
246 })
247}
248 
249/// Tests that when the second element exceeds the size constraint, it is clamped and
250/// subsequent items continue to be laid out if they fit.
251#[test]
252fn test_second_element_exceeds_size_constraint() {
253 App::test((), |mut app| async move {
254 let child_size = vec2f(100., 100.);
255 let children = vec![child_size, vec2f(300., 300.), child_size, child_size];
256 
257 let app = &mut app;
258 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
259 TestRootView::new(vec2f(250., 250.), children, Axis::Vertical, 10.)
260 });
261 test_view.update(app, |_, ctx| {
262 ctx.notify();
263 });
264 
265 // The first element (100x100) is in column 1. The oversized second element
266 // (250x300 after ConstrainedBox clamp, then 250x250 for layout) doesn't fit
267 // in the current run and creates a new one. Its clamped cross-axis size (250)
268 // plus existing runs (100 + 10) exceeds 250, so only the first child fits.
269 assert_bounds_of_rects(app, window_id, [RectF::new(vec2f(0., 0.), child_size)]);
270 })
271}
272 
273#[test]
274fn test_min_size_along_row() {
275 App::test((), |mut app| async move {
276 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
277 TestDynamicView::new(|_| {
278 // Use a Container so there's a rect for the Wrap element's bounds.
279 ConstrainedBox::new(
280 Container::new(
281 Wrap::row()
282 .with_main_axis_size(MainAxisSize::Min)
283 .with_children([
284 ConstrainedBox::new(Empty::new().finish())
285 .with_width(100.)
286 .with_height(100.)
287 .finish(),
288 ConstrainedBox::new(Empty::new().finish())
289 .with_width(100.)
290 .with_height(100.)
291 .finish(),
292 ])
293 .finish(),
294 )
295 .finish(),
296 )
297 // Ensure the Wrap has _more_ than enough space for the two children.
298 .with_max_width(250.)
299 .with_max_height(250.)
300 .finish()
301 })
302 });
303 
304 test_view.update(&mut app, |_, ctx| ctx.notify());
305 
306 // The Wrap element is painted using the minimum space needed for the two children, even
307 // though it could expand further.
308 assert_bounds_of_rects(
309 &mut app,
310 window_id,
311 [RectF::new(vec2f(0., 0.), vec2f(200., 100.))],
312 );
313 });
314}
315 
316#[test]
317fn test_fill_element_within_size_constraint() {
318 App::test((), |mut app| async move {
319 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
320 TestDynamicView::new(|_| {
321 ConstrainedBox::new(
322 Wrap::row()
323 .with_children([
324 ConstrainedBox::new(Rect::new().finish())
325 .with_width(100.)
326 .with_height(100.)
327 .finish(),
328 WrapFill::new(200., Rect::new().finish()).finish(),
329 ])
330 .finish(),
331 )
332 .with_width(400.)
333 .with_height(100.)
334 .finish()
335 })
336 });
337 
338 test_view.update(&mut app, |_, ctx| ctx.notify());
339 
340 // Both children are painted, and the second expands to fill available space.
341 assert_bounds_of_rects(
342 &mut app,
343 window_id,
344 [
345 RectF::new(vec2f(0., 0.), vec2f(100., 100.)),
346 RectF::new(vec2f(100., 0.), vec2f(300., 100.)),
347 ],
348 );
349 });
350}
351 
352#[test]
353fn test_wrap_spacing() {
354 App::test((), |mut app| async move {
355 let app = &mut app;
356 
357 // Test with spacing between children in the same run
358 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
359 TestDynamicView::new(|_| {
360 let child_size = vec2f(100., 50.);
361 // Create a Wrap that can fit 3 children horizontally with spacing
362 let mut wrap = Wrap::row().with_spacing(10.);
363 wrap.extend((0..3).map(|_| {
364 ConstrainedBox::new(Rect::new().finish())
365 .with_height(child_size.y())
366 .with_width(child_size.x())
367 .finish()
368 }));
369 
370 ConstrainedBox::new(wrap.finish())
371 .with_width(350.) // 100 + 10 + 100 + 10 + 100 = 320, so they fit in one run
372 .with_height(200.)
373 .finish()
374 })
375 });
376 test_view.update(app, |_, ctx| {
377 ctx.notify();
378 });
379 
380 let child_size = vec2f(100., 50.);
381 // All 3 children should be in the same run with 10px spacing between them
382 assert_bounds_of_rects(
383 app,
384 window_id,
385 [
386 RectF::new(vec2f(0., 0.), child_size),
387 RectF::new(vec2f(110., 0.), child_size), // 100 + 10
388 RectF::new(vec2f(220., 0.), child_size), // 100 + 10 + 100 + 10
389 ],
390 );
391 })
392}
393 
394#[test]
395fn test_wrap_spacing_with_wrapping() {
396 App::test((), |mut app| async move {
397 let app = &mut app;
398 
399 // Test with spacing that forces wrapping
400 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
401 TestDynamicView::new(|_| {
402 let child_size = vec2f(100., 50.);
403 let mut wrap = Wrap::row().with_spacing(20.);
404 wrap.extend((0..4).map(|_| {
405 ConstrainedBox::new(Rect::new().finish())
406 .with_height(child_size.y())
407 .with_width(child_size.x())
408 .finish()
409 }));
410 
411 ConstrainedBox::new(wrap.finish())
412 .with_width(250.) // Can only fit 2 children per run: 100 + 20 + 100 = 220
413 .with_height(200.)
414 .finish()
415 })
416 });
417 test_view.update(app, |_, ctx| {
418 ctx.notify();
419 });
420 
421 let child_size = vec2f(100., 50.);
422 // First 2 children in first run, next 2 in second run
423 assert_bounds_of_rects(
424 app,
425 window_id,
426 [
427 RectF::new(vec2f(0., 0.), child_size),
428 RectF::new(vec2f(120., 0.), child_size), // 100 + 20
429 RectF::new(vec2f(0., 50.), child_size), // New run
430 RectF::new(vec2f(120., 50.), child_size), // 100 + 20 in second run
431 ],
432 );
433 })
434}
435 
436#[test]
437fn test_wrap_run_spacing() {
438 App::test((), |mut app| async move {
439 let app = &mut app;
440 
441 // Test run_spacing (vertical spacing between runs)
442 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
443 TestDynamicView::new(|_| {
444 let child_size = vec2f(80., 40.);
445 let mut wrap = Wrap::row().with_run_spacing(30.);
446 wrap.extend((0..4).map(|_| {
447 ConstrainedBox::new(Rect::new().finish())
448 .with_height(child_size.y())
449 .with_width(child_size.x())
450 .finish()
451 }));
452 
453 ConstrainedBox::new(wrap.finish())
454 .with_width(170.) // Can fit 2 children per run: 80 + 80 = 160
455 .with_height(200.)
456 .finish()
457 })
458 });
459 test_view.update(app, |_, ctx| {
460 ctx.notify();
461 });
462 
463 let child_size = vec2f(80., 40.);
464 // Two runs with 30px spacing between them
465 assert_bounds_of_rects(
466 app,
467 window_id,
468 [
469 RectF::new(vec2f(0., 0.), child_size),
470 RectF::new(vec2f(80., 0.), child_size),
471 RectF::new(vec2f(0., 70.), child_size), // 40 + 30
472 RectF::new(vec2f(80., 70.), child_size), // 40 + 30
473 ],
474 );
475 })
476}
477 
478#[test]
479fn test_wrap_both_spacings() {
480 App::test((), |mut app| async move {
481 let app = &mut app;
482 
483 // Test both spacing and run_spacing together
484 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
485 TestDynamicView::new(|_| {
486 let child_size = vec2f(60., 30.);
487 let mut wrap = Wrap::row()
488 .with_spacing(15.) // 15px between children in same run
489 .with_run_spacing(25.); // 25px between runs
490 wrap.extend((0..6).map(|_| {
491 ConstrainedBox::new(Rect::new().finish())
492 .with_height(child_size.y())
493 .with_width(child_size.x())
494 .finish()
495 }));
496 
497 ConstrainedBox::new(wrap.finish())
498 .with_width(200.) // Can fit 2 children per run: 60 + 15 + 60 = 135
499 .with_height(300.)
500 .finish()
501 })
502 });
503 test_view.update(app, |_, ctx| {
504 ctx.notify();
505 });
506 
507 let child_size = vec2f(60., 30.);
508 // 3 runs with 2 children each
509 assert_bounds_of_rects(
510 app,
511 window_id,
512 [
513 // First run
514 RectF::new(vec2f(0., 0.), child_size),
515 RectF::new(vec2f(75., 0.), child_size), // 60 + 15
516 // Second run (30 + 25 = 55)
517 RectF::new(vec2f(0., 55.), child_size),
518 RectF::new(vec2f(75., 55.), child_size),
519 // Third run (30 + 25 + 30 + 25 = 110)
520 RectF::new(vec2f(0., 110.), child_size),
521 RectF::new(vec2f(75., 110.), child_size),
522 ],
523 );
524 })
525}
526 
527#[test]
528fn test_wrap_column_spacing() {
529 App::test((), |mut app| async move {
530 let app = &mut app;
531 
532 // Test spacing in column wrap
533 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
534 TestDynamicView::new(|_| {
535 let child_size = vec2f(50., 80.);
536 let mut wrap = Wrap::column().with_spacing(12.);
537 wrap.extend((0..4).map(|_| {
538 ConstrainedBox::new(Rect::new().finish())
539 .with_height(child_size.y())
540 .with_width(child_size.x())
541 .finish()
542 }));
543 
544 ConstrainedBox::new(wrap.finish())
545 .with_width(200.)
546 .with_height(185.) // Can fit 2 children per column: 80 + 12 + 80 = 172
547 .finish()
548 })
549 });
550 test_view.update(app, |_, ctx| {
551 ctx.notify();
552 });
553 
554 let child_size = vec2f(50., 80.);
555 // Two columns with 2 children each
556 assert_bounds_of_rects(
557 app,
558 window_id,
559 [
560 RectF::new(vec2f(0., 0.), child_size),
561 RectF::new(vec2f(0., 92.), child_size), // 80 + 12
562 RectF::new(vec2f(50., 0.), child_size), // New column
563 RectF::new(vec2f(50., 92.), child_size), // 80 + 12 in second column
564 ],
565 );
566 })
567}
568 
569#[test]
570fn test_wrap_column_run_spacing() {
571 App::test((), |mut app| async move {
572 let app = &mut app;
573 
574 // Test run_spacing in column wrap (horizontal spacing between columns)
575 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
576 TestDynamicView::new(|_| {
577 let child_size = vec2f(40., 70.);
578 let mut wrap = Wrap::column().with_run_spacing(20.);
579 wrap.extend((0..4).map(|_| {
580 ConstrainedBox::new(Rect::new().finish())
581 .with_height(child_size.y())
582 .with_width(child_size.x())
583 .finish()
584 }));
585 
586 ConstrainedBox::new(wrap.finish())
587 .with_width(200.)
588 .with_height(145.) // Can fit 2 children per column: 70 + 70 = 140
589 .finish()
590 })
591 });
592 test_view.update(app, |_, ctx| {
593 ctx.notify();
594 });
595 
596 let child_size = vec2f(40., 70.);
597 // Two columns with 20px horizontal spacing between them
598 assert_bounds_of_rects(
599 app,
600 window_id,
601 [
602 RectF::new(vec2f(0., 0.), child_size),
603 RectF::new(vec2f(0., 70.), child_size),
604 RectF::new(vec2f(60., 0.), child_size), // 40 + 20
605 RectF::new(vec2f(60., 70.), child_size), // 40 + 20
606 ],
607 );
608 })
609}
610 
611#[test]
612fn test_wrap_spacing_edge_cases() {
613 App::test((), |mut app| async move {
614 let app = &mut app;
615 
616 // Test single child with spacing (should have no effect)
617 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
618 TestDynamicView::new(|_| {
619 let child_size = vec2f(80., 40.);
620 let mut wrap = Wrap::row().with_spacing(20.);
621 wrap.extend((0..1).map(|_| {
622 ConstrainedBox::new(Rect::new().finish())
623 .with_height(child_size.y())
624 .with_width(child_size.x())
625 .finish()
626 }));
627 
628 ConstrainedBox::new(wrap.finish())
629 .with_width(300.)
630 .with_height(200.)
631 .finish()
632 })
633 });
634 test_view.update(app, |_, ctx| {
635 ctx.notify();
636 });
637 
638 let child_size = vec2f(80., 40.);
639 // Single child should be positioned at origin regardless of spacing
640 assert_bounds_of_rects(app, window_id, [RectF::new(vec2f(0., 0.), child_size)]);
641 })
642}
643 
644#[test]
645fn test_wrap_zero_spacing() {
646 App::test((), |mut app| async move {
647 let app = &mut app;
648 
649 // Test zero spacing and zero run_spacing
650 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
651 TestDynamicView::new(|_| {
652 let child_size = vec2f(60., 30.);
653 let mut wrap = Wrap::row()
654 .with_spacing(0.) // No spacing between children
655 .with_run_spacing(0.); // No spacing between runs
656 wrap.extend((0..4).map(|_| {
657 ConstrainedBox::new(Rect::new().finish())
658 .with_height(child_size.y())
659 .with_width(child_size.x())
660 .finish()
661 }));
662 
663 ConstrainedBox::new(wrap.finish())
664 .with_width(130.) // Can fit 2 children per run: 60 + 60 = 120
665 .with_height(200.)
666 .finish()
667 })
668 });
669 test_view.update(app, |_, ctx| {
670 ctx.notify();
671 });
672 
673 let child_size = vec2f(60., 30.);
674 // Children should be tightly packed with no gaps
675 assert_bounds_of_rects(
676 app,
677 window_id,
678 [
679 RectF::new(vec2f(0., 0.), child_size),
680 RectF::new(vec2f(60., 0.), child_size), // No spacing
681 RectF::new(vec2f(0., 30.), child_size), // New run, no run_spacing
682 RectF::new(vec2f(60., 30.), child_size), // No spacing in second run
683 ],
684 );
685 })
686}
687 
688#[test]
689fn test_wrap_empty() {
690 App::test((), |mut app| async move {
691 let app = &mut app;
692 
693 // Test empty wrap with spacing settings
694 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
695 TestDynamicView::new(|_| {
696 let wrap = Wrap::row().with_spacing(15.).with_run_spacing(25.);
697 // No children added
698 
699 ConstrainedBox::new(wrap.finish())
700 .with_width(300.)
701 .with_height(200.)
702 .finish()
703 })
704 });
705 test_view.update(app, |_, ctx| {
706 ctx.notify();
707 });
708 
709 // Empty wrap should render no children
710 assert_bounds_of_rects(app, window_id, []);
711 })
712}
713 
714/// Tests the core fix: an oversized item in a row wrap is clamped so that
715/// subsequent items continue to be laid out on later runs.
716#[test]
717fn test_oversized_item_does_not_block_subsequent_items() {
718 App::test((), |mut app| async move {
719 let app = &mut app;
720 
721 // Container is 300x200 (row wrap). Children:
722 // 1) 100x50 (fits)
723 // 2) 500x50 (exceeds 300 on main axis, will be clamped to 300x50)
724 // 3) 100x50 (fits on a new run after the oversized item)
725 // 4) 100x50 (fits on the same run as child 3)
726 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
727 TestDynamicView::new(|_| {
728 let mut wrap = Wrap::row();
729 wrap.extend([
730 ConstrainedBox::new(Rect::new().finish())
731 .with_width(100.)
732 .with_height(50.)
733 .finish(),
734 ConstrainedBox::new(Rect::new().finish())
735 .with_width(500.)
736 .with_height(50.)
737 .finish(),
738 ConstrainedBox::new(Rect::new().finish())
739 .with_width(100.)
740 .with_height(50.)
741 .finish(),
742 ConstrainedBox::new(Rect::new().finish())
743 .with_width(100.)
744 .with_height(50.)
745 .finish(),
746 ]);
747 
748 ConstrainedBox::new(wrap.finish())
749 .with_width(300.)
750 .with_height(200.)
751 .finish()
752 })
753 });
754 test_view.update(app, |_, ctx| ctx.notify());
755 
756 // Row 1: child 1 (100x50)
757 // Row 2: child 2, laid out at 300x50 (ConstrainedBox cross-clamp) then clamped
758 // to 300x50 for layout — it takes the full main axis.
759 // Row 3: child 3 + child 4 side by side.
760 // Previously, the break at line 190 would have stopped all layout after child 2.
761 assert_bounds_of_rects(
762 app,
763 window_id,
764 [
765 RectF::new(vec2f(0., 0.), vec2f(100., 50.)),
766 // The oversized child paints at its full 500px width; the clip
767 // layer on the Wrap visually clips it to 300px.
768 RectF::new(vec2f(0., 50.), vec2f(500., 50.)),
769 RectF::new(vec2f(0., 100.), vec2f(100., 50.)),
770 RectF::new(vec2f(100., 100.), vec2f(100., 50.)),
771 ],
772 );
773 })
774}
775 
776#[test]
777fn test_fill_element_exceeds_size_constraint() {
778 App::test((), |mut app| async move {
779 let (window_id, test_view) = app.add_window(WindowStyle::NotStealFocus, |_| {
780 TestDynamicView::new(|_| {
781 ConstrainedBox::new(
782 Wrap::row()
783 .with_children([
784 ConstrainedBox::new(Rect::new().finish())
785 .with_width(100.)
786 .with_height(100.)
787 .finish(),
788 WrapFill::new(200., Rect::new().finish()).finish(),
789 ])
790 .finish(),
791 )
792 .with_width(250.)
793 .with_height(250.)
794 .finish()
795 })
796 });
797 
798 test_view.update(&mut app, |_, ctx| ctx.notify());
799 
800 // Both children are painted, and the second wraps to a new row while filling the remaining
801 // height.
802 assert_bounds_of_rects(
803 &mut app,
804 window_id,
805 [
806 RectF::new(vec2f(0., 0.), vec2f(100., 100.)),
807 RectF::new(vec2f(0., 100.), vec2f(250., 150.)),
808 ],
809 );
810 });
811}
812