tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definiciones

⎇ a74aeab0db69345646e44e759d70a8bd0b526578 src/compute/grid/explicit_grid.rs Rust · 505 líneas · 24.6 KB Historia Quién tocó qué crudo

Fix CSS Grid "auto track" / placement bugs (#481) a74aeab0d · Nico Burns ·

1//! Helper functions for intialising GridTrack's from styles
2//! This mainly consists of evaluating GridAutoTracks
3use super::types::{GridTrack, TrackCounts};
4use crate::axis::AbsoluteAxis;
5use crate::math::MaybeMath;
6use crate::resolve::ResolveOrZero;
7use crate::style::{GridTrackRepetition, LengthPercentage, NonRepeatedTrackSizingFunction, Style, TrackSizingFunction};
8use crate::style_helpers::TaffyAuto;
9use crate::sys::{GridTrackVec, Vec};
10
11#[cfg(not(feature = "std"))]
12use num_traits::float::FloatCore;
13
14/// Compute the number of rows and columns in the explicit grid
15pub(crate) fn compute_explicit_grid_size_in_axis(style: &Style, axis: AbsoluteAxis) -> u16 {
16    // Load the grid-template-rows or grid-template-columns definition (depending on the axis)
17    let template = style.grid_template_tracks(axis);
18
19    // If template contains no tracks, then there are trivially zero explcit tracks
20    if template.is_empty() {
21        return 0;
22    }
23
24    // If there are any repetitions that contains no tracks, then the whole definition should be considered invalid
25    // and we default to no explicit tracks
26    let template_has_repetitions_with_zero_tracks = template.iter().any(|track_def| match track_def {
27        TrackSizingFunction::Single(_) => false,
28        TrackSizingFunction::Repeat(_, tracks) => tracks.is_empty(),
29    });
30    if template_has_repetitions_with_zero_tracks {
31        return 0;
32    }
33
34    // Compute that number of track generated by single track definition and repetitions with a fixed repetition count
35    let non_auto_repeating_track_count = template
36        .iter()
37        .map(|track_def| {
38            use GridTrackRepetition::{AutoFill, AutoFit, Count};
39            match track_def {
40                TrackSizingFunction::Single(_) => 1,
41                TrackSizingFunction::Repeat(Count(count), tracks) => count * tracks.len() as u16,
42                TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0,
43            }
44        })
45        .sum::<u16>();
46
47    let auto_repetition_count = template.iter().filter(|track_def| track_def.is_auto_repetition()).count() as u16;
48    let all_track_defs_have_fixed_component = template.iter().all(|track_def| match track_def {
49        TrackSizingFunction::Single(sizing_function) => sizing_function.has_fixed_component(),
50        TrackSizingFunction::Repeat(_, tracks) => {
51            tracks.iter().all(|sizing_function| sizing_function.has_fixed_component())
52        }
53    });
54
55    let template_is_valid =
56        auto_repetition_count == 0 || (auto_repetition_count == 1 && all_track_defs_have_fixed_component);
57
58    // If the template is invalid because it contains multiple auto-repetition definitions or it combines an auto-repetition
59    // definition with non-fixed-size track sizing functions, then disregard it entirely and default to zero explicit tracks
60    if !template_is_valid {
61        return 0;
62    }
63
64    // If there are no repetitions, then the number of explicit tracks is simply equal to the lengths of the track definition
65    // vector (as each item in the Vec represents one track).
66    if auto_repetition_count == 0 {
67        return non_auto_repeating_track_count;
68    }
69
70    let repetition_definition = template
71        .iter()
72        .find_map(|def| {
73            use GridTrackRepetition::{AutoFill, AutoFit, Count};
74            match def {
75                TrackSizingFunction::Single(_) => None,
76                TrackSizingFunction::Repeat(Count(_), _) => None,
77                TrackSizingFunction::Repeat(AutoFit | AutoFill, tracks) => Some(tracks),
78            }
79        })
80        .unwrap();
81    let repetition_track_count = repetition_definition.len() as u16;
82
83    // Otherwise, run logic to resolve the auto-repeated track count:
84    //
85    // If the grid container has a definite size or max size in the relevant axis:
86    //   - then the number of repetitions is the largest possible positive integer that does not cause the grid to overflow the content
87    //     box of its grid container.
88    // Otherwise, if the grid container has a definite min size in the relevant axis:
89    //   - then the number of repetitions is the smallest possible positive integer that fulfills that minimum requirement
90    // Otherwise, the specified track list repeats only once.
91    let style_size = style.size.get_abs(axis).into_option();
92    let style_min_size = style.min_size.get_abs(axis).into_option();
93    let style_max_size = style.max_size.get_abs(axis).into_option();
94
95    let outer_container_size = style_size.maybe_min(style_max_size).or(style_max_size).or(style_min_size);
96    let inner_container_size = outer_container_size.map(|size| {
97        let padding_sum = style.padding.resolve_or_zero(outer_container_size).grid_axis_sum(axis);
98        let border_sum = style.border.resolve_or_zero(outer_container_size).grid_axis_sum(axis);
99        size - padding_sum - border_sum
100    });
101    let size_is_maximum = style_size.is_some() || style_max_size.is_some();
102
103    // Determine the number of repetitions
104    let num_repetitions: u16 = match inner_container_size {
105        None => 1,
106        Some(inner_container_size) => {
107            let parent_size = Some(inner_container_size);
108
109            /// ...treating each track as its max track sizing function if that is definite or as its minimum track sizing function
110            /// otherwise, flooring the max track sizing function by the min track sizing function if both are definite
111            fn track_definite_value(sizing_function: &NonRepeatedTrackSizingFunction, parent_size: Option<f32>) -> f32 {
112                let max_size = sizing_function.max.definite_value(parent_size);
113                let min_size = sizing_function.max.definite_value(parent_size);
114                max_size.map(|max| max.maybe_min(min_size)).or(min_size).unwrap()
115            }
116
117            let non_repeating_track_used_space: f32 = template
118                .iter()
119                .map(|track_def| {
120                    use GridTrackRepetition::{AutoFill, AutoFit, Count};
121                    match track_def {
122                        TrackSizingFunction::Single(sizing_function) => {
123                            track_definite_value(sizing_function, parent_size)
124                        }
125                        TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
126                            let sum = repeated_tracks
127                                .iter()
128                                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
129                                .sum::<f32>();
130                            sum * (*count as f32)
131                        }
132                        TrackSizingFunction::Repeat(AutoFit | AutoFill, _) => 0.0,
133                    }
134                })
135                .sum();
136            let gap_size = style.gap.get_abs(axis).resolve_or_zero(Some(inner_container_size));
137
138            // Compute the amount of space that a single repetition of the repeated track list takes
139            let per_repetition_track_used_space: f32 = repetition_definition
140                .iter()
141                .map(|sizing_function| track_definite_value(sizing_function, parent_size))
142                .sum::<f32>();
143
144            // We special case the first repetition here because the number of gaps in the first repetition
145            // depends on the number of non-repeating tracks in the template
146            let first_repetition_and_non_repeating_tracks_used_space = non_repeating_track_used_space
147                + per_repetition_track_used_space
148                + ((non_auto_repeating_track_count + repetition_track_count).saturating_sub(1) as f32 * gap_size);
149
150            // If a single repetition already overflows the container then we return 1 as the repetition count
151            // (the number of repetitions is floored at 1)
152            if first_repetition_and_non_repeating_tracks_used_space > inner_container_size {
153                1u16
154            } else {
155                let per_repetition_gap_used_space = (repetition_definition.len() as f32) * gap_size;
156                let per_repetition_used_space = per_repetition_track_used_space + per_repetition_gap_used_space;
157                let num_repetition_that_fit = (inner_container_size
158                    - first_repetition_and_non_repeating_tracks_used_space)
159                    / per_repetition_used_space;
160
161                // If the container size is a preferred or maximum size:
162                //   Then we return the maximum number of repetitions that fit into the container without overflowing.
163                // If the container size is a minimum size:
164                //   - Then we return the minimum number of repititions required to overflow the size.
165                //
166                // In all cases we add the additional repetition that was already accounted for in the special-case computation above
167                if size_is_maximum {
168                    (num_repetition_that_fit.floor() as u16) + 1
169                } else {
170                    (num_repetition_that_fit.ceil() as u16) + 1
171                }
172            }
173        }
174    };
175
176    non_auto_repeating_track_count + (repetition_track_count * num_repetitions)
177}
178
179/// Resolve the track sizing functions of explicit tracks, automatically created tracks, and gutters
180/// given a set of track counts and all of the relevant styles
181pub(super) fn initialize_grid_tracks(
182    tracks: &mut Vec<GridTrack>,
183    counts: TrackCounts,
184    track_template: &GridTrackVec<TrackSizingFunction>,
185    auto_tracks: &Vec<NonRepeatedTrackSizingFunction>,
186    gap: LengthPercentage,
187    track_has_items: impl Fn(usize) -> bool,
188) {
189    // Clear vector (in case this is a re-layout), reserve space for all tracks ahead of time to reduce allocations,
190    // and push the initial gutter
191    tracks.clear();
192    tracks.reserve((counts.len() * 2) + 1);
193    tracks.push(GridTrack::gutter(gap));
194
195    // Create negative implicit tracks
196    if counts.negative_implicit > 0 {
197        if auto_tracks.is_empty() {
198            let iter = core::iter::repeat(NonRepeatedTrackSizingFunction::AUTO);
199            create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
200        } else {
201            let offset = auto_tracks.len() - (counts.negative_implicit as usize % auto_tracks.len());
202            let iter = auto_tracks.iter().copied().cycle().skip(offset);
203            create_implicit_tracks(tracks, counts.negative_implicit, iter, gap)
204        }
205    }
206
207    let mut current_track_index = (counts.negative_implicit) as usize;
208
209    // Create explicit tracks
210    // An explicit check against the count (rather than just relying on track_template being empty) is required here
211    // because a count of zero can result from the track_template being invalid, in which case it should be ignored.
212    if counts.explicit > 0 {
213        track_template.iter().for_each(|track_sizing_function| {
214            use GridTrackRepetition::{AutoFill, AutoFit, Count};
215            match track_sizing_function {
216                TrackSizingFunction::Single(sizing_function) => {
217                    tracks.push(GridTrack::new(
218                        sizing_function.min_sizing_function(),
219                        sizing_function.max_sizing_function(),
220                    ));
221                    tracks.push(GridTrack::gutter(gap));
222                    current_track_index += 1;
223                }
224                TrackSizingFunction::Repeat(Count(count), repeated_tracks) => {
225                    let track_iter = repeated_tracks.iter().cycle().take(repeated_tracks.len() * *count as usize);
226                    track_iter.for_each(|sizing_function| {
227                        tracks.push(GridTrack::new(
228                            sizing_function.min_sizing_function(),
229                            sizing_function.max_sizing_function(),
230                        ));
231                        tracks.push(GridTrack::gutter(gap));
232                        current_track_index += 1;
233                    });
234                }
235                TrackSizingFunction::Repeat(repetition_kind @ (AutoFit | AutoFill), repeated_tracks) => {
236                    let auto_repeated_track_count = (counts.explicit - (track_template.len() as u16 - 1)) as usize;
237                    let iter = repeated_tracks.iter().copied().cycle();
238                    for track_def in iter.take(auto_repeated_track_count) {
239                        let mut track =
240                            GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function());
241                        let mut gutter = GridTrack::gutter(gap);
242
243                        // Auto-fit tracks that don't contain should be collapsed.
244                        if *repetition_kind == AutoFit && !track_has_items(current_track_index) {
245                            track.collapse();
246                            gutter.collapse();
247                        }
248
249                        tracks.push(track);
250                        tracks.push(gutter);
251
252                        current_track_index += 1;
253                    }
254                }
255            }
256        });
257    }
258
259    // Create positive implicit tracks
260    if auto_tracks.is_empty() {
261        let iter = core::iter::repeat(NonRepeatedTrackSizingFunction::AUTO);
262        create_implicit_tracks(tracks, counts.positive_implicit, iter, gap)
263    } else {
264        let iter = auto_tracks.iter().copied().cycle();
265        create_implicit_tracks(tracks, counts.positive_implicit, iter, gap)
266    }
267
268    // Mark first and last grid lines as collapsed
269    tracks.first_mut().unwrap().collapse();
270    tracks.last_mut().unwrap().collapse();
271}
272
273/// Utility function for repeating logic of creating implicit tracks
274fn create_implicit_tracks(
275    tracks: &mut Vec<GridTrack>,
276    count: u16,
277    mut auto_tracks_iter: impl Iterator<Item = NonRepeatedTrackSizingFunction>,
278    gap: LengthPercentage,
279) {
280    for _ in 0..count {
281        let track_def = auto_tracks_iter.next().unwrap();
282        tracks.push(GridTrack::new(track_def.min_sizing_function(), track_def.max_sizing_function()));
283        tracks.push(GridTrack::gutter(gap));
284    }
285}
286
287#[cfg(test)]
288mod test {
289    use super::compute_explicit_grid_size_in_axis;
290    use super::initialize_grid_tracks;
291    use crate::axis::AbsoluteAxis;
292    use crate::compute::grid::types::GridTrackKind;
293    use crate::compute::grid::types::TrackCounts;
294    use crate::compute::grid::util::*;
295    use crate::prelude::*;
296
297    #[test]
298    fn explicit_grid_sizing_no_repeats() {
299        let grid_style = (600.0, 600.0, 2, 4).into_grid();
300        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
301        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
302        assert_eq!(width, 2);
303        assert_eq!(height, 4);
304    }
305
306    #[test]
307    fn explicit_grid_sizing_auto_fill_exact_fit() {
308        use GridTrackRepetition::AutoFill;
309        let grid_style = Style {
310            display: Display::Grid,
311            size: Size { width: points(120.0), height: points(80.0) },
312            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0)])],
313            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
314            ..Default::default()
315        };
316        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
317        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
318        assert_eq!(width, 3);
319        assert_eq!(height, 4);
320    }
321
322    #[test]
323    fn explicit_grid_sizing_auto_fill_non_exact_fit() {
324        use GridTrackRepetition::AutoFill;
325        let grid_style = Style {
326            display: Display::Grid,
327            size: Size { width: points(140.0), height: points(90.0) },
328            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0)])],
329            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
330            ..Default::default()
331        };
332        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
333        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
334        assert_eq!(width, 3);
335        assert_eq!(height, 4);
336    }
337
338    #[test]
339    fn explicit_grid_sizing_auto_fill_min_size_exact_fit() {
340        use GridTrackRepetition::AutoFill;
341        let grid_style = Style {
342            display: Display::Grid,
343            min_size: Size { width: points(120.0), height: points(80.0) },
344            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0)])],
345            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
346            ..Default::default()
347        };
348        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
349        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
350        assert_eq!(width, 3);
351        assert_eq!(height, 4);
352    }
353
354    #[test]
355    fn explicit_grid_sizing_auto_fill_min_size_non_exact_fit() {
356        use GridTrackRepetition::AutoFill;
357        let grid_style = Style {
358            display: Display::Grid,
359            min_size: Size { width: points(140.0), height: points(90.0) },
360            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0)])],
361            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
362            ..Default::default()
363        };
364        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
365        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
366        assert_eq!(width, 4);
367        assert_eq!(height, 5);
368    }
369
370    #[test]
371    fn explicit_grid_sizing_auto_fill_multiple_repeated_tracks() {
372        use GridTrackRepetition::AutoFill;
373        let grid_style = Style {
374            display: Display::Grid,
375            size: Size { width: points(140.0), height: points(100.0) },
376            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0), points(20.0)])],
377            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0), points(10.0)])],
378            ..Default::default()
379        };
380        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
381        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
382        assert_eq!(width, 4); // 2 repetitions * 2 repeated tracks = 4 tracks in total
383        assert_eq!(height, 6); // 3 repetitions * 2 repeated tracks = 4 tracks in total
384    }
385
386    #[test]
387    fn explicit_grid_sizing_auto_fill_gap() {
388        use GridTrackRepetition::AutoFill;
389        let grid_style = Style {
390            display: Display::Grid,
391            size: Size { width: points(140.0), height: points(100.0) },
392            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0)])],
393            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
394            gap: points(20.0),
395            ..Default::default()
396        };
397        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
398        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
399        assert_eq!(width, 2); // 2 tracks + 1 gap
400        assert_eq!(height, 3); // 3 tracks + 2 gaps
401    }
402
403    #[test]
404    fn explicit_grid_sizing_no_defined_size() {
405        use GridTrackRepetition::AutoFill;
406        let grid_style = Style {
407            display: Display::Grid,
408            grid_template_columns: vec![repeat(AutoFill, vec![points(40.0), percent(0.5), points(20.0)])],
409            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
410            gap: points(20.0),
411            ..Default::default()
412        };
413        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
414        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
415        assert_eq!(width, 3);
416        assert_eq!(height, 1);
417    }
418
419    #[test]
420    fn explicit_grid_sizing_mix_repeated_and_non_repeated() {
421        use GridTrackRepetition::AutoFill;
422        let grid_style = Style {
423            display: Display::Grid,
424            size: Size { width: points(140.0), height: points(100.0) },
425            grid_template_columns: vec![points(20.0), repeat(AutoFill, vec![points(40.0)])],
426            grid_template_rows: vec![points(40.0), repeat(AutoFill, vec![points(20.0)])],
427            gap: points(20.0),
428            ..Default::default()
429        };
430        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
431        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
432        assert_eq!(width, 3); // 3 tracks + 2 gaps
433        assert_eq!(height, 2); // 2 tracks + 1 gap
434    }
435
436    #[test]
437    fn explicit_grid_sizing_mix_with_padding() {
438        use GridTrackRepetition::AutoFill;
439        let grid_style = Style {
440            display: Display::Grid,
441            size: Size { width: points(120.0), height: points(120.0) },
442            padding: Rect { left: points(10.0), right: points(10.0), top: points(20.0), bottom: points(20.0) },
443            grid_template_columns: vec![repeat(AutoFill, vec![points(20.0)])],
444            grid_template_rows: vec![repeat(AutoFill, vec![points(20.0)])],
445            ..Default::default()
446        };
447        let width = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Horizontal);
448        let height = compute_explicit_grid_size_in_axis(&grid_style, AbsoluteAxis::Vertical);
449        assert_eq!(width, 5); // 40px horizontal padding
450        assert_eq!(height, 4); // 20px vertical padding
451    }
452
453    #[test]
454    fn test_initialize_grid_tracks() {
455        let px0 = LengthPercentage::Points(0.0);
456        let px20 = LengthPercentage::Points(20.0);
457        let px100 = LengthPercentage::Points(100.0);
458
459        // Setup test
460        let track_template = vec![points(100.0), minmax(points(100.0), fr(2.0)), fr(1.0)];
461        let track_counts =
462            TrackCounts { negative_implicit: 3, explicit: track_template.len() as u16, positive_implicit: 3 };
463        let auto_tracks = vec![auto(), points(100.0)];
464        let gap = px20;
465
466        // Call function
467        let mut tracks = Vec::new();
468        initialize_grid_tracks(&mut tracks, track_counts, &track_template, &auto_tracks, gap, |_| false);
469
470        // Assertions
471        let expected = vec![
472            // Gutter
473            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px0), MaxTrackSizingFunction::Fixed(px0)),
474            // Negative implict tracks
475            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
476            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
477            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
478            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
479            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
480            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
481            // Explicit tracks
482            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
483            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
484            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fraction(2.0)), // Note: separate min-max functions
485            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
486            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Fraction(1.0)), // Note: min sizing function of flex sizing functions is auto
487            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
488            // Positive implict tracks
489            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
490            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
491            (GridTrackKind::Track, MinTrackSizingFunction::Fixed(px100), MaxTrackSizingFunction::Fixed(px100)),
492            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px20), MaxTrackSizingFunction::Fixed(px20)),
493            (GridTrackKind::Track, MinTrackSizingFunction::Auto, MaxTrackSizingFunction::Auto),
494            (GridTrackKind::Gutter, MinTrackSizingFunction::Fixed(px0), MaxTrackSizingFunction::Fixed(px0)),
495        ];
496
497        assert_eq!(tracks.len(), expected.len(), "Number of tracks doesn't match");
498
499        for (idx, (actual, (kind, min, max))) in tracks.into_iter().zip(expected).enumerate() {
500            assert_eq!(actual.kind, kind, "Track {idx} (0-based index)");
501            assert_eq!(actual.min_track_sizing_function, min, "Track {idx} (0-based index)");
502            assert_eq!(actual.max_track_sizing_function, max, "Track {idx} (0-based index)");
503        }
504    }
505}