1use crate::corety::AzString;
6use alloc::string::{String, ToString};
7
8use crate::props::{
9 basic::pixel::{
10 parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue,
11 },
12 macros::PixelValueTaker,
13};
14
15macro_rules! define_border_radius_property {
18 ($struct_name:ident) => {
19 #[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20 #[repr(C)]
21 pub struct $struct_name {
22 pub inner: PixelValue,
23 }
24
25 impl ::core::fmt::Debug for $struct_name {
26 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
27 write!(f, "{}", self.inner)
28 }
29 }
30
31 impl PixelValueTaker for $struct_name {
32 fn from_pixel_value(inner: PixelValue) -> Self {
33 Self { inner }
34 }
35 }
36
37 impl_pixel_value!($struct_name);
38 };
39}
40
41define_border_radius_property!(StyleBorderTopLeftRadius);
43define_border_radius_property!(StyleBorderTopRightRadius);
45define_border_radius_property!(StyleBorderBottomLeftRadius);
47define_border_radius_property!(StyleBorderBottomRightRadius);
49
50#[cfg(feature = "parser")]
54#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub struct StyleBorderRadius {
56 pub top_left: PixelValue,
57 pub top_right: PixelValue,
58 pub bottom_left: PixelValue,
59 pub bottom_right: PixelValue,
60}
61
62#[derive(Clone, PartialEq, Eq)]
66pub enum CssBorderRadiusParseError<'a> {
67 TooManyValues(&'a str),
69 PixelValue(CssPixelValueParseError<'a>),
71}
72
73impl_debug_as_display!(CssBorderRadiusParseError<'a>);
74impl_display! { CssBorderRadiusParseError<'a>, {
75 TooManyValues(val) => format!("Too many values for border-radius: \"{}\"", val),
76 PixelValue(e) => format!("{}", e),
77}}
78impl_from!(
79 CssPixelValueParseError<'a>,
80 CssBorderRadiusParseError::PixelValue
81);
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85#[repr(C, u8)]
86pub enum CssBorderRadiusParseErrorOwned {
87 TooManyValues(AzString),
88 PixelValue(CssPixelValueParseErrorOwned),
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
93#[repr(C)]
94pub struct CssStyleBorderRadiusParseErrorOwned {
95 pub inner: CssBorderRadiusParseErrorOwned,
96}
97
98impl From<CssBorderRadiusParseErrorOwned> for CssStyleBorderRadiusParseErrorOwned {
99 fn from(v: CssBorderRadiusParseErrorOwned) -> Self {
100 Self { inner: v }
101 }
102}
103
104impl CssBorderRadiusParseError<'_> {
105 #[must_use]
106 pub fn to_contained(&self) -> CssBorderRadiusParseErrorOwned {
107 match self {
108 CssBorderRadiusParseError::TooManyValues(s) => {
109 CssBorderRadiusParseErrorOwned::TooManyValues((*s).to_string().into())
110 }
111 CssBorderRadiusParseError::PixelValue(e) => {
112 CssBorderRadiusParseErrorOwned::PixelValue(e.to_contained())
113 }
114 }
115 }
116}
117
118impl CssBorderRadiusParseErrorOwned {
119 #[must_use]
120 pub fn to_shared(&self) -> CssBorderRadiusParseError<'_> {
121 match self {
122 Self::TooManyValues(s) => CssBorderRadiusParseError::TooManyValues(s),
123 Self::PixelValue(e) => CssBorderRadiusParseError::PixelValue(e.to_shared()),
124 }
125 }
126}
127
128macro_rules! define_border_radius_parse_error {
130 ($error_name:ident, $error_name_owned:ident) => {
131 #[derive(Clone, PartialEq, Eq)]
132 pub enum $error_name<'a> {
133 PixelValue(CssPixelValueParseError<'a>),
134 }
135
136 impl_debug_as_display!($error_name<'a>);
137 impl_display! { $error_name<'a>, {
138 PixelValue(e) => format!("{}", e),
139 }}
140
141 impl_from!(CssPixelValueParseError<'a>, $error_name::PixelValue);
142
143 #[derive(Debug, Clone, PartialEq, Eq)]
144 #[repr(C, u8)]
145 pub enum $error_name_owned {
146 PixelValue(CssPixelValueParseErrorOwned),
147 }
148
149 impl $error_name<'_> {
150 #[must_use]
151 pub fn to_contained(&self) -> $error_name_owned {
152 match self {
153 $error_name::PixelValue(e) => $error_name_owned::PixelValue(e.to_contained()),
154 }
155 }
156 }
157
158 impl $error_name_owned {
159 #[must_use]
160 pub fn to_shared(&self) -> $error_name<'_> {
161 match self {
162 $error_name_owned::PixelValue(e) => $error_name::PixelValue(e.to_shared()),
163 }
164 }
165 }
166 };
167}
168
169define_border_radius_parse_error!(
170 StyleBorderTopLeftRadiusParseError,
171 StyleBorderTopLeftRadiusParseErrorOwned
172);
173define_border_radius_parse_error!(
174 StyleBorderTopRightRadiusParseError,
175 StyleBorderTopRightRadiusParseErrorOwned
176);
177define_border_radius_parse_error!(
178 StyleBorderBottomLeftRadiusParseError,
179 StyleBorderBottomLeftRadiusParseErrorOwned
180);
181define_border_radius_parse_error!(
182 StyleBorderBottomRightRadiusParseError,
183 StyleBorderBottomRightRadiusParseErrorOwned
184);
185
186#[cfg(feature = "parser")]
190pub fn parse_style_border_radius(
194 input: &str,
195) -> Result<StyleBorderRadius, CssBorderRadiusParseError<'_>> {
196 let components: Vec<_> = input.split_whitespace().collect();
197 let mut values = Vec::with_capacity(components.len());
198 for comp in &components {
199 values.push(parse_pixel_value(comp)?);
200 }
201
202 match values.len() {
203 1 => Ok(StyleBorderRadius {
204 top_left: values[0],
205 top_right: values[0],
206 bottom_right: values[0],
207 bottom_left: values[0],
208 }),
209 2 => Ok(StyleBorderRadius {
210 top_left: values[0],
211 top_right: values[1],
212 bottom_right: values[0],
213 bottom_left: values[1],
214 }),
215 3 => Ok(StyleBorderRadius {
216 top_left: values[0],
217 top_right: values[1],
218 bottom_right: values[2],
219 bottom_left: values[1],
220 }),
221 4 => Ok(StyleBorderRadius {
222 top_left: values[0],
223 top_right: values[1],
224 bottom_right: values[2],
225 bottom_left: values[3],
226 }),
227 _ => Err(CssBorderRadiusParseError::TooManyValues(input)),
228 }
229}
230
231#[cfg(feature = "parser")]
233pub fn parse_style_border_top_left_radius(
237 input: &str,
238) -> Result<StyleBorderTopLeftRadius, StyleBorderTopLeftRadiusParseError<'_>> {
239 let pixel_value = parse_pixel_value(input)?;
240 Ok(StyleBorderTopLeftRadius { inner: pixel_value })
241}
242
243#[cfg(feature = "parser")]
245pub fn parse_style_border_top_right_radius(
249 input: &str,
250) -> Result<StyleBorderTopRightRadius, StyleBorderTopRightRadiusParseError<'_>> {
251 let pixel_value = parse_pixel_value(input)?;
252 Ok(StyleBorderTopRightRadius { inner: pixel_value })
253}
254
255#[cfg(feature = "parser")]
257pub fn parse_style_border_bottom_left_radius(
261 input: &str,
262) -> Result<StyleBorderBottomLeftRadius, StyleBorderBottomLeftRadiusParseError<'_>> {
263 let pixel_value = parse_pixel_value(input)?;
264 Ok(StyleBorderBottomLeftRadius { inner: pixel_value })
265}
266
267#[cfg(feature = "parser")]
269pub fn parse_style_border_bottom_right_radius(
273 input: &str,
274) -> Result<StyleBorderBottomRightRadius, StyleBorderBottomRightRadiusParseError<'_>> {
275 let pixel_value = parse_pixel_value(input)?;
276 Ok(StyleBorderBottomRightRadius { inner: pixel_value })
277}
278
279#[cfg(all(test, feature = "parser"))]
280mod tests {
281 use super::*;
282
283 #[test]
284 fn test_parse_border_radius_shorthand() {
285 let result = parse_style_border_radius("10px").unwrap();
287 assert_eq!(result.top_left, PixelValue::px(10.0));
288 assert_eq!(result.top_right, PixelValue::px(10.0));
289 assert_eq!(result.bottom_right, PixelValue::px(10.0));
290 assert_eq!(result.bottom_left, PixelValue::px(10.0));
291
292 let result = parse_style_border_radius("10px 5%").unwrap();
294 assert_eq!(result.top_left, PixelValue::px(10.0));
295 assert_eq!(result.top_right, PixelValue::percent(5.0));
296 assert_eq!(result.bottom_right, PixelValue::px(10.0));
297 assert_eq!(result.bottom_left, PixelValue::percent(5.0));
298
299 let result = parse_style_border_radius("2px 4px 8px").unwrap();
301 assert_eq!(result.top_left, PixelValue::px(2.0));
302 assert_eq!(result.top_right, PixelValue::px(4.0));
303 assert_eq!(result.bottom_right, PixelValue::px(8.0));
304 assert_eq!(result.bottom_left, PixelValue::px(4.0));
305
306 let result = parse_style_border_radius("1px 0 3px 4px").unwrap();
308 assert_eq!(result.top_left, PixelValue::px(1.0));
309 assert_eq!(result.top_right, PixelValue::px(0.0));
310 assert_eq!(result.bottom_right, PixelValue::px(3.0));
311 assert_eq!(result.bottom_left, PixelValue::px(4.0));
312
313 let result = parse_style_border_radius(" 1em 2em ").unwrap();
315 assert_eq!(result.top_left, PixelValue::em(1.0));
316 assert_eq!(result.top_right, PixelValue::em(2.0));
317 }
318
319 #[test]
320 fn test_parse_border_radius_shorthand_errors() {
321 assert!(parse_style_border_radius("").is_err());
322 assert!(parse_style_border_radius("1px 2px 3px 4px 5px").is_err());
323 assert!(parse_style_border_radius("1px bad 3px").is_err());
324 }
325
326 #[test]
327 fn test_parse_longhand_radius() {
328 let result = parse_style_border_top_left_radius("25%").unwrap();
329 assert_eq!(result.inner, PixelValue::percent(25.0));
330 }
331}
332
333#[cfg(all(test, feature = "parser"))]
334#[allow(
335 clippy::float_cmp,
336 clippy::unreadable_literal,
337 clippy::too_many_lines,
338 clippy::cast_precision_loss
339)]
340mod autotest_generated {
341 use super::*;
342 use crate::{css::PrintAsCssValue, props::basic::length::SizeMetric};
343
344 const ROUNDTRIPPABLE_METRICS: [SizeMetric; 11] = [
347 SizeMetric::Px,
348 SizeMetric::Pt,
349 SizeMetric::Em,
350 SizeMetric::Rem,
351 SizeMetric::In,
352 SizeMetric::Cm,
353 SizeMetric::Mm,
354 SizeMetric::Percent,
355 SizeMetric::Vw,
356 SizeMetric::Vh,
357 SizeMetric::Vmax,
358 ];
359
360 const EXACT_VALUES: [f32; 6] = [0.0, 1.0, 12.5, -3.25, 0.125, 1000.5];
364
365 const GARBAGE: [&str; 14] = [
367 "bad",
368 "px",
369 "%",
370 "em",
371 "-",
372 "+",
373 ".",
374 "e",
375 "1..2px",
376 "1,2px",
377 "10px;",
378 "10px!important",
379 "\0",
380 "\u{7f}\u{1}",
381 ];
382
383 fn all_longhands(input: &str) -> [Result<PixelValue, ()>; 4] {
384 [
385 parse_style_border_top_left_radius(input)
386 .map(|v| v.inner)
387 .map_err(|_| ()),
388 parse_style_border_top_right_radius(input)
389 .map(|v| v.inner)
390 .map_err(|_| ()),
391 parse_style_border_bottom_left_radius(input)
392 .map(|v| v.inner)
393 .map_err(|_| ()),
394 parse_style_border_bottom_right_radius(input)
395 .map(|v| v.inner)
396 .map_err(|_| ()),
397 ]
398 }
399
400 #[test]
403 fn shorthand_rejects_empty_and_whitespace_only() {
404 for input in ["", " ", " ", "\t\n", "\r\n\t ", "\u{a0}"] {
408 let err = parse_style_border_radius(input)
409 .expect_err("whitespace-only border-radius must not parse");
410 assert!(
411 matches!(err, CssBorderRadiusParseError::TooManyValues(_)),
412 "unexpected error for {input:?}: {err}"
413 );
414 }
415 }
416
417 #[test]
418 fn shorthand_rejects_garbage_without_panicking() {
419 for input in GARBAGE {
420 assert!(
421 parse_style_border_radius(input).is_err(),
422 "garbage {input:?} was accepted"
423 );
424 }
425 }
426
427 #[test]
428 fn shorthand_rejects_more_than_four_values() {
429 let input = "1px 2px 3px 4px 5px";
430 let err = parse_style_border_radius(input).unwrap_err();
431 assert_eq!(err, CssBorderRadiusParseError::TooManyValues(input));
432 assert!(format!("{err}").contains(input));
434 }
435
436 #[test]
437 fn shorthand_reports_the_bad_component_before_counting_values() {
438 let err = parse_style_border_radius("1px 2px 3px 4px 5px bad").unwrap_err();
441 assert_eq!(
442 err,
443 CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::InvalidPixelValue(
444 "bad"
445 ))
446 );
447 }
448
449 #[test]
450 fn shorthand_rejects_elliptical_slash_syntax() {
451 assert!(parse_style_border_radius("10px / 20px").is_err());
455 assert!(parse_style_border_radius("10px/20px").is_err());
456 assert!(parse_style_border_radius("1px 2px / 3px 4px").is_err());
457 }
458
459 #[test]
460 fn shorthand_rejects_split_number_and_unit() {
461 let err = parse_style_border_radius("10 px").unwrap_err();
464 assert_eq!(
465 err,
466 CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::NoValueGiven(
467 "px",
468 SizeMetric::Px
469 ))
470 );
471 }
472
473 #[test]
476 fn shorthand_boundary_numbers_never_produce_nan_or_infinity() {
477 let inputs = [
480 "0",
481 "-0",
482 "+0",
483 "0px",
484 "-0px",
485 "0.0001px",
486 "1e-30px",
487 "1e30px",
488 "-1e30px",
489 "3.4028235e38px",
490 "-3.4028235e38px",
491 "9223372036854775807px",
492 "-9223372036854775808px",
493 "340282350000000000000000000000000000000%",
494 ];
495 for input in inputs {
496 let Ok(radius) = parse_style_border_radius(input) else {
497 continue; };
499 for corner in [
500 radius.top_left,
501 radius.top_right,
502 radius.bottom_left,
503 radius.bottom_right,
504 ] {
505 let n = corner.number.get();
506 assert!(n.is_finite(), "{input:?} produced non-finite {n}");
507 }
508 }
509 }
510
511 #[test]
512 fn shorthand_nan_input_is_flattened_to_zero() {
513 for input in ["NaN", "nan", "NaNpx", "-nan%"] {
518 let radius = parse_style_border_radius(input)
519 .unwrap_or_else(|e| panic!("{input:?} unexpectedly rejected: {e}"));
520 let n = radius.top_left.number.get();
521 assert!(!n.is_nan(), "{input:?} leaked a NaN");
522 assert_eq!(n, 0.0, "{input:?} should quantize to 0");
523 }
524 }
525
526 #[test]
527 fn shorthand_infinite_input_saturates_instead_of_overflowing() {
528 let pos = parse_style_border_radius("infpx").unwrap().top_left;
529 let neg = parse_style_border_radius("-infpx").unwrap().top_left;
530 assert!(pos.number.get().is_finite());
531 assert!(neg.number.get().is_finite());
532 assert!(pos.number.get() > 0.0);
533 assert!(neg.number.get() < 0.0);
534 assert_eq!(pos.number.number(), isize::MAX);
536 assert_eq!(neg.number.number(), isize::MIN);
537 }
538
539 #[test]
540 fn shorthand_sub_quantum_values_truncate_to_zero() {
541 let radius = parse_style_border_radius("0.0001px").unwrap();
544 assert_eq!(radius.top_left, PixelValue::px(0.0));
545 assert_eq!(radius.top_left.number.number(), 0);
546 }
547
548 #[test]
549 fn shorthand_negative_zero_equals_positive_zero() {
550 let neg = parse_style_border_radius("-0px").unwrap();
551 let pos = parse_style_border_radius("0px").unwrap();
552 assert_eq!(neg, pos);
553 assert_eq!(neg.top_left.number.number(), 0);
554 }
555
556 #[test]
559 fn shorthand_extremely_long_input_terminates() {
560 let many = "1px ".repeat(100_000);
562 assert!(matches!(
563 parse_style_border_radius(&many),
564 Err(CssBorderRadiusParseError::TooManyValues(_))
565 ));
566
567 let huge = format!("{}px", "1".repeat(100_000));
570 let radius = parse_style_border_radius(&huge).unwrap();
571 assert!(radius.top_left.number.get().is_finite());
572
573 let junk = "z".repeat(100_000);
575 assert!(parse_style_border_radius(&junk).is_err());
576 }
577
578 #[test]
579 fn shorthand_deeply_nested_brackets_do_not_stack_overflow() {
580 let nested = format!("{}{}", "(".repeat(10_000), ")".repeat(10_000));
581 assert!(parse_style_border_radius(&nested).is_err());
582
583 let calls = format!("{}1px{}", "calc(".repeat(10_000), ")".repeat(10_000));
584 assert!(parse_style_border_radius(&calls).is_err());
585 }
586
587 #[test]
588 fn shorthand_unicode_input_is_rejected_without_panicking() {
589 for input in [
591 "\u{1F600}",
592 "10px\u{1F600}",
593 "\u{1F600}px",
594 "\u{661}\u{660}px", "10\u{301}px", "10px\u{200b}", "10px", "10\u{202e}px", ] {
600 assert!(
601 parse_style_border_radius(input).is_err(),
602 "unicode {input:?} was accepted"
603 );
604 }
605 }
606
607 #[test]
608 fn shorthand_treats_non_breaking_space_as_a_value_separator() {
609 let radius = parse_style_border_radius("1px\u{a0}2px").unwrap();
614 assert_eq!(radius.top_left, PixelValue::px(1.0));
615 assert_eq!(radius.top_right, PixelValue::px(2.0));
616 }
617
618 #[test]
621 fn shorthand_one_value_fills_every_corner() {
622 let radius = parse_style_border_radius("7px").unwrap();
623 assert_eq!(radius.top_left, PixelValue::px(7.0));
624 assert_eq!(radius.top_right, PixelValue::px(7.0));
625 assert_eq!(radius.bottom_left, PixelValue::px(7.0));
626 assert_eq!(radius.bottom_right, PixelValue::px(7.0));
627 }
628
629 #[test]
630 fn shorthand_two_and_three_values_expand_along_the_diagonals() {
631 let two = parse_style_border_radius("1px 2px").unwrap();
633 assert_eq!(two.top_left, two.bottom_right);
634 assert_eq!(two.top_right, two.bottom_left);
635 assert_eq!(two.top_left, PixelValue::px(1.0));
636 assert_eq!(two.top_right, PixelValue::px(2.0));
637
638 let three = parse_style_border_radius("1px 2px 3px").unwrap();
640 assert_eq!(three.top_left, PixelValue::px(1.0));
641 assert_eq!(three.top_right, PixelValue::px(2.0));
642 assert_eq!(three.bottom_left, PixelValue::px(2.0));
643 assert_eq!(three.bottom_right, PixelValue::px(3.0));
644 }
645
646 #[test]
647 fn shorthand_four_values_map_clockwise_from_top_left() {
648 let four = parse_style_border_radius("1px 2px 3px 4px").unwrap();
649 assert_eq!(four.top_left, PixelValue::px(1.0));
650 assert_eq!(four.top_right, PixelValue::px(2.0));
651 assert_eq!(four.bottom_right, PixelValue::px(3.0));
652 assert_eq!(four.bottom_left, PixelValue::px(4.0));
653 assert_ne!(four.top_left, four.top_right);
656 assert_ne!(four.bottom_right, four.bottom_left);
657 }
658
659 #[test]
660 fn shorthand_preserves_per_corner_units() {
661 let radius = parse_style_border_radius("1px 2em 3% 4rem").unwrap();
662 assert_eq!(radius.top_left, PixelValue::px(1.0));
663 assert_eq!(radius.top_right, PixelValue::em(2.0));
664 assert_eq!(radius.bottom_right, PixelValue::percent(3.0));
665 assert_eq!(radius.bottom_left, PixelValue::rem(4.0));
666 }
667
668 #[test]
669 fn shorthand_valid_minimal_input() {
670 let radius = parse_style_border_radius("0").unwrap();
671 assert_eq!(radius.top_left, PixelValue::px(0.0));
672 }
673
674 #[test]
675 fn shorthand_tolerates_arbitrary_ascii_whitespace_runs() {
676 let radius = parse_style_border_radius("\n\t 1px \t 2px\r\n").unwrap();
677 assert_eq!(radius.top_left, PixelValue::px(1.0));
678 assert_eq!(radius.top_right, PixelValue::px(2.0));
679 }
680
681 #[test]
684 fn longhands_reject_empty_whitespace_and_garbage() {
685 for input in ["", " ", "\t\n"] {
686 for result in all_longhands(input) {
687 assert_eq!(result, Err(()), "{input:?} was accepted by a longhand");
688 }
689 }
690 for input in GARBAGE {
691 for result in all_longhands(input) {
692 assert_eq!(result, Err(()), "{input:?} was accepted by a longhand");
693 }
694 }
695 }
696
697 #[test]
698 fn longhands_reject_multiple_values() {
699 for input in ["1px 2px", "1px 2px 3px 4px", "1px,2px"] {
702 for result in all_longhands(input) {
703 assert_eq!(result, Err(()), "{input:?} was accepted by a longhand");
704 }
705 }
706 }
707
708 #[test]
709 fn all_four_longhands_agree_on_every_input() {
710 let inputs = [
713 "0",
714 "10px",
715 "-3.25em",
716 "50%",
717 "1.5rem",
718 "2pt",
719 "1in",
720 "2.54cm",
721 "10mm",
722 "5vw",
723 "5vh",
724 "5vmax",
725 "bad",
726 "",
727 " ",
728 "\u{1F600}",
729 "1e30px",
730 "NaNpx",
731 ];
732 for input in inputs {
733 let [tl, tr, bl, br] = all_longhands(input);
734 assert_eq!(tl, tr, "top-left vs top-right disagree on {input:?}");
735 assert_eq!(tl, bl, "top-left vs bottom-left disagree on {input:?}");
736 assert_eq!(tl, br, "top-left vs bottom-right disagree on {input:?}");
737 }
738 }
739
740 #[test]
741 fn longhands_agree_with_the_shorthand_on_single_values() {
742 for input in ["0", "10px", "-3.25em", "50%", "1.5rem", "2pt"] {
743 let shorthand = parse_style_border_radius(input).unwrap();
744 let [tl, tr, bl, br] = all_longhands(input);
745 assert_eq!(tl, Ok(shorthand.top_left), "{input:?}");
746 assert_eq!(tr, Ok(shorthand.top_right), "{input:?}");
747 assert_eq!(bl, Ok(shorthand.bottom_left), "{input:?}");
748 assert_eq!(br, Ok(shorthand.bottom_right), "{input:?}");
749 }
750 }
751
752 #[test]
753 fn longhands_accept_a_gap_between_number_and_unit_but_the_shorthand_does_not() {
754 for result in all_longhands("10 px") {
758 assert_eq!(result, Ok(PixelValue::px(10.0)));
759 }
760 assert!(parse_style_border_radius("10 px").is_err());
761 }
762
763 #[test]
764 fn longhands_survive_extremely_long_and_nested_input() {
765 let long = format!("{}px", "9".repeat(100_000));
766 for result in all_longhands(&long) {
767 assert!(result.unwrap().number.get().is_finite());
768 }
769 let nested = format!("{}1px{}", "(".repeat(10_000), ")".repeat(10_000));
770 for result in all_longhands(&nested) {
771 assert_eq!(result, Err(()));
772 }
773 }
774
775 #[test]
776 fn longhands_boundary_numbers_stay_finite() {
777 for input in [
778 "0",
779 "-0",
780 "1e-30px",
781 "1e30px",
782 "-1e30px",
783 "9223372036854775807px",
784 "NaNpx",
785 "infpx",
786 "-infpx",
787 ] {
788 for result in all_longhands(input) {
789 let value = result.unwrap_or_else(|()| panic!("{input:?} rejected"));
790 let n = value.number.get();
791 assert!(n.is_finite() && !n.is_nan(), "{input:?} produced {n}");
792 }
793 }
794 }
795
796 #[test]
799 fn print_as_css_value_round_trips_through_every_longhand_parser() {
800 for metric in ROUNDTRIPPABLE_METRICS {
801 for value in EXACT_VALUES {
802 let pixel = PixelValue::from_metric(metric, value);
803
804 let tl = StyleBorderTopLeftRadius { inner: pixel };
805 let reparsed = parse_style_border_top_left_radius(&tl.print_as_css_value())
806 .unwrap_or_else(|e| {
807 panic!("{:?} did not re-parse: {e}", tl.print_as_css_value())
808 });
809 assert_eq!(reparsed, tl);
810
811 let tr = StyleBorderTopRightRadius { inner: pixel };
812 assert_eq!(
813 parse_style_border_top_right_radius(&tr.print_as_css_value()).unwrap(),
814 tr
815 );
816
817 let bl = StyleBorderBottomLeftRadius { inner: pixel };
818 assert_eq!(
819 parse_style_border_bottom_left_radius(&bl.print_as_css_value()).unwrap(),
820 bl
821 );
822
823 let br = StyleBorderBottomRightRadius { inner: pixel };
824 assert_eq!(
825 parse_style_border_bottom_right_radius(&br.print_as_css_value()).unwrap(),
826 br
827 );
828 }
829 }
830 }
831
832 #[test]
833 fn shorthand_round_trips_through_the_printed_corner_values() {
834 let original = parse_style_border_radius("1px 2em 3% 4rem").unwrap();
835 let printed = format!(
836 "{} {} {} {}",
837 StyleBorderTopLeftRadius {
838 inner: original.top_left
839 }
840 .print_as_css_value(),
841 StyleBorderTopRightRadius {
842 inner: original.top_right
843 }
844 .print_as_css_value(),
845 StyleBorderBottomRightRadius {
846 inner: original.bottom_right
847 }
848 .print_as_css_value(),
849 StyleBorderBottomLeftRadius {
850 inner: original.bottom_left
851 }
852 .print_as_css_value(),
853 );
854 assert_eq!(parse_style_border_radius(&printed).unwrap(), original);
855 }
856
857 #[test]
858 fn debug_output_matches_the_printed_css_value() {
859 let pixel = PixelValue::em(1.5);
862 let tl = StyleBorderTopLeftRadius { inner: pixel };
863 assert_eq!(format!("{tl:?}"), "1.5em");
864 assert_eq!(format!("{tl:?}"), tl.print_as_css_value());
865 }
866
867 #[test]
868 fn vmin_radius_parses() {
869 let printed = StyleBorderTopLeftRadius {
874 inner: PixelValue::from_metric(SizeMetric::Vmin, 3.0),
875 }
876 .print_as_css_value();
877 assert_eq!(printed, "3vmin");
878 assert_eq!(
879 parse_style_border_top_left_radius(&printed).unwrap().inner,
880 PixelValue::from_metric(SizeMetric::Vmin, 3.0)
881 );
882 }
883
884 #[test]
887 fn default_and_zero_agree_for_every_corner_type() {
888 assert_eq!(
889 StyleBorderTopLeftRadius::default(),
890 StyleBorderTopLeftRadius::zero()
891 );
892 assert_eq!(
893 StyleBorderTopRightRadius::default(),
894 StyleBorderTopRightRadius::zero()
895 );
896 assert_eq!(
897 StyleBorderBottomLeftRadius::default(),
898 StyleBorderBottomLeftRadius::zero()
899 );
900 assert_eq!(
901 StyleBorderBottomRightRadius::default(),
902 StyleBorderBottomRightRadius::zero()
903 );
904 assert_eq!(
905 StyleBorderTopLeftRadius::default().inner,
906 PixelValue::px(0.0)
907 );
908 }
909
910 #[test]
911 fn const_constructors_agree_with_the_float_constructors() {
912 assert_eq!(
913 StyleBorderTopLeftRadius::const_px(10),
914 StyleBorderTopLeftRadius::px(10.0)
915 );
916 assert_eq!(
917 StyleBorderTopLeftRadius::const_em(2),
918 StyleBorderTopLeftRadius::em(2.0)
919 );
920 assert_eq!(
921 StyleBorderTopLeftRadius::const_percent(50),
922 StyleBorderTopLeftRadius::percent(50.0)
923 );
924 assert_eq!(
925 StyleBorderTopLeftRadius::const_pt(-3),
926 StyleBorderTopLeftRadius::pt(-3.0)
927 );
928 assert_eq!(
929 StyleBorderTopLeftRadius::from_pixel_value(PixelValue::px(4.0)),
930 StyleBorderTopLeftRadius::px(4.0)
931 );
932 }
933
934 #[test]
935 fn interpolate_returns_the_endpoints_for_matching_metrics() {
936 let a = StyleBorderTopLeftRadius::px(0.0);
937 let b = StyleBorderTopLeftRadius::px(10.0);
938 assert_eq!(a.interpolate(&b, 0.0), a);
939 assert_eq!(a.interpolate(&b, 1.0), b);
940 assert_eq!(a.interpolate(&b, 0.5), StyleBorderTopLeftRadius::px(5.0));
941 }
942
943 #[test]
944 fn interpolate_stays_finite_for_hostile_t_values() {
945 let a = StyleBorderTopLeftRadius::px(0.0);
946 let b = StyleBorderTopLeftRadius::px(10.0);
947 for t in [
948 -1.0,
949 2.0,
950 1e30,
951 -1e30,
952 f32::INFINITY,
953 f32::NEG_INFINITY,
954 f32::NAN,
955 ] {
956 let n = a.interpolate(&b, t).inner.number.get();
957 assert!(n.is_finite(), "t={t} produced {n}");
958 assert!(!n.is_nan(), "t={t} produced NaN");
959 }
960 assert_eq!(a.interpolate(&b, f32::NAN).inner.number.get(), 0.0);
962 }
963
964 #[test]
965 fn interpolate_across_metrics_converts_to_px() {
966 let em = StyleBorderTopLeftRadius::em(1.0);
969 let px = StyleBorderTopLeftRadius::px(10.0);
970 let mid = em.interpolate(&px, 0.5);
971 assert_eq!(mid.inner.metric, SizeMetric::Px);
972 assert_eq!(mid.inner.number.get(), 13.0);
973 assert_eq!(em.interpolate(&px, 0.0), StyleBorderTopLeftRadius::px(16.0));
975 }
976
977 #[test]
980 fn shorthand_error_to_contained_round_trips_for_every_variant() {
981 let float_err = "x".parse::<f32>().unwrap_err();
982 let variants = [
983 CssBorderRadiusParseError::TooManyValues("1px 2px 3px 4px 5px"),
984 CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::EmptyString),
985 CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::NoValueGiven(
986 "px",
987 SizeMetric::Px,
988 )),
989 CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::ValueParseErr(
990 float_err, "bad",
991 )),
992 CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::InvalidPixelValue(
993 "nope",
994 )),
995 ];
996 for variant in variants {
997 let owned = variant.to_contained();
998 assert_eq!(owned.to_shared(), variant);
999 assert_eq!(format!("{}", owned.to_shared()), format!("{variant}"));
1001 }
1002 }
1003
1004 #[test]
1005 fn shorthand_error_to_contained_handles_empty_unicode_and_huge_inputs() {
1006 let huge = "1px ".repeat(50_000);
1007 for input in ["", " ", "\u{1F600}\u{301}", "\0", huge.as_str()] {
1008 let err = CssBorderRadiusParseError::TooManyValues(input);
1009 let owned = err.to_contained();
1010 assert_eq!(owned.to_shared(), err);
1011 match owned {
1012 CssBorderRadiusParseErrorOwned::TooManyValues(s) => {
1013 assert_eq!(s.as_str(), input);
1014 }
1015 CssBorderRadiusParseErrorOwned::PixelValue(_) => panic!("wrong variant"),
1016 }
1017 }
1018 }
1019
1020 #[test]
1021 fn shorthand_errors_from_the_parser_round_trip() {
1022 for input in ["", "1px 2px 3px 4px 5px", "1px bad 3px", "px", "\u{1F600}"] {
1025 let err = parse_style_border_radius(input).unwrap_err();
1026 let owned = err.to_contained();
1027 assert_eq!(owned.to_shared(), err);
1028 }
1029 }
1030
1031 #[test]
1032 fn error_debug_delegates_to_display() {
1033 let err = CssBorderRadiusParseError::TooManyValues("a b c d e");
1034 assert_eq!(format!("{err:?}"), format!("{err}"));
1035 assert!(format!("{err}").contains("a b c d e"));
1036
1037 let inner = CssBorderRadiusParseError::PixelValue(CssPixelValueParseError::EmptyString);
1038 assert_eq!(
1039 format!("{inner}"),
1040 format!("{}", CssPixelValueParseError::EmptyString)
1041 );
1042 }
1043
1044 #[test]
1045 fn longhand_error_types_round_trip() {
1046 let tl = parse_style_border_top_left_radius("bad").unwrap_err();
1049 assert_eq!(tl.to_contained().to_shared(), tl);
1050
1051 let tr = parse_style_border_top_right_radius("").unwrap_err();
1052 assert_eq!(tr.to_contained().to_shared(), tr);
1053
1054 let bl = parse_style_border_bottom_left_radius("px").unwrap_err();
1055 assert_eq!(bl.to_contained().to_shared(), bl);
1056
1057 let br = parse_style_border_bottom_right_radius("\u{1F600}").unwrap_err();
1058 assert_eq!(br.to_contained().to_shared(), br);
1059
1060 assert_eq!(
1062 tl.to_contained(),
1063 StyleBorderTopLeftRadiusParseErrorOwned::PixelValue(
1064 CssPixelValueParseError::InvalidPixelValue("bad").to_contained()
1065 )
1066 );
1067 }
1068
1069 #[test]
1070 fn owned_error_newtype_wrapper_preserves_the_inner_error() {
1071 let owned = CssBorderRadiusParseError::TooManyValues("1 2 3 4 5").to_contained();
1072 let wrapped = CssStyleBorderRadiusParseErrorOwned::from(owned.clone());
1073 assert_eq!(wrapped.inner, owned);
1074 assert_eq!(wrapped.inner.to_shared().to_contained(), owned);
1075 }
1076}