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}