tailwind_rs_core/utilities/
sizing.rs

1//! Sizing utilities for tailwind-rs
2//!
3//! This module provides utilities for width, height, min-width, max-width,
4//! min-height, and max-height. It follows Tailwind CSS conventions and
5//! provides type-safe sizing values.
6
7use crate::classes::ClassBuilder;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11/// Sizing utility values for width and height
12/// 
13/// # Examples
14/// 
15/// ```rust
16/// use tailwind_rs_core::utilities::sizing::{SizingValue, WidthUtilities, HeightUtilities};
17/// use tailwind_rs_core::classes::ClassBuilder;
18/// 
19/// let classes = ClassBuilder::new()
20///     .width(SizingValue::Full)
21///     .height(SizingValue::Screen)
22///     .build();
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
25pub enum SizingValue {
26    /// Zero sizing (0)
27    Zero,
28    /// 1px sizing
29    Px,
30    /// Fractional sizing (0.5, 1.5, 2.5, 3.5)
31    Fractional(f32),
32    /// Integer sizing (1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 72, 80, 96)
33    Integer(u32),
34    /// Auto sizing
35    Auto,
36    /// Full sizing (100%)
37    Full,
38    /// Screen sizing (100vh for height, 100vw for width)
39    Screen,
40    /// Min content sizing
41    Min,
42    /// Max content sizing
43    Max,
44    /// Fit content sizing
45    Fit,
46    /// Fractional sizing (1/2, 1/3, 2/3, 1/4, 2/4, 3/4, 1/5, 2/5, 3/5, 4/5, 1/6, 2/6, 3/6, 4/6, 5/6)
47    Fraction(Fraction),
48    /// 12-column grid fractions (1/12, 2/12, 3/12, 4/12, 5/12, 6/12, 7/12, 8/12, 9/12, 10/12, 11/12)
49    GridFraction(GridFraction),
50}
51
52/// Fractional sizing values
53#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
54pub enum Fraction {
55    /// 1/2
56    Half,
57    /// 1/3
58    Third,
59    /// 2/3
60    TwoThirds,
61    /// 1/4
62    Quarter,
63    /// 2/4
64    TwoQuarters,
65    /// 3/4
66    ThreeQuarters,
67    /// 1/5
68    Fifth,
69    /// 2/5
70    TwoFifths,
71    /// 3/5
72    ThreeFifths,
73    /// 4/5
74    FourFifths,
75    /// 1/6
76    Sixth,
77    /// 2/6
78    TwoSixths,
79    /// 3/6
80    ThreeSixths,
81    /// 4/6
82    FourSixths,
83    /// 5/6
84    FiveSixths,
85}
86
87/// 12-column grid fractions
88#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
89pub enum GridFraction {
90    /// 1/12
91    OneTwelfth,
92    /// 2/12
93    TwoTwelfths,
94    /// 3/12
95    ThreeTwelfths,
96    /// 4/12
97    FourTwelfths,
98    /// 5/12
99    FiveTwelfths,
100    /// 6/12
101    SixTwelfths,
102    /// 7/12
103    SevenTwelfths,
104    /// 8/12
105    EightTwelfths,
106    /// 9/12
107    NineTwelfths,
108    /// 10/12
109    TenTwelfths,
110    /// 11/12
111    ElevenTwelfths,
112}
113
114impl std::hash::Hash for SizingValue {
115    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
116        match self {
117            SizingValue::Zero => 0u8.hash(state),
118            SizingValue::Px => 1u8.hash(state),
119            SizingValue::Fractional(f) => {
120                2u8.hash(state);
121                ((f * 1000.0) as u32).hash(state);
122            }
123            SizingValue::Integer(i) => {
124                3u8.hash(state);
125                i.hash(state);
126            }
127            SizingValue::Auto => 4u8.hash(state),
128            SizingValue::Full => 5u8.hash(state),
129            SizingValue::Screen => 6u8.hash(state),
130            SizingValue::Min => 7u8.hash(state),
131            SizingValue::Max => 8u8.hash(state),
132            SizingValue::Fit => 9u8.hash(state),
133            SizingValue::Fraction(f) => {
134                10u8.hash(state);
135                std::mem::discriminant(f).hash(state);
136            }
137            SizingValue::GridFraction(gf) => {
138                11u8.hash(state);
139                std::mem::discriminant(gf).hash(state);
140            }
141        }
142    }
143}
144
145impl std::hash::Hash for Fraction {
146    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
147        std::mem::discriminant(self).hash(state);
148    }
149}
150
151impl std::hash::Hash for GridFraction {
152    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
153        std::mem::discriminant(self).hash(state);
154    }
155}
156
157impl std::cmp::Eq for SizingValue {}
158impl std::cmp::Eq for Fraction {}
159impl std::cmp::Eq for GridFraction {}
160
161impl SizingValue {
162    /// Convert to CSS value
163    pub fn to_css_value(&self) -> String {
164        match self {
165            SizingValue::Zero => "0".to_string(),
166            SizingValue::Px => "1px".to_string(),
167            SizingValue::Fractional(f) => format!("{}rem", f * 0.25),
168            SizingValue::Integer(i) => format!("{}rem", *i as f32 * 0.25),
169            SizingValue::Auto => "auto".to_string(),
170            SizingValue::Full => "100%".to_string(),
171            SizingValue::Screen => "100vh".to_string(), // For height, will be overridden for width
172            SizingValue::Min => "min-content".to_string(),
173            SizingValue::Max => "max-content".to_string(),
174            SizingValue::Fit => "fit-content".to_string(),
175            SizingValue::Fraction(f) => f.to_css_value(),
176            SizingValue::GridFraction(gf) => gf.to_css_value(),
177        }
178    }
179    
180    /// Convert to CSS value for width (screen becomes 100vw)
181    pub fn to_css_value_width(&self) -> String {
182        match self {
183            SizingValue::Screen => "100vw".to_string(),
184            _ => self.to_css_value(),
185        }
186    }
187    
188    /// Convert to CSS value for height (screen becomes 100vh)
189    pub fn to_css_value_height(&self) -> String {
190        match self {
191            SizingValue::Screen => "100vh".to_string(),
192            _ => self.to_css_value(),
193        }
194    }
195    
196    /// Convert to class name
197    pub fn to_class_name(&self) -> String {
198        match self {
199            SizingValue::Zero => "0".to_string(),
200            SizingValue::Px => "px".to_string(),
201            SizingValue::Fractional(f) => format!("{}", f),
202            SizingValue::Integer(i) => i.to_string(),
203            SizingValue::Auto => "auto".to_string(),
204            SizingValue::Full => "full".to_string(),
205            SizingValue::Screen => "screen".to_string(),
206            SizingValue::Min => "min".to_string(),
207            SizingValue::Max => "max".to_string(),
208            SizingValue::Fit => "fit".to_string(),
209            SizingValue::Fraction(f) => f.to_class_name(),
210            SizingValue::GridFraction(gf) => gf.to_class_name(),
211        }
212    }
213    
214    /// Get all valid sizing values
215    pub fn all_values() -> Vec<SizingValue> {
216        let mut values = vec![
217            SizingValue::Zero,
218            SizingValue::Px,
219            SizingValue::Fractional(0.5),
220            SizingValue::Fractional(1.5),
221            SizingValue::Fractional(2.5),
222            SizingValue::Fractional(3.5),
223            SizingValue::Integer(1),
224            SizingValue::Integer(2),
225            SizingValue::Integer(3),
226            SizingValue::Integer(4),
227            SizingValue::Integer(5),
228            SizingValue::Integer(6),
229            SizingValue::Integer(8),
230            SizingValue::Integer(10),
231            SizingValue::Integer(12),
232            SizingValue::Integer(16),
233            SizingValue::Integer(20),
234            SizingValue::Integer(24),
235            SizingValue::Integer(32),
236            SizingValue::Integer(40),
237            SizingValue::Integer(48),
238            SizingValue::Integer(56),
239            SizingValue::Integer(64),
240            SizingValue::Integer(72),
241            SizingValue::Integer(80),
242            SizingValue::Integer(96),
243            SizingValue::Auto,
244            SizingValue::Full,
245            SizingValue::Screen,
246            SizingValue::Min,
247            SizingValue::Max,
248            SizingValue::Fit,
249        ];
250        
251        // Add fractions
252        values.extend([
253            SizingValue::Fraction(Fraction::Half),
254            SizingValue::Fraction(Fraction::Third),
255            SizingValue::Fraction(Fraction::TwoThirds),
256            SizingValue::Fraction(Fraction::Quarter),
257            SizingValue::Fraction(Fraction::TwoQuarters),
258            SizingValue::Fraction(Fraction::ThreeQuarters),
259            SizingValue::Fraction(Fraction::Fifth),
260            SizingValue::Fraction(Fraction::TwoFifths),
261            SizingValue::Fraction(Fraction::ThreeFifths),
262            SizingValue::Fraction(Fraction::FourFifths),
263            SizingValue::Fraction(Fraction::Sixth),
264            SizingValue::Fraction(Fraction::TwoSixths),
265            SizingValue::Fraction(Fraction::ThreeSixths),
266            SizingValue::Fraction(Fraction::FourSixths),
267            SizingValue::Fraction(Fraction::FiveSixths),
268        ]);
269        
270        // Add grid fractions
271        values.extend([
272            SizingValue::GridFraction(GridFraction::OneTwelfth),
273            SizingValue::GridFraction(GridFraction::TwoTwelfths),
274            SizingValue::GridFraction(GridFraction::ThreeTwelfths),
275            SizingValue::GridFraction(GridFraction::FourTwelfths),
276            SizingValue::GridFraction(GridFraction::FiveTwelfths),
277            SizingValue::GridFraction(GridFraction::SixTwelfths),
278            SizingValue::GridFraction(GridFraction::SevenTwelfths),
279            SizingValue::GridFraction(GridFraction::EightTwelfths),
280            SizingValue::GridFraction(GridFraction::NineTwelfths),
281            SizingValue::GridFraction(GridFraction::TenTwelfths),
282            SizingValue::GridFraction(GridFraction::ElevenTwelfths),
283        ]);
284        
285        values
286    }
287}
288
289impl Fraction {
290    pub fn to_css_value(&self) -> String {
291        match self {
292            Fraction::Half => "50%".to_string(),
293            Fraction::Third => "33.333333%".to_string(),
294            Fraction::TwoThirds => "66.666667%".to_string(),
295            Fraction::Quarter => "25%".to_string(),
296            Fraction::TwoQuarters => "50%".to_string(),
297            Fraction::ThreeQuarters => "75%".to_string(),
298            Fraction::Fifth => "20%".to_string(),
299            Fraction::TwoFifths => "40%".to_string(),
300            Fraction::ThreeFifths => "60%".to_string(),
301            Fraction::FourFifths => "80%".to_string(),
302            Fraction::Sixth => "16.666667%".to_string(),
303            Fraction::TwoSixths => "33.333333%".to_string(),
304            Fraction::ThreeSixths => "50%".to_string(),
305            Fraction::FourSixths => "66.666667%".to_string(),
306            Fraction::FiveSixths => "83.333333%".to_string(),
307        }
308    }
309    
310    pub fn to_class_name(&self) -> String {
311        match self {
312            Fraction::Half => "1/2".to_string(),
313            Fraction::Third => "1/3".to_string(),
314            Fraction::TwoThirds => "2/3".to_string(),
315            Fraction::Quarter => "1/4".to_string(),
316            Fraction::TwoQuarters => "2/4".to_string(),
317            Fraction::ThreeQuarters => "3/4".to_string(),
318            Fraction::Fifth => "1/5".to_string(),
319            Fraction::TwoFifths => "2/5".to_string(),
320            Fraction::ThreeFifths => "3/5".to_string(),
321            Fraction::FourFifths => "4/5".to_string(),
322            Fraction::Sixth => "1/6".to_string(),
323            Fraction::TwoSixths => "2/6".to_string(),
324            Fraction::ThreeSixths => "3/6".to_string(),
325            Fraction::FourSixths => "4/6".to_string(),
326            Fraction::FiveSixths => "5/6".to_string(),
327        }
328    }
329}
330
331impl GridFraction {
332    pub fn to_css_value(&self) -> String {
333        match self {
334            GridFraction::OneTwelfth => "8.333333%".to_string(),
335            GridFraction::TwoTwelfths => "16.666667%".to_string(),
336            GridFraction::ThreeTwelfths => "25%".to_string(),
337            GridFraction::FourTwelfths => "33.333333%".to_string(),
338            GridFraction::FiveTwelfths => "41.666667%".to_string(),
339            GridFraction::SixTwelfths => "50%".to_string(),
340            GridFraction::SevenTwelfths => "58.333333%".to_string(),
341            GridFraction::EightTwelfths => "66.666667%".to_string(),
342            GridFraction::NineTwelfths => "75%".to_string(),
343            GridFraction::TenTwelfths => "83.333333%".to_string(),
344            GridFraction::ElevenTwelfths => "91.666667%".to_string(),
345        }
346    }
347    
348    pub fn to_class_name(&self) -> String {
349        match self {
350            GridFraction::OneTwelfth => "1/12".to_string(),
351            GridFraction::TwoTwelfths => "2/12".to_string(),
352            GridFraction::ThreeTwelfths => "3/12".to_string(),
353            GridFraction::FourTwelfths => "4/12".to_string(),
354            GridFraction::FiveTwelfths => "5/12".to_string(),
355            GridFraction::SixTwelfths => "6/12".to_string(),
356            GridFraction::SevenTwelfths => "7/12".to_string(),
357            GridFraction::EightTwelfths => "8/12".to_string(),
358            GridFraction::NineTwelfths => "9/12".to_string(),
359            GridFraction::TenTwelfths => "10/12".to_string(),
360            GridFraction::ElevenTwelfths => "11/12".to_string(),
361        }
362    }
363}
364
365impl fmt::Display for SizingValue {
366    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367        write!(f, "{}", self.to_class_name())
368    }
369}
370
371/// Trait for adding width utilities to a class builder
372pub trait WidthUtilities {
373    /// Set width
374    fn width(self, value: SizingValue) -> Self;
375    
376    /// Set min-width
377    fn min_width(self, value: SizingValue) -> Self;
378    
379    /// Set max-width
380    fn max_width(self, value: SizingValue) -> Self;
381}
382
383impl WidthUtilities for ClassBuilder {
384    fn width(self, value: SizingValue) -> Self {
385        self.class(format!("w-{}", value.to_class_name()))
386    }
387    
388    fn min_width(self, value: SizingValue) -> Self {
389        self.class(format!("min-w-{}", value.to_class_name()))
390    }
391    
392    fn max_width(self, value: SizingValue) -> Self {
393        self.class(format!("max-w-{}", value.to_class_name()))
394    }
395}
396
397/// Trait for adding height utilities to a class builder
398pub trait HeightUtilities {
399    /// Set height
400    fn height(self, value: SizingValue) -> Self;
401    
402    /// Set min-height
403    fn min_height(self, value: SizingValue) -> Self;
404    
405    /// Set max-height
406    fn max_height(self, value: SizingValue) -> Self;
407}
408
409impl HeightUtilities for ClassBuilder {
410    fn height(self, value: SizingValue) -> Self {
411        self.class(format!("h-{}", value.to_class_name()))
412    }
413    
414    fn min_height(self, value: SizingValue) -> Self {
415        self.class(format!("min-h-{}", value.to_class_name()))
416    }
417    
418    fn max_height(self, value: SizingValue) -> Self {
419        self.class(format!("max-h-{}", value.to_class_name()))
420    }
421}
422
423/// Trait for adding aspect-ratio utilities to a class builder
424pub trait AspectRatioUtilities {
425    /// Set aspect ratio
426    fn aspect_ratio(self, ratio: &str) -> Self;
427}
428
429impl AspectRatioUtilities for ClassBuilder {
430    fn aspect_ratio(self, ratio: &str) -> Self {
431        match ratio {
432            "1/1" => self.class("aspect-square"),
433            "16/9" => self.class("aspect-video"),
434            "4/3" => self.class("aspect-[4/3]"),
435            "3/2" => self.class("aspect-[3/2]"),
436            "2/3" => self.class("aspect-[2/3]"),
437            "9/16" => self.class("aspect-[9/16]"),
438            _ => self.class(format!("aspect-[{}]", ratio)),
439        }
440    }
441}
442
443/// Convenience methods for aspect-ratio utilities
444impl ClassBuilder {
445    /// Add aspect-square utility
446    pub fn aspect_square(self) -> Self {
447        self.aspect_ratio("1/1")
448    }
449    
450    /// Add aspect-video utility
451    pub fn aspect_video(self) -> Self {
452        self.aspect_ratio("16/9")
453    }
454    
455    /// Add aspect-[4/3] utility
456    pub fn aspect_4_3(self) -> Self {
457        self.aspect_ratio("4/3")
458    }
459}
460
461#[cfg(test)]
462mod tests {
463    use super::*;
464    
465    #[test]
466    fn test_sizing_value_to_css_value() {
467        assert_eq!(SizingValue::Zero.to_css_value(), "0");
468        assert_eq!(SizingValue::Px.to_css_value(), "1px");
469        assert_eq!(SizingValue::Fractional(0.5).to_css_value(), "0.125rem");
470        assert_eq!(SizingValue::Integer(4).to_css_value(), "1rem");
471        assert_eq!(SizingValue::Auto.to_css_value(), "auto");
472        assert_eq!(SizingValue::Full.to_css_value(), "100%");
473        assert_eq!(SizingValue::Screen.to_css_value(), "100vh");
474    }
475    
476    #[test]
477    fn test_sizing_value_to_css_value_width() {
478        assert_eq!(SizingValue::Screen.to_css_value_width(), "100vw");
479        assert_eq!(SizingValue::Full.to_css_value_width(), "100%");
480    }
481    
482    #[test]
483    fn test_sizing_value_to_css_value_height() {
484        assert_eq!(SizingValue::Screen.to_css_value_height(), "100vh");
485        assert_eq!(SizingValue::Full.to_css_value_height(), "100%");
486    }
487    
488    #[test]
489    fn test_fraction_to_css_value() {
490        assert_eq!(Fraction::Half.to_css_value(), "50%");
491        assert_eq!(Fraction::Third.to_css_value(), "33.333333%");
492        assert_eq!(Fraction::TwoThirds.to_css_value(), "66.666667%");
493    }
494    
495    #[test]
496    fn test_fraction_to_class_name() {
497        assert_eq!(Fraction::Half.to_class_name(), "1/2");
498        assert_eq!(Fraction::Third.to_class_name(), "1/3");
499        assert_eq!(Fraction::TwoThirds.to_class_name(), "2/3");
500    }
501    
502    #[test]
503    fn test_grid_fraction_to_css_value() {
504        assert_eq!(GridFraction::OneTwelfth.to_css_value(), "8.333333%");
505        assert_eq!(GridFraction::SixTwelfths.to_css_value(), "50%");
506        assert_eq!(GridFraction::ElevenTwelfths.to_css_value(), "91.666667%");
507    }
508    
509    #[test]
510    fn test_grid_fraction_to_class_name() {
511        assert_eq!(GridFraction::OneTwelfth.to_class_name(), "1/12");
512        assert_eq!(GridFraction::SixTwelfths.to_class_name(), "6/12");
513        assert_eq!(GridFraction::ElevenTwelfths.to_class_name(), "11/12");
514    }
515    
516    #[test]
517    fn test_width_utilities() {
518        let classes = ClassBuilder::new()
519            .width(SizingValue::Full)
520            .min_width(SizingValue::Integer(4))
521            .max_width(SizingValue::Integer(8))
522            .build();
523        
524        let css_classes = classes.to_css_classes();
525        assert!(css_classes.contains("w-full"));
526        assert!(css_classes.contains("min-w-4"));
527        assert!(css_classes.contains("max-w-8"));
528    }
529    
530    #[test]
531    fn test_height_utilities() {
532        let classes = ClassBuilder::new()
533            .height(SizingValue::Screen)
534            .min_height(SizingValue::Integer(4))
535            .max_height(SizingValue::Integer(8))
536            .build();
537        
538        let css_classes = classes.to_css_classes();
539        assert!(css_classes.contains("h-screen"));
540        assert!(css_classes.contains("min-h-4"));
541        assert!(css_classes.contains("max-h-8"));
542    }
543    
544    #[test]
545    fn test_fractional_sizing() {
546        let classes = ClassBuilder::new()
547            .width(SizingValue::Fraction(Fraction::Half))
548            .height(SizingValue::Fraction(Fraction::Third))
549            .build();
550        
551        let css_classes = classes.to_css_classes();
552        assert!(css_classes.contains("w-1/2"));
553        assert!(css_classes.contains("h-1/3"));
554    }
555    
556    #[test]
557    fn test_grid_fractional_sizing() {
558        let classes = ClassBuilder::new()
559            .width(SizingValue::GridFraction(GridFraction::SixTwelfths))
560            .height(SizingValue::GridFraction(GridFraction::FourTwelfths))
561            .build();
562        
563        let css_classes = classes.to_css_classes();
564        assert!(css_classes.contains("w-6/12"));
565        assert!(css_classes.contains("h-4/12"));
566    }
567    
568    #[test]
569    fn test_special_sizing_values() {
570        let classes = ClassBuilder::new()
571            .width(SizingValue::Auto)
572            .height(SizingValue::Fit)
573            .min_width(SizingValue::Min)
574            .max_width(SizingValue::Max)
575            .build();
576        
577        let css_classes = classes.to_css_classes();
578        assert!(css_classes.contains("w-auto"));
579        assert!(css_classes.contains("h-fit"));
580        assert!(css_classes.contains("min-w-min"));
581        assert!(css_classes.contains("max-w-max"));
582    }
583    
584    /// Test that all Tailwind CSS sizing values are supported
585    #[test]
586    fn test_all_tailwind_sizing_values() {
587        // Test all standard Tailwind CSS sizing values
588        let test_values = vec![
589            // Standard integer values (0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 56, 64, 72, 80, 96)
590            (SizingValue::Zero, "w-0"),
591            (SizingValue::Integer(1), "w-1"),
592            (SizingValue::Integer(2), "w-2"),
593            (SizingValue::Integer(3), "w-3"),
594            (SizingValue::Integer(4), "w-4"),
595            (SizingValue::Integer(5), "w-5"),
596            (SizingValue::Integer(6), "w-6"),
597            (SizingValue::Integer(8), "w-8"),
598            (SizingValue::Integer(10), "w-10"),
599            (SizingValue::Integer(12), "w-12"),
600            (SizingValue::Integer(16), "w-16"),
601            (SizingValue::Integer(20), "w-20"),
602            (SizingValue::Integer(24), "w-24"),
603            (SizingValue::Integer(32), "w-32"),
604            (SizingValue::Integer(40), "w-40"),
605            (SizingValue::Integer(48), "w-48"),
606            (SizingValue::Integer(56), "w-56"),
607            (SizingValue::Integer(64), "w-64"),
608            (SizingValue::Integer(72), "w-72"),
609            (SizingValue::Integer(80), "w-80"),
610            (SizingValue::Integer(96), "w-96"),
611            // Fractional values (0.5, 1.5, 2.5, 3.5)
612            (SizingValue::Fractional(0.5), "w-0.5"),
613            (SizingValue::Fractional(1.5), "w-1.5"),
614            (SizingValue::Fractional(2.5), "w-2.5"),
615            (SizingValue::Fractional(3.5), "w-3.5"),
616            // Special values
617            (SizingValue::Px, "w-px"),
618            (SizingValue::Auto, "w-auto"),
619            (SizingValue::Full, "w-full"),
620            (SizingValue::Screen, "w-screen"),
621            (SizingValue::Min, "w-min"),
622            (SizingValue::Max, "w-max"),
623            (SizingValue::Fit, "w-fit"),
624        ];
625        
626        for (value, expected_class) in test_values {
627            let classes = ClassBuilder::new().width(value).build();
628            let css_classes = classes.to_css_classes();
629            assert!(css_classes.contains(expected_class), 
630                "Missing sizing value: {} (expected class: {})", 
631                format!("{:?}", value), expected_class);
632        }
633    }
634    
635    /// Test that aspect-ratio utilities are implemented
636    #[test]
637    fn test_aspect_ratio_utilities() {
638        // This test will fail until we implement aspect-ratio utilities
639        let classes = ClassBuilder::new()
640            .aspect_ratio("1/1")  // aspect-square
641            .aspect_ratio("16/9")  // aspect-video
642            .aspect_ratio("4/3")  // aspect-[4/3]
643            .build();
644        
645        let css_classes = classes.to_css_classes();
646        assert!(css_classes.contains("aspect-square"));
647        assert!(css_classes.contains("aspect-video"));
648        assert!(css_classes.contains("aspect-[4/3]"));
649    }
650
651    #[test]
652    fn test_sizing_value_display() {
653        // Test that SizingValue displays correctly
654        assert_eq!(format!("{}", SizingValue::Zero), "0");
655        assert_eq!(format!("{}", SizingValue::Px), "px");
656        assert_eq!(format!("{}", SizingValue::Fractional(0.5)), "0.5");
657        assert_eq!(format!("{}", SizingValue::Integer(4)), "4");
658        assert_eq!(format!("{}", SizingValue::Auto), "auto");
659        assert_eq!(format!("{}", SizingValue::Full), "full");
660        assert_eq!(format!("{}", SizingValue::Screen), "screen");
661        assert_eq!(format!("{}", SizingValue::Min), "min");
662        assert_eq!(format!("{}", SizingValue::Max), "max");
663        assert_eq!(format!("{}", SizingValue::Fit), "fit");
664        assert_eq!(format!("{}", SizingValue::Fraction(Fraction::Half)), "1/2");
665        assert_eq!(format!("{}", SizingValue::GridFraction(GridFraction::SixTwelfths)), "6/12");
666    }
667
668    #[test]
669    fn test_fraction_display() {
670        // Test that Fraction displays correctly
671        assert_eq!(Fraction::Half.to_class_name(), "1/2");
672        assert_eq!(Fraction::Third.to_class_name(), "1/3");
673        assert_eq!(Fraction::TwoThirds.to_class_name(), "2/3");
674        assert_eq!(Fraction::Quarter.to_class_name(), "1/4");
675        assert_eq!(Fraction::TwoQuarters.to_class_name(), "2/4");
676        assert_eq!(Fraction::ThreeQuarters.to_class_name(), "3/4");
677        assert_eq!(Fraction::Fifth.to_class_name(), "1/5");
678        assert_eq!(Fraction::TwoFifths.to_class_name(), "2/5");
679        assert_eq!(Fraction::ThreeFifths.to_class_name(), "3/5");
680        assert_eq!(Fraction::FourFifths.to_class_name(), "4/5");
681        assert_eq!(Fraction::Sixth.to_class_name(), "1/6");
682        assert_eq!(Fraction::TwoSixths.to_class_name(), "2/6");
683        assert_eq!(Fraction::ThreeSixths.to_class_name(), "3/6");
684        assert_eq!(Fraction::FourSixths.to_class_name(), "4/6");
685        assert_eq!(Fraction::FiveSixths.to_class_name(), "5/6");
686    }
687
688    #[test]
689    fn test_grid_fraction_display() {
690        // Test that GridFraction displays correctly
691        assert_eq!(GridFraction::OneTwelfth.to_class_name(), "1/12");
692        assert_eq!(GridFraction::TwoTwelfths.to_class_name(), "2/12");
693        assert_eq!(GridFraction::ThreeTwelfths.to_class_name(), "3/12");
694        assert_eq!(GridFraction::FourTwelfths.to_class_name(), "4/12");
695        assert_eq!(GridFraction::FiveTwelfths.to_class_name(), "5/12");
696        assert_eq!(GridFraction::SixTwelfths.to_class_name(), "6/12");
697        assert_eq!(GridFraction::SevenTwelfths.to_class_name(), "7/12");
698        assert_eq!(GridFraction::EightTwelfths.to_class_name(), "8/12");
699        assert_eq!(GridFraction::NineTwelfths.to_class_name(), "9/12");
700        assert_eq!(GridFraction::TenTwelfths.to_class_name(), "10/12");
701        assert_eq!(GridFraction::ElevenTwelfths.to_class_name(), "11/12");
702    }
703
704    #[test]
705    fn test_sizing_value_class_names() {
706        // Test that SizingValue generates correct class names
707        assert_eq!(SizingValue::Zero.to_class_name(), "0");
708        assert_eq!(SizingValue::Px.to_class_name(), "px");
709        assert_eq!(SizingValue::Fractional(0.5).to_class_name(), "0.5");
710        assert_eq!(SizingValue::Integer(4).to_class_name(), "4");
711        assert_eq!(SizingValue::Auto.to_class_name(), "auto");
712        assert_eq!(SizingValue::Full.to_class_name(), "full");
713        assert_eq!(SizingValue::Screen.to_class_name(), "screen");
714        assert_eq!(SizingValue::Min.to_class_name(), "min");
715        assert_eq!(SizingValue::Max.to_class_name(), "max");
716        assert_eq!(SizingValue::Fit.to_class_name(), "fit");
717        assert_eq!(SizingValue::Fraction(Fraction::Half).to_class_name(), "1/2");
718        assert_eq!(SizingValue::GridFraction(GridFraction::SixTwelfths).to_class_name(), "6/12");
719    }
720
721    #[test]
722    fn test_all_fraction_css_values() {
723        // Test that all Fraction variants generate correct CSS values
724        assert_eq!(Fraction::Half.to_css_value(), "50%");
725        assert_eq!(Fraction::Third.to_css_value(), "33.333333%");
726        assert_eq!(Fraction::TwoThirds.to_css_value(), "66.666667%");
727        assert_eq!(Fraction::Quarter.to_css_value(), "25%");
728        assert_eq!(Fraction::TwoQuarters.to_css_value(), "50%");
729        assert_eq!(Fraction::ThreeQuarters.to_css_value(), "75%");
730        assert_eq!(Fraction::Fifth.to_css_value(), "20%");
731        assert_eq!(Fraction::TwoFifths.to_css_value(), "40%");
732        assert_eq!(Fraction::ThreeFifths.to_css_value(), "60%");
733        assert_eq!(Fraction::FourFifths.to_css_value(), "80%");
734        assert_eq!(Fraction::Sixth.to_css_value(), "16.666667%");
735        assert_eq!(Fraction::TwoSixths.to_css_value(), "33.333333%");
736        assert_eq!(Fraction::ThreeSixths.to_css_value(), "50%");
737        assert_eq!(Fraction::FourSixths.to_css_value(), "66.666667%");
738        assert_eq!(Fraction::FiveSixths.to_css_value(), "83.333333%");
739    }
740
741    #[test]
742    fn test_all_fraction_class_names() {
743        // Test that all Fraction variants generate correct class names
744        assert_eq!(Fraction::Half.to_class_name(), "1/2");
745        assert_eq!(Fraction::Third.to_class_name(), "1/3");
746        assert_eq!(Fraction::TwoThirds.to_class_name(), "2/3");
747        assert_eq!(Fraction::Quarter.to_class_name(), "1/4");
748        assert_eq!(Fraction::TwoQuarters.to_class_name(), "2/4");
749        assert_eq!(Fraction::ThreeQuarters.to_class_name(), "3/4");
750        assert_eq!(Fraction::Fifth.to_class_name(), "1/5");
751        assert_eq!(Fraction::TwoFifths.to_class_name(), "2/5");
752        assert_eq!(Fraction::ThreeFifths.to_class_name(), "3/5");
753        assert_eq!(Fraction::FourFifths.to_class_name(), "4/5");
754        assert_eq!(Fraction::Sixth.to_class_name(), "1/6");
755        assert_eq!(Fraction::TwoSixths.to_class_name(), "2/6");
756        assert_eq!(Fraction::ThreeSixths.to_class_name(), "3/6");
757        assert_eq!(Fraction::FourSixths.to_class_name(), "4/6");
758        assert_eq!(Fraction::FiveSixths.to_class_name(), "5/6");
759    }
760
761    #[test]
762    fn test_all_grid_fraction_css_values() {
763        // Test that all GridFraction variants generate correct CSS values
764        assert_eq!(GridFraction::OneTwelfth.to_css_value(), "8.333333%");
765        assert_eq!(GridFraction::TwoTwelfths.to_css_value(), "16.666667%");
766        assert_eq!(GridFraction::ThreeTwelfths.to_css_value(), "25%");
767        assert_eq!(GridFraction::FourTwelfths.to_css_value(), "33.333333%");
768        assert_eq!(GridFraction::FiveTwelfths.to_css_value(), "41.666667%");
769        assert_eq!(GridFraction::SixTwelfths.to_css_value(), "50%");
770        assert_eq!(GridFraction::SevenTwelfths.to_css_value(), "58.333333%");
771        assert_eq!(GridFraction::EightTwelfths.to_css_value(), "66.666667%");
772        assert_eq!(GridFraction::NineTwelfths.to_css_value(), "75%");
773        assert_eq!(GridFraction::TenTwelfths.to_css_value(), "83.333333%");
774        assert_eq!(GridFraction::ElevenTwelfths.to_css_value(), "91.666667%");
775    }
776
777    #[test]
778    fn test_all_grid_fraction_class_names() {
779        // Test that all GridFraction variants generate correct class names
780        assert_eq!(GridFraction::OneTwelfth.to_class_name(), "1/12");
781        assert_eq!(GridFraction::TwoTwelfths.to_class_name(), "2/12");
782        assert_eq!(GridFraction::ThreeTwelfths.to_class_name(), "3/12");
783        assert_eq!(GridFraction::FourTwelfths.to_class_name(), "4/12");
784        assert_eq!(GridFraction::FiveTwelfths.to_class_name(), "5/12");
785        assert_eq!(GridFraction::SixTwelfths.to_class_name(), "6/12");
786        assert_eq!(GridFraction::SevenTwelfths.to_class_name(), "7/12");
787        assert_eq!(GridFraction::EightTwelfths.to_class_name(), "8/12");
788        assert_eq!(GridFraction::NineTwelfths.to_class_name(), "9/12");
789        assert_eq!(GridFraction::TenTwelfths.to_class_name(), "10/12");
790        assert_eq!(GridFraction::ElevenTwelfths.to_class_name(), "11/12");
791    }
792
793    #[test]
794    fn test_sizing_serialization() {
795        // Test that sizing enums can be serialized and deserialized
796        let sizing_value = SizingValue::Full;
797        let serialized = serde_json::to_string(&sizing_value).unwrap();
798        let deserialized: SizingValue = serde_json::from_str(&serialized).unwrap();
799        assert_eq!(sizing_value, deserialized);
800
801        let fraction = Fraction::Half;
802        let serialized = serde_json::to_string(&fraction).unwrap();
803        let deserialized: Fraction = serde_json::from_str(&serialized).unwrap();
804        assert_eq!(fraction, deserialized);
805
806        let grid_fraction = GridFraction::SixTwelfths;
807        let serialized = serde_json::to_string(&grid_fraction).unwrap();
808        let deserialized: GridFraction = serde_json::from_str(&serialized).unwrap();
809        assert_eq!(grid_fraction, deserialized);
810    }
811
812    #[test]
813    fn test_sizing_equality_and_hash() {
814        // Test that sizing enums can be compared for equality and hashed
815        let sizing_value1 = SizingValue::Full;
816        let sizing_value2 = SizingValue::Full;
817        let sizing_value3 = SizingValue::Auto;
818        
819        assert_eq!(sizing_value1, sizing_value2);
820        assert_ne!(sizing_value1, sizing_value3);
821        
822        // Test that equal enums have the same hash
823        use std::collections::hash_map::DefaultHasher;
824        use std::hash::{Hash, Hasher};
825        
826        let mut hasher1 = DefaultHasher::new();
827        let mut hasher2 = DefaultHasher::new();
828        sizing_value1.hash(&mut hasher1);
829        sizing_value2.hash(&mut hasher2);
830        assert_eq!(hasher1.finish(), hasher2.finish());
831    }
832
833    #[test]
834    fn test_aspect_ratio_convenience_methods() {
835        // Test that aspect ratio convenience methods work
836        let classes = ClassBuilder::new()
837            .aspect_square()
838            .aspect_video()
839            .aspect_4_3()
840            .build();
841        
842        let css_classes = classes.to_css_classes();
843        assert!(css_classes.contains("aspect-square"));
844        assert!(css_classes.contains("aspect-video"));
845        assert!(css_classes.contains("aspect-[4/3]"));
846    }
847
848    #[test]
849    fn test_comprehensive_sizing_utilities() {
850        // Test comprehensive usage of all sizing utility methods
851        let classes = ClassBuilder::new()
852            // Width utilities
853            .width(SizingValue::Zero)
854            .width(SizingValue::Px)
855            .width(SizingValue::Fractional(0.5))
856            .width(SizingValue::Integer(4))
857            .width(SizingValue::Auto)
858            .width(SizingValue::Full)
859            .width(SizingValue::Screen)
860            .width(SizingValue::Min)
861            .width(SizingValue::Max)
862            .width(SizingValue::Fit)
863            .width(SizingValue::Fraction(Fraction::Half))
864            .width(SizingValue::GridFraction(GridFraction::SixTwelfths))
865            
866            // Min width utilities
867            .min_width(SizingValue::Integer(2))
868            .min_width(SizingValue::Fraction(Fraction::Third))
869            
870            // Max width utilities
871            .max_width(SizingValue::Integer(8))
872            .max_width(SizingValue::Fraction(Fraction::TwoThirds))
873            
874            // Height utilities
875            .height(SizingValue::Zero)
876            .height(SizingValue::Px)
877            .height(SizingValue::Fractional(1.5))
878            .height(SizingValue::Integer(6))
879            .height(SizingValue::Auto)
880            .height(SizingValue::Full)
881            .height(SizingValue::Screen)
882            .height(SizingValue::Min)
883            .height(SizingValue::Max)
884            .height(SizingValue::Fit)
885            .height(SizingValue::Fraction(Fraction::Quarter))
886            .height(SizingValue::GridFraction(GridFraction::FourTwelfths))
887            
888            // Min height utilities
889            .min_height(SizingValue::Integer(3))
890            .min_height(SizingValue::Fraction(Fraction::Fifth))
891            
892            // Max height utilities
893            .max_height(SizingValue::Integer(10))
894            .max_height(SizingValue::Fraction(Fraction::ThreeQuarters))
895            
896            // Aspect ratio utilities
897            .aspect_ratio("1/1")
898            .aspect_ratio("16/9")
899            .aspect_ratio("4/3")
900            .aspect_ratio("3/2")
901            .aspect_ratio("2/3")
902            .aspect_ratio("9/16")
903            .aspect_ratio("21/9")
904            .build();
905        
906        let css_classes = classes.to_css_classes();
907        
908        // Verify width utilities
909        assert!(css_classes.contains("w-0"));
910        assert!(css_classes.contains("w-px"));
911        assert!(css_classes.contains("w-0.5"));
912        assert!(css_classes.contains("w-4"));
913        assert!(css_classes.contains("w-auto"));
914        assert!(css_classes.contains("w-full"));
915        assert!(css_classes.contains("w-screen"));
916        assert!(css_classes.contains("w-min"));
917        assert!(css_classes.contains("w-max"));
918        assert!(css_classes.contains("w-fit"));
919        assert!(css_classes.contains("w-1/2"));
920        assert!(css_classes.contains("w-6/12"));
921        
922        // Verify min width utilities
923        assert!(css_classes.contains("min-w-2"));
924        assert!(css_classes.contains("min-w-1/3"));
925        
926        // Verify max width utilities
927        assert!(css_classes.contains("max-w-8"));
928        assert!(css_classes.contains("max-w-2/3"));
929        
930        // Verify height utilities
931        assert!(css_classes.contains("h-0"));
932        assert!(css_classes.contains("h-px"));
933        assert!(css_classes.contains("h-1.5"));
934        assert!(css_classes.contains("h-6"));
935        assert!(css_classes.contains("h-auto"));
936        assert!(css_classes.contains("h-full"));
937        assert!(css_classes.contains("h-screen"));
938        assert!(css_classes.contains("h-min"));
939        assert!(css_classes.contains("h-max"));
940        assert!(css_classes.contains("h-fit"));
941        assert!(css_classes.contains("h-1/4"));
942        assert!(css_classes.contains("h-4/12"));
943        
944        // Verify min height utilities
945        assert!(css_classes.contains("min-h-3"));
946        assert!(css_classes.contains("min-h-1/5"));
947        
948        // Verify max height utilities
949        assert!(css_classes.contains("max-h-10"));
950        assert!(css_classes.contains("max-h-3/4"));
951        
952        // Verify aspect ratio utilities
953        assert!(css_classes.contains("aspect-square"));
954        assert!(css_classes.contains("aspect-video"));
955        assert!(css_classes.contains("aspect-[4/3]"));
956        assert!(css_classes.contains("aspect-[3/2]"));
957        assert!(css_classes.contains("aspect-[2/3]"));
958        assert!(css_classes.contains("aspect-[9/16]"));
959        assert!(css_classes.contains("aspect-[21/9]"));
960    }
961}