takumi_css/style/properties/
border.rs1use crate::style::unexpected_token;
2use cssparser::Parser;
3
4use crate::style::{
5 BorderStyle, ColorInput, CssSyntaxKind, CssToken, FromCss, MakeComputed, ParseResult,
6 SizingContext, properties::Length,
7};
8
9#[derive(Debug, Default, Clone, Copy, PartialEq)]
11#[non_exhaustive]
12pub struct Border {
13 pub width: Length,
15 pub style: BorderStyle,
17 pub color: ColorInput,
19}
20
21impl<'i> FromCss<'i> for Border {
22 fn from_css(input: &mut Parser<'i, '_>) -> ParseResult<'i, Self> {
23 let mut width = None;
24 let mut style = None;
25 let mut color = None;
26
27 loop {
28 if input.is_exhausted() {
29 break;
30 }
31
32 if let Ok(value) = input.try_parse(Length::from_css) {
33 width = Some(value);
34 continue;
35 }
36
37 if let Ok(value) = input.try_parse(BorderStyle::from_css) {
38 style = Some(value);
39 continue;
40 }
41
42 if let Ok(value) = input.try_parse(ColorInput::from_css) {
43 color = Some(value);
44 continue;
45 }
46
47 return Err(unexpected_token!(
48 input.current_source_location(),
49 input.next()?,
50 ));
51 }
52
53 Ok(Border {
54 width: width.unwrap_or_default(),
55 style: style.unwrap_or_default(),
56 color: color.unwrap_or_default(),
57 })
58 }
59
60 const VALID_TOKENS: &'static [CssToken] = &[
61 CssToken::Syntax(CssSyntaxKind::Length),
62 CssToken::Syntax(CssSyntaxKind::BorderStyle),
63 CssToken::Syntax(CssSyntaxKind::Color),
64 ];
65}
66
67impl MakeComputed for Border {
68 fn make_computed(&mut self, sizing: &SizingContext) {
69 self.width.make_computed(sizing);
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use crate::style::Color;
76
77 use super::*;
78
79 #[test]
80 fn test_parse_border_style_solid() {
81 assert_eq!(BorderStyle::from_str("solid"), Ok(BorderStyle::Solid));
82 }
83
84 #[test]
85 fn test_parse_border_style_dashed() {
86 assert_eq!(BorderStyle::from_str("dashed"), Ok(BorderStyle::Dashed));
87 }
88
89 #[test]
90 fn test_parse_border_width_only() {
91 assert_eq!(
92 Border::from_str("10px"),
93 Ok(Border {
94 width: Length::Px(10.0),
95 style: BorderStyle::None,
96 color: ColorInput::CurrentColor,
97 })
98 );
99 }
100
101 #[test]
102 fn test_parse_border_style_only() {
103 assert_eq!(
104 Border::from_str("solid"),
105 Ok(Border {
106 width: Length::default(),
107 style: BorderStyle::Solid,
108 color: ColorInput::CurrentColor,
109 })
110 );
111 }
112
113 #[test]
114 fn test_parse_border_color_only() {
115 assert_eq!(
116 Border::from_str("red"),
117 Ok(Border {
118 width: Length::default(),
119 style: BorderStyle::None,
120 color: ColorInput::Value(Color([255, 0, 0, 255])),
121 })
122 );
123 }
124
125 #[test]
126 fn test_parse_border_width_and_style() {
127 assert_eq!(
128 Border::from_str("2px solid"),
129 Ok(Border {
130 width: Length::Px(2.0),
131 style: BorderStyle::Solid,
132 color: ColorInput::CurrentColor,
133 })
134 );
135 }
136
137 #[test]
138 fn test_parse_border_width_style_color() {
139 assert_eq!(
140 Border::from_str("2px solid red"),
141 Ok(Border {
142 width: Length::Px(2.0),
143 style: BorderStyle::Solid,
144 color: ColorInput::Value(Color([255, 0, 0, 255])),
145 })
146 );
147 }
148
149 #[test]
150 fn test_parse_border_style_width_color() {
151 assert_eq!(
152 Border::from_str("solid 2px red"),
153 Ok(Border {
154 width: Length::Px(2.0),
155 style: BorderStyle::Solid,
156 color: ColorInput::Value(Color([255, 0, 0, 255])),
157 })
158 );
159 }
160
161 #[test]
162 fn test_parse_border_color_style_width() {
163 assert_eq!(
164 Border::from_str("red solid 2px"),
165 Ok(Border {
166 width: Length::Px(2.0),
167 style: BorderStyle::Solid,
168 color: ColorInput::Value(Color([255, 0, 0, 255])),
169 })
170 );
171 }
172
173 #[test]
174 fn test_parse_border_rem_units() {
175 assert_eq!(
176 Border::from_str("1.5rem solid blue"),
177 Ok(Border {
178 width: Length::Rem(1.5),
179 style: BorderStyle::Solid,
180 color: ColorInput::Value(Color([0, 0, 255, 255])),
181 })
182 );
183 }
184
185 #[test]
186 fn test_parse_border_hex_color() {
187 assert_eq!(
188 Border::from_str("3px solid #ff0000"),
189 Ok(Border {
190 width: Length::Px(3.0),
191 style: BorderStyle::Solid,
192 color: ColorInput::Value(Color([255, 0, 0, 255])),
193 })
194 );
195 }
196
197 #[test]
198 fn test_parse_border_rgb_color() {
199 assert_eq!(
200 Border::from_str("4px solid rgb(0, 255, 0)"),
201 Ok(Border {
202 width: Length::Px(4.0),
203 style: BorderStyle::Solid,
204 color: ColorInput::Value(Color([0, 255, 0, 255])),
205 })
206 );
207 }
208
209 #[test]
210 fn test_parse_border_dashed() {
211 assert_eq!(
212 Border::from_str("2px dashed red"),
213 Ok(Border {
214 width: Length::Px(2.0),
215 style: BorderStyle::Dashed,
216 color: ColorInput::Value(Color([255, 0, 0, 255])),
217 })
218 );
219 }
220
221 #[test]
222 fn test_parse_border_invalid_color() {
223 assert!(Border::from_str("2px solid invalid-color").is_err());
224 }
225
226 #[test]
227 fn test_parse_border_empty() {
228 assert_eq!(Border::from_str(""), Ok(Border::default()));
229 }
230
231 #[test]
232 fn test_border_value_from_css() {
233 assert_eq!(
234 Border::from_str("3px solid blue"),
235 Ok(Border {
236 width: Length::Px(3.0),
237 style: BorderStyle::Solid,
238 color: ColorInput::Value(Color([0, 0, 255, 255])),
239 })
240 );
241 }
242
243 #[test]
244 fn test_border_value_from_invalid_css() {
245 assert!(Border::from_str("invalid border").is_err());
246 }
247}