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