stylex_css_parser/css_types/
angle.rs1use stylex_macros::stylex_unreachable;
8
9use crate::{token_parser::TokenParser, token_types::SimpleToken};
10use std::fmt::{self, Display};
11
12pub const ANGLE_UNITS: &[&str] = &["deg", "grad", "rad", "turn"];
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct Angle {
18 pub value: f32,
19 pub unit: String, }
21
22impl Angle {
23 pub fn new(value: f32, unit: String) -> Self {
25 Self { value, unit }
26 }
27
28 pub fn units() -> &'static [&'static str] {
30 ANGLE_UNITS
31 }
32
33 pub fn is_valid_unit(unit: &str) -> bool {
35 ANGLE_UNITS.contains(&unit)
36 }
37
38 pub fn parser() -> TokenParser<Angle> {
40 let dimension_parser = TokenParser::<SimpleToken>::token(
42 SimpleToken::Dimension {
43 value: 0.0,
44 unit: String::new(),
45 },
46 Some("Dimension"),
47 )
48 .where_fn(
49 |token| {
50 if let SimpleToken::Dimension { unit, .. } = token {
51 Self::is_valid_unit(unit)
52 } else {
53 false
54 }
55 },
56 Some("valid_angle_unit"),
57 )
58 .map(
59 |token| {
60 if let SimpleToken::Dimension { value, unit } = token {
61 Angle::new(value as f32, unit)
62 } else {
63 stylex_unreachable!()
64 }
65 },
66 Some("to_angle"),
67 );
68
69 let zero_parser = TokenParser::<SimpleToken>::token(SimpleToken::Number(0.0), Some("Number"))
71 .where_fn(
72 |token| {
73 if let SimpleToken::Number(value) = token {
74 *value == 0.0
75 } else {
76 false
77 }
78 },
79 Some("zero_value"),
80 )
81 .map(|_| Angle::new(0.0, "deg".to_string()), Some("zero_angle"));
82
83 TokenParser::one_of(vec![dimension_parser, zero_parser])
85 }
86}
87
88impl Display for Angle {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 write!(f, "{}{}", self.value, self.unit)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_angle_creation() {
100 let angle = Angle::new(45.0, "deg".to_string());
101 assert_eq!(angle.value, 45.0);
102 assert_eq!(angle.unit, "deg");
103 }
104
105 #[test]
106 fn test_angle_display() {
107 let degrees = Angle::new(45.0, "deg".to_string());
108 assert_eq!(degrees.to_string(), "45deg");
109
110 let radians = Angle::new(1.57, "rad".to_string());
111 assert_eq!(radians.to_string(), "1.57rad");
112
113 let gradians = Angle::new(100.0, "grad".to_string());
114 assert_eq!(gradians.to_string(), "100grad");
115
116 let turns = Angle::new(0.25, "turn".to_string());
117 assert_eq!(turns.to_string(), "0.25turn");
118
119 let zero_angle = Angle::new(0.0, "deg".to_string());
121 assert_eq!(zero_angle.to_string(), "0deg");
122 }
123
124 #[test]
125 fn test_valid_angle_units() {
126 assert!(Angle::is_valid_unit("deg"));
127 assert!(Angle::is_valid_unit("grad"));
128 assert!(Angle::is_valid_unit("rad"));
129 assert!(Angle::is_valid_unit("turn"));
130
131 assert!(!Angle::is_valid_unit("px"));
133 assert!(!Angle::is_valid_unit("s"));
134 assert!(!Angle::is_valid_unit("Hz"));
135 assert!(!Angle::is_valid_unit("invalid"));
136 }
137
138 #[test]
139 fn test_angle_units_constant() {
140 let units = Angle::units();
141 assert_eq!(units.len(), 4);
142 assert!(units.contains(&"deg"));
143 assert!(units.contains(&"grad"));
144 assert!(units.contains(&"rad"));
145 assert!(units.contains(&"turn"));
146 }
147
148 #[test]
149 fn test_angle_parser_creation() {
150 let _parser = Angle::parser();
152 }
153
154 #[test]
155 fn test_angle_equality() {
156 let angle1 = Angle::new(45.0, "deg".to_string());
157 let angle2 = Angle::new(45.0, "deg".to_string());
158 let angle3 = Angle::new(90.0, "deg".to_string());
159 let angle4 = Angle::new(45.0, "rad".to_string());
160
161 assert_eq!(angle1, angle2);
162 assert_ne!(angle1, angle3);
163 assert_ne!(angle1, angle4);
164 }
165
166 #[test]
167 fn test_angle_units_coverage() {
168 assert!(Angle::is_valid_unit("deg")); assert!(Angle::is_valid_unit("grad")); assert!(Angle::is_valid_unit("rad")); assert!(Angle::is_valid_unit("turn")); }
174}