1use alloc::string::String;
10
11use crate::props::{
12 basic::color::{parse_css_color, ColorU, CssColorParseError, CssColorParseErrorOwned},
13 formatter::PrintAsCssValue,
14};
15
16const DEFAULT_SELECTION_BG: ColorU = ColorU::new(173, 214, 255, 255);
18
19#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23#[repr(C)]
24pub struct SelectionBackgroundColor {
25 pub inner: ColorU,
26}
27
28impl Default for SelectionBackgroundColor {
29 fn default() -> Self {
30 Self {
31 inner: DEFAULT_SELECTION_BG,
32 }
33 }
34}
35
36impl PrintAsCssValue for SelectionBackgroundColor {
37 fn print_as_css_value(&self) -> String {
38 self.inner.to_hash()
39 }
40}
41
42impl crate::codegen::format::FormatAsRustCode for SelectionBackgroundColor {
43 fn format_as_rust_code(&self, _tabs: usize) -> String {
44 format!(
45 "SelectionBackgroundColor {{ inner: {} }}",
46 crate::codegen::format::format_color_value(&self.inner)
47 )
48 }
49}
50
51#[cfg(feature = "parser")]
53pub fn parse_selection_background_color(
57 input: &str,
58) -> Result<SelectionBackgroundColor, CssColorParseError<'_>> {
59 parse_css_color(input).map(|inner| SelectionBackgroundColor { inner })
60}
61
62#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
66#[repr(C)]
67pub struct SelectionColor {
68 pub inner: ColorU,
69}
70
71impl Default for SelectionColor {
72 fn default() -> Self {
73 Self {
74 inner: ColorU::BLACK,
75 }
76 }
77}
78
79impl PrintAsCssValue for SelectionColor {
80 fn print_as_css_value(&self) -> String {
81 self.inner.to_hash()
82 }
83}
84
85impl crate::codegen::format::FormatAsRustCode for SelectionColor {
86 fn format_as_rust_code(&self, _tabs: usize) -> String {
87 format!(
88 "SelectionColor {{ inner: {} }}",
89 crate::codegen::format::format_color_value(&self.inner)
90 )
91 }
92}
93
94#[cfg(feature = "parser")]
96pub fn parse_selection_color(input: &str) -> Result<SelectionColor, CssColorParseError<'_>> {
100 parse_css_color(input).map(|inner| SelectionColor { inner })
101}
102
103use crate::props::basic::{
106 pixel::{parse_pixel_value, CssPixelValueParseError, CssPixelValueParseErrorOwned, PixelValue},
107 SizeMetric,
108};
109
110#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
112#[repr(C)]
113pub struct SelectionRadius {
114 pub inner: PixelValue,
115}
116
117impl Default for SelectionRadius {
118 fn default() -> Self {
119 Self {
120 inner: PixelValue::zero(),
121 }
122 }
123}
124
125impl PrintAsCssValue for SelectionRadius {
126 fn print_as_css_value(&self) -> String {
127 self.inner.to_string()
128 }
129}
130
131impl crate::codegen::format::FormatAsRustCode for SelectionRadius {
132 fn format_as_rust_code(&self, _tabs: usize) -> String {
133 format!(
135 "SelectionRadius {{ inner: PixelValue::from_metric(SizeMetric::{:?}, {}) }}",
136 self.inner.metric,
137 self.inner.number.get()
138 )
139 }
140}
141
142#[cfg(feature = "parser")]
144pub fn parse_selection_radius(input: &str) -> Result<SelectionRadius, CssPixelValueParseError<'_>> {
148 parse_pixel_value(input).map(|inner| SelectionRadius { inner })
149}
150
151#[cfg(all(test, feature = "parser"))]
152mod autotest_generated {
153 use std::{
154 collections::hash_map::DefaultHasher,
155 hash::{Hash, Hasher},
156 };
157
158 use super::*;
159 use crate::codegen::format::FormatAsRustCode;
160
161 fn hash_of<T: Hash>(t: &T) -> u64 {
162 let mut h = DefaultHasher::new();
163 t.hash(&mut h);
164 h.finish()
165 }
166
167 const ROUND_TRIPPABLE_METRICS: [SizeMetric; 11] = [
172 SizeMetric::Px,
173 SizeMetric::Pt,
174 SizeMetric::Em,
175 SizeMetric::Rem,
176 SizeMetric::In,
177 SizeMetric::Cm,
178 SizeMetric::Mm,
179 SizeMetric::Percent,
180 SizeMetric::Vw,
181 SizeMetric::Vh,
182 SizeMetric::Vmax,
183 ];
184
185 fn hostile_corpus() -> Vec<String> {
188 vec![
189 String::new(),
190 " ".to_string(),
191 "\t\n\r\u{b}\u{c}".to_string(),
192 "\u{a0}".to_string(), "\u{200b}".to_string(), "\0".to_string(),
195 "#\0\0\0".to_string(),
196 ";".to_string(),
197 "}{".to_string(),
198 ")rgb(".to_string(),
199 "#".to_string(),
200 "##".to_string(),
201 "#zzz".to_string(),
202 "#-fff000f".to_string(), "rgb(".to_string(),
204 "rgb()".to_string(),
205 "rgb(1,2)".to_string(),
206 "rgb(-1,-1,-1)".to_string(),
207 "rgb(999,999,999)".to_string(),
208 "rgba(0,0,0,NaN)".to_string(),
209 "rgba(255,255,255,inf)".to_string(),
210 "hsl(400,-10%,999%)".to_string(),
211 "-".to_string(),
212 "--".to_string(),
213 "+".to_string(),
214 "e".to_string(),
215 "E9".to_string(),
216 "NaN".to_string(),
217 "inf".to_string(),
218 "-inf".to_string(),
219 "infinity".to_string(),
220 "0x10px".to_string(),
221 "px".to_string(),
222 "vmin".to_string(),
223 "5 5px".to_string(),
224 "5px;".to_string(),
225 "%".to_string(),
226 "-0".to_string(),
227 i64::MAX.to_string(),
228 i64::MIN.to_string(),
229 format!("{}px", i64::MAX),
230 "1e400px".to_string(),
231 "\u{1F600}".to_string(),
232 "#\u{1F600}".to_string(), "#\u{e9}1".to_string(), "a\u{0301}\u{0301}\u{0301}".to_string(), "\u{202e}der".to_string(), "\u{130}".to_string(), "RED".to_string(),
238 "red red".to_string(),
239 ]
240 }
241
242 #[test]
243 fn hostile_inputs_never_panic_in_any_selection_parser() {
244 for input in hostile_corpus() {
245 let _ = parse_selection_background_color(&input);
246 let _ = parse_selection_color(&input);
247
248 if let Ok(r) = parse_selection_radius(&input) {
253 assert!(
254 r.inner.number.get().is_finite(),
255 "{input:?} produced a non-finite radius"
256 );
257 }
258 }
259 }
260
261 #[test]
264 fn empty_and_whitespace_only_input_is_rejected() {
265 for blank in ["", " ", " ", "\t\n", "\r\n\t ", "\u{a0}", "\u{2003}"] {
267 assert!(
268 matches!(
269 parse_selection_background_color(blank),
270 Err(CssColorParseError::EmptyInput)
271 ),
272 "{blank:?} should be EmptyInput"
273 );
274 assert!(
275 matches!(
276 parse_selection_color(blank),
277 Err(CssColorParseError::EmptyInput)
278 ),
279 "{blank:?} should be EmptyInput"
280 );
281 assert!(
282 matches!(
283 parse_selection_radius(blank),
284 Err(CssPixelValueParseError::EmptyString)
285 ),
286 "{blank:?} should be EmptyString"
287 );
288 }
289
290 assert!(parse_selection_color("\u{200b}").is_err());
293 assert!(parse_selection_radius("\u{200b}").is_err());
294 }
295
296 #[test]
299 fn malformed_color_input_is_rejected_without_panicking() {
300 for bad in [
301 "#",
302 "##",
303 "#f",
304 "#ff",
305 "#fffff",
306 "#zzz",
307 "#gggggg",
308 "rgb(",
309 "rgb()",
310 "rgb(1,2)",
311 "rgb(1,2,3",
312 "rgba(1,2,3)",
313 "hsl(1,2)",
314 "not-a-color",
315 "}{",
316 ";",
317 ")rgb(",
318 "rgb (0,0,0)", ] {
320 assert!(
321 parse_selection_background_color(bad).is_err(),
322 "{bad:?} was accepted as a background color"
323 );
324 assert!(
325 parse_selection_color(bad).is_err(),
326 "{bad:?} was accepted as a color"
327 );
328 }
329 }
330
331 #[test]
332 fn malformed_radius_input_is_rejected_without_panicking() {
333 for bad in [
334 "px", "em", "%", "5 5px", "5px;", "px5", "5pxpx", "--5px", "5,px", "#5px", "5 px x",
335 "0x10px", "e", "+",
336 ] {
337 assert!(
338 parse_selection_radius(bad).is_err(),
339 "{bad:?} was accepted as a radius"
340 );
341 }
342
343 assert!(matches!(
345 parse_selection_radius("px"),
346 Err(CssPixelValueParseError::NoValueGiven("px", SizeMetric::Px))
347 ));
348 }
349
350 #[test]
353 fn surrounding_whitespace_is_trimmed_but_trailing_junk_is_rejected() {
354 assert_eq!(
356 parse_selection_background_color(" #ff0000ff ")
357 .unwrap()
358 .inner,
359 ColorU::new(255, 0, 0, 255)
360 );
361 assert_eq!(
362 parse_selection_color("\t red \n").unwrap().inner,
363 ColorU::new(255, 0, 0, 255)
364 );
365 assert_eq!(
366 parse_selection_radius(" 5px \n").unwrap().inner,
367 PixelValue::px(5.0)
368 );
369 assert_eq!(
371 parse_selection_radius("5 px").unwrap().inner,
372 PixelValue::px(5.0)
373 );
374
375 for junk in ["#ff0000;", "red;", "red garbage", "#ff0000ff extra"] {
377 assert!(
378 parse_selection_color(junk).is_err(),
379 "{junk:?} was silently truncated to a valid color"
380 );
381 }
382 for junk in ["5px;", "5px garbage", "5px 6px"] {
383 assert!(
384 parse_selection_radius(junk).is_err(),
385 "{junk:?} was silently truncated to a valid radius"
386 );
387 }
388 }
389
390 #[test]
393 fn extremely_long_input_neither_panics_nor_hangs() {
394 let long_hex = format!("#{}", "f".repeat(1_000_000));
395 assert!(parse_selection_background_color(&long_hex).is_err());
396 assert!(parse_selection_color(&long_hex).is_err());
397
398 let long_word = "z".repeat(1_000_000);
399 assert!(parse_selection_color(&long_word).is_err());
400 assert!(parse_selection_radius(&long_word).is_err());
401
402 let long_number = format!("{}px", "9".repeat(1_000_000));
405 let r = parse_selection_radius(&long_number).unwrap();
406 assert!(r.inner.number.get().is_finite());
407 assert_eq!(r.inner, PixelValue::px(f32::INFINITY));
408 }
409
410 #[test]
411 fn deeply_nested_input_does_not_stack_overflow() {
412 let open = "(".repeat(10_000);
413 assert!(parse_selection_color(&open).is_err());
414 assert!(parse_selection_radius(&open).is_err());
415
416 let unclosed = "rgb(".repeat(10_000);
417 assert!(parse_selection_background_color(&unclosed).is_err());
418
419 let balanced = format!("{}{}", "rgb(".repeat(10_000), ")".repeat(10_000));
420 assert!(parse_selection_background_color(&balanced).is_err());
421 }
422
423 #[test]
426 fn unicode_input_is_rejected_without_slicing_through_a_char() {
427 for u in [
431 "#\u{e9}1", "#\u{1F600}", "#\u{e9}\u{e9}\u{e9}", "\u{1F600}",
435 "a\u{0301}\u{0301}",
436 "\u{202e}red",
437 "\u{130}", "r\u{e9}d",
439 ] {
440 assert!(
441 parse_selection_background_color(u).is_err(),
442 "{u:?} was accepted as a color"
443 );
444 assert!(parse_selection_color(u).is_err(), "{u:?} was accepted");
445 assert!(parse_selection_radius(u).is_err(), "{u:?} was accepted");
446 }
447 }
448
449 #[test]
452 fn radius_nan_and_infinity_saturate_instead_of_leaking_into_layout() {
453 let nan = parse_selection_radius("NaN").unwrap();
458 assert_eq!(nan.inner.number.get(), 0.0, "NaN must collapse to 0");
459 assert!(nan.inner.number.get().is_finite());
460
461 let pos_inf = parse_selection_radius("inf").unwrap();
462 assert!(pos_inf.inner.number.get().is_finite());
463 assert!(pos_inf.inner.number.get() > 0.0);
464
465 let neg_inf = parse_selection_radius("-inf").unwrap();
466 assert!(neg_inf.inner.number.get().is_finite());
467 assert!(neg_inf.inner.number.get() < 0.0);
468
469 assert_eq!(pos_inf.inner, PixelValue::px(f32::INFINITY));
471 assert_eq!(
472 parse_selection_radius("1e40px").unwrap().inner,
473 PixelValue::px(f32::INFINITY)
474 );
475 assert_eq!(
476 parse_selection_radius(&format!("{}px", i64::MAX))
477 .unwrap()
478 .inner,
479 PixelValue::px(f32::INFINITY)
480 );
481 assert_eq!(
482 parse_selection_radius(&format!("{}px", i64::MIN))
483 .unwrap()
484 .inner,
485 PixelValue::px(f32::NEG_INFINITY)
486 );
487 }
488
489 #[test]
490 fn radius_zero_boundaries_are_normalized() {
491 assert_eq!(
492 parse_selection_radius("0px").unwrap().inner,
493 PixelValue::px(0.0)
494 );
495 assert_eq!(
497 parse_selection_radius("-0px").unwrap().inner,
498 parse_selection_radius("0px").unwrap().inner
499 );
500 assert_eq!(
501 parse_selection_radius("-0.0px").unwrap().inner,
502 PixelValue::zero()
503 );
504
505 assert_eq!(
507 parse_selection_radius("1e-45px")
508 .unwrap()
509 .inner
510 .number
511 .get(),
512 0.0
513 );
514 assert_eq!(
515 parse_selection_radius("0.0001px")
516 .unwrap()
517 .inner
518 .number
519 .get(),
520 0.0
521 );
522 assert_eq!(
524 parse_selection_radius("0.001px")
525 .unwrap()
526 .inner
527 .number
528 .get(),
529 0.001
530 );
531 }
532
533 #[test]
534 fn radius_accepts_unitless_numbers_which_css_would_reject() {
535 assert_eq!(
539 parse_selection_radius("12").unwrap().inner,
540 PixelValue::px(12.0)
541 );
542 assert_eq!(
543 parse_selection_radius("-7.5").unwrap().inner,
544 PixelValue::px(-7.5)
545 );
546 assert_eq!(
547 parse_selection_radius("1e3").unwrap().inner,
548 PixelValue::px(1000.0)
549 );
550 }
551
552 #[test]
553 fn color_component_boundaries_are_enforced() {
554 assert_eq!(
555 parse_selection_color("rgb(0,0,0)").unwrap().inner,
556 ColorU::new(0, 0, 0, 255)
557 );
558 assert_eq!(
559 parse_selection_color("rgb(255,255,255)").unwrap().inner,
560 ColorU::new(255, 255, 255, 255)
561 );
562 for bad in [
564 "rgb(256,0,0)",
565 "rgb(-1,0,0)",
566 "rgb(999999999999999999999,0,0)",
567 "rgba(0,0,0,2.5)",
568 "rgba(0,0,0,-1)",
569 ] {
570 assert!(
571 parse_selection_background_color(bad).is_err(),
572 "{bad:?} was accepted — a component wrapped instead of erroring"
573 );
574 }
575 }
576
577 #[test]
580 fn background_color_round_trips_through_print_as_css_value() {
581 for c in [
582 ColorU::new(0, 0, 0, 0),
583 ColorU::new(255, 255, 255, 255),
584 ColorU::new(0, 0, 0, 255),
585 ColorU::new(173, 214, 255, 255),
586 ColorU::new(1, 2, 3, 4),
587 ColorU::new(254, 253, 252, 251),
588 ColorU::new(255, 0, 128, 1),
589 SelectionBackgroundColor::default().inner,
590 ] {
591 let v = SelectionBackgroundColor { inner: c };
592 let printed = v.print_as_css_value();
593 let reparsed = parse_selection_background_color(&printed)
594 .unwrap_or_else(|e| panic!("{printed:?} did not re-parse: {e:?}"));
595 assert_eq!(reparsed, v, "round-trip changed the value via {printed:?}");
596 assert_eq!(reparsed.print_as_css_value(), printed);
598 }
599 }
600
601 #[test]
602 fn selection_color_round_trips_through_print_as_css_value() {
603 for c in [
604 ColorU::new(0, 0, 0, 0),
605 ColorU::new(255, 255, 255, 255),
606 ColorU::new(18, 52, 86, 120),
607 ColorU::new(255, 0, 0, 255),
608 SelectionColor::default().inner,
609 ] {
610 let v = SelectionColor { inner: c };
611 let printed = v.print_as_css_value();
612 let reparsed = parse_selection_color(&printed)
613 .unwrap_or_else(|e| panic!("{printed:?} did not re-parse: {e:?}"));
614 assert_eq!(reparsed, v);
615 assert_eq!(reparsed.print_as_css_value(), printed);
616 }
617 }
618
619 #[test]
620 fn every_byte_of_the_color_channels_survives_the_round_trip() {
621 for b in 0u8..=255 {
624 let c = ColorU::new(b, 255 - b, b.wrapping_mul(3), 255 - b / 2);
625 let v = SelectionColor { inner: c };
626 assert_eq!(
627 parse_selection_color(&v.print_as_css_value()).unwrap(),
628 v,
629 "channel round-trip failed for {c:?}"
630 );
631 }
632 }
633
634 #[test]
635 fn radius_round_trips_for_every_metric_except_vmin() {
636 for metric in ROUND_TRIPPABLE_METRICS {
637 for value in [0.0_f32, 1.0, 1.5, 12.0, -3.25, 0.001, -0.5, 999.999] {
638 let v = SelectionRadius {
639 inner: PixelValue::from_metric(metric, value),
640 };
641 let printed = v.print_as_css_value();
642 let reparsed = parse_selection_radius(&printed)
643 .unwrap_or_else(|e| panic!("{printed:?} did not re-parse: {e:?}"));
644 assert_eq!(
645 reparsed, v,
646 "round-trip changed {value} {metric:?} via {printed:?}"
647 );
648 assert_eq!(reparsed.inner.metric, metric);
649 assert_eq!(reparsed.print_as_css_value(), printed);
650 }
651 }
652 }
653
654 #[test]
664 fn known_bug_vmin_radius_is_rejected_by_metric_table_order() {
665 assert_eq!(
668 parse_selection_radius("5vmin").unwrap().inner.metric,
669 SizeMetric::Vmin
670 );
671
672 let v = SelectionRadius {
674 inner: PixelValue::from_metric(SizeMetric::Vmin, 5.0),
675 };
676 assert_eq!(v.print_as_css_value(), "5vmin");
677 assert_eq!(
678 parse_selection_radius(&v.print_as_css_value())
679 .unwrap()
680 .inner
681 .metric,
682 SizeMetric::Vmin
683 );
684
685 assert_eq!(
688 parse_selection_radius("5vmax").unwrap().inner.metric,
689 SizeMetric::Vmax
690 );
691 assert_eq!(
692 parse_selection_radius("5vw").unwrap().inner.metric,
693 SizeMetric::Vw
694 );
695 assert_eq!(
696 parse_selection_radius("5vh").unwrap().inner.metric,
697 SizeMetric::Vh
698 );
699 assert_eq!(
700 parse_selection_radius("5in").unwrap().inner.metric,
701 SizeMetric::In
702 );
703 }
704
705 #[test]
713 fn hex_color_rejects_a_leading_plus_sign() {
714 assert!(parse_selection_color("#+fff000f").is_err());
719 assert!(parse_selection_background_color("#+fff00").is_err());
720
721 assert!(parse_selection_color("#-fff000f").is_err());
724 assert!(parse_selection_color("#+ff").is_err());
725 assert!(parse_selection_color("#+fff").is_err());
726 }
727
728 #[test]
731 fn defaults_are_the_documented_values_and_re_parse_to_themselves() {
732 assert_eq!(
733 SelectionBackgroundColor::default().inner,
734 ColorU::new(173, 214, 255, 255)
735 );
736 assert_eq!(
737 SelectionBackgroundColor::default().inner,
738 DEFAULT_SELECTION_BG
739 );
740 assert_eq!(SelectionColor::default().inner, ColorU::BLACK);
741 assert_eq!(SelectionRadius::default().inner, PixelValue::zero());
742
743 assert_eq!(
744 SelectionBackgroundColor::default().print_as_css_value(),
745 "#add6ffff"
746 );
747 assert_eq!(SelectionColor::default().print_as_css_value(), "#000000ff");
748 assert_eq!(SelectionRadius::default().print_as_css_value(), "0px");
749
750 assert_eq!(
751 parse_selection_background_color("#add6ffff").unwrap(),
752 SelectionBackgroundColor::default()
753 );
754 assert_eq!(
755 parse_selection_color("#000000ff").unwrap(),
756 SelectionColor::default()
757 );
758 assert_eq!(
759 parse_selection_radius("0px").unwrap(),
760 SelectionRadius::default()
761 );
762 }
763
764 #[test]
765 fn equal_values_hash_and_compare_equal() {
766 let a = SelectionColor {
767 inner: ColorU::new(1, 2, 3, 4),
768 };
769 let b = SelectionColor {
770 inner: ColorU::new(1, 2, 3, 4),
771 };
772 assert_eq!(a, b);
773 assert_eq!(hash_of(&a), hash_of(&b));
774 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
775
776 let black = SelectionColor {
777 inner: ColorU::new(0, 0, 0, 255),
778 };
779 let white = SelectionColor {
780 inner: ColorU::new(255, 255, 255, 255),
781 };
782 assert!(black < white);
783 assert!(white > black);
784
785 assert_eq!(
787 parse_selection_radius("1.5em").unwrap(),
788 parse_selection_radius("1.5em").unwrap()
789 );
790 assert_eq!(
791 hash_of(&parse_selection_radius("1.5em").unwrap()),
792 hash_of(&parse_selection_radius("1.5em").unwrap())
793 );
794 }
795
796 #[test]
797 fn format_as_rust_code_emits_a_constructor_for_each_type() {
798 let radius = SelectionRadius {
799 inner: PixelValue::px(5.0),
800 };
801 assert_eq!(
802 radius.format_as_rust_code(0),
803 "SelectionRadius { inner: PixelValue::from_metric(SizeMetric::Px, 5) }"
804 );
805 assert_eq!(
806 SelectionRadius {
807 inner: PixelValue::from_metric(SizeMetric::Percent, 50.0),
808 }
809 .format_as_rust_code(0),
810 "SelectionRadius { inner: PixelValue::from_metric(SizeMetric::Percent, 50) }"
811 );
812 assert_eq!(
813 SelectionRadius::default().format_as_rust_code(0),
814 "SelectionRadius { inner: PixelValue::from_metric(SizeMetric::Px, 0) }"
815 );
816
817 let bg = SelectionBackgroundColor::default().format_as_rust_code(0);
820 assert!(bg.starts_with("SelectionBackgroundColor { inner: "), "{bg}");
821 assert!(bg.ends_with(" }"), "{bg}");
822 let fg = SelectionColor::default().format_as_rust_code(0);
823 assert!(fg.starts_with("SelectionColor { inner: "), "{fg}");
824 assert!(fg.ends_with(" }"), "{fg}");
825
826 assert_eq!(radius.format_as_rust_code(0), radius.format_as_rust_code(9));
828 }
829
830 #[test]
831 fn parse_errors_survive_the_owned_round_trip() {
832 let color_err = parse_selection_color("not-a-color").unwrap_err();
835 let owned: CssColorParseErrorOwned = color_err.to_contained();
836 assert_eq!(format!("{:?}", owned.to_shared()), format!("{color_err:?}"));
837
838 let px_err = parse_selection_radius("5 5px").unwrap_err();
839 let owned_px: CssPixelValueParseErrorOwned = px_err.to_contained();
840 assert_eq!(format!("{:?}", owned_px.to_shared()), format!("{px_err:?}"));
841
842 assert!(!format!("{color_err:?}").is_empty());
844 assert!(!format!("{px_err:?}").is_empty());
845 }
846
847 #[test]
848 fn the_two_color_properties_agree_on_every_input() {
849 for input in hostile_corpus() {
852 let bg = parse_selection_background_color(&input);
853 let fg = parse_selection_color(&input);
854 match (bg, fg) {
855 (Ok(b), Ok(f)) => assert_eq!(b.inner, f.inner, "diverged on {input:?}"),
856 (Err(_), Err(_)) => {}
857 (b, f) => panic!("{input:?} parsed inconsistently: {b:?} vs {f:?}"),
858 }
859 }
860 }
861}