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/// Auto-place items in a grid.
528#[must_use]
529pub fn auto_place_items(
530    template: &GridTemplate,
531    items: &[GridItem],
532    flow: GridAutoFlow,
533) -> Vec<(usize, usize)> {
534    let col_count = template.columns.len().max(1);
535    let mut occupied: Vec<Vec<bool>> = Vec::new();
536    let mut placements = Vec::with_capacity(items.len());
537
538    for item in items {
539        // Check if item has explicit placement
540        if item.column_start > 0 && item.row_start > 0 {
541            placements.push((item.row_start - 1, item.column_start - 1));
542            continue;
543        }
544
545        // Check if item uses a named area
546        if let Some(area_name) = &item.area {
547            if let Some(area) = template.areas.get(area_name) {
548                placements.push((area.row_start, area.col_start));
549                continue;
550            }
551        }
552
553        // Auto-place
554        let col_span = item.effective_column_span();
555        let row_span = item.effective_row_span();
556
557        let (row, col) = match flow {
558            GridAutoFlow::Row | GridAutoFlow::RowDense => {
559                find_next_position_row(&mut occupied, col_count, col_span, row_span)
560            }
561            GridAutoFlow::Column | GridAutoFlow::ColumnDense => {
562                find_next_position_column(&mut occupied, col_count, col_span, row_span)
563            }
564        };
565
566        // Mark as occupied
567        ensure_rows(&mut occupied, row + row_span, col_count);
568        for r in row..(row + row_span) {
569            for c in col..(col + col_span) {
570                if c < col_count {
571                    occupied[r][c] = true;
572                }
573            }
574        }
575
576        placements.push((row, col));
577    }
578
579    placements
580}
581
582fn find_next_position_row(
583    occupied: &mut Vec<Vec<bool>>,
584    col_count: usize,
585    col_span: usize,
586    row_span: usize,
587) -> (usize, usize) {
588    let mut row = 0;
589    loop {
590        ensure_rows(occupied, row + row_span, col_count);
591        for col in 0..=(col_count.saturating_sub(col_span)) {
592            if can_place(occupied, row, col, row_span, col_span) {
593                return (row, col);
594            }
595        }
596        row += 1;
597    }
598}
599
600fn find_next_position_column(
601    occupied: &mut Vec<Vec<bool>>,
602    col_count: usize,
603    col_span: usize,
604    row_span: usize,
605) -> (usize, usize) {
606    for col in 0..=(col_count.saturating_sub(col_span)) {
607        let mut row = 0;
608        loop {
609            ensure_rows(occupied, row + row_span, col_count);
610            if can_place(occupied, row, col, row_span, col_span) {
611                return (row, col);
612            }
613            row += 1;
614            if row > 1000 {
615                break; // Safety limit
616            }
617        }
618    }
619    (0, 0) // Fallback
620}
621
622fn ensure_rows(occupied: &mut Vec<Vec<bool>>, min_rows: usize, col_count: usize) {
623    while occupied.len() < min_rows {
624        occupied.push(vec![false; col_count]);
625    }
626}
627
628fn can_place(
629    occupied: &[Vec<bool>],
630    row: usize,
631    col: usize,
632    row_span: usize,
633    col_span: usize,
634) -> bool {
635    for r in row..(row + row_span) {
636        for c in col..(col + col_span) {
637            if let Some(row_data) = occupied.get(r) {
638                if row_data.get(c).copied().unwrap_or(false) {
639                    return false;
640                }
641            }
642        }
643    }
644    true
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650
651    // =========================================================================
652    // TrackSize Tests
653    // =========================================================================
654
655    #[test]
656    fn test_track_size_default() {
657        assert_eq!(TrackSize::default(), TrackSize::Fr(1.0));
658    }
659
660    #[test]
661    fn test_track_size_px() {
662        let size = TrackSize::px(100.0);
663        assert_eq!(size, TrackSize::Px(100.0));
664    }
665
666    #[test]
667    fn test_track_size_fr() {
668        let size = TrackSize::fr(2.0);
669        assert_eq!(size, TrackSize::Fr(2.0));
670    }
671
672    // =========================================================================
673    // GridTemplate Tests
674    // =========================================================================
675
676    #[test]
677    fn test_grid_template_new() {
678        let template = GridTemplate::new();
679        assert!(template.columns.is_empty());
680        assert!(template.rows.is_empty());
681        assert_eq!(template.column_gap, 0.0);
682        assert_eq!(template.row_gap, 0.0);
683    }
684
685    #[test]
686    fn test_grid_template_columns() {
687        let template = GridTemplate::columns([TrackSize::px(100.0), TrackSize::fr(1.0)]);
688        assert_eq!(template.columns.len(), 2);
689        assert_eq!(template.columns[0], TrackSize::Px(100.0));
690        assert_eq!(template.columns[1], TrackSize::Fr(1.0));
691    }
692
693    #[test]
694    fn test_grid_template_twelve_column() {
695        let template = GridTemplate::twelve_column();
696        assert_eq!(template.columns.len(), 12);
697        assert_eq!(template.column_gap, 16.0);
698        assert_eq!(template.row_gap, 16.0);
699    }
700
701    #[test]
702    fn test_grid_template_builder() {
703        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(2.0)])
704            .with_rows([TrackSize::px(50.0)])
705            .with_gap(8.0);
706
707        assert_eq!(template.columns.len(), 2);
708        assert_eq!(template.rows.len(), 1);
709        assert_eq!(template.column_gap, 8.0);
710        assert_eq!(template.row_gap, 8.0);
711    }
712
713    #[test]
714    fn test_grid_template_with_area() {
715        let template = GridTemplate::twelve_column()
716            .with_area("header", GridArea::row_span(0, 0, 12))
717            .with_area("sidebar", GridArea::col_span(0, 1, 4));
718
719        assert!(template.areas.contains_key("header"));
720        assert!(template.areas.contains_key("sidebar"));
721    }
722
723    // =========================================================================
724    // GridArea Tests
725    // =========================================================================
726
727    #[test]
728    fn test_grid_area_new() {
729        let area = GridArea::new(1, 2, 3, 4);
730        assert_eq!(area.row_start, 1);
731        assert_eq!(area.col_start, 2);
732        assert_eq!(area.row_end, 3);
733        assert_eq!(area.col_end, 4);
734    }
735
736    #[test]
737    fn test_grid_area_cell() {
738        let area = GridArea::cell(2, 3);
739        assert_eq!(area.row_start, 2);
740        assert_eq!(area.row_end, 3);
741        assert_eq!(area.col_start, 3);
742        assert_eq!(area.col_end, 4);
743    }
744
745    #[test]
746    fn test_grid_area_row_span() {
747        let area = GridArea::row_span(0, 0, 6);
748        assert_eq!(area.row_span_count(), 1);
749        assert_eq!(area.col_span_count(), 6);
750    }
751
752    #[test]
753    fn test_grid_area_col_span() {
754        let area = GridArea::col_span(0, 0, 3);
755        assert_eq!(area.row_span_count(), 3);
756        assert_eq!(area.col_span_count(), 1);
757    }
758
759    // =========================================================================
760    // GridItem Tests
761    // =========================================================================
762
763    #[test]
764    fn test_grid_item_new() {
765        let item = GridItem::new();
766        assert_eq!(item.column_span, 1);
767        assert_eq!(item.row_span, 1);
768    }
769
770    #[test]
771    fn test_grid_item_builder() {
772        let item = GridItem::new()
773            .column(2)
774            .row(1)
775            .span_columns(3)
776            .span_rows(2);
777
778        assert_eq!(item.column_start, 2);
779        assert_eq!(item.row_start, 1);
780        assert_eq!(item.column_span, 3);
781        assert_eq!(item.row_span, 2);
782    }
783
784    #[test]
785    fn test_grid_item_effective_span() {
786        let item1 = GridItem::new().span_columns(2);
787        assert_eq!(item1.effective_column_span(), 2);
788
789        let mut item2 = GridItem::new();
790        item2.column_start = 1;
791        item2.column_end = 4;
792        assert_eq!(item2.effective_column_span(), 3);
793    }
794
795    #[test]
796    fn test_grid_item_in_area() {
797        let item = GridItem::new().in_area("sidebar");
798        assert_eq!(item.area, Some("sidebar".to_string()));
799    }
800
801    // =========================================================================
802    // GridAlign Tests
803    // =========================================================================
804
805    #[test]
806    fn test_grid_align_default() {
807        assert_eq!(GridAlign::default(), GridAlign::Center);
808    }
809
810    // =========================================================================
811    // compute_track_sizes Tests
812    // =========================================================================
813
814    #[test]
815    fn test_compute_track_sizes_fixed() {
816        let tracks = vec![TrackSize::Px(100.0), TrackSize::Px(200.0)];
817        let result = compute_track_sizes(&tracks, 400.0, 0.0, &[]);
818
819        assert_eq!(result.len(), 2);
820        assert_eq!(result[0], (0.0, 100.0));
821        assert_eq!(result[1], (100.0, 200.0));
822    }
823
824    #[test]
825    fn test_compute_track_sizes_fr() {
826        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
827        let result = compute_track_sizes(&tracks, 200.0, 0.0, &[]);
828
829        assert_eq!(result.len(), 2);
830        assert_eq!(result[0], (0.0, 100.0));
831        assert_eq!(result[1], (100.0, 100.0));
832    }
833
834    #[test]
835    fn test_compute_track_sizes_mixed() {
836        let tracks = vec![TrackSize::Px(50.0), TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
837        let result = compute_track_sizes(&tracks, 250.0, 0.0, &[]);
838
839        assert_eq!(result.len(), 3);
840        assert_eq!(result[0], (0.0, 50.0));
841        assert_eq!(result[1], (50.0, 100.0));
842        assert_eq!(result[2], (150.0, 100.0));
843    }
844
845    #[test]
846    fn test_compute_track_sizes_with_gap() {
847        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
848        let result = compute_track_sizes(&tracks, 210.0, 10.0, &[]);
849
850        assert_eq!(result.len(), 2);
851        assert_eq!(result[0], (0.0, 100.0));
852        assert_eq!(result[1], (110.0, 100.0));
853    }
854
855    #[test]
856    fn test_compute_track_sizes_auto() {
857        let tracks = vec![TrackSize::Auto, TrackSize::Fr(1.0)];
858        let content_sizes = vec![80.0];
859        let result = compute_track_sizes(&tracks, 200.0, 0.0, &content_sizes);
860
861        assert_eq!(result.len(), 2);
862        assert_eq!(result[0], (0.0, 80.0));
863        assert_eq!(result[1], (80.0, 120.0));
864    }
865
866    #[test]
867    fn test_compute_track_sizes_empty() {
868        let result = compute_track_sizes(&[], 200.0, 0.0, &[]);
869        assert!(result.is_empty());
870    }
871
872    // =========================================================================
873    // compute_grid_layout Tests
874    // =========================================================================
875
876    #[test]
877    fn test_compute_grid_layout_basic() {
878        let template = GridTemplate::columns([TrackSize::Fr(1.0), TrackSize::Fr(1.0)]);
879        let layout = compute_grid_layout(&template, 200.0, 100.0, &[(50.0, 50.0), (50.0, 50.0)]);
880
881        assert_eq!(layout.columns.len(), 2);
882        assert_eq!(layout.width, 200.0);
883    }
884
885    #[test]
886    fn test_compute_grid_layout_twelve_column() {
887        let template = GridTemplate::twelve_column();
888        let layout = compute_grid_layout(&template, 1200.0, 400.0, &[]);
889
890        assert_eq!(layout.columns.len(), 12);
891        // With 16px gap between 12 columns = 11 * 16 = 176px for gaps
892        // Remaining: 1200 - 176 = 1024px for columns
893        // Each column: 1024 / 12 ≈ 85.33px
894    }
895
896    #[test]
897    fn test_grid_layout_area_bounds() {
898        let template = GridTemplate::columns([TrackSize::px(100.0), TrackSize::px(100.0)]);
899        let layout = compute_grid_layout(&template, 200.0, 100.0, &[(50.0, 50.0)]);
900
901        let bounds = layout.area_bounds(&GridArea::cell(0, 0));
902        assert!(bounds.is_some());
903        let (x, y, w, _h) = bounds.unwrap();
904        assert_eq!(x, 0.0);
905        assert_eq!(y, 0.0);
906        assert_eq!(w, 100.0);
907    }
908
909    #[test]
910    fn test_grid_layout_area_bounds_span() {
911        let template = GridTemplate::columns([
912            TrackSize::px(100.0),
913            TrackSize::px(100.0),
914            TrackSize::px(100.0),
915        ]);
916        let layout = compute_grid_layout(&template, 300.0, 100.0, &[(50.0, 50.0)]);
917
918        let bounds = layout.area_bounds(&GridArea::row_span(0, 0, 2));
919        assert!(bounds.is_some());
920        let (x, y, w, _h) = bounds.unwrap();
921        assert_eq!(x, 0.0);
922        assert_eq!(y, 0.0);
923        assert_eq!(w, 200.0);
924    }
925
926    // =========================================================================
927    // auto_place_items Tests
928    // =========================================================================
929
930    #[test]
931    fn test_auto_place_items_simple() {
932        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
933        let items = vec![GridItem::new(), GridItem::new(), GridItem::new()];
934
935        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
936
937        assert_eq!(placements.len(), 3);
938        assert_eq!(placements[0], (0, 0));
939        assert_eq!(placements[1], (0, 1));
940        assert_eq!(placements[2], (1, 0));
941    }
942
943    #[test]
944    fn test_auto_place_items_with_span() {
945        let template =
946            GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0), TrackSize::fr(1.0)]);
947        let items = vec![
948            GridItem::new().span_columns(2),
949            GridItem::new(),
950            GridItem::new(),
951        ];
952
953        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
954
955        assert_eq!(placements.len(), 3);
956        assert_eq!(placements[0], (0, 0)); // Spans 2 columns
957        assert_eq!(placements[1], (0, 2)); // Fits in remaining column
958        assert_eq!(placements[2], (1, 0)); // Next row
959    }
960
961    #[test]
962    fn test_auto_place_items_explicit() {
963        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
964        let items = vec![GridItem::new().column(2).row(2), GridItem::new()];
965
966        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
967
968        assert_eq!(placements[0], (1, 1)); // Explicit (converted to 0-indexed)
969        assert_eq!(placements[1], (0, 0)); // Auto-placed
970    }
971
972    #[test]
973    fn test_auto_place_items_named_area() {
974        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)])
975            .with_area("main", GridArea::cell(1, 1));
976        let items = vec![GridItem::new().in_area("main"), GridItem::new()];
977
978        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
979
980        assert_eq!(placements[0], (1, 1)); // Named area
981        assert_eq!(placements[1], (0, 0)); // Auto-placed
982    }
983
984    #[test]
985    fn test_auto_place_items_column_flow() {
986        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
987        let items = vec![GridItem::new(), GridItem::new(), GridItem::new()];
988
989        let placements = auto_place_items(&template, &items, GridAutoFlow::Column);
990
991        assert_eq!(placements.len(), 3);
992        // Column flow fills down first
993        assert_eq!(placements[0], (0, 0));
994        assert_eq!(placements[1], (1, 0));
995        assert_eq!(placements[2], (2, 0));
996    }
997
998    // =========================================================================
999    // GridAutoFlow Tests
1000    // =========================================================================
1001
1002    #[test]
1003    fn test_grid_auto_flow_default() {
1004        assert_eq!(GridAutoFlow::default(), GridAutoFlow::Row);
1005    }
1006
1007    #[test]
1008    fn test_grid_auto_flow_all_variants() {
1009        assert_eq!(GridAutoFlow::Row, GridAutoFlow::Row);
1010        assert_eq!(GridAutoFlow::Column, GridAutoFlow::Column);
1011        assert_eq!(GridAutoFlow::RowDense, GridAutoFlow::RowDense);
1012        assert_eq!(GridAutoFlow::ColumnDense, GridAutoFlow::ColumnDense);
1013    }
1014
1015    #[test]
1016    fn test_grid_auto_flow_clone() {
1017        let flow = GridAutoFlow::ColumnDense;
1018        let cloned = flow;
1019        assert_eq!(flow, cloned);
1020    }
1021
1022    #[test]
1023    fn test_grid_auto_flow_debug() {
1024        let flow = GridAutoFlow::RowDense;
1025        let debug = format!("{:?}", flow);
1026        assert!(debug.contains("RowDense"));
1027    }
1028
1029    // =========================================================================
1030    // TrackSize Additional Tests
1031    // =========================================================================
1032
1033    #[test]
1034    fn test_track_size_auto_const() {
1035        assert_eq!(TrackSize::AUTO, TrackSize::Auto);
1036    }
1037
1038    #[test]
1039    fn test_track_size_min_content() {
1040        let size = TrackSize::MinContent;
1041        assert_eq!(size, TrackSize::MinContent);
1042    }
1043
1044    #[test]
1045    fn test_track_size_max_content() {
1046        let size = TrackSize::MaxContent;
1047        assert_eq!(size, TrackSize::MaxContent);
1048    }
1049
1050    #[test]
1051    fn test_track_size_clone() {
1052        let size = TrackSize::Fr(2.5);
1053        let cloned = size;
1054        assert_eq!(size, cloned);
1055    }
1056
1057    #[test]
1058    fn test_track_size_debug() {
1059        let size = TrackSize::Px(100.0);
1060        let debug = format!("{:?}", size);
1061        assert!(debug.contains("Px"));
1062        assert!(debug.contains("100"));
1063    }
1064
1065    #[test]
1066    fn test_track_size_serialize() {
1067        let size = TrackSize::Fr(1.5);
1068        let json = serde_json::to_string(&size).unwrap();
1069        assert!(json.contains("Fr"));
1070    }
1071
1072    #[test]
1073    fn test_track_size_deserialize() {
1074        let json = r#"{"Px":200.0}"#;
1075        let size: TrackSize = serde_json::from_str(json).unwrap();
1076        assert_eq!(size, TrackSize::Px(200.0));
1077    }
1078
1079    // =========================================================================
1080    // GridTemplate Additional Tests
1081    // =========================================================================
1082
1083    #[test]
1084    fn test_grid_template_with_column_gap() {
1085        let template = GridTemplate::new().with_column_gap(20.0);
1086        assert_eq!(template.column_gap, 20.0);
1087        assert_eq!(template.row_gap, 0.0);
1088    }
1089
1090    #[test]
1091    fn test_grid_template_with_row_gap() {
1092        let template = GridTemplate::new().with_row_gap(15.0);
1093        assert_eq!(template.row_gap, 15.0);
1094        assert_eq!(template.column_gap, 0.0);
1095    }
1096
1097    #[test]
1098    fn test_grid_template_column_count() {
1099        let template =
1100            GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1101        assert_eq!(template.column_count(), 3);
1102    }
1103
1104    #[test]
1105    fn test_grid_template_row_count() {
1106        let template = GridTemplate::new().with_rows([TrackSize::px(50.0), TrackSize::px(100.0)]);
1107        assert_eq!(template.row_count(), 2);
1108    }
1109
1110    #[test]
1111    fn test_grid_template_empty_counts() {
1112        let template = GridTemplate::new();
1113        assert_eq!(template.column_count(), 0);
1114        assert_eq!(template.row_count(), 0);
1115    }
1116
1117    #[test]
1118    fn test_grid_template_default() {
1119        let template = GridTemplate::default();
1120        assert!(template.columns.is_empty());
1121        assert!(template.areas.is_empty());
1122    }
1123
1124    #[test]
1125    fn test_grid_template_clone() {
1126        let template = GridTemplate::twelve_column().with_area("header", GridArea::cell(0, 0));
1127        let cloned = template.clone();
1128        assert_eq!(cloned.columns.len(), 12);
1129        assert!(cloned.areas.contains_key("header"));
1130    }
1131
1132    #[test]
1133    fn test_grid_template_serialize() {
1134        let template = GridTemplate::columns([TrackSize::px(100.0)]);
1135        let json = serde_json::to_string(&template).unwrap();
1136        assert!(json.contains("columns"));
1137    }
1138
1139    // =========================================================================
1140    // GridArea Additional Tests
1141    // =========================================================================
1142
1143    #[test]
1144    fn test_grid_area_span_counts_zero() {
1145        let area = GridArea::new(5, 5, 5, 5); // Same start and end
1146        assert_eq!(area.row_span_count(), 0);
1147        assert_eq!(area.col_span_count(), 0);
1148    }
1149
1150    #[test]
1151    fn test_grid_area_span_counts_saturating() {
1152        let area = GridArea::new(10, 10, 5, 5); // End before start
1153        assert_eq!(area.row_span_count(), 0);
1154        assert_eq!(area.col_span_count(), 0);
1155    }
1156
1157    #[test]
1158    fn test_grid_area_clone() {
1159        let area = GridArea::new(1, 2, 3, 4);
1160        let cloned = area;
1161        assert_eq!(area, cloned);
1162    }
1163
1164    #[test]
1165    fn test_grid_area_debug() {
1166        let area = GridArea::cell(0, 0);
1167        let debug = format!("{:?}", area);
1168        assert!(debug.contains("GridArea"));
1169    }
1170
1171    #[test]
1172    fn test_grid_area_serialize() {
1173        let area = GridArea::new(0, 0, 2, 4);
1174        let json = serde_json::to_string(&area).unwrap();
1175        assert!(json.contains("row_start"));
1176    }
1177
1178    #[test]
1179    fn test_grid_area_deserialize() {
1180        let json = r#"{"row_start":1,"row_end":3,"col_start":0,"col_end":2}"#;
1181        let area: GridArea = serde_json::from_str(json).unwrap();
1182        assert_eq!(area.row_start, 1);
1183        assert_eq!(area.col_end, 2);
1184    }
1185
1186    // =========================================================================
1187    // GridItem Additional Tests
1188    // =========================================================================
1189
1190    #[test]
1191    fn test_grid_item_default() {
1192        let item = GridItem::default();
1193        assert_eq!(item.column_start, 0);
1194        assert_eq!(item.row_start, 0);
1195        assert!(item.area.is_none());
1196    }
1197
1198    #[test]
1199    fn test_grid_item_justify_self() {
1200        let item = GridItem::new().justify_self(GridAlign::End);
1201        assert_eq!(item.justify_self, Some(GridAlign::End));
1202    }
1203
1204    #[test]
1205    fn test_grid_item_align_self() {
1206        let item = GridItem::new().align_self(GridAlign::Stretch);
1207        assert_eq!(item.align_self, Some(GridAlign::Stretch));
1208    }
1209
1210    #[test]
1211    fn test_grid_item_effective_row_span() {
1212        let item = GridItem::new().span_rows(3);
1213        assert_eq!(item.effective_row_span(), 3);
1214
1215        let mut item2 = GridItem::new();
1216        item2.row_start = 1;
1217        item2.row_end = 5;
1218        assert_eq!(item2.effective_row_span(), 4);
1219    }
1220
1221    #[test]
1222    fn test_grid_item_effective_span_minimum() {
1223        let mut item = GridItem::new();
1224        item.column_span = 0; // Edge case
1225        assert_eq!(item.effective_column_span(), 1); // Min 1
1226
1227        item.row_span = 0;
1228        assert_eq!(item.effective_row_span(), 1); // Min 1
1229    }
1230
1231    #[test]
1232    fn test_grid_item_clone() {
1233        let item = GridItem::new().column(2).row(3).span_columns(2);
1234        let cloned = item.clone();
1235        assert_eq!(cloned.column_start, 2);
1236        assert_eq!(cloned.row_start, 3);
1237    }
1238
1239    #[test]
1240    fn test_grid_item_serialize() {
1241        let item = GridItem::new().column(1).row(1);
1242        let json = serde_json::to_string(&item).unwrap();
1243        assert!(json.contains("column_start"));
1244    }
1245
1246    // =========================================================================
1247    // GridAlign Additional Tests
1248    // =========================================================================
1249
1250    #[test]
1251    fn test_grid_align_all_variants() {
1252        assert_eq!(GridAlign::Start, GridAlign::Start);
1253        assert_eq!(GridAlign::End, GridAlign::End);
1254        assert_eq!(GridAlign::Center, GridAlign::Center);
1255        assert_eq!(GridAlign::Stretch, GridAlign::Stretch);
1256    }
1257
1258    #[test]
1259    fn test_grid_align_clone() {
1260        let align = GridAlign::Stretch;
1261        let cloned = align;
1262        assert_eq!(align, cloned);
1263    }
1264
1265    #[test]
1266    fn test_grid_align_debug() {
1267        let align = GridAlign::Start;
1268        let debug = format!("{:?}", align);
1269        assert!(debug.contains("Start"));
1270    }
1271
1272    #[test]
1273    fn test_grid_align_serialize() {
1274        let align = GridAlign::End;
1275        let json = serde_json::to_string(&align).unwrap();
1276        assert!(json.contains("End"));
1277    }
1278
1279    // =========================================================================
1280    // GridLayout Tests
1281    // =========================================================================
1282
1283    #[test]
1284    fn test_grid_layout_default() {
1285        let layout = GridLayout::default();
1286        assert!(layout.columns.is_empty());
1287        assert!(layout.rows.is_empty());
1288        assert_eq!(layout.width, 0.0);
1289        assert_eq!(layout.height, 0.0);
1290    }
1291
1292    #[test]
1293    fn test_grid_layout_area_bounds_out_of_range() {
1294        let layout = GridLayout {
1295            columns: vec![(0.0, 100.0)],
1296            rows: vec![(0.0, 50.0)],
1297            width: 100.0,
1298            height: 50.0,
1299        };
1300
1301        // Out of range area
1302        let bounds = layout.area_bounds(&GridArea::cell(10, 10));
1303        assert!(bounds.is_none());
1304    }
1305
1306    #[test]
1307    fn test_grid_layout_area_bounds_partial_out_of_range() {
1308        let layout = GridLayout {
1309            columns: vec![(0.0, 100.0), (100.0, 100.0)],
1310            rows: vec![(0.0, 50.0)],
1311            width: 200.0,
1312            height: 50.0,
1313        };
1314
1315        // Area extends beyond grid - should clamp
1316        let bounds = layout.area_bounds(&GridArea::new(0, 0, 5, 5));
1317        assert!(bounds.is_some());
1318        let (_, _, w, _) = bounds.unwrap();
1319        assert_eq!(w, 200.0); // Clamped to available columns
1320    }
1321
1322    #[test]
1323    fn test_grid_layout_item_bounds() {
1324        let layout = GridLayout {
1325            columns: vec![(0.0, 100.0), (100.0, 100.0)],
1326            rows: vec![(0.0, 50.0), (50.0, 50.0)],
1327            width: 200.0,
1328            height: 100.0,
1329        };
1330
1331        let item = GridItem::new().span_columns(2);
1332        let bounds = layout.item_bounds(&item, 0, 0);
1333        assert!(bounds.is_some());
1334        let (x, y, w, h) = bounds.unwrap();
1335        assert_eq!(x, 0.0);
1336        assert_eq!(y, 0.0);
1337        assert_eq!(w, 200.0);
1338        assert_eq!(h, 50.0);
1339    }
1340
1341    #[test]
1342    fn test_grid_layout_clone() {
1343        let layout = GridLayout {
1344            columns: vec![(0.0, 100.0)],
1345            rows: vec![(0.0, 50.0)],
1346            width: 100.0,
1347            height: 50.0,
1348        };
1349        let cloned = layout.clone();
1350        assert_eq!(cloned.width, 100.0);
1351    }
1352
1353    #[test]
1354    fn test_grid_layout_debug() {
1355        let layout = GridLayout::default();
1356        let debug = format!("{:?}", layout);
1357        assert!(debug.contains("GridLayout"));
1358    }
1359
1360    // =========================================================================
1361    // compute_track_sizes Edge Cases
1362    // =========================================================================
1363
1364    #[test]
1365    fn test_compute_track_sizes_all_fr() {
1366        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(2.0), TrackSize::Fr(1.0)];
1367        let result = compute_track_sizes(&tracks, 400.0, 0.0, &[]);
1368
1369        assert_eq!(result.len(), 3);
1370        assert_eq!(result[0].1, 100.0); // 1/4
1371        assert_eq!(result[1].1, 200.0); // 2/4
1372        assert_eq!(result[2].1, 100.0); // 1/4
1373    }
1374
1375    #[test]
1376    fn test_compute_track_sizes_zero_fr() {
1377        let tracks = vec![TrackSize::Fr(0.0), TrackSize::Fr(1.0)];
1378        let result = compute_track_sizes(&tracks, 200.0, 0.0, &[]);
1379
1380        assert_eq!(result.len(), 2);
1381        assert_eq!(result[0].1, 0.0);
1382        assert_eq!(result[1].1, 200.0);
1383    }
1384
1385    #[test]
1386    fn test_compute_track_sizes_insufficient_space() {
1387        let tracks = vec![TrackSize::Px(300.0), TrackSize::Fr(1.0)];
1388        let result = compute_track_sizes(&tracks, 200.0, 0.0, &[]);
1389
1390        // Fixed takes precedence, fr gets 0
1391        assert_eq!(result[0].1, 300.0);
1392        assert_eq!(result[1].1, 0.0);
1393    }
1394
1395    #[test]
1396    fn test_compute_track_sizes_large_gap() {
1397        let tracks = vec![TrackSize::Fr(1.0), TrackSize::Fr(1.0)];
1398        let result = compute_track_sizes(&tracks, 100.0, 200.0, &[]);
1399
1400        // Gap is larger than available space - tracks get 0
1401        assert!(result[0].1 <= 0.0);
1402    }
1403
1404    #[test]
1405    fn test_compute_track_sizes_min_content() {
1406        let tracks = vec![TrackSize::MinContent, TrackSize::Fr(1.0)];
1407        let content = vec![60.0, 0.0];
1408        let result = compute_track_sizes(&tracks, 200.0, 0.0, &content);
1409
1410        assert_eq!(result[0].1, 60.0);
1411        assert_eq!(result[1].1, 140.0);
1412    }
1413
1414    #[test]
1415    fn test_compute_track_sizes_max_content() {
1416        let tracks = vec![TrackSize::MaxContent, TrackSize::Fr(1.0)];
1417        let content = vec![80.0];
1418        let result = compute_track_sizes(&tracks, 200.0, 0.0, &content);
1419
1420        assert_eq!(result[0].1, 80.0);
1421        assert_eq!(result[1].1, 120.0);
1422    }
1423
1424    // =========================================================================
1425    // compute_grid_layout Edge Cases
1426    // =========================================================================
1427
1428    #[test]
1429    fn test_compute_grid_layout_empty_template() {
1430        let template = GridTemplate::new();
1431        let layout = compute_grid_layout(&template, 200.0, 100.0, &[]);
1432
1433        assert!(layout.columns.is_empty());
1434        assert_eq!(layout.width, 0.0);
1435    }
1436
1437    #[test]
1438    fn test_compute_grid_layout_auto_rows() {
1439        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1440        let children = vec![(50.0, 30.0), (50.0, 30.0), (50.0, 30.0), (50.0, 30.0)];
1441        let layout = compute_grid_layout(&template, 200.0, 200.0, &children);
1442
1443        // 4 items in 2 columns = 2 rows
1444        assert_eq!(layout.rows.len(), 2);
1445    }
1446
1447    #[test]
1448    fn test_compute_grid_layout_with_gaps() {
1449        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)])
1450            .with_rows([TrackSize::px(50.0)])
1451            .with_gap(10.0);
1452        let layout = compute_grid_layout(&template, 210.0, 50.0, &[]);
1453
1454        // Total width should account for gap
1455        let col1_end = layout.columns[0].0 + layout.columns[0].1;
1456        let col2_start = layout.columns[1].0;
1457        assert!((col2_start - col1_end - 10.0).abs() < 0.01);
1458    }
1459
1460    // =========================================================================
1461    // auto_place_items Edge Cases
1462    // =========================================================================
1463
1464    #[test]
1465    fn test_auto_place_items_empty() {
1466        let template = GridTemplate::columns([TrackSize::fr(1.0)]);
1467        let placements = auto_place_items(&template, &[], GridAutoFlow::Row);
1468        assert!(placements.is_empty());
1469    }
1470
1471    #[test]
1472    fn test_auto_place_items_single_column() {
1473        let template = GridTemplate::columns([TrackSize::fr(1.0)]);
1474        let items = vec![GridItem::new(), GridItem::new(), GridItem::new()];
1475        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1476
1477        assert_eq!(placements[0], (0, 0));
1478        assert_eq!(placements[1], (1, 0));
1479        assert_eq!(placements[2], (2, 0));
1480    }
1481
1482    #[test]
1483    fn test_auto_place_items_span_exceeds_grid() {
1484        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1485        let items = vec![GridItem::new().span_columns(5)]; // Spans more than available
1486        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1487
1488        // Should still place somewhere
1489        assert_eq!(placements.len(), 1);
1490    }
1491
1492    #[test]
1493    fn test_auto_place_items_row_span() {
1494        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1495        let items = vec![
1496            GridItem::new().span_rows(2),
1497            GridItem::new(),
1498            GridItem::new(),
1499        ];
1500        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1501
1502        // First item spans 2 rows, second should go to column 1
1503        assert_eq!(placements[0], (0, 0));
1504        assert_eq!(placements[1], (0, 1));
1505    }
1506
1507    #[test]
1508    fn test_auto_place_items_missing_area() {
1509        let template = GridTemplate::columns([TrackSize::fr(1.0)]);
1510        let items = vec![GridItem::new().in_area("nonexistent")];
1511        let placements = auto_place_items(&template, &items, GridAutoFlow::Row);
1512
1513        // Should fall back to auto-placement
1514        assert_eq!(placements[0], (0, 0));
1515    }
1516
1517    #[test]
1518    fn test_auto_place_items_column_dense() {
1519        let template = GridTemplate::columns([TrackSize::fr(1.0), TrackSize::fr(1.0)]);
1520        let items = vec![GridItem::new(), GridItem::new()];
1521        let placements = auto_place_items(&template, &items, GridAutoFlow::ColumnDense);
1522
1523        // ColumnDense should fill columns first
1524        assert_eq!(placements.len(), 2);
1525    }
1526}