Skip to main content

presentar_layout/
grid.rs

1//! CSS Grid-like layout system.
2//!
3//! This module provides a grid layout system similar to CSS Grid,
4//! supporting:
5//! - Fixed and flexible track sizes (px, fr units)
6//! - Row and column spans
7//! - Gaps between tracks
8//! - Named grid areas
9//! - Auto-placement
10
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14/// A track size specification.
15#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub enum TrackSize {
17    /// Fixed size in pixels
18    Px(f32),
19    /// Flexible fraction of remaining space
20    Fr(f32),
21    /// Size based on content
22    Auto,
23    /// Minimum content size
24    MinContent,
25    /// Maximum content size
26    MaxContent,
27}
28
29impl Default for TrackSize {
30    fn default() -> Self {
31        Self::Fr(1.0)
32    }
33}
34
35impl TrackSize {
36    /// Create a fixed pixel size.
37    #[must_use]
38    pub const fn px(value: f32) -> Self {
39        Self::Px(value)
40    }
41
42    /// Create a flexible fraction.
43    #[must_use]
44    pub const fn fr(value: f32) -> Self {
45        Self::Fr(value)
46    }
47
48    /// Auto size.
49    pub const AUTO: Self = Self::Auto;
50}
51
52/// Grid template definition.
53#[derive(Debug, Clone, Default, Serialize, Deserialize)]
54pub struct GridTemplate {
55    /// Column track sizes
56    pub columns: Vec<TrackSize>,
57    /// Row track sizes
58    pub rows: Vec<TrackSize>,
59    /// Gap between columns
60    pub column_gap: f32,
61    /// Gap between rows
62    pub row_gap: f32,
63    /// Named grid areas (row-major order)
64    pub areas: HashMap<String, GridArea>,
65}
66
67impl GridTemplate {
68    /// Create a new empty grid template.
69    #[must_use]
70    pub fn new() -> Self {
71        Self::default()
72    }
73
74    /// Create a grid with specified columns and default 1fr rows.
75    #[must_use]
76    pub fn columns(cols: impl IntoIterator<Item = TrackSize>) -> Self {
77        Self {
78            columns: cols.into_iter().collect(),
79            ..Self::default()
80        }
81    }
82
83    /// Create a 12-column grid (common dashboard layout).
84    #[must_use]
85    pub fn twelve_column() -> Self {
86        Self {
87            columns: vec![TrackSize::Fr(1.0); 12],
88            column_gap: 16.0,
89            row_gap: 16.0,
90            ..Self::default()
91        }
92    }
93
94    /// Set row track sizes.
95    #[must_use]
96    pub fn with_rows(mut self, rows: impl IntoIterator<Item = TrackSize>) -> Self {
97        self.rows = rows.into_iter().collect();
98        self
99    }
100
101    /// Set column gap.
102    #[must_use]
103    pub const fn with_column_gap(mut self, gap: f32) -> Self {
104        self.column_gap = gap;
105        self
106    }
107
108    /// Set row gap.
109    #[must_use]
110    pub const fn with_row_gap(mut self, gap: f32) -> Self {
111        self.row_gap = gap;
112        self
113    }
114
115    /// Set both gaps.
116    #[must_use]
117    pub const fn with_gap(mut self, gap: f32) -> Self {
118        self.column_gap = gap;
119        self.row_gap = gap;
120        self
121    }
122
123    /// Add a named area.
124    #[must_use]
125    pub fn with_area(mut self, name: impl Into<String>, area: GridArea) -> Self {
126        self.areas.insert(name.into(), area);
127        self
128    }
129
130    /// Get number of columns.
131    #[must_use]
132    pub fn column_count(&self) -> usize {
133        self.columns.len()
134    }
135
136    /// Get number of explicit rows.
137    #[must_use]
138    pub fn row_count(&self) -> usize {
139        self.rows.len()
140    }
141}
142
143/// A named grid area spanning rows and columns.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
145pub struct GridArea {
146    /// Starting row (0-indexed)
147    pub row_start: usize,
148    /// Ending row (exclusive)
149    pub row_end: usize,
150    /// Starting column (0-indexed)
151    pub col_start: usize,
152    /// Ending column (exclusive)
153    pub col_end: usize,
154}
155
156impl GridArea {
157    /// Create a new grid area.
158    #[must_use]
159    pub const fn new(row_start: usize, col_start: usize, row_end: usize, col_end: usize) -> Self {
160        Self {
161            row_start,
162            row_end,
163            col_start,
164            col_end,
165        }
166    }
167
168    /// Create a single-cell area.
169    #[must_use]
170    pub const fn cell(row: usize, col: usize) -> Self {
171        Self {
172            row_start: row,
173            row_end: row + 1,
174            col_start: col,
175            col_end: col + 1,
176        }
177    }
178
179    /// Create an area spanning multiple columns in a single row.
180    #[must_use]
181    pub const fn row_span(row: usize, col_start: usize, col_end: usize) -> Self {
182        Self {
183            row_start: row,
184            row_end: row + 1,
185            col_start,
186            col_end,
187        }
188    }
189
190    /// Create an area spanning multiple rows in a single column.
191    #[must_use]
192    pub const fn col_span(col: usize, row_start: usize, row_end: usize) -> Self {
193        Self {
194            row_start,
195            row_end,
196            col_start: col,
197            col_end: col + 1,
198        }
199    }
200
201    /// Get the number of rows this area spans.
202    #[must_use]
203    pub const fn row_span_count(&self) -> usize {
204        self.row_end.saturating_sub(self.row_start)
205    }
206
207    /// Get the number of columns this area spans.
208    #[must_use]
209    pub const fn col_span_count(&self) -> usize {
210        self.col_end.saturating_sub(self.col_start)
211    }
212}
213
214/// Grid item placement properties.
215#[derive(Debug, Clone, Default, Serialize, Deserialize)]
216pub struct GridItem {
217    /// Column start (1-indexed like CSS, 0 = auto)
218    pub column_start: usize,
219    /// Column end (exclusive, 0 = auto)
220    pub column_end: usize,
221    /// Row start (1-indexed like CSS, 0 = auto)
222    pub row_start: usize,
223    /// Row end (exclusive, 0 = auto)
224    pub row_end: usize,
225    /// Named area to place in
226    pub area: Option<String>,
227    /// Column span (alternative to column_end)
228    pub column_span: usize,
229    /// Row span (alternative to row_end)
230    pub row_span: usize,
231    /// Alignment within cell (horizontal)
232    pub justify_self: Option<GridAlign>,
233    /// Alignment within cell (vertical)
234    pub align_self: Option<GridAlign>,
235}
236
237impl GridItem {
238    /// Create a new grid item with auto placement.
239    #[must_use]
240    pub fn new() -> Self {
241        Self {
242            column_span: 1,
243            row_span: 1,
244            ..Self::default()
245        }
246    }
247
248    /// Place in a specific column.
249    #[must_use]
250    pub const fn column(mut self, col: usize) -> Self {
251        self.column_start = col;
252        self.column_end = col + 1;
253        self
254    }
255
256    /// Place in a specific row.
257    #[must_use]
258    pub const fn row(mut self, row: usize) -> Self {
259        self.row_start = row;
260        self.row_end = row + 1;
261        self
262    }
263
264    /// Span multiple columns.
265    #[must_use]
266    pub const fn span_columns(mut self, span: usize) -> Self {
267        self.column_span = span;
268        self
269    }
270
271    /// Span multiple rows.
272    #[must_use]
273    pub const fn span_rows(mut self, span: usize) -> Self {
274        self.row_span = span;
275        self
276    }
277
278    /// Place in a named area.
279    #[must_use]
280    pub fn in_area(mut self, area: impl Into<String>) -> Self {
281        self.area = Some(area.into());
282        self
283    }
284
285    /// Set horizontal alignment.
286    #[must_use]
287    pub const fn justify_self(mut self, align: GridAlign) -> Self {
288        self.justify_self = Some(align);
289        self
290    }
291
292    /// Set vertical alignment.
293    #[must_use]
294    pub const fn align_self(mut self, align: GridAlign) -> Self {
295        self.align_self = Some(align);
296        self
297    }
298
299    /// Get effective column span.
300    #[must_use]
301    pub fn effective_column_span(&self) -> usize {
302        if self.column_end > self.column_start {
303            self.column_end - self.column_start
304        } else {
305            self.column_span.max(1)
306        }
307    }
308
309    /// Get effective row span.
310    #[must_use]
311    pub fn effective_row_span(&self) -> usize {
312        if self.row_end > self.row_start {
313            self.row_end - self.row_start
314        } else {
315            self.row_span.max(1)
316        }
317    }
318}
319
320/// Alignment within a grid cell.
321#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
322pub enum GridAlign {
323    /// Align to start
324    Start,
325    /// Align to end
326    End,
327    /// Center
328    #[default]
329    Center,
330    /// Stretch to fill
331    Stretch,
332}
333
334/// Auto-placement flow direction.
335#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
336pub enum GridAutoFlow {
337    /// Fill rows first
338    #[default]
339    Row,
340    /// Fill columns first
341    Column,
342    /// Row with dense packing
343    RowDense,
344    /// Column with dense packing
345    ColumnDense,
346}
347
348/// Computed grid layout result.
349#[derive(Debug, Clone, Default)]
350pub struct GridLayout {
351    /// Computed column positions and sizes (start, size)
352    pub columns: Vec<(f32, f32)>,
353    /// Computed row positions and sizes (start, size)
354    pub rows: Vec<(f32, f32)>,
355    /// Total grid width
356    pub width: f32,
357    /// Total grid height
358    pub height: f32,
359}
360
361impl GridLayout {
362    /// Get the bounds for a grid area.
363    #[must_use]
364    pub fn area_bounds(&self, area: &GridArea) -> Option<(f32, f32, f32, f32)> {
365        if area.col_start >= self.columns.len() || area.row_start >= self.rows.len() {
366            return None;
367        }
368
369        let col_start = area.col_start;
370        let col_end = area.col_end.min(self.columns.len());
371        let row_start = area.row_start;
372        let row_end = area.row_end.min(self.rows.len());
373
374        let x = self.columns.get(col_start).map(|(pos, _)| *pos)?;
375        let y = self.rows.get(row_start).map(|(pos, _)| *pos)?;
376
377        let width: f32 = self.columns[col_start..col_end]
378            .iter()
379            .map(|(_, size)| size)
380            .sum();
381        let height: f32 = self.rows[row_start..row_end]
382            .iter()
383            .map(|(_, size)| size)
384            .sum();
385
386        Some((x, y, width, height))
387    }
388
389    /// Get the bounds for a grid item.
390    #[must_use]
391    pub fn item_bounds(
392        &self,
393        item: &GridItem,
394        row: usize,
395        col: usize,
396    ) -> Option<(f32, f32, f32, f32)> {
397        let area = GridArea::new(
398            row,
399            col,
400            row + item.effective_row_span(),
401            col + item.effective_column_span(),
402        );
403        self.area_bounds(&area)
404    }
405}
406
407/// Compute grid track sizes.
408pub fn compute_grid_layout(
409    template: &GridTemplate,
410    available_width: f32,
411    available_height: f32,
412    child_sizes: &[(f32, f32)],
413) -> GridLayout {
414    // Calculate column sizes
415    let columns = compute_track_sizes(
416        &template.columns,
417        available_width,
418        template.column_gap,
419        child_sizes
420            .iter()
421            .map(|(w, _)| *w)
422            .collect::<Vec<_>>()
423            .as_slice(),
424    );
425
426    // Determine row count from items if not specified
427    let row_count = if template.rows.is_empty() {
428        // Auto-generate rows based on number of items and columns
429        let col_count = template.columns.len().max(1);
430        (child_sizes.len() + col_count - 1) / col_count
431    } else {
432        template.rows.len()
433    };
434
435    // Calculate row sizes
436    let row_templates: Vec<TrackSize> = if template.rows.is_empty() {
437        vec![TrackSize::Auto; row_count]
438    } else {
439        template.rows.clone()
440    };
441
442    let rows = compute_track_sizes(
443        &row_templates,
444        available_height,
445        template.row_gap,
446        child_sizes
447            .iter()
448            .map(|(_, h)| *h)
449            .collect::<Vec<_>>()
450            .as_slice(),
451    );
452
453    let width = columns.last().map(|(pos, size)| pos + size).unwrap_or(0.0);
454    let height = rows.last().map(|(pos, size)| pos + size).unwrap_or(0.0);
455
456    GridLayout {
457        columns,
458        rows,
459        width,
460        height,
461    }
462}
463
464/// Compute track sizes (used for both rows and columns).
465fn compute_track_sizes(
466    tracks: &[TrackSize],
467    available: f32,
468    gap: f32,
469    content_sizes: &[f32],
470) -> Vec<(f32, f32)> {
471    if tracks.is_empty() {
472        return Vec::new();
473    }
474
475    let track_count = tracks.len();
476    let total_gap = gap * (track_count.saturating_sub(1)) as f32;
477    let available_for_tracks = (available - total_gap).max(0.0);
478
479    // First pass: compute fixed and auto sizes
480    let mut sizes: Vec<f32> = Vec::with_capacity(track_count);
481    let mut total_fixed = 0.0;
482    let mut total_fr = 0.0;
483
484    for (i, track) in tracks.iter().enumerate() {
485        match track {
486            TrackSize::Px(px) => {
487                sizes.push(*px);
488                total_fixed += px;
489            }
490            TrackSize::Fr(fr) => {
491                sizes.push(0.0); // Placeholder
492                total_fr += fr;
493            }
494            TrackSize::Auto | TrackSize::MinContent | TrackSize::MaxContent => {
495                let content_size = content_sizes.get(i).copied().unwrap_or(0.0);
496                sizes.push(content_size);
497                total_fixed += content_size;
498            }
499        }
500    }
501
502    // Second pass: distribute flexible space
503    let remaining = (available_for_tracks - total_fixed).max(0.0);
504    if total_fr > 0.0 {
505        for (i, track) in tracks.iter().enumerate() {
506            if let TrackSize::Fr(fr) = track {
507                sizes[i] = remaining * fr / total_fr;
508            }
509        }
510    }
511
512    // Convert to positions
513    let mut result = Vec::with_capacity(track_count);
514    let mut position = 0.0;
515
516    for (i, &size) in sizes.iter().enumerate() {
517        result.push((position, size));
518        position += size;
519        if i < track_count - 1 {
520            position += gap;
521        }
522    }
523
524    result
525}
526
527/// Get explicit placement from item's row/column or named area.
528fn get_explicit_position(item: &GridItem, template: &GridTemplate) -> Option<(usize, usize)> {
529    if item.column_start > 0 && item.row_start > 0 {
530        return Some((item.row_start - 1, item.column_start - 1));
531    }
532    item.area
533        .as_ref()
534        .and_then(|name| template.areas.get(name))
535        .map(|area| (area.row_start, area.col_start))
536}
537
538/// Mark grid cells as occupied.
539fn mark_occupied(
540    occupied: &mut Vec<Vec<bool>>,
541    row: usize,
542    col: usize,
543    row_span: usize,
544    col_span: usize,
545    col_count: usize,
546) {
547    ensure_rows(occupied, row + row_span, col_count);
548    for r in row..(row + row_span) {
549        for c in col..(col + col_span).min(col_count) {
550            occupied[r][c] = true;
551        }
552    }
553}
554
555/// Auto-place items in a grid.
556#[must_use]
557pub fn auto_place_items(
558    template: &GridTemplate,
559    items: &[GridItem],
560    flow: GridAutoFlow,
561) -> Vec<(usize, usize)> {
562    let col_count = template.columns.len().max(1);
563    let mut occupied: Vec<Vec<bool>> = Vec::new();
564    let mut placements = Vec::with_capacity(items.len());
565
566    for item in items {
567        // Check for explicit placement
568        if let Some(pos) = get_explicit_position(item, template) {
569            placements.push(pos);
570            continue;
571        }
572
573        // Auto-place
574        let col_span = item.effective_column_span();
575        let row_span = item.effective_row_span();
576
577        let (row, col) = match flow {
578            GridAutoFlow::Row | GridAutoFlow::RowDense => {
579                find_next_position_row(&mut occupied, col_count, col_span, row_span)
580            }
581            GridAutoFlow::Column | GridAutoFlow::ColumnDense => {
582                find_next_position_column(&mut occupied, col_count, col_span, row_span)
583            }
584        };
585
586        mark_occupied(&mut occupied, row, col, row_span, col_span, col_count);
587        placements.push((row, col));
588    }
589
590    placements
591}
592
593fn find_next_position_row(
594    occupied: &mut Vec<Vec<bool>>,
595    col_count: usize,
596    col_span: usize,
597    row_span: usize,
598) -> (usize, usize) {
599    let mut row = 0;
600    loop {
601        ensure_rows(occupied, row + row_span, col_count);
602        for col in 0..=(col_count.saturating_sub(col_span)) {
603            if can_place(occupied, row, col, row_span, col_span) {
604                return (row, col);
605            }
606        }
607        row += 1;
608    }
609}
610
611fn find_next_position_column(
612    occupied: &mut Vec<Vec<bool>>,
613    col_count: usize,
614    col_span: usize,
615    row_span: usize,
616) -> (usize, usize) {
617    for col in 0..=(col_count.saturating_sub(col_span)) {
618        let mut row = 0;
619        loop {
620            ensure_rows(occupied, row + row_span, col_count);
621            if can_place(occupied, row, col, row_span, col_span) {
622                return (row, col);
623            }
624            row += 1;
625            if row > 1000 {
626                break; // Safety limit
627            }
628        }
629    }
630    (0, 0) // Fallback
631}
632
633fn ensure_rows(occupied: &mut Vec<Vec<bool>>, min_rows: usize, col_count: usize) {
634    while occupied.len() < min_rows {
635        occupied.push(vec![false; col_count]);
636    }
637}
638
639fn can_place(
640    occupied: &[Vec<bool>],
641    row: usize,
642    col: usize,
643    row_span: usize,
644    col_span: usize,
645) -> bool {
646    for r in row..(row + row_span) {
647        for c in col..(col + col_span) {
648            if let Some(row_data) = occupied.get(r) {
649                if row_data.get(c).copied().unwrap_or(false) {
650                    return false;
651                }
652            }
653        }
654    }
655    true
656}
657
658#[cfg(test)]
659mod tests {
660    use super::*;
661
662    // =========================================================================
663    // TrackSize Tests
664    // =========================================================================
665
666    #[test]
667    fn test_track_size_default() {
668        assert_eq!(TrackSize::default(), TrackSize::Fr(1.0));
669    }
670
671    #[test]
672    fn test_track_size_px() {
673        let size = TrackSize::px(100.0);
674        assert_eq!(size, TrackSize::Px(100.0));
675    }
676
677    #[test]
678    fn test_track_size_fr() {
679        let size = TrackSize::fr(2.0);
680        assert_eq!(size, TrackSize::Fr(2.0));
681    }
682
683    // =========================================================================
684    // GridTemplate Tests
685    // =========================================================================
686
687    #[test]
688    fn test_grid_template_new() {
689        let template = GridTemplate::new();
690        assert!(template.columns.is_empty());
691        assert!(template.rows.is_empty());
692        assert_eq!(template.column_gap, 0.0);
693        assert_eq!(template.row_gap, 0.0);
694    }
695
696    #[test]
697    fn test_grid_template_columns() {
698        let template = GridTemplate::columns([TrackSize::px(100.0), TrackSize::fr(1.0)]);
699        assert_eq!(template.columns.len(), 2);
700        assert_eq!(template.columns[0], TrackSize::Px(100.0));
701        assert_eq!(template.columns[1], TrackSize::Fr(1.0));
702    }
703
704    #[test]
705    fn test_grid_template_twelve_column() {
706        let template = GridTemplate::twelve_column();
707        assert_eq!(template.columns.len(), 12);
708        assert_eq!(template.column_gap, 16.0);
709        assert_eq!(template.row_gap, 16.0);
710    }
711
712    #[test]
713    fn test_grid_template_builder() {
714        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(2.0)])
715            .with_rows([TrackSize::px(50.0)])
716            .with_gap(8.0);
717
718        assert_eq!(template.columns.len(), 2);
719        assert_eq!(template.rows.len(), 1);
720        assert_eq!(template.column_gap, 8.0);
721        assert_eq!(template.row_gap, 8.0);
722    }
723
724    #[test]
725    fn test_grid_template_with_area() {
726        let template = GridTemplate::twelve_column()
727            .with_area("header", GridArea::row_span(0, 0, 12))
728            .with_area("sidebar", GridArea::col_span(0, 1, 4));
729
730        assert!(template.areas.contains_key("header"));
731        assert!(template.areas.contains_key("sidebar"));
732    }
733
734    // =========================================================================
735    // GridArea Tests
736    // =========================================================================
737
738    #[test]
739    fn test_grid_area_new() {
740        let area = GridArea::new(1, 2, 3, 4);
741        assert_eq!(area.row_start, 1);
742        assert_eq!(area.col_start, 2);
743        assert_eq!(area.row_end, 3);
744        assert_eq!(area.col_end, 4);
745    }
746
747    #[test]
748    fn test_grid_area_cell() {
749        let area = GridArea::cell(2, 3);
750        assert_eq!(area.row_start, 2);
751        assert_eq!(area.row_end, 3);
752        assert_eq!(area.col_start, 3);
753        assert_eq!(area.col_end, 4);
754    }
755
756    #[test]
757    fn test_grid_area_row_span() {
758        let area = GridArea::row_span(0, 0, 6);
759        assert_eq!(area.row_span_count(), 1);
760        assert_eq!(area.col_span_count(), 6);
761    }
762
763    #[test]
764    fn test_grid_area_col_span() {
765        let area = GridArea::col_span(0, 0, 3);
766        assert_eq!(area.row_span_count(), 3);
767        assert_eq!(area.col_span_count(), 1);
768    }
769
770    // =========================================================================
771    // GridItem Tests
772    // =========================================================================
773
774    #[test]
775    fn test_grid_item_new() {
776        let item = GridItem::new();
777        assert_eq!(item.column_span, 1);
778        assert_eq!(item.row_span, 1);
779    }
780
781    #[test]
782    fn test_grid_item_builder() {
783        let item = GridItem::new()
784            .column(2)
785            .row(1)
786            .span_columns(3)
787            .span_rows(2);
788
789        assert_eq!(item.column_start, 2);
790        assert_eq!(item.row_start, 1);
791        assert_eq!(item.column_span, 3);
792        assert_eq!(item.row_span, 2);
793    }
794
795    #[test]
796    fn test_grid_item_effective_span() {
797        let item1 = GridItem::new().span_columns(2);
798        assert_eq!(item1.effective_column_span(), 2);
799
800        let mut item2 = GridItem::new();
801        item2.column_start = 1;
802        item2.column_end = 4;
803        assert_eq!(item2.effective_column_span(), 3);
804    }
805
806    #[test]
807    fn test_grid_item_in_area() {
808        let item = GridItem::new().in_area("sidebar");
809        assert_eq!(item.area, Some("sidebar".to_string()));
810    }
811
812    // =========================================================================
813    // GridAlign Tests
814    // =========================================================================
815
816    #[test]
817    fn test_grid_align_default() {
818        assert_eq!(GridAlign::default(), GridAlign::Center);
819    }
820
821    // =========================================================================
822    // compute_track_sizes Tests
823    // =========================================================================
824
825    #[test]
826    fn test_compute_track_sizes_fixed() {
827        let tracks = vec![TrackSize::Px(100.0), TrackSize::Px(200.0)];
828        let result = compute_track_sizes(&tracks, 400.0, 0.0, &[]);
829
830        assert_eq!(result.len(), 2);
831        assert_eq!(result[0], (0.0, 100.0));
832        assert_eq!(result[1], (100.0, 200.0));
833    }
834
835    #[test]
836    fn test_compute_track_sizes_fr() {
837        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
838        let result = compute_track_sizes(&tracks, 200.0, 0.0, &[]);
839
840        assert_eq!(result.len(), 2);
841        assert_eq!(result[0], (0.0, 100.0));
842        assert_eq!(result[1], (100.0, 100.0));
843    }
844
845    #[test]
846    fn test_compute_track_sizes_mixed() {
847        let tracks = vec![TrackSize::Px(50.0), TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
848        let result = compute_track_sizes(&tracks, 250.0, 0.0, &[]);
849
850        assert_eq!(result.len(), 3);
851        assert_eq!(result[0], (0.0, 50.0));
852        assert_eq!(result[1], (50.0, 100.0));
853        assert_eq!(result[2], (150.0, 100.0));
854    }
855
856    #[test]
857    fn test_compute_track_sizes_with_gap() {
858        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
859        let result = compute_track_sizes(&tracks, 210.0, 10.0, &[]);
860
861        assert_eq!(result.len(), 2);
862        assert_eq!(result[0], (0.0, 100.0));
863        assert_eq!(result[1], (110.0, 100.0));
864    }
865
866    #[test]
867    fn test_compute_track_sizes_auto() {
868        let tracks = vec![TrackSize::Auto, TrackSize::Fr(1.0)];
869        let content_sizes = vec![80.0];
870        let result = compute_track_sizes(&tracks, 200.0, 0.0, &content_sizes);
871
872        assert_eq!(result.len(), 2);
873        assert_eq!(result[0], (0.0, 80.0));
874        assert_eq!(result[1], (80.0, 120.0));
875    }
876
877    #[test]
878    fn test_compute_track_sizes_empty() {
879        let result = compute_track_sizes(&[], 200.0, 0.0, &[]);
880        assert!(result.is_empty());
881    }
882
883    // =========================================================================
884    // compute_grid_layout Tests
885    // =========================================================================
886
887    #[test]
888    fn test_compute_grid_layout_basic() {
889        let template = GridTemplate::columns([TrackSize::Fr(1.0), TrackSize::Fr(1.0)]);
890        let layout = compute_grid_layout(&template, 200.0, 100.0, &[(50.0, 50.0), (50.0, 50.0)]);
891
892        assert_eq!(layout.columns.len(), 2);
893        assert_eq!(layout.width, 200.0);
894    }
895
896    #[test]
897    fn test_compute_grid_layout_twelve_column() {
898        let template = GridTemplate::twelve_column();
899        let layout = compute_grid_layout(&template, 1200.0, 400.0, &[]);
900
901        assert_eq!(layout.columns.len(), 12);
902        // With 16px gap between 12 columns = 11 * 16 = 176px for gaps
903        // Remaining: 1200 - 176 = 1024px for columns
904        // Each column: 1024 / 12 ≈ 85.33px
905    }
906
907    #[test]
908    fn test_grid_layout_area_bounds() {
909        let template = GridTemplate::columns([TrackSize::px(100.0), TrackSize::px(100.0)]);
910        let layout = compute_grid_layout(&template, 200.0, 100.0, &[(50.0, 50.0)]);
911
912        let bounds = layout.area_bounds(&GridArea::cell(0, 0));
913        assert!(bounds.is_some());
914        let (x, y, w, _h) = bounds.unwrap();
915        assert_eq!(x, 0.0);
916        assert_eq!(y, 0.0);
917        assert_eq!(w, 100.0);
918    }
919
920    #[test]
921    fn test_grid_layout_area_bounds_span() {
922        let template = GridTemplate::columns([
923            TrackSize::px(100.0),
924            TrackSize::px(100.0),
925            TrackSize::px(100.0),
926        ]);
927        let layout = compute_grid_layout(&template, 300.0, 100.0, &[(50.0, 50.0)]);
928
929        let bounds = layout.area_bounds(&GridArea::row_span(0, 0, 2));
930        assert!(bounds.is_some());
931        let (x, y, w, _h) = bounds.unwrap();
932        assert_eq!(x, 0.0);
933        assert_eq!(y, 0.0);
934        assert_eq!(w, 200.0);
935    }
936
937    // =========================================================================
938    // auto_place_items Tests
939    // =========================================================================
940
941    #[test]
942    fn test_auto_place_items_simple() {
943        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
944        let items = vec![GridItem::new(), GridItem::new(), GridItem::new()];
945
946        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
947
948        assert_eq!(placements.len(), 3);
949        assert_eq!(placements[0], (0, 0));
950        assert_eq!(placements[1], (0, 1));
951        assert_eq!(placements[2], (1, 0));
952    }
953
954    #[test]
955    fn test_auto_place_items_with_span() {
956        let template =
957            GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0), TrackSize::fr(1.0)]);
958        let items = vec![
959            GridItem::new().span_columns(2),
960            GridItem::new(),
961            GridItem::new(),
962        ];
963
964        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
965
966        assert_eq!(placements.len(), 3);
967        assert_eq!(placements[0], (0, 0)); // Spans 2 columns
968        assert_eq!(placements[1], (0, 2)); // Fits in remaining column
969        assert_eq!(placements[2], (1, 0)); // Next row
970    }
971
972    #[test]
973    fn test_auto_place_items_explicit() {
974        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
975        let items = vec![GridItem::new().column(2).row(2), GridItem::new()];
976
977        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
978
979        assert_eq!(placements[0], (1, 1)); // Explicit (converted to 0-indexed)
980        assert_eq!(placements[1], (0, 0)); // Auto-placed
981    }
982
983    #[test]
984    fn test_auto_place_items_named_area() {
985        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)])
986            .with_area("main", GridArea::cell(1, 1));
987        let items = vec![GridItem::new().in_area("main"), GridItem::new()];
988
989        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
990
991        assert_eq!(placements[0], (1, 1)); // Named area
992        assert_eq!(placements[1], (0, 0)); // Auto-placed
993    }
994
995    #[test]
996    fn test_auto_place_items_column_flow() {
997        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
998        let items = vec![GridItem::new(), GridItem::new(), GridItem::new()];
999
1000        let placements = auto_place_items(&template, &items, GridAutoFlow::Column);
1001
1002        assert_eq!(placements.len(), 3);
1003        // Column flow fills down first
1004        assert_eq!(placements[0], (0, 0));
1005        assert_eq!(placements[1], (1, 0));
1006        assert_eq!(placements[2], (2, 0));
1007    }
1008
1009    // =========================================================================
1010    // GridAutoFlow Tests
1011    // =========================================================================
1012
1013    #[test]
1014    fn test_grid_auto_flow_default() {
1015        assert_eq!(GridAutoFlow::default(), GridAutoFlow::Row);
1016    }
1017
1018    #[test]
1019    fn test_grid_auto_flow_all_variants() {
1020        assert_eq!(GridAutoFlow::Row, GridAutoFlow::Row);
1021        assert_eq!(GridAutoFlow::Column, GridAutoFlow::Column);
1022        assert_eq!(GridAutoFlow::RowDense, GridAutoFlow::RowDense);
1023        assert_eq!(GridAutoFlow::ColumnDense, GridAutoFlow::ColumnDense);
1024    }
1025
1026    #[test]
1027    fn test_grid_auto_flow_clone() {
1028        let flow = GridAutoFlow::ColumnDense;
1029        let cloned = flow;
1030        assert_eq!(flow, cloned);
1031    }
1032
1033    #[test]
1034    fn test_grid_auto_flow_debug() {
1035        let flow = GridAutoFlow::RowDense;
1036        let debug = format!("{:?}", flow);
1037        assert!(debug.contains("RowDense"));
1038    }
1039
1040    // =========================================================================
1041    // TrackSize Additional Tests
1042    // =========================================================================
1043
1044    #[test]
1045    fn test_track_size_auto_const() {
1046        assert_eq!(TrackSize::AUTO, TrackSize::Auto);
1047    }
1048
1049    #[test]
1050    fn test_track_size_min_content() {
1051        let size = TrackSize::MinContent;
1052        assert_eq!(size, TrackSize::MinContent);
1053    }
1054
1055    #[test]
1056    fn test_track_size_max_content() {
1057        let size = TrackSize::MaxContent;
1058        assert_eq!(size, TrackSize::MaxContent);
1059    }
1060
1061    #[test]
1062    fn test_track_size_clone() {
1063        let size = TrackSize::Fr(2.5);
1064        let cloned = size;
1065        assert_eq!(size, cloned);
1066    }
1067
1068    #[test]
1069    fn test_track_size_debug() {
1070        let size = TrackSize::Px(100.0);
1071        let debug = format!("{:?}", size);
1072        assert!(debug.contains("Px"));
1073        assert!(debug.contains("100"));
1074    }
1075
1076    #[test]
1077    fn test_track_size_serialize() {
1078        let size = TrackSize::Fr(1.5);
1079        let json = serde_json::to_string(&size).unwrap();
1080        assert!(json.contains("Fr"));
1081    }
1082
1083    #[test]
1084    fn test_track_size_deserialize() {
1085        let json = r#"{"Px":200.0}"#;
1086        let size: TrackSize = serde_json::from_str(json).unwrap();
1087        assert_eq!(size, TrackSize::Px(200.0));
1088    }
1089
1090    // =========================================================================
1091    // GridTemplate Additional Tests
1092    // =========================================================================
1093
1094    #[test]
1095    fn test_grid_template_with_column_gap() {
1096        let template = GridTemplate::new().with_column_gap(20.0);
1097        assert_eq!(template.column_gap, 20.0);
1098        assert_eq!(template.row_gap, 0.0);
1099    }
1100
1101    #[test]
1102    fn test_grid_template_with_row_gap() {
1103        let template = GridTemplate::new().with_row_gap(15.0);
1104        assert_eq!(template.row_gap, 15.0);
1105        assert_eq!(template.column_gap, 0.0);
1106    }
1107
1108    #[test]
1109    fn test_grid_template_column_count() {
1110        let template =
1111            GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1112        assert_eq!(template.column_count(), 3);
1113    }
1114
1115    #[test]
1116    fn test_grid_template_row_count() {
1117        let template = GridTemplate::new().with_rows([TrackSize::px(50.0), TrackSize::px(100.0)]);
1118        assert_eq!(template.row_count(), 2);
1119    }
1120
1121    #[test]
1122    fn test_grid_template_empty_counts() {
1123        let template = GridTemplate::new();
1124        assert_eq!(template.column_count(), 0);
1125        assert_eq!(template.row_count(), 0);
1126    }
1127
1128    #[test]
1129    fn test_grid_template_default() {
1130        let template = GridTemplate::default();
1131        assert!(template.columns.is_empty());
1132        assert!(template.areas.is_empty());
1133    }
1134
1135    #[test]
1136    fn test_grid_template_clone() {
1137        let template = GridTemplate::twelve_column().with_area("header", GridArea::cell(0, 0));
1138        let cloned = template.clone();
1139        assert_eq!(cloned.columns.len(), 12);
1140        assert!(cloned.areas.contains_key("header"));
1141    }
1142
1143    #[test]
1144    fn test_grid_template_serialize() {
1145        let template = GridTemplate::columns([TrackSize::px(100.0)]);
1146        let json = serde_json::to_string(&template).unwrap();
1147        assert!(json.contains("columns"));
1148    }
1149
1150    // =========================================================================
1151    // GridArea Additional Tests
1152    // =========================================================================
1153
1154    #[test]
1155    fn test_grid_area_span_counts_zero() {
1156        let area = GridArea::new(5, 5, 5, 5); // Same start and end
1157        assert_eq!(area.row_span_count(), 0);
1158        assert_eq!(area.col_span_count(), 0);
1159    }
1160
1161    #[test]
1162    fn test_grid_area_span_counts_saturating() {
1163        let area = GridArea::new(10, 10, 5, 5); // End before start
1164        assert_eq!(area.row_span_count(), 0);
1165        assert_eq!(area.col_span_count(), 0);
1166    }
1167
1168    #[test]
1169    fn test_grid_area_clone() {
1170        let area = GridArea::new(1, 2, 3, 4);
1171        let cloned = area;
1172        assert_eq!(area, cloned);
1173    }
1174
1175    #[test]
1176    fn test_grid_area_debug() {
1177        let area = GridArea::cell(0, 0);
1178        let debug = format!("{:?}", area);
1179        assert!(debug.contains("GridArea"));
1180    }
1181
1182    #[test]
1183    fn test_grid_area_serialize() {
1184        let area = GridArea::new(0, 0, 2, 4);
1185        let json = serde_json::to_string(&area).unwrap();
1186        assert!(json.contains("row_start"));
1187    }
1188
1189    #[test]
1190    fn test_grid_area_deserialize() {
1191        let json = r#"{"row_start":1,"row_end":3,"col_start":0,"col_end":2}"#;
1192        let area: GridArea = serde_json::from_str(json).unwrap();
1193        assert_eq!(area.row_start, 1);
1194        assert_eq!(area.col_end, 2);
1195    }
1196
1197    // =========================================================================
1198    // GridItem Additional Tests
1199    // =========================================================================
1200
1201    #[test]
1202    fn test_grid_item_default() {
1203        let item = GridItem::default();
1204        assert_eq!(item.column_start, 0);
1205        assert_eq!(item.row_start, 0);
1206        assert!(item.area.is_none());
1207    }
1208
1209    #[test]
1210    fn test_grid_item_justify_self() {
1211        let item = GridItem::new().justify_self(GridAlign::End);
1212        assert_eq!(item.justify_self, Some(GridAlign::End));
1213    }
1214
1215    #[test]
1216    fn test_grid_item_align_self() {
1217        let item = GridItem::new().align_self(GridAlign::Stretch);
1218        assert_eq!(item.align_self, Some(GridAlign::Stretch));
1219    }
1220
1221    #[test]
1222    fn test_grid_item_effective_row_span() {
1223        let item = GridItem::new().span_rows(3);
1224        assert_eq!(item.effective_row_span(), 3);
1225
1226        let mut item2 = GridItem::new();
1227        item2.row_start = 1;
1228        item2.row_end = 5;
1229        assert_eq!(item2.effective_row_span(), 4);
1230    }
1231
1232    #[test]
1233    fn test_grid_item_effective_span_minimum() {
1234        let mut item = GridItem::new();
1235        item.column_span = 0; // Edge case
1236        assert_eq!(item.effective_column_span(), 1); // Min 1
1237
1238        item.row_span = 0;
1239        assert_eq!(item.effective_row_span(), 1); // Min 1
1240    }
1241
1242    #[test]
1243    fn test_grid_item_clone() {
1244        let item = GridItem::new().column(2).row(3).span_columns(2);
1245        let cloned = item.clone();
1246        assert_eq!(cloned.column_start, 2);
1247        assert_eq!(cloned.row_start, 3);
1248    }
1249
1250    #[test]
1251    fn test_grid_item_serialize() {
1252        let item = GridItem::new().column(1).row(1);
1253        let json = serde_json::to_string(&item).unwrap();
1254        assert!(json.contains("column_start"));
1255    }
1256
1257    // =========================================================================
1258    // GridAlign Additional Tests
1259    // =========================================================================
1260
1261    #[test]
1262    fn test_grid_align_all_variants() {
1263        assert_eq!(GridAlign::Start, GridAlign::Start);
1264        assert_eq!(GridAlign::End, GridAlign::End);
1265        assert_eq!(GridAlign::Center, GridAlign::Center);
1266        assert_eq!(GridAlign::Stretch, GridAlign::Stretch);
1267    }
1268
1269    #[test]
1270    fn test_grid_align_clone() {
1271        let align = GridAlign::Stretch;
1272        let cloned = align;
1273        assert_eq!(align, cloned);
1274    }
1275
1276    #[test]
1277    fn test_grid_align_debug() {
1278        let align = GridAlign::Start;
1279        let debug = format!("{:?}", align);
1280        assert!(debug.contains("Start"));
1281    }
1282
1283    #[test]
1284    fn test_grid_align_serialize() {
1285        let align = GridAlign::End;
1286        let json = serde_json::to_string(&align).unwrap();
1287        assert!(json.contains("End"));
1288    }
1289
1290    // =========================================================================
1291    // GridLayout Tests
1292    // =========================================================================
1293
1294    #[test]
1295    fn test_grid_layout_default() {
1296        let layout = GridLayout::default();
1297        assert!(layout.columns.is_empty());
1298        assert!(layout.rows.is_empty());
1299        assert_eq!(layout.width, 0.0);
1300        assert_eq!(layout.height, 0.0);
1301    }
1302
1303    #[test]
1304    fn test_grid_layout_area_bounds_out_of_range() {
1305        let layout = GridLayout {
1306            columns: vec![(0.0, 100.0)],
1307            rows: vec![(0.0, 50.0)],
1308            width: 100.0,
1309            height: 50.0,
1310        };
1311
1312        // Out of range area
1313        let bounds = layout.area_bounds(&GridArea::cell(10, 10));
1314        assert!(bounds.is_none());
1315    }
1316
1317    #[test]
1318    fn test_grid_layout_area_bounds_partial_out_of_range() {
1319        let layout = GridLayout {
1320            columns: vec![(0.0, 100.0), (100.0, 100.0)],
1321            rows: vec![(0.0, 50.0)],
1322            width: 200.0,
1323            height: 50.0,
1324        };
1325
1326        // Area extends beyond grid - should clamp
1327        let bounds = layout.area_bounds(&GridArea::new(0, 0, 5, 5));
1328        assert!(bounds.is_some());
1329        let (_, _, w, _) = bounds.unwrap();
1330        assert_eq!(w, 200.0); // Clamped to available columns
1331    }
1332
1333    #[test]
1334    fn test_grid_layout_item_bounds() {
1335        let layout = GridLayout {
1336            columns: vec![(0.0, 100.0), (100.0, 100.0)],
1337            rows: vec![(0.0, 50.0), (50.0, 50.0)],
1338            width: 200.0,
1339            height: 100.0,
1340        };
1341
1342        let item = GridItem::new().span_columns(2);
1343        let bounds = layout.item_bounds(&item, 0, 0);
1344        assert!(bounds.is_some());
1345        let (x, y, w, h) = bounds.unwrap();
1346        assert_eq!(x, 0.0);
1347        assert_eq!(y, 0.0);
1348        assert_eq!(w, 200.0);
1349        assert_eq!(h, 50.0);
1350    }
1351
1352    #[test]
1353    fn test_grid_layout_clone() {
1354        let layout = GridLayout {
1355            columns: vec![(0.0, 100.0)],
1356            rows: vec![(0.0, 50.0)],
1357            width: 100.0,
1358            height: 50.0,
1359        };
1360        let cloned = layout.clone();
1361        assert_eq!(cloned.width, 100.0);
1362    }
1363
1364    #[test]
1365    fn test_grid_layout_debug() {
1366        let layout = GridLayout::default();
1367        let debug = format!("{:?}", layout);
1368        assert!(debug.contains("GridLayout"));
1369    }
1370
1371    // =========================================================================
1372    // compute_track_sizes Edge Cases
1373    // =========================================================================
1374
1375    #[test]
1376    fn test_compute_track_sizes_all_fr() {
1377        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(2.0), TrackSize::Fr(1.0)];
1378        let result = compute_track_sizes(&tracks, 400.0, 0.0, &[]);
1379
1380        assert_eq!(result.len(), 3);
1381        assert_eq!(result[0].1, 100.0); // 1/4
1382        assert_eq!(result[1].1, 200.0); // 2/4
1383        assert_eq!(result[2].1, 100.0); // 1/4
1384    }
1385
1386    #[test]
1387    fn test_compute_track_sizes_zero_fr() {
1388        let tracks = vec![TrackSize::Fr(0.0), TrackSize::Fr(1.0)];
1389        let result = compute_track_sizes(&tracks, 200.0, 0.0, &[]);
1390
1391        assert_eq!(result.len(), 2);
1392        assert_eq!(result[0].1, 0.0);
1393        assert_eq!(result[1].1, 200.0);
1394    }
1395
1396    #[test]
1397    fn test_compute_track_sizes_insufficient_space() {
1398        let tracks = vec![TrackSize::Px(300.0), TrackSize::Fr(1.0)];
1399        let result = compute_track_sizes(&tracks, 200.0, 0.0, &[]);
1400
1401        // Fixed takes precedence, fr gets 0
1402        assert_eq!(result[0].1, 300.0);
1403        assert_eq!(result[1].1, 0.0);
1404    }
1405
1406    #[test]
1407    fn test_compute_track_sizes_large_gap() {
1408        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
1409        let result = compute_track_sizes(&tracks, 100.0, 200.0, &[]);
1410
1411        // Gap is larger than available space - tracks get 0
1412        assert!(result[0].1 <= 0.0);
1413    }
1414
1415    #[test]
1416    fn test_compute_track_sizes_min_content() {
1417        let tracks = vec![TrackSize::MinContent, TrackSize::Fr(1.0)];
1418        let content = vec![60.0, 0.0];
1419        let result = compute_track_sizes(&tracks, 200.0, 0.0, &content);
1420
1421        assert_eq!(result[0].1, 60.0);
1422        assert_eq!(result[1].1, 140.0);
1423    }
1424
1425    #[test]
1426    fn test_compute_track_sizes_max_content() {
1427        let tracks = vec![TrackSize::MaxContent, TrackSize::Fr(1.0)];
1428        let content = vec![80.0];
1429        let result = compute_track_sizes(&tracks, 200.0, 0.0, &content);
1430
1431        assert_eq!(result[0].1, 80.0);
1432        assert_eq!(result[1].1, 120.0);
1433    }
1434
1435    // =========================================================================
1436    // compute_grid_layout Edge Cases
1437    // =========================================================================
1438
1439    #[test]
1440    fn test_compute_grid_layout_empty_template() {
1441        let template = GridTemplate::new();
1442        let layout = compute_grid_layout(&template, 200.0, 100.0, &[]);
1443
1444        assert!(layout.columns.is_empty());
1445        assert_eq!(layout.width, 0.0);
1446    }
1447
1448    #[test]
1449    fn test_compute_grid_layout_auto_rows() {
1450        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1451        let children = vec![(50.0, 30.0), (50.0, 30.0), (50.0, 30.0), (50.0, 30.0)];
1452        let layout = compute_grid_layout(&template, 200.0, 200.0, &children);
1453
1454        // 4 items in 2 columns = 2 rows
1455        assert_eq!(layout.rows.len(), 2);
1456    }
1457
1458    #[test]
1459    fn test_compute_grid_layout_with_gaps() {
1460        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)])
1461            .with_rows([TrackSize::px(50.0)])
1462            .with_gap(10.0);
1463        let layout = compute_grid_layout(&template, 210.0, 50.0, &[]);
1464
1465        // Total width should account for gap
1466        let col1_end = layout.columns[0].0 + layout.columns[0].1;
1467        let col2_start = layout.columns[1].0;
1468        assert!((col2_start - col1_end - 10.0).abs() < 0.01);
1469    }
1470
1471    // =========================================================================
1472    // auto_place_items Edge Cases
1473    // =========================================================================
1474
1475    #[test]
1476    fn test_auto_place_items_empty() {
1477        let template = GridTemplate::columns([TrackSize::fr(1.0)]);
1478        let placements = auto_place_items(&template, &[], GridAutoFlow::Row);
1479        assert!(placements.is_empty());
1480    }
1481
1482    #[test]
1483    fn test_auto_place_items_single_column() {
1484        let template = GridTemplate::columns([TrackSize::fr(1.0)]);
1485        let items = vec![GridItem::new(), GridItem::new(), GridItem::new()];
1486        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1487
1488        assert_eq!(placements[0], (0, 0));
1489        assert_eq!(placements[1], (1, 0));
1490        assert_eq!(placements[2], (2, 0));
1491    }
1492
1493    #[test]
1494    fn test_auto_place_items_span_exceeds_grid() {
1495        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1496        let items = vec![GridItem::new().span_columns(5)]; // Spans more than available
1497        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1498
1499        // Should still place somewhere
1500        assert_eq!(placements.len(), 1);
1501    }
1502
1503    #[test]
1504    fn test_auto_place_items_row_span() {
1505        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1506        let items = vec![
1507            GridItem::new().span_rows(2),
1508            GridItem::new(),
1509            GridItem::new(),
1510        ];
1511        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1512
1513        // First item spans 2 rows, second should go to column 1
1514        assert_eq!(placements[0], (0, 0));
1515        assert_eq!(placements[1], (0, 1));
1516    }
1517
1518    #[test]
1519    fn test_auto_place_items_missing_area() {
1520        let template = GridTemplate::columns([TrackSize::fr(1.0)]);
1521        let items = vec![GridItem::new().in_area("nonexistent")];
1522        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1523
1524        // Should fall back to auto-placement
1525        assert_eq!(placements[0], (0, 0));
1526    }
1527
1528    #[test]
1529    fn test_auto_place_items_column_dense() {
1530        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1531        let items = vec![GridItem::new(), GridItem::new()];
1532        let placements = auto_place_items(&template, &items, GridAutoFlow::ColumnDense);
1533
1534        // ColumnDense should fill columns first
1535        assert_eq!(placements.len(), 2);
1536    }
1537}