1use alloc::string::{String, ToString};
4use core::fmt;
5use crate::corety::AzString;
6
7#[cfg(feature = "parser")]
8use crate::props::basic::{color::parse_css_color, pixel::parse_pixel_value};
9use crate::{
10 css::PrintAsCssValue,
11 props::{
12 basic::{
13 color::{ColorU, CssColorParseError, CssColorParseErrorOwned},
14 pixel::{
15 CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
16 MEDIUM_BORDER_THICKNESS, THICK_BORDER_THICKNESS, THIN_BORDER_THICKNESS,
17 },
18 },
19 macros::PixelValueTaker,
20 },
21};
22
23#[derive(Debug, Copy, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
25#[repr(C)]
26#[derive(Default)]
28pub enum BorderStyle {
29 #[default]
30 None,
31 Solid,
32 Double,
33 Dotted,
34 Dashed,
35 Hidden,
36 Groove,
37 Ridge,
38 Inset,
39 Outset,
40}
41
42
43impl fmt::Display for BorderStyle {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(
46 f,
47 "{}",
48 match self {
49 Self::None => "none",
50 Self::Solid => "solid",
51 Self::Double => "double",
52 Self::Dotted => "dotted",
53 Self::Dashed => "dashed",
54 Self::Hidden => "hidden",
55 Self::Groove => "groove",
56 Self::Ridge => "ridge",
57 Self::Inset => "inset",
58 Self::Outset => "outset",
59 }
60 )
61 }
62}
63
64impl PrintAsCssValue for BorderStyle {
65 fn print_as_css_value(&self) -> String {
66 self.to_string()
67 }
68}
69
70macro_rules! define_border_side_property {
72 ($struct_name:ident, $inner_type:ty, $default:expr) => {
74 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
75 #[repr(C)]
76 pub struct $struct_name {
77 pub inner: $inner_type,
78 }
79 impl ::core::fmt::Debug for $struct_name {
80 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
81 write!(f, "{}", self.inner)
82 }
83 }
84 impl Default for $struct_name {
85 fn default() -> Self {
86 Self { inner: $default }
87 }
88 }
89 };
90 ($struct_name:ident,ColorU) => {
92 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93 #[repr(C)]
94 pub struct $struct_name {
95 pub inner: ColorU,
96 }
97 impl ::core::fmt::Debug for $struct_name {
98 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
99 write!(f, "{}", self.inner.to_hash())
100 }
101 }
102 impl Default for $struct_name {
105 fn default() -> Self {
106 Self {
107 inner: ColorU::BLACK,
108 }
109 }
110 }
111 impl $struct_name {
112 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
113 Self {
114 inner: self.inner.interpolate(&other.inner, t),
115 }
116 }
117 }
118 };
119 }
124
125define_border_side_property!(StyleBorderTopStyle, BorderStyle, BorderStyle::None);
131define_border_side_property!(StyleBorderRightStyle, BorderStyle, BorderStyle::None);
133define_border_side_property!(StyleBorderBottomStyle, BorderStyle, BorderStyle::None);
135define_border_side_property!(StyleBorderLeftStyle, BorderStyle, BorderStyle::None);
137
138impl crate::codegen::format::FormatAsRustCode for StyleBorderTopStyle {
140 fn format_as_rust_code(&self, tabs: usize) -> String {
141 format!(
142 "StyleBorderTopStyle {{ inner: {} }}",
143 &self.inner.format_as_rust_code(tabs)
144 )
145 }
146}
147
148impl crate::codegen::format::FormatAsRustCode for StyleBorderRightStyle {
149 fn format_as_rust_code(&self, tabs: usize) -> String {
150 format!(
151 "StyleBorderRightStyle {{ inner: {} }}",
152 &self.inner.format_as_rust_code(tabs)
153 )
154 }
155}
156
157impl crate::codegen::format::FormatAsRustCode for StyleBorderLeftStyle {
158 fn format_as_rust_code(&self, tabs: usize) -> String {
159 format!(
160 "StyleBorderLeftStyle {{ inner: {} }}",
161 &self.inner.format_as_rust_code(tabs)
162 )
163 }
164}
165
166impl crate::codegen::format::FormatAsRustCode for StyleBorderBottomStyle {
167 fn format_as_rust_code(&self, tabs: usize) -> String {
168 format!(
169 "StyleBorderBottomStyle {{ inner: {} }}",
170 &self.inner.format_as_rust_code(tabs)
171 )
172 }
173}
174
175define_border_side_property!(StyleBorderTopColor, ColorU);
178define_border_side_property!(StyleBorderRightColor, ColorU);
180define_border_side_property!(StyleBorderBottomColor, ColorU);
182define_border_side_property!(StyleBorderLeftColor, ColorU);
184
185define_border_side_property!(LayoutBorderTopWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
190define_border_side_property!(LayoutBorderRightWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
192define_border_side_property!(LayoutBorderBottomWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
194define_border_side_property!(LayoutBorderLeftWidth, PixelValue, MEDIUM_BORDER_THICKNESS);
196
197macro_rules! impl_border_width_helpers {
198 ($($t:ty),+) => { $(
199 impl $t {
200 #[must_use] pub fn interpolate(&self, other: &Self, t: f32) -> Self {
201 Self { inner: self.inner.interpolate(&other.inner, t) }
202 }
203 #[must_use] pub const fn const_px(value: isize) -> Self {
204 Self { inner: PixelValue::const_px(value) }
205 }
206 }
207 )+ };
208}
209
210impl_border_width_helpers!(
211 LayoutBorderTopWidth,
212 LayoutBorderRightWidth,
213 LayoutBorderBottomWidth,
214 LayoutBorderLeftWidth
215);
216
217#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
220pub struct StyleBorderSide {
221 pub border_width: PixelValue,
222 pub border_style: BorderStyle,
223 pub border_color: ColorU,
224}
225
226#[cfg(feature = "parser")]
231#[derive(Clone, PartialEq, Eq)]
232pub enum CssBorderStyleParseError<'a> {
233 InvalidStyle(&'a str),
234}
235
236#[cfg(feature = "parser")]
237impl_debug_as_display!(CssBorderStyleParseError<'a>);
238#[cfg(feature = "parser")]
239impl_display! { CssBorderStyleParseError<'a>, {
240 InvalidStyle(val) => format!("Invalid border style: \"{}\"", val),
241}}
242
243#[cfg(feature = "parser")]
244#[derive(Debug, Clone, PartialEq, Eq)]
245#[repr(C, u8)]
246pub enum CssBorderStyleParseErrorOwned {
247 InvalidStyle(AzString),
248}
249
250#[cfg(feature = "parser")]
251impl CssBorderStyleParseError<'_> {
252 #[must_use] pub fn to_contained(&self) -> CssBorderStyleParseErrorOwned {
253 match self {
254 CssBorderStyleParseError::InvalidStyle(s) => {
255 CssBorderStyleParseErrorOwned::InvalidStyle((*s).to_string().into())
256 }
257 }
258 }
259}
260
261#[cfg(feature = "parser")]
262impl CssBorderStyleParseErrorOwned {
263 #[must_use] pub fn to_shared(&self) -> CssBorderStyleParseError<'_> {
264 match self {
265 Self::InvalidStyle(s) => {
266 CssBorderStyleParseError::InvalidStyle(s.as_str())
267 }
268 }
269 }
270}
271
272#[cfg(feature = "parser")]
273pub fn parse_border_style(input: &str) -> Result<BorderStyle, CssBorderStyleParseError<'_>> {
277 match input.trim() {
278 "none" => Ok(BorderStyle::None),
279 "solid" => Ok(BorderStyle::Solid),
280 "double" => Ok(BorderStyle::Double),
281 "dotted" => Ok(BorderStyle::Dotted),
282 "dashed" => Ok(BorderStyle::Dashed),
283 "hidden" => Ok(BorderStyle::Hidden),
284 "groove" => Ok(BorderStyle::Groove),
285 "ridge" => Ok(BorderStyle::Ridge),
286 "inset" => Ok(BorderStyle::Inset),
287 "outset" => Ok(BorderStyle::Outset),
288 _ => Err(CssBorderStyleParseError::InvalidStyle(input)),
289 }
290}
291
292#[cfg(feature = "parser")]
295#[derive(Clone, PartialEq)]
296pub enum CssBorderSideParseError<'a> {
297 InvalidDeclaration(&'a str),
298 Width(CssPixelValueParseError<'a>),
299 Style(CssBorderStyleParseError<'a>),
300 Color(CssColorParseError<'a>),
301}
302
303#[cfg(feature = "parser")]
304impl_debug_as_display!(CssBorderSideParseError<'a>);
305#[cfg(feature = "parser")]
306impl_display! { CssBorderSideParseError<'a>, {
307 InvalidDeclaration(e) => format!("Invalid border declaration: \"{}\"", e),
308 Width(e) => format!("Invalid border-width component: {}", e),
309 Style(e) => format!("Invalid border-style component: {}", e),
310 Color(e) => format!("Invalid border-color component: {}", e),
311}}
312
313#[cfg(feature = "parser")]
314impl_from!(CssPixelValueParseError<'a>, CssBorderSideParseError::Width);
315#[cfg(feature = "parser")]
316impl_from!(CssBorderStyleParseError<'a>, CssBorderSideParseError::Style);
317#[cfg(feature = "parser")]
318impl_from!(CssColorParseError<'a>, CssBorderSideParseError::Color);
319
320#[cfg(feature = "parser")]
321#[derive(Debug, Clone, PartialEq)]
322#[repr(C, u8)]
323pub enum CssBorderSideParseErrorOwned {
324 InvalidDeclaration(AzString),
325 Width(CssPixelValueParseErrorOwned),
326 Style(CssBorderStyleParseErrorOwned),
327 Color(CssColorParseErrorOwned),
328}
329
330#[cfg(feature = "parser")]
331impl CssBorderSideParseError<'_> {
332 #[must_use] pub fn to_contained(&self) -> CssBorderSideParseErrorOwned {
333 match self {
334 CssBorderSideParseError::InvalidDeclaration(s) => {
335 CssBorderSideParseErrorOwned::InvalidDeclaration((*s).to_string().into())
336 }
337 CssBorderSideParseError::Width(e) => {
338 CssBorderSideParseErrorOwned::Width(e.to_contained())
339 }
340 CssBorderSideParseError::Style(e) => {
341 CssBorderSideParseErrorOwned::Style(e.to_contained())
342 }
343 CssBorderSideParseError::Color(e) => {
344 CssBorderSideParseErrorOwned::Color(e.to_contained())
345 }
346 }
347 }
348}
349
350#[cfg(feature = "parser")]
351impl CssBorderSideParseErrorOwned {
352 #[must_use] pub fn to_shared(&self) -> CssBorderSideParseError<'_> {
353 match self {
354 Self::InvalidDeclaration(s) => {
355 CssBorderSideParseError::InvalidDeclaration(s.as_str())
356 }
357 Self::Width(e) => CssBorderSideParseError::Width(e.to_shared()),
358 Self::Style(e) => CssBorderSideParseError::Style(e.to_shared()),
359 Self::Color(e) => CssBorderSideParseError::Color(e.to_shared()),
360 }
361 }
362}
363
364#[cfg(feature = "parser")]
366pub type CssBorderParseError<'a> = CssBorderSideParseError<'a>;
367
368#[cfg(feature = "parser")]
370#[derive(Debug, Clone, PartialEq)]
371#[repr(C)]
372pub struct CssBorderParseErrorOwned {
373 pub inner: CssBorderSideParseErrorOwned,
374}
375
376#[cfg(feature = "parser")]
377impl From<CssBorderSideParseErrorOwned> for CssBorderParseErrorOwned {
378 fn from(v: CssBorderSideParseErrorOwned) -> Self {
379 Self { inner: v }
380 }
381}
382
383#[cfg(feature = "parser")]
386fn parse_border_side(
387 input: &str,
388) -> Result<StyleBorderSide, CssBorderSideParseError<'_>> {
389 let mut width = None;
390 let mut style = None;
391 let mut color = None;
392
393 if input.trim().is_empty() {
394 return Err(CssBorderSideParseError::InvalidDeclaration(input));
395 }
396
397 for part in input.split_whitespace() {
398 if width.is_none() {
400 if let Ok(w) = parse_border_width_value(part) {
401 width = Some(w);
402 continue;
403 }
404 }
405
406 if style.is_none() {
408 if let Ok(s) = parse_border_style(part) {
409 style = Some(s);
410 continue;
411 }
412 }
413
414 if color.is_none() {
416 if let Ok(c) = parse_css_color(part) {
417 color = Some(c);
418 continue;
419 }
420 }
421
422 return Err(CssBorderSideParseError::InvalidDeclaration(input));
424 }
425
426 Ok(StyleBorderSide {
427 border_width: width.unwrap_or(MEDIUM_BORDER_THICKNESS),
428 border_style: style.unwrap_or(BorderStyle::None),
429 border_color: color.unwrap_or(ColorU::BLACK),
430 })
431}
432
433#[cfg(feature = "parser")]
436fn parse_border_width_value(
437 input: &str,
438) -> Result<PixelValue, CssPixelValueParseError<'_>> {
439 match input.trim() {
440 "thin" => Ok(THIN_BORDER_THICKNESS),
441 "medium" => Ok(MEDIUM_BORDER_THICKNESS),
442 "thick" => Ok(THICK_BORDER_THICKNESS),
443 _ => parse_pixel_value(input),
444 }
445}
446
447#[cfg(feature = "parser")]
448pub fn parse_border_top_width(
452 input: &str,
453) -> Result<LayoutBorderTopWidth, CssPixelValueParseError<'_>> {
454 parse_border_width_value(input).map(|inner| LayoutBorderTopWidth { inner })
455}
456
457#[cfg(feature = "parser")]
458pub fn parse_border_right_width(
462 input: &str,
463) -> Result<LayoutBorderRightWidth, CssPixelValueParseError<'_>> {
464 parse_border_width_value(input).map(|inner| LayoutBorderRightWidth { inner })
465}
466
467#[cfg(feature = "parser")]
468pub fn parse_border_bottom_width(
472 input: &str,
473) -> Result<LayoutBorderBottomWidth, CssPixelValueParseError<'_>> {
474 parse_border_width_value(input).map(|inner| LayoutBorderBottomWidth { inner })
475}
476
477#[cfg(feature = "parser")]
478pub fn parse_border_left_width(
482 input: &str,
483) -> Result<LayoutBorderLeftWidth, CssPixelValueParseError<'_>> {
484 parse_border_width_value(input).map(|inner| LayoutBorderLeftWidth { inner })
485}
486
487#[cfg(feature = "parser")]
488pub fn parse_border_top_style(
492 input: &str,
493) -> Result<StyleBorderTopStyle, CssBorderStyleParseError<'_>> {
494 parse_border_style(input).map(|inner| StyleBorderTopStyle { inner })
495}
496#[cfg(feature = "parser")]
497pub fn parse_border_right_style(
501 input: &str,
502) -> Result<StyleBorderRightStyle, CssBorderStyleParseError<'_>> {
503 parse_border_style(input).map(|inner| StyleBorderRightStyle { inner })
504}
505#[cfg(feature = "parser")]
506pub fn parse_border_bottom_style(
510 input: &str,
511) -> Result<StyleBorderBottomStyle, CssBorderStyleParseError<'_>> {
512 parse_border_style(input).map(|inner| StyleBorderBottomStyle { inner })
513}
514#[cfg(feature = "parser")]
515pub fn parse_border_left_style(
519 input: &str,
520) -> Result<StyleBorderLeftStyle, CssBorderStyleParseError<'_>> {
521 parse_border_style(input).map(|inner| StyleBorderLeftStyle { inner })
522}
523
524#[cfg(feature = "parser")]
525pub fn parse_border_top_color(
529 input: &str,
530) -> Result<StyleBorderTopColor, CssColorParseError<'_>> {
531 parse_css_color(input).map(|inner| StyleBorderTopColor { inner })
532}
533#[cfg(feature = "parser")]
534pub fn parse_border_right_color(
538 input: &str,
539) -> Result<StyleBorderRightColor, CssColorParseError<'_>> {
540 parse_css_color(input).map(|inner| StyleBorderRightColor { inner })
541}
542#[cfg(feature = "parser")]
543pub fn parse_border_bottom_color(
547 input: &str,
548) -> Result<StyleBorderBottomColor, CssColorParseError<'_>> {
549 parse_css_color(input).map(|inner| StyleBorderBottomColor { inner })
550}
551#[cfg(feature = "parser")]
552pub fn parse_border_left_color(
556 input: &str,
557) -> Result<StyleBorderLeftColor, CssColorParseError<'_>> {
558 parse_css_color(input).map(|inner| StyleBorderLeftColor { inner })
559}
560
561#[cfg(feature = "parser")]
565#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
566pub struct StyleBorderColors {
567 pub top: ColorU,
568 pub right: ColorU,
569 pub bottom: ColorU,
570 pub left: ColorU,
571}
572
573#[cfg(feature = "parser")]
579pub fn parse_style_border_color(
583 input: &str,
584) -> Result<StyleBorderColors, CssColorParseError<'_>> {
585 let input = input.trim();
586 let parts: Vec<&str> = input.split_whitespace().collect();
587
588 match parts.len() {
589 1 => {
590 let color = parse_css_color(parts[0])?;
591 Ok(StyleBorderColors {
592 top: color,
593 right: color,
594 bottom: color,
595 left: color,
596 })
597 }
598 2 => {
599 let top_bottom = parse_css_color(parts[0])?;
600 let left_right = parse_css_color(parts[1])?;
601 Ok(StyleBorderColors {
602 top: top_bottom,
603 right: left_right,
604 bottom: top_bottom,
605 left: left_right,
606 })
607 }
608 3 => {
609 let top = parse_css_color(parts[0])?;
610 let left_right = parse_css_color(parts[1])?;
611 let bottom = parse_css_color(parts[2])?;
612 Ok(StyleBorderColors {
613 top,
614 right: left_right,
615 bottom,
616 left: left_right,
617 })
618 }
619 4 => {
620 let top = parse_css_color(parts[0])?;
621 let right = parse_css_color(parts[1])?;
622 let bottom = parse_css_color(parts[2])?;
623 let left = parse_css_color(parts[3])?;
624 Ok(StyleBorderColors {
625 top,
626 right,
627 bottom,
628 left,
629 })
630 }
631 _ => Err(CssColorParseError::InvalidColor(input)),
632 }
633}
634
635#[cfg(feature = "parser")]
639#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
640pub struct StyleBorderStyles {
641 pub top: BorderStyle,
642 pub right: BorderStyle,
643 pub bottom: BorderStyle,
644 pub left: BorderStyle,
645}
646
647#[cfg(feature = "parser")]
649pub fn parse_style_border_style(
653 input: &str,
654) -> Result<StyleBorderStyles, CssBorderStyleParseError<'_>> {
655 let input = input.trim();
656 let parts: Vec<&str> = input.split_whitespace().collect();
657
658 match parts.len() {
659 1 => {
660 let style = parse_border_style(parts[0])?;
661 Ok(StyleBorderStyles {
662 top: style,
663 right: style,
664 bottom: style,
665 left: style,
666 })
667 }
668 2 => {
669 let top_bottom = parse_border_style(parts[0])?;
670 let left_right = parse_border_style(parts[1])?;
671 Ok(StyleBorderStyles {
672 top: top_bottom,
673 right: left_right,
674 bottom: top_bottom,
675 left: left_right,
676 })
677 }
678 3 => {
679 let top = parse_border_style(parts[0])?;
680 let left_right = parse_border_style(parts[1])?;
681 let bottom = parse_border_style(parts[2])?;
682 Ok(StyleBorderStyles {
683 top,
684 right: left_right,
685 bottom,
686 left: left_right,
687 })
688 }
689 4 => {
690 let top = parse_border_style(parts[0])?;
691 let right = parse_border_style(parts[1])?;
692 let bottom = parse_border_style(parts[2])?;
693 let left = parse_border_style(parts[3])?;
694 Ok(StyleBorderStyles {
695 top,
696 right,
697 bottom,
698 left,
699 })
700 }
701 _ => Err(CssBorderStyleParseError::InvalidStyle(input)),
702 }
703}
704
705#[cfg(feature = "parser")]
709#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
710pub struct StyleBorderWidths {
711 pub top: PixelValue,
712 pub right: PixelValue,
713 pub bottom: PixelValue,
714 pub left: PixelValue,
715}
716
717#[cfg(feature = "parser")]
719pub fn parse_style_border_width(
723 input: &str,
724) -> Result<StyleBorderWidths, CssPixelValueParseError<'_>> {
725 let input = input.trim();
726 let parts: Vec<&str> = input.split_whitespace().collect();
727
728 match parts.len() {
729 1 => {
730 let width = parse_pixel_value(parts[0])?;
731 Ok(StyleBorderWidths {
732 top: width,
733 right: width,
734 bottom: width,
735 left: width,
736 })
737 }
738 2 => {
739 let top_bottom = parse_pixel_value(parts[0])?;
740 let left_right = parse_pixel_value(parts[1])?;
741 Ok(StyleBorderWidths {
742 top: top_bottom,
743 right: left_right,
744 bottom: top_bottom,
745 left: left_right,
746 })
747 }
748 3 => {
749 let top = parse_pixel_value(parts[0])?;
750 let left_right = parse_pixel_value(parts[1])?;
751 let bottom = parse_pixel_value(parts[2])?;
752 Ok(StyleBorderWidths {
753 top,
754 right: left_right,
755 bottom,
756 left: left_right,
757 })
758 }
759 4 => {
760 let top = parse_pixel_value(parts[0])?;
761 let right = parse_pixel_value(parts[1])?;
762 let bottom = parse_pixel_value(parts[2])?;
763 let left = parse_pixel_value(parts[3])?;
764 Ok(StyleBorderWidths {
765 top,
766 right,
767 bottom,
768 left,
769 })
770 }
771 _ => Err(CssPixelValueParseError::InvalidPixelValue(input)),
772 }
773}
774
775#[cfg(feature = "parser")]
777pub fn parse_style_border(input: &str) -> Result<StyleBorderSide, CssBorderParseError<'_>> {
781 parse_border_side(input)
782}
783
784#[cfg(all(test, feature = "parser"))]
785mod tests {
786 use super::*;
787
788 #[test]
789 fn test_parse_border_style() {
790 assert_eq!(parse_border_style("solid").unwrap(), BorderStyle::Solid);
791 assert_eq!(parse_border_style("dotted").unwrap(), BorderStyle::Dotted);
792 assert_eq!(parse_border_style("none").unwrap(), BorderStyle::None);
793 assert_eq!(
794 parse_border_style(" dashed ").unwrap(),
795 BorderStyle::Dashed
796 );
797 assert!(parse_border_style("solidd").is_err());
798 }
799
800 #[test]
801 fn test_parse_border_side_shorthand() {
802 let result = parse_border_side("2px dotted #ff0000").unwrap();
804 assert_eq!(result.border_width, PixelValue::px(2.0));
805 assert_eq!(result.border_style, BorderStyle::Dotted);
806 assert_eq!(result.border_color, ColorU::new_rgb(255, 0, 0));
807
808 let result = parse_border_side("solid green 1em").unwrap();
810 assert_eq!(result.border_width, PixelValue::em(1.0));
811 assert_eq!(result.border_style, BorderStyle::Solid);
812 assert_eq!(result.border_color, ColorU::new_rgb(0, 128, 0));
813
814 let result = parse_border_side("ridge #f0f").unwrap();
816 assert_eq!(result.border_width, MEDIUM_BORDER_THICKNESS); assert_eq!(result.border_style, BorderStyle::Ridge);
818 assert_eq!(result.border_color, ColorU::new_rgb(255, 0, 255));
819
820 let result = parse_border_side("5pt blue").unwrap();
822 assert_eq!(result.border_width, PixelValue::pt(5.0));
823 assert_eq!(result.border_style, BorderStyle::None); assert_eq!(result.border_color, ColorU::BLUE);
825
826 let result = parse_border_side("thick double").unwrap();
828 assert_eq!(result.border_width, PixelValue::px(5.0));
829 assert_eq!(result.border_style, BorderStyle::Double);
830 assert_eq!(result.border_color, ColorU::BLACK); let result = parse_border_side("inset").unwrap();
834 assert_eq!(result.border_width, MEDIUM_BORDER_THICKNESS);
835 assert_eq!(result.border_style, BorderStyle::Inset);
836 assert_eq!(result.border_color, ColorU::BLACK);
837 }
838
839 #[test]
840 fn test_parse_border_side_invalid() {
841 assert!(parse_border_side("1px 2px solid red").is_err());
843 assert!(parse_border_side("solid dashed red").is_err());
845 assert!(parse_border_side("red blue solid").is_err());
847 assert!(parse_border_side("").is_err());
849 assert!(parse_border_side("1px unknown red").is_err());
851 }
852
853 #[test]
854 fn test_parse_longhand_border() {
855 assert_eq!(
856 parse_border_top_width("1.5em").unwrap().inner,
857 PixelValue::em(1.5)
858 );
859 assert_eq!(
860 parse_border_left_style("groove").unwrap().inner,
861 BorderStyle::Groove
862 );
863 assert_eq!(
864 parse_border_right_color("rgba(10, 20, 30, 0.5)")
865 .unwrap()
866 .inner,
867 ColorU::new(10, 20, 30, 128)
868 );
869 }
870}
871
872#[cfg(test)]
873mod autotest_generated {
874 use super::*;
875
876 const ALL_STYLES: [BorderStyle; 10] = [
877 BorderStyle::None,
878 BorderStyle::Solid,
879 BorderStyle::Double,
880 BorderStyle::Dotted,
881 BorderStyle::Dashed,
882 BorderStyle::Hidden,
883 BorderStyle::Groove,
884 BorderStyle::Ridge,
885 BorderStyle::Inset,
886 BorderStyle::Outset,
887 ];
888
889 #[test]
894 fn border_style_display_is_a_unique_lowercase_keyword_for_every_variant() {
895 let mut seen: Vec<String> = Vec::new();
896 for style in ALL_STYLES {
897 let s = style.to_string();
898 assert!(!s.is_empty(), "{style:?} renders as the empty string");
899 assert!(
900 s.chars().all(|c| c.is_ascii_lowercase()),
901 "{style:?} renders as {s:?}, which is not a lowercase ASCII keyword"
902 );
903 assert!(
904 !seen.contains(&s),
905 "two BorderStyle variants both render as {s:?} (copy-paste in Display)"
906 );
907 seen.push(s);
908 }
909 assert_eq!(seen.len(), ALL_STYLES.len());
910 }
911
912 #[test]
913 fn border_style_print_as_css_value_matches_display() {
914 for style in ALL_STYLES {
915 assert_eq!(style.print_as_css_value(), style.to_string());
916 }
917 }
918
919 #[test]
920 fn border_style_default_is_none_and_formats_as_none() {
921 assert_eq!(BorderStyle::default(), BorderStyle::None);
922 assert_eq!(BorderStyle::default().to_string(), "none");
923 }
924
925 #[test]
930 fn border_side_property_defaults_match_the_css_initial_values() {
931 assert_eq!(StyleBorderTopStyle::default().inner, BorderStyle::None);
933 assert_eq!(StyleBorderRightStyle::default().inner, BorderStyle::None);
934 assert_eq!(StyleBorderBottomStyle::default().inner, BorderStyle::None);
935 assert_eq!(StyleBorderLeftStyle::default().inner, BorderStyle::None);
936
937 assert_eq!(StyleBorderTopColor::default().inner, ColorU::BLACK);
939 assert_eq!(StyleBorderRightColor::default().inner, ColorU::BLACK);
940 assert_eq!(StyleBorderBottomColor::default().inner, ColorU::BLACK);
941 assert_eq!(StyleBorderLeftColor::default().inner, ColorU::BLACK);
942
943 assert_eq!(
945 LayoutBorderTopWidth::default().inner,
946 MEDIUM_BORDER_THICKNESS
947 );
948 assert_eq!(
949 LayoutBorderRightWidth::default().inner,
950 MEDIUM_BORDER_THICKNESS
951 );
952 assert_eq!(
953 LayoutBorderBottomWidth::default().inner,
954 MEDIUM_BORDER_THICKNESS
955 );
956 assert_eq!(
957 LayoutBorderLeftWidth::default().inner,
958 MEDIUM_BORDER_THICKNESS
959 );
960 assert_eq!(MEDIUM_BORDER_THICKNESS, PixelValue::px(3.0));
961 }
962
963 #[test]
964 fn border_side_property_debug_impls_are_the_documented_shapes() {
965 assert_eq!(format!("{:?}", StyleBorderTopStyle::default()), "none");
968 assert_eq!(
969 format!(
970 "{:?}",
971 StyleBorderLeftStyle {
972 inner: BorderStyle::Groove
973 }
974 ),
975 "groove"
976 );
977 assert_eq!(
978 format!("{:?}", StyleBorderTopColor::default()),
979 "#000000ff"
980 );
981 assert_eq!(format!("{:?}", LayoutBorderTopWidth::default()), "3px");
982 }
983
984 #[test]
985 fn layout_border_width_const_px_matches_the_runtime_constructor() {
986 assert_eq!(
987 LayoutBorderTopWidth::const_px(5).inner,
988 PixelValue::px(5.0)
989 );
990 assert_eq!(LayoutBorderRightWidth::const_px(0).inner, PixelValue::zero());
991 assert_eq!(
992 LayoutBorderBottomWidth::const_px(-2).inner,
993 PixelValue::px(-2.0)
994 );
995 let max_safe = isize::MAX / 1000;
999 assert!(LayoutBorderLeftWidth::const_px(max_safe)
1000 .inner
1001 .number
1002 .get()
1003 .is_finite());
1004 }
1005
1006 #[test]
1007 fn layout_border_width_interpolate_endpoints_are_exact() {
1008 let a = LayoutBorderTopWidth::const_px(0);
1009 let b = LayoutBorderTopWidth::const_px(10);
1010 assert_eq!(a.interpolate(&b, 0.0), a);
1011 assert_eq!(a.interpolate(&b, 1.0), b);
1012 assert_eq!(a.interpolate(&b, 0.5).inner, PixelValue::px(5.0));
1013 }
1014
1015 #[test]
1016 fn layout_border_width_interpolate_stays_finite_for_hostile_t() {
1017 let a = LayoutBorderTopWidth::const_px(0);
1018 let b = LayoutBorderTopWidth::const_px(10);
1019 for t in [
1020 0.0,
1021 1.0,
1022 -1.0,
1023 2.0,
1024 f32::NAN,
1025 f32::INFINITY,
1026 f32::NEG_INFINITY,
1027 f32::MAX,
1028 f32::MIN,
1029 ] {
1030 assert!(
1033 a.interpolate(&b, t).inner.number.get().is_finite(),
1034 "interpolate(t = {t}) produced a non-finite width"
1035 );
1036 assert!(b.interpolate(&a, t).inner.number.get().is_finite());
1037 }
1038 }
1039
1040 #[test]
1041 fn layout_border_width_interpolate_across_metrics_stays_finite() {
1042 let px = LayoutBorderRightWidth {
1043 inner: PixelValue::px(4.0),
1044 };
1045 let em = LayoutBorderRightWidth {
1046 inner: PixelValue::em(2.0),
1047 };
1048 let percent = LayoutBorderRightWidth {
1049 inner: PixelValue::percent(100.0),
1050 };
1051 for t in [0.0, 0.5, 1.0, -3.0, f32::NAN, f32::INFINITY] {
1052 assert!(px.interpolate(&em, t).inner.number.get().is_finite());
1053 assert!(em.interpolate(&percent, t).inner.number.get().is_finite());
1054 assert!(percent.interpolate(&px, t).inner.number.get().is_finite());
1055 }
1056 }
1057
1058 #[test]
1059 fn style_border_color_interpolate_endpoints_are_exact() {
1060 let black = StyleBorderTopColor {
1061 inner: ColorU::BLACK,
1062 };
1063 let white = StyleBorderTopColor {
1064 inner: ColorU::WHITE,
1065 };
1066 assert_eq!(black.interpolate(&white, 0.0).inner, ColorU::BLACK);
1067 assert_eq!(black.interpolate(&white, 1.0).inner, ColorU::WHITE);
1068 let mid = black.interpolate(&white, 0.5).inner;
1069 assert_eq!((mid.r, mid.g, mid.b), (128, 128, 128));
1070 }
1071
1072 #[test]
1073 fn style_border_color_interpolate_hostile_t_does_not_panic() {
1074 let a = StyleBorderLeftColor {
1075 inner: ColorU::new(10, 20, 30, 40),
1076 };
1077 let b = StyleBorderLeftColor {
1078 inner: ColorU::new(200, 210, 220, 230),
1079 };
1080 for t in [
1081 -1000.0,
1082 1000.0,
1083 f32::NAN,
1084 f32::INFINITY,
1085 f32::NEG_INFINITY,
1086 f32::MAX,
1087 ] {
1088 let _ = a.interpolate(&b, t);
1090 let _ = b.interpolate(&a, t);
1091 }
1092 }
1093
1094 #[cfg(feature = "parser")]
1099 #[test]
1100 fn parse_border_style_accepts_every_keyword() {
1101 assert_eq!(parse_border_style("none").unwrap(), BorderStyle::None);
1102 assert_eq!(parse_border_style("solid").unwrap(), BorderStyle::Solid);
1103 assert_eq!(parse_border_style("double").unwrap(), BorderStyle::Double);
1104 assert_eq!(parse_border_style("dotted").unwrap(), BorderStyle::Dotted);
1105 assert_eq!(parse_border_style("dashed").unwrap(), BorderStyle::Dashed);
1106 assert_eq!(parse_border_style("hidden").unwrap(), BorderStyle::Hidden);
1107 assert_eq!(parse_border_style("groove").unwrap(), BorderStyle::Groove);
1108 assert_eq!(parse_border_style("ridge").unwrap(), BorderStyle::Ridge);
1109 assert_eq!(parse_border_style("inset").unwrap(), BorderStyle::Inset);
1110 assert_eq!(parse_border_style("outset").unwrap(), BorderStyle::Outset);
1111 }
1112
1113 #[cfg(feature = "parser")]
1114 #[test]
1115 fn parse_border_style_round_trips_through_display() {
1116 for style in ALL_STYLES {
1117 let encoded = style.to_string();
1118 assert_eq!(
1119 parse_border_style(&encoded).unwrap(),
1120 style,
1121 "{encoded} did not round-trip"
1122 );
1123 assert_eq!(
1125 parse_border_style(&style.print_as_css_value()).unwrap(),
1126 style
1127 );
1128 }
1129 }
1130
1131 #[cfg(feature = "parser")]
1132 #[test]
1133 fn parse_border_style_trims_surrounding_whitespace() {
1134 for input in [" solid", "solid ", "\t\nsolid\r\n ", " solid "] {
1135 assert_eq!(
1136 parse_border_style(input).unwrap(),
1137 BorderStyle::Solid,
1138 "{input:?} should trim to `solid`"
1139 );
1140 }
1141 }
1142
1143 #[cfg(feature = "parser")]
1144 #[test]
1145 fn parse_border_style_empty_and_whitespace_only_are_errors() {
1146 for input in ["", " ", " ", "\t", "\n", "\r\n\t "] {
1147 assert!(
1148 parse_border_style(input).is_err(),
1149 "{input:?} must not parse as a border style"
1150 );
1151 }
1152 }
1153
1154 #[cfg(feature = "parser")]
1155 #[test]
1156 fn parse_border_style_error_carries_the_untrimmed_input() {
1157 let input = " bogus ";
1159 let err = parse_border_style(input).unwrap_err();
1160 assert!(
1161 matches!(err, CssBorderStyleParseError::InvalidStyle(s) if s == input),
1162 "unexpected error payload: {err:?}"
1163 );
1164 }
1165
1166 #[cfg(feature = "parser")]
1167 #[test]
1168 fn parse_border_style_rejects_uppercase_keywords() {
1169 for input in ["SOLID", "Solid", "sOlId", "NONE", "Dashed"] {
1173 assert!(
1174 parse_border_style(input).is_err(),
1175 "{input:?} unexpectedly parsed (case-insensitivity was added?)"
1176 );
1177 }
1178 }
1179
1180 #[cfg(feature = "parser")]
1181 #[test]
1182 fn parse_border_style_rejects_garbage_unicode_and_numbers() {
1183 for input in [
1184 "solidd",
1185 "soli",
1186 "solid solid",
1187 "solid;garbage",
1188 "solid!important",
1189 "0",
1190 "-0",
1191 "1px",
1192 "9223372036854775807",
1193 "NaN",
1194 "inf",
1195 "-inf",
1196 "\u{1F600}",
1197 "s\u{0301}olid",
1198 "sölid",
1199 "\u{0}",
1200 "\u{202e}solid",
1201 "sol\tid",
1202 "()",
1203 "solid()",
1204 ] {
1205 assert!(
1206 parse_border_style(input).is_err(),
1207 "{input:?} unexpectedly parsed as a border style"
1208 );
1209 }
1210 }
1211
1212 #[cfg(feature = "parser")]
1213 #[test]
1214 fn parse_border_style_handles_huge_and_nested_input_without_panicking() {
1215 let huge = "a".repeat(100_000);
1216 assert!(parse_border_style(&huge).is_err());
1217
1218 let repeated = "solid ".repeat(50_000);
1219 assert!(parse_border_style(&repeated).is_err());
1220
1221 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
1222 assert!(parse_border_style(&nested).is_err());
1223
1224 let padded = format!("{}solid{}", " ".repeat(100_000), " ".repeat(100_000));
1225 assert_eq!(parse_border_style(&padded).unwrap(), BorderStyle::Solid);
1226 }
1227
1228 #[cfg(feature = "parser")]
1233 #[test]
1234 fn border_style_longhands_agree_with_parse_border_style() {
1235 for style in ALL_STYLES {
1236 let input = style.to_string();
1237 assert_eq!(parse_border_top_style(&input).unwrap().inner, style);
1238 assert_eq!(parse_border_right_style(&input).unwrap().inner, style);
1239 assert_eq!(parse_border_bottom_style(&input).unwrap().inner, style);
1240 assert_eq!(parse_border_left_style(&input).unwrap().inner, style);
1241 }
1242 }
1243
1244 #[cfg(feature = "parser")]
1245 #[test]
1246 fn border_style_longhands_reject_everything_parse_border_style_rejects() {
1247 for input in ["", " ", "SOLID", "solidd", "\u{1F600}", "1px", "solid red"] {
1248 assert!(parse_border_top_style(input).is_err(), "top: {input:?}");
1249 assert!(parse_border_right_style(input).is_err(), "right: {input:?}");
1250 assert!(
1251 parse_border_bottom_style(input).is_err(),
1252 "bottom: {input:?}"
1253 );
1254 assert!(parse_border_left_style(input).is_err(), "left: {input:?}");
1255 }
1256 }
1257
1258 #[cfg(feature = "parser")]
1263 #[test]
1264 fn parse_border_width_value_accepts_the_three_keywords() {
1265 assert_eq!(
1266 parse_border_width_value("thin").unwrap(),
1267 THIN_BORDER_THICKNESS
1268 );
1269 assert_eq!(
1270 parse_border_width_value("medium").unwrap(),
1271 MEDIUM_BORDER_THICKNESS
1272 );
1273 assert_eq!(
1274 parse_border_width_value("thick").unwrap(),
1275 THICK_BORDER_THICKNESS
1276 );
1277 assert_eq!(
1279 parse_border_width_value(" \tthick\n ").unwrap(),
1280 THICK_BORDER_THICKNESS
1281 );
1282 }
1283
1284 #[cfg(feature = "parser")]
1285 #[test]
1286 fn parse_border_width_value_rejects_uppercase_keywords() {
1287 for input in ["THIN", "Medium", "THICK"] {
1289 assert!(
1290 parse_border_width_value(input).is_err(),
1291 "{input:?} unexpectedly parsed"
1292 );
1293 }
1294 }
1295
1296 #[cfg(feature = "parser")]
1297 #[test]
1298 fn parse_border_width_value_empty_and_whitespace_only_are_errors() {
1299 for input in ["", " ", "\t\n", " "] {
1300 let err = parse_border_width_value(input).unwrap_err();
1301 assert!(
1302 matches!(err, CssPixelValueParseError::EmptyString),
1303 "{input:?} -> {err:?}"
1304 );
1305 }
1306 }
1307
1308 #[cfg(feature = "parser")]
1309 #[test]
1310 fn parse_border_width_value_bare_number_is_interpreted_as_px() {
1311 assert_eq!(parse_border_width_value("0").unwrap(), PixelValue::px(0.0));
1312 assert_eq!(parse_border_width_value("42").unwrap(), PixelValue::px(42.0));
1313 assert_eq!(
1314 parse_border_width_value("1.5").unwrap(),
1315 PixelValue::px(1.5)
1316 );
1317 assert_eq!(parse_border_width_value("-0").unwrap(), PixelValue::px(0.0));
1319 assert_eq!(
1322 parse_border_width_value("-5px").unwrap(),
1323 PixelValue::px(-5.0)
1324 );
1325 }
1326
1327 #[cfg(feature = "parser")]
1328 #[test]
1329 fn parse_border_width_value_nan_saturates_to_zero() {
1330 let parsed = parse_border_width_value("NaN").unwrap();
1333 assert!(parsed.number.get().is_finite());
1334 assert_eq!(parsed, PixelValue::px(0.0));
1335
1336 let parsed = parse_border_width_value("NaNpx").unwrap();
1337 assert_eq!(parsed, PixelValue::px(0.0));
1338 }
1339
1340 #[cfg(feature = "parser")]
1341 #[test]
1342 fn parse_border_width_value_infinities_and_overflow_saturate_finite() {
1343 for input in [
1344 "inf",
1345 "-inf",
1346 "infpx",
1347 "1e999",
1348 "-1e999",
1349 "1e40px",
1350 "340282350000000000000000000000000000000px", "9223372036854775807", "-9223372036854775808", ] {
1354 let parsed = parse_border_width_value(input)
1355 .unwrap_or_else(|e| panic!("{input:?} failed to parse: {e:?}"));
1356 assert!(
1357 parsed.number.get().is_finite(),
1358 "{input:?} produced a non-finite width: {parsed:?}"
1359 );
1360 }
1361 }
1362
1363 #[cfg(feature = "parser")]
1364 #[test]
1365 fn parse_border_width_value_rejects_garbage_and_unicode() {
1366 for input in [
1367 "px",
1368 "em",
1369 "abc",
1370 "1px 2px",
1371 "1px;",
1372 "--1px",
1373 "1PX",
1374 "\u{1F600}",
1375 "1\u{1F600}px",
1376 "١px", "()",
1378 "calc(1px + 2px)",
1379 ] {
1380 assert!(
1381 parse_border_width_value(input).is_err(),
1382 "{input:?} unexpectedly parsed as a border width"
1383 );
1384 }
1385
1386 assert_eq!(parse_border_width_value("1 px").unwrap(), PixelValue::px(1.0));
1389 }
1390
1391 #[cfg(feature = "parser")]
1392 #[test]
1393 fn parse_border_width_value_bare_unit_reports_no_value_given() {
1394 let err = parse_border_width_value("px").unwrap_err();
1395 assert!(
1396 matches!(err, CssPixelValueParseError::NoValueGiven(..)),
1397 "expected NoValueGiven, got {err:?}"
1398 );
1399 }
1400
1401 #[cfg(feature = "parser")]
1402 #[test]
1403 fn parse_border_width_value_huge_input_does_not_hang() {
1404 let huge_digits = format!("{}px", "9".repeat(1_000));
1405 let parsed = parse_border_width_value(&huge_digits).unwrap();
1406 assert!(parsed.number.get().is_finite());
1407
1408 let huge_garbage = "z".repeat(100_000);
1409 assert!(parse_border_width_value(&huge_garbage).is_err());
1410
1411 let padded = format!("{}1px{}", " ".repeat(50_000), " ".repeat(50_000));
1412 assert_eq!(
1413 parse_border_width_value(&padded).unwrap(),
1414 PixelValue::px(1.0)
1415 );
1416 }
1417
1418 #[cfg(feature = "parser")]
1423 #[test]
1424 fn border_width_longhands_agree_with_each_other() {
1425 for input in ["thin", "medium", "thick", "0", "1.5em", "3px", "50%", "-2pt"] {
1426 let expected = parse_border_width_value(input).unwrap();
1427 assert_eq!(parse_border_top_width(input).unwrap().inner, expected);
1428 assert_eq!(parse_border_right_width(input).unwrap().inner, expected);
1429 assert_eq!(parse_border_bottom_width(input).unwrap().inner, expected);
1430 assert_eq!(parse_border_left_width(input).unwrap().inner, expected);
1431 }
1432 for input in ["", " ", "px", "abc", "\u{1F600}", "1px 2px"] {
1433 assert!(parse_border_top_width(input).is_err(), "top: {input:?}");
1434 assert!(parse_border_right_width(input).is_err(), "right: {input:?}");
1435 assert!(
1436 parse_border_bottom_width(input).is_err(),
1437 "bottom: {input:?}"
1438 );
1439 assert!(parse_border_left_width(input).is_err(), "left: {input:?}");
1440 }
1441 }
1442
1443 #[cfg(feature = "parser")]
1444 #[test]
1445 fn border_width_longhands_round_trip_through_display() {
1446 for value in [
1447 PixelValue::px(0.0),
1448 PixelValue::px(1.0),
1449 PixelValue::px(1.5),
1450 PixelValue::px(-3.25),
1451 PixelValue::em(2.0),
1452 PixelValue::rem(0.5),
1453 PixelValue::pt(12.0),
1454 PixelValue::inch(1.0),
1455 PixelValue::cm(2.5),
1456 PixelValue::mm(10.0),
1457 PixelValue::percent(50.0),
1458 THIN_BORDER_THICKNESS,
1459 MEDIUM_BORDER_THICKNESS,
1460 THICK_BORDER_THICKNESS,
1461 ] {
1462 let encoded = value.to_string();
1463 let decoded = parse_border_top_width(&encoded)
1464 .unwrap_or_else(|e| panic!("{encoded} failed to re-parse: {e:?}"))
1465 .inner;
1466 assert_eq!(decoded, value, "{encoded} did not round-trip");
1467 }
1468 }
1469
1470 #[cfg(feature = "parser")]
1471 #[test]
1472 fn border_width_longhands_inherit_the_vmin_suffix_shadowing_bug() {
1473 assert_eq!(
1477 parse_border_top_width("5vmin").unwrap().inner,
1478 PixelValue::from_metric(crate::props::basic::SizeMetric::Vmin, 5.0)
1479 );
1480 assert_eq!(
1481 parse_border_left_width("5vmin").unwrap().inner,
1482 PixelValue::from_metric(crate::props::basic::SizeMetric::Vmin, 5.0)
1483 );
1484 assert_eq!(
1485 parse_border_top_width("5vmax").unwrap().inner,
1486 PixelValue::from_metric(crate::props::basic::SizeMetric::Vmax, 5.0)
1487 );
1488 assert_eq!(
1489 parse_border_top_width("5vw").unwrap().inner,
1490 PixelValue::from_metric(crate::props::basic::SizeMetric::Vw, 5.0)
1491 );
1492 }
1493
1494 #[cfg(feature = "parser")]
1499 #[test]
1500 fn border_color_longhands_agree_with_each_other() {
1501 for input in [
1502 "#ff0000",
1503 "#f0f",
1504 "#11223344",
1505 "red",
1506 "transparent",
1507 "rgb(1, 2, 3)",
1508 "rgba(10, 20, 30, 0.5)",
1509 "hsl(0, 100%, 50%)",
1510 ] {
1511 let expected = parse_css_color(input)
1512 .unwrap_or_else(|e| panic!("{input:?} failed to parse: {e:?}"));
1513 assert_eq!(parse_border_top_color(input).unwrap().inner, expected);
1514 assert_eq!(parse_border_right_color(input).unwrap().inner, expected);
1515 assert_eq!(parse_border_bottom_color(input).unwrap().inner, expected);
1516 assert_eq!(parse_border_left_color(input).unwrap().inner, expected);
1517 }
1518 }
1519
1520 #[cfg(feature = "parser")]
1521 #[test]
1522 fn border_color_longhands_round_trip_through_to_hash() {
1523 for color in [
1524 ColorU::BLACK,
1525 ColorU::WHITE,
1526 ColorU::RED,
1527 ColorU::BLUE,
1528 ColorU::TRANSPARENT,
1529 ColorU::new(1, 2, 3, 4),
1530 ColorU::new(255, 254, 253, 252),
1531 ColorU::new(0, 128, 0, 255),
1532 ] {
1533 let encoded = color.to_hash();
1534 assert_eq!(
1535 parse_border_left_color(&encoded)
1536 .unwrap_or_else(|e| panic!("{encoded} failed to re-parse: {e:?}"))
1537 .inner,
1538 color,
1539 "{encoded} did not round-trip"
1540 );
1541 }
1542 }
1543
1544 #[cfg(feature = "parser")]
1545 #[test]
1546 fn border_color_longhands_empty_input_is_an_error() {
1547 for input in ["", " ", "\t\n "] {
1548 let err = parse_border_top_color(input).unwrap_err();
1549 assert!(
1550 matches!(err, CssColorParseError::EmptyInput),
1551 "{input:?} -> {err:?}"
1552 );
1553 }
1554 }
1555
1556 #[cfg(feature = "parser")]
1557 #[test]
1558 fn border_color_longhands_reject_garbage_and_unicode() {
1559 for input in [
1560 "#",
1561 "#z",
1562 "#ff",
1563 "#fffff",
1564 "#\u{1F600}",
1565 "notacolor",
1566 "rgb(1, 2)",
1567 "rgb(1, 2, 3, 4, 5)",
1568 "\u{1F600}",
1569 "red;",
1570 "red blue",
1571 "0",
1572 ] {
1573 assert!(
1574 parse_border_top_color(input).is_err(),
1575 "{input:?} unexpectedly parsed as a color"
1576 );
1577 }
1578 }
1579
1580 #[cfg(feature = "parser")]
1581 #[test]
1582 fn border_color_longhands_never_panic_on_hostile_input() {
1583 let long = "f".repeat(100_000);
1584 let nested = format!("rgb{}1,2,3{}", "(".repeat(10_000), ")".repeat(10_000));
1585 let deep_parens = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
1586 let hostile: [&str; 18] = [
1587 "#",
1588 "##ff0000",
1589 "rgb(",
1590 "rgb()",
1591 "rgba(0,0,0,NaN)",
1592 "rgba(0,0,0,inf)",
1593 "rgb(-1,-2,-3)",
1594 "rgb(999,999,999)",
1595 "rgb(NaN, NaN, NaN)",
1596 "hsl(inf, 0%, 0%)",
1597 "hsla(NaN, NaN%, NaN%, NaN)",
1598 "\u{0}",
1599 "\u{202e}",
1600 "s\u{0301}",
1601 ")))",
1602 &long,
1603 &nested,
1604 &deep_parens,
1605 ];
1606 for input in hostile {
1607 let _ = parse_border_top_color(input);
1609 let _ = parse_border_right_color(input);
1610 let _ = parse_border_bottom_color(input);
1611 let _ = parse_border_left_color(input);
1612 }
1613 }
1614
1615 #[cfg(feature = "parser")]
1620 #[test]
1621 fn parse_border_side_positive_control() {
1622 let side = parse_border_side("1px solid red").unwrap();
1623 assert_eq!(side.border_width, PixelValue::px(1.0));
1624 assert_eq!(side.border_style, BorderStyle::Solid);
1625 assert_eq!(side.border_color, ColorU::RED);
1626 }
1627
1628 #[cfg(feature = "parser")]
1629 #[test]
1630 fn parse_border_side_is_component_order_independent() {
1631 let expected = StyleBorderSide {
1632 border_width: PixelValue::px(2.0),
1633 border_style: BorderStyle::Dashed,
1634 border_color: ColorU::new_rgb(0, 255, 0),
1635 };
1636 for input in [
1637 "2px dashed #00ff00",
1638 "2px #00ff00 dashed",
1639 "dashed 2px #00ff00",
1640 "dashed #00ff00 2px",
1641 "#00ff00 2px dashed",
1642 "#00ff00 dashed 2px",
1643 " 2px dashed #00ff00 ",
1644 "\t2px\ndashed\r#00ff00\t",
1645 ] {
1646 assert_eq!(
1647 parse_border_side(input).unwrap(),
1648 expected,
1649 "{input:?} parsed differently"
1650 );
1651 }
1652 }
1653
1654 #[cfg(feature = "parser")]
1655 #[test]
1656 fn parse_border_side_applies_defaults_for_missing_components() {
1657 let only_style = parse_border_side("inset").unwrap();
1659 assert_eq!(only_style.border_width, MEDIUM_BORDER_THICKNESS);
1660 assert_eq!(only_style.border_style, BorderStyle::Inset);
1661 assert_eq!(only_style.border_color, ColorU::BLACK);
1662
1663 let only_width = parse_border_side("7px").unwrap();
1664 assert_eq!(only_width.border_width, PixelValue::px(7.0));
1665 assert_eq!(only_width.border_style, BorderStyle::None);
1666 assert_eq!(only_width.border_color, ColorU::BLACK);
1667
1668 let only_color = parse_border_side("blue").unwrap();
1669 assert_eq!(only_color.border_width, MEDIUM_BORDER_THICKNESS);
1670 assert_eq!(only_color.border_style, BorderStyle::None);
1671 assert_eq!(only_color.border_color, ColorU::BLUE);
1672
1673 let keyword = parse_border_side("thin solid").unwrap();
1675 assert_eq!(keyword.border_width, THIN_BORDER_THICKNESS);
1676 assert_eq!(keyword.border_style, BorderStyle::Solid);
1677 }
1678
1679 #[cfg(feature = "parser")]
1680 #[test]
1681 fn parse_border_side_rejects_duplicate_components() {
1682 for input in [
1683 "1px 2px solid red",
1684 "solid dashed red",
1685 "red blue solid",
1686 "1px solid red 2px",
1687 "1px solid red solid",
1688 "1px solid red red",
1689 ] {
1690 let err = parse_border_side(input).unwrap_err();
1691 assert!(
1692 matches!(err, CssBorderSideParseError::InvalidDeclaration(s) if s == input),
1693 "{input:?} -> {err:?}"
1694 );
1695 }
1696 }
1697
1698 #[cfg(feature = "parser")]
1699 #[test]
1700 fn parse_border_side_empty_and_whitespace_only_are_errors() {
1701 for input in ["", " ", "\t\n", " "] {
1702 let err = parse_border_side(input).unwrap_err();
1703 assert!(
1705 matches!(err, CssBorderSideParseError::InvalidDeclaration(s) if s == input),
1706 "{input:?} -> {err:?}"
1707 );
1708 }
1709 }
1710
1711 #[cfg(feature = "parser")]
1712 #[test]
1713 fn parse_border_side_rejects_unknown_tokens() {
1714 for input in [
1715 "1px unknown red",
1716 "1px solid red !important",
1717 "1px solid red;",
1718 "\u{1F600}",
1719 "1px solid \u{1F600}",
1720 "solid \u{0}",
1721 "1px, solid, red",
1722 ] {
1723 assert!(
1724 parse_border_side(input).is_err(),
1725 "{input:?} unexpectedly parsed as a border shorthand"
1726 );
1727 }
1728 }
1729
1730 #[cfg(feature = "parser")]
1731 #[test]
1732 fn parse_border_side_hostile_numbers_stay_finite() {
1733 for input in ["NaN solid red", "inf solid red", "1e999 solid red"] {
1736 let side = parse_border_side(input)
1737 .unwrap_or_else(|e| panic!("{input:?} failed to parse: {e:?}"));
1738 assert!(
1739 side.border_width.number.get().is_finite(),
1740 "{input:?} produced a non-finite width: {:?}",
1741 side.border_width
1742 );
1743 assert_eq!(side.border_style, BorderStyle::Solid);
1744 assert_eq!(side.border_color, ColorU::RED);
1745 }
1746 assert_eq!(
1747 parse_border_side("NaN solid red").unwrap().border_width,
1748 PixelValue::px(0.0)
1749 );
1750 }
1751
1752 #[cfg(feature = "parser")]
1753 #[test]
1754 fn parse_border_side_long_and_nested_input_does_not_hang() {
1755 let repeated = "solid ".repeat(50_000);
1758 assert!(parse_border_side(&repeated).is_err());
1759
1760 let repeated_px = "1px ".repeat(50_000);
1761 assert!(parse_border_side(&repeated_px).is_err());
1762
1763 let huge_token = "z".repeat(100_000);
1764 assert!(parse_border_side(&huge_token).is_err());
1765
1766 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
1767 assert!(parse_border_side(&nested).is_err());
1768
1769 let padded = format!("{}1px solid red{}", " ".repeat(50_000), " ".repeat(50_000));
1770 assert_eq!(
1771 parse_border_side(&padded).unwrap().border_style,
1772 BorderStyle::Solid
1773 );
1774 }
1775
1776 #[cfg(feature = "parser")]
1777 #[test]
1778 fn parse_style_border_is_an_alias_of_parse_border_side() {
1779 for input in [
1780 "1px solid red",
1781 "thick double",
1782 "inset",
1783 "",
1784 " ",
1785 "1px 2px solid red",
1786 "\u{1F600}",
1787 "solid green 1em",
1788 ] {
1789 match (parse_style_border(input), parse_border_side(input)) {
1790 (Ok(a), Ok(b)) => assert_eq!(a, b, "{input:?}"),
1791 (Err(a), Err(b)) => assert_eq!(a, b, "{input:?}"),
1792 (a, b) => panic!("{input:?}: alias disagrees: {a:?} vs {b:?}"),
1793 }
1794 }
1795 }
1796
1797 #[cfg(feature = "parser")]
1802 #[test]
1803 fn parse_style_border_color_expands_one_to_four_values() {
1804 let red = ColorU::RED;
1805 let blue = ColorU::BLUE;
1806 let green = ColorU::new_rgb(0, 128, 0);
1807 let white = ColorU::WHITE;
1808
1809 let one = parse_style_border_color("red").unwrap();
1810 assert_eq!(
1811 one,
1812 StyleBorderColors {
1813 top: red,
1814 right: red,
1815 bottom: red,
1816 left: red
1817 }
1818 );
1819
1820 let two = parse_style_border_color("red blue").unwrap();
1822 assert_eq!(
1823 two,
1824 StyleBorderColors {
1825 top: red,
1826 right: blue,
1827 bottom: red,
1828 left: blue
1829 }
1830 );
1831
1832 let three = parse_style_border_color("red blue green").unwrap();
1834 assert_eq!(
1835 three,
1836 StyleBorderColors {
1837 top: red,
1838 right: blue,
1839 bottom: green,
1840 left: blue
1841 }
1842 );
1843
1844 let four = parse_style_border_color("red blue green white").unwrap();
1846 assert_eq!(
1847 four,
1848 StyleBorderColors {
1849 top: red,
1850 right: blue,
1851 bottom: green,
1852 left: white
1853 }
1854 );
1855 }
1856
1857 #[cfg(feature = "parser")]
1858 #[test]
1859 fn parse_style_border_color_normalizes_whitespace() {
1860 let expected = parse_style_border_color("red blue green").unwrap();
1861 for input in [
1862 " red blue green ",
1863 "red\tblue\ngreen",
1864 "red blue \r\n green",
1865 ] {
1866 assert_eq!(parse_style_border_color(input).unwrap(), expected, "{input:?}");
1867 }
1868 }
1869
1870 #[cfg(feature = "parser")]
1871 #[test]
1872 fn parse_style_border_color_rejects_zero_and_more_than_four_values() {
1873 for input in ["", " ", "\t\n"] {
1874 let err = parse_style_border_color(input).unwrap_err();
1875 assert!(
1876 matches!(err, CssColorParseError::InvalidColor(_)),
1877 "{input:?} -> {err:?}"
1878 );
1879 }
1880 let too_many = "red ".repeat(1_000);
1881 let inputs: [&str; 3] = [
1882 "red red red red red",
1883 "red blue green white black yellow",
1884 &too_many,
1885 ];
1886 for input in inputs {
1887 assert!(
1888 parse_style_border_color(input).is_err(),
1889 "{input:?} unexpectedly parsed"
1890 );
1891 }
1892 }
1893
1894 #[cfg(feature = "parser")]
1895 #[test]
1896 fn parse_style_border_color_propagates_component_errors() {
1897 for input in [
1898 "notacolor",
1899 "red notacolor",
1900 "red blue notacolor",
1901 "red blue green notacolor",
1902 "red \u{1F600}",
1903 "#zzz",
1904 ] {
1905 assert!(
1906 parse_style_border_color(input).is_err(),
1907 "{input:?} unexpectedly parsed"
1908 );
1909 }
1910 }
1911
1912 #[cfg(feature = "parser")]
1913 #[test]
1914 fn parse_style_border_color_round_trips_through_to_hash() {
1915 let colors = StyleBorderColors {
1916 top: ColorU::new(1, 2, 3, 4),
1917 right: ColorU::new(255, 0, 0, 255),
1918 bottom: ColorU::TRANSPARENT,
1919 left: ColorU::new(9, 8, 7, 6),
1920 };
1921 let encoded = format!(
1922 "{} {} {} {}",
1923 colors.top.to_hash(),
1924 colors.right.to_hash(),
1925 colors.bottom.to_hash(),
1926 colors.left.to_hash()
1927 );
1928 assert_eq!(parse_style_border_color(&encoded).unwrap(), colors);
1929 }
1930
1931 #[cfg(feature = "parser")]
1936 #[test]
1937 fn parse_style_border_style_expands_one_to_four_values() {
1938 assert_eq!(
1939 parse_style_border_style("solid").unwrap(),
1940 StyleBorderStyles {
1941 top: BorderStyle::Solid,
1942 right: BorderStyle::Solid,
1943 bottom: BorderStyle::Solid,
1944 left: BorderStyle::Solid,
1945 }
1946 );
1947 assert_eq!(
1948 parse_style_border_style("solid dashed").unwrap(),
1949 StyleBorderStyles {
1950 top: BorderStyle::Solid,
1951 right: BorderStyle::Dashed,
1952 bottom: BorderStyle::Solid,
1953 left: BorderStyle::Dashed,
1954 }
1955 );
1956 assert_eq!(
1957 parse_style_border_style("solid dashed dotted").unwrap(),
1958 StyleBorderStyles {
1959 top: BorderStyle::Solid,
1960 right: BorderStyle::Dashed,
1961 bottom: BorderStyle::Dotted,
1962 left: BorderStyle::Dashed,
1963 }
1964 );
1965 assert_eq!(
1966 parse_style_border_style("solid dashed dotted double").unwrap(),
1967 StyleBorderStyles {
1968 top: BorderStyle::Solid,
1969 right: BorderStyle::Dashed,
1970 bottom: BorderStyle::Dotted,
1971 left: BorderStyle::Double,
1972 }
1973 );
1974 }
1975
1976 #[cfg(feature = "parser")]
1977 #[test]
1978 fn parse_style_border_style_rejects_zero_and_more_than_four_values() {
1979 for input in ["", " ", "\t\n"] {
1980 let err = parse_style_border_style(input).unwrap_err();
1981 assert!(
1984 matches!(err, CssBorderStyleParseError::InvalidStyle(s) if s == input.trim()),
1985 "{input:?} -> {err:?}"
1986 );
1987 }
1988 let too_many = "dotted ".repeat(1_000);
1989 let inputs: [&str; 2] = ["solid solid solid solid solid", &too_many];
1990 for input in inputs {
1991 assert!(
1992 parse_style_border_style(input).is_err(),
1993 "{input:?} unexpectedly parsed"
1994 );
1995 }
1996 }
1997
1998 #[cfg(feature = "parser")]
1999 #[test]
2000 fn parse_style_border_style_propagates_component_errors() {
2001 for input in [
2002 "bogus",
2003 "solid bogus",
2004 "solid solid bogus",
2005 "solid solid solid bogus",
2006 "solid \u{1F600}",
2007 "SOLID",
2008 ] {
2009 assert!(
2010 parse_style_border_style(input).is_err(),
2011 "{input:?} unexpectedly parsed"
2012 );
2013 }
2014 }
2015
2016 #[cfg(feature = "parser")]
2017 #[test]
2018 fn parse_style_border_style_round_trips_through_display() {
2019 for (t, r, b, l) in [
2020 (
2021 BorderStyle::Solid,
2022 BorderStyle::Dashed,
2023 BorderStyle::Dotted,
2024 BorderStyle::Double,
2025 ),
2026 (
2027 BorderStyle::None,
2028 BorderStyle::Hidden,
2029 BorderStyle::Groove,
2030 BorderStyle::Ridge,
2031 ),
2032 (
2033 BorderStyle::Inset,
2034 BorderStyle::Outset,
2035 BorderStyle::None,
2036 BorderStyle::Solid,
2037 ),
2038 ] {
2039 let expected = StyleBorderStyles {
2040 top: t,
2041 right: r,
2042 bottom: b,
2043 left: l,
2044 };
2045 let encoded = format!("{t} {r} {b} {l}");
2046 assert_eq!(
2047 parse_style_border_style(&encoded).unwrap(),
2048 expected,
2049 "{encoded} did not round-trip"
2050 );
2051 }
2052 }
2053
2054 #[cfg(feature = "parser")]
2059 #[test]
2060 fn parse_style_border_width_expands_one_to_four_values() {
2061 assert_eq!(
2062 parse_style_border_width("1px").unwrap(),
2063 StyleBorderWidths {
2064 top: PixelValue::px(1.0),
2065 right: PixelValue::px(1.0),
2066 bottom: PixelValue::px(1.0),
2067 left: PixelValue::px(1.0),
2068 }
2069 );
2070 assert_eq!(
2071 parse_style_border_width("1px 2px").unwrap(),
2072 StyleBorderWidths {
2073 top: PixelValue::px(1.0),
2074 right: PixelValue::px(2.0),
2075 bottom: PixelValue::px(1.0),
2076 left: PixelValue::px(2.0),
2077 }
2078 );
2079 assert_eq!(
2080 parse_style_border_width("1px 2px 3px").unwrap(),
2081 StyleBorderWidths {
2082 top: PixelValue::px(1.0),
2083 right: PixelValue::px(2.0),
2084 bottom: PixelValue::px(3.0),
2085 left: PixelValue::px(2.0),
2086 }
2087 );
2088 assert_eq!(
2089 parse_style_border_width("1px 2em 3pt 4%").unwrap(),
2090 StyleBorderWidths {
2091 top: PixelValue::px(1.0),
2092 right: PixelValue::em(2.0),
2093 bottom: PixelValue::pt(3.0),
2094 left: PixelValue::percent(4.0),
2095 }
2096 );
2097 }
2098
2099 #[cfg(feature = "parser")]
2100 #[test]
2101 fn parse_style_border_width_rejects_zero_and_more_than_four_values() {
2102 for input in ["", " ", "\t\n"] {
2103 let err = parse_style_border_width(input).unwrap_err();
2104 assert!(
2105 matches!(err, CssPixelValueParseError::InvalidPixelValue(s) if s == input.trim()),
2106 "{input:?} -> {err:?}"
2107 );
2108 }
2109 let too_many = "1px ".repeat(1_000);
2110 let inputs: [&str; 2] = ["1px 1px 1px 1px 1px", &too_many];
2111 for input in inputs {
2112 assert!(
2113 parse_style_border_width(input).is_err(),
2114 "{input:?} unexpectedly parsed"
2115 );
2116 }
2117 }
2118
2119 #[cfg(feature = "parser")]
2120 #[test]
2121 fn parse_style_border_width_rejects_the_thin_medium_thick_keywords() {
2122 let err = parse_style_border_width("thin").unwrap_err();
2128 assert!(
2129 matches!(err, CssPixelValueParseError::ValueParseErr(_, s) if s == "th"),
2130 "expected `thin` to be misread as inches, got {err:?}"
2131 );
2132 assert!(parse_style_border_width("medium").is_err());
2133 assert!(parse_style_border_width("thick").is_err());
2134 assert!(parse_style_border_width("thin thick").is_err());
2135
2136 assert_eq!(
2138 parse_border_top_width("thin").unwrap().inner,
2139 THIN_BORDER_THICKNESS
2140 );
2141 }
2142
2143 #[cfg(feature = "parser")]
2144 #[test]
2145 fn parse_style_border_width_propagates_component_errors() {
2146 for input in [
2147 "abc",
2148 "1px abc",
2149 "1px 2px abc",
2150 "1px 2px 3px abc",
2151 "1px \u{1F600}",
2152 "1PX",
2153 ] {
2154 assert!(
2155 parse_style_border_width(input).is_err(),
2156 "{input:?} unexpectedly parsed"
2157 );
2158 }
2159 }
2160
2161 #[cfg(feature = "parser")]
2162 #[test]
2163 fn parse_style_border_width_hostile_numbers_stay_finite() {
2164 let widths = parse_style_border_width("NaN inf -inf 1e999").unwrap();
2165 for w in [widths.top, widths.right, widths.bottom, widths.left] {
2166 assert!(
2167 w.number.get().is_finite(),
2168 "hostile width did not saturate: {w:?}"
2169 );
2170 }
2171 assert_eq!(widths.top, PixelValue::px(0.0)); }
2173
2174 #[cfg(feature = "parser")]
2175 #[test]
2176 fn parse_style_border_width_round_trips_through_display() {
2177 let widths = StyleBorderWidths {
2178 top: PixelValue::px(1.5),
2179 right: PixelValue::em(2.0),
2180 bottom: PixelValue::pt(3.25),
2181 left: PixelValue::percent(50.0),
2182 };
2183 let encoded = format!(
2184 "{} {} {} {}",
2185 widths.top, widths.right, widths.bottom, widths.left
2186 );
2187 assert_eq!(parse_style_border_width(&encoded).unwrap(), widths);
2188 }
2189
2190 #[cfg(feature = "parser")]
2195 #[test]
2196 fn css_border_style_parse_error_round_trips_owned_and_shared() {
2197 let huge = "x".repeat(10_000);
2198 let payloads: [&str; 7] = ["", " ", "bogus", "\u{1F600}", "s\u{0301}", "\u{0}", &huge];
2199 for payload in payloads {
2200 let shared = CssBorderStyleParseError::InvalidStyle(payload);
2201 let owned = shared.to_contained();
2202 assert_eq!(owned.to_shared(), shared, "{payload:?} did not round-trip");
2203 assert!(
2204 matches!(&owned, CssBorderStyleParseErrorOwned::InvalidStyle(s) if s.as_str() == payload)
2205 );
2206 }
2207 }
2208
2209 #[cfg(feature = "parser")]
2210 #[test]
2211 fn css_border_style_parse_error_from_a_real_parse_failure_round_trips() {
2212 let err = parse_border_style("\u{1F600}bogus").unwrap_err();
2213 let owned = err.to_contained();
2214 assert_eq!(owned.to_shared(), err);
2215 assert!(format!("{err}").contains("bogus"));
2217 assert!(format!("{err:?}").contains("bogus"));
2218 assert!(!format!("{owned:?}").is_empty());
2219 }
2220
2221 #[cfg(feature = "parser")]
2222 #[test]
2223 fn css_border_side_parse_error_round_trips_for_every_variant() {
2224 let variants = [
2225 CssBorderSideParseError::InvalidDeclaration(""),
2226 CssBorderSideParseError::InvalidDeclaration("1px 2px solid"),
2227 CssBorderSideParseError::InvalidDeclaration("\u{1F600}"),
2228 CssBorderSideParseError::Width(CssPixelValueParseError::EmptyString),
2229 CssBorderSideParseError::Width(CssPixelValueParseError::InvalidPixelValue("zz")),
2230 CssBorderSideParseError::Style(CssBorderStyleParseError::InvalidStyle("zz")),
2231 CssBorderSideParseError::Style(CssBorderStyleParseError::InvalidStyle("")),
2232 CssBorderSideParseError::Color(CssColorParseError::InvalidColor("zz")),
2233 CssBorderSideParseError::Color(CssColorParseError::EmptyInput),
2234 CssBorderSideParseError::Color(CssColorParseError::InvalidColorComponent(u8::MAX)),
2235 ];
2236 for shared in variants {
2237 let owned = shared.to_contained();
2238 assert_eq!(owned.to_shared(), shared, "{shared:?} did not round-trip");
2239 assert!(!format!("{shared}").is_empty());
2240 assert!(!format!("{owned:?}").is_empty());
2241 }
2242 }
2243
2244 #[cfg(feature = "parser")]
2245 #[test]
2246 fn css_border_side_parse_error_from_conversions_pick_the_right_variant() {
2247 let width: CssBorderSideParseError<'_> =
2248 CssPixelValueParseError::InvalidPixelValue("zz").into();
2249 assert!(matches!(width, CssBorderSideParseError::Width(_)));
2250
2251 let style: CssBorderSideParseError<'_> =
2252 CssBorderStyleParseError::InvalidStyle("zz").into();
2253 assert!(matches!(style, CssBorderSideParseError::Style(_)));
2254
2255 let color: CssBorderSideParseError<'_> = CssColorParseError::InvalidColor("zz").into();
2256 assert!(matches!(color, CssBorderSideParseError::Color(_)));
2257 }
2258
2259 #[cfg(feature = "parser")]
2260 #[test]
2261 fn css_border_parse_error_owned_newtype_wraps_the_side_error() {
2262 let owned = CssBorderSideParseError::InvalidDeclaration("bogus").to_contained();
2263 let wrapped = CssBorderParseErrorOwned::from(owned.clone());
2264 assert_eq!(wrapped.inner, owned);
2265 assert_eq!(wrapped.inner.to_shared().to_contained(), owned);
2266 }
2267
2268 #[cfg(feature = "parser")]
2269 #[test]
2270 fn error_display_mentions_the_offending_input() {
2271 let err = parse_border_side("1px bogus red").unwrap_err();
2272 let msg = format!("{err}");
2273 assert!(msg.contains("1px bogus red"), "unhelpful message: {msg}");
2274
2275 let err = parse_border_style("bogus").unwrap_err();
2276 assert!(format!("{err}").contains("bogus"));
2277
2278 let err = parse_border_top_width("bogus").unwrap_err();
2279 assert!(format!("{err}").contains("bogus"));
2280 }
2281}