1use ratatui::style::{Color, Modifier, Style};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ColorSupport {
6 TrueColor256,
8 Basic8,
10 None,
12}
13
14pub fn detect_color_support(no_color: bool) -> ColorSupport {
27 if no_color {
28 return ColorSupport::None;
29 }
30
31 if std::env::var("NO_COLOR").is_ok() {
33 return ColorSupport::None;
34 }
35
36 if let Ok(colorterm) = std::env::var("COLORTERM") {
38 let ct = colorterm.to_lowercase();
39 if ct.contains("truecolor") || ct.contains("24bit") {
40 return ColorSupport::TrueColor256;
41 }
42 }
43
44 if let Ok(term) = std::env::var("TERM") {
46 let t = term.to_lowercase();
47 if t.contains("256color") {
48 return ColorSupport::TrueColor256;
49 }
50 if t == "dumb" || t.is_empty() {
51 return ColorSupport::None;
52 }
53 }
54
55 ColorSupport::Basic8
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ThemeMode {
66 Dark,
67 Light,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
72pub enum ThemePreference {
73 Auto,
75 Dark,
77 Light,
79}
80
81pub fn parse_colorfgbg(value: &str) -> Option<ThemeMode> {
91 let bg_idx: u8 = value
92 .rsplit(';')
93 .find_map(|f| f.trim().parse::<u8>().ok())?;
94 Some(if bg_idx >= 7 {
95 ThemeMode::Light
96 } else {
97 ThemeMode::Dark
98 })
99}
100
101fn classify_theme(luma: Option<f32>, colorfgbg: Option<&str>) -> ThemeMode {
111 if let Some(l) = luma {
112 return if l > 0.6 {
113 ThemeMode::Light
114 } else {
115 ThemeMode::Dark
116 };
117 }
118 if let Some(value) = colorfgbg {
119 if let Some(mode) = parse_colorfgbg(value) {
120 return mode;
121 }
122 }
123 ThemeMode::Dark
124}
125
126pub fn detect_system_theme() -> ThemeMode {
132 let luma = terminal_light::luma().ok();
133 let colorfgbg = std::env::var("COLORFGBG").ok();
134 classify_theme(luma, colorfgbg.as_deref())
135}
136
137pub fn resolve_theme(preference: ThemePreference, is_headless: bool) -> ThemeMode {
142 match preference {
143 ThemePreference::Dark => ThemeMode::Dark,
144 ThemePreference::Light => ThemeMode::Light,
145 ThemePreference::Auto => {
146 if is_headless {
147 ThemeMode::Dark
148 } else {
149 detect_system_theme()
150 }
151 }
152 }
153}
154
155#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct Palette {
165 pub fg: Color,
167 pub fg_dim: Color,
169 pub bg: Option<Color>,
171 pub bg_alt: Option<Color>,
173 pub accent: Color,
175 pub accent_dim: Color,
177 pub success: Color,
179 pub warning: Color,
181 pub error: Color,
183 pub info: Color,
185 pub border: Color,
187 pub border_focused: Color,
189 pub selection_fg: Color,
191 pub selection_bg: Color,
193}
194
195pub fn dark_palette() -> Palette {
197 Palette {
198 fg: Color::White,
199 fg_dim: Color::DarkGray,
200 bg: None,
201 bg_alt: Some(Color::White),
202 accent: Color::Cyan,
203 accent_dim: Color::DarkGray,
204 success: Color::Green,
205 warning: Color::Yellow,
206 error: Color::Red,
207 info: Color::Magenta,
208 border: Color::Gray,
209 border_focused: Color::Cyan,
210 selection_fg: Color::Black,
211 selection_bg: Color::LightBlue,
212 }
213}
214
215pub fn light_palette() -> Palette {
217 Palette {
218 fg: Color::Black,
219 fg_dim: Color::DarkGray,
220 bg: None,
221 bg_alt: Some(Color::Black),
222 accent: Color::Blue,
223 accent_dim: Color::Gray,
224 success: Color::Green,
225 warning: Color::Indexed(208), error: Color::Red,
227 info: Color::Magenta,
228 border: Color::Gray,
229 border_focused: Color::Blue,
230 selection_fg: Color::White,
231 selection_bg: Color::Blue,
232 }
233}
234
235fn palette_for(mode: ThemeMode) -> Palette {
237 match mode {
238 ThemeMode::Dark => dark_palette(),
239 ThemeMode::Light => light_palette(),
240 }
241}
242
243fn to_basic8(c: Color) -> Color {
249 match c {
250 Color::LightRed => Color::Red,
251 Color::LightGreen => Color::Green,
252 Color::LightYellow => Color::Yellow,
253 Color::LightBlue => Color::Blue,
254 Color::LightMagenta => Color::Magenta,
255 Color::LightCyan => Color::Cyan,
256 Color::Indexed(208) => Color::Yellow, other => other,
258 }
259}
260
261fn downgrade_palette(p: Palette) -> Palette {
263 Palette {
264 fg: to_basic8(p.fg),
265 fg_dim: to_basic8(p.fg_dim),
266 bg: p.bg.map(to_basic8),
267 bg_alt: p.bg_alt.map(to_basic8),
268 accent: to_basic8(p.accent),
269 accent_dim: to_basic8(p.accent_dim),
270 success: to_basic8(p.success),
271 warning: to_basic8(p.warning),
272 error: to_basic8(p.error),
273 info: to_basic8(p.info),
274 border: to_basic8(p.border),
275 border_focused: to_basic8(p.border_focused),
276 selection_fg: to_basic8(p.selection_fg),
277 selection_bg: to_basic8(p.selection_bg),
278 }
279}
280
281fn build_palette(mode: ThemeMode, color_support: ColorSupport) -> Palette {
285 let palette = palette_for(mode);
286 if color_support == ColorSupport::Basic8 {
287 downgrade_palette(palette)
288 } else {
289 palette
290 }
291}
292
293pub struct Theme {
305 pub no_color: bool,
306 pub color_support: ColorSupport,
307 pub mode: ThemeMode,
308 palette: Palette,
309}
310
311impl Theme {
312 pub fn new(no_color: bool, mode: ThemeMode) -> Self {
315 let color_support = detect_color_support(no_color);
316 Self {
317 no_color,
318 color_support,
319 mode,
320 palette: build_palette(mode, color_support),
321 }
322 }
323
324 pub fn with_color_support(color_support: ColorSupport) -> Self {
327 Self::with_color_support_mode(color_support, ThemeMode::Dark)
328 }
329
330 pub fn with_color_support_mode(color_support: ColorSupport, mode: ThemeMode) -> Self {
332 Self {
333 no_color: color_support == ColorSupport::None,
334 color_support,
335 mode,
336 palette: build_palette(mode, color_support),
337 }
338 }
339
340 pub fn toggle_mode(&mut self) {
343 self.mode = match self.mode {
344 ThemeMode::Dark => ThemeMode::Light,
345 ThemeMode::Light => ThemeMode::Dark,
346 };
347 self.palette = build_palette(self.mode, self.color_support);
348 }
349
350 fn has_color(&self) -> bool {
352 self.color_support != ColorSupport::None
353 }
354
355 pub fn sidebar_style(&self) -> Style {
356 if !self.has_color() {
357 Style::default()
358 } else {
359 Style::default().fg(self.palette.fg)
360 }
361 }
362
363 pub fn sidebar_selected(&self) -> Style {
364 if !self.has_color() {
365 Style::default().add_modifier(Modifier::REVERSED)
366 } else {
367 Style::default()
368 .fg(self.palette.selection_fg)
369 .bg(self.palette.accent)
370 .add_modifier(Modifier::BOLD)
371 }
372 }
373
374 pub fn list_item(&self) -> Style {
375 if !self.has_color() {
376 Style::default()
377 } else {
378 Style::default().fg(self.palette.fg)
379 }
380 }
381
382 pub fn list_selected(&self) -> Style {
383 if !self.has_color() {
384 Style::default().add_modifier(Modifier::REVERSED)
385 } else {
386 Style::default()
387 .fg(self.palette.selection_fg)
388 .bg(self.palette.selection_bg)
389 .add_modifier(Modifier::BOLD)
390 }
391 }
392
393 pub fn detail_key(&self) -> Style {
394 if !self.has_color() {
395 Style::default().add_modifier(Modifier::BOLD)
396 } else {
397 Style::default()
398 .fg(self.palette.warning)
399 .add_modifier(Modifier::BOLD)
400 }
401 }
402
403 pub fn detail_value(&self) -> Style {
404 if !self.has_color() {
405 Style::default()
406 } else {
407 Style::default().fg(self.palette.fg)
408 }
409 }
410
411 pub fn status_bar(&self) -> Style {
412 if !self.has_color() {
413 Style::default().add_modifier(Modifier::REVERSED)
414 } else {
415 let mut style = Style::default().fg(self.palette.selection_fg);
419 if let Some(bg) = self.palette.bg_alt {
420 style = style.bg(bg);
421 }
422 style
423 }
424 }
425
426 pub fn help_bar(&self) -> Style {
427 if !self.has_color() {
428 Style::default().add_modifier(Modifier::DIM)
429 } else {
430 Style::default().fg(self.palette.accent_dim)
431 }
432 }
433
434 pub fn error_style(&self) -> Style {
435 if !self.has_color() {
436 Style::default().add_modifier(Modifier::BOLD)
437 } else {
438 Style::default()
439 .fg(self.palette.error)
440 .add_modifier(Modifier::BOLD)
441 }
442 }
443
444 pub fn search_style(&self) -> Style {
445 if !self.has_color() {
446 Style::default().add_modifier(Modifier::UNDERLINED)
447 } else {
448 Style::default()
449 .fg(self.palette.success)
450 .add_modifier(Modifier::BOLD)
451 }
452 }
453
454 pub fn border_style(&self) -> Style {
455 if !self.has_color() {
456 Style::default()
457 } else {
458 Style::default().fg(self.palette.border)
459 }
460 }
461
462 pub fn border_focused(&self) -> Style {
465 if !self.has_color() {
466 Style::default().add_modifier(Modifier::BOLD)
467 } else {
468 Style::default()
469 .fg(self.palette.border_focused)
470 .add_modifier(Modifier::BOLD)
471 }
472 }
473
474 pub fn border_unfocused(&self) -> Style {
477 if !self.has_color() {
478 Style::default().add_modifier(Modifier::DIM)
479 } else {
480 Style::default().fg(self.palette.accent_dim)
481 }
482 }
483
484 pub fn gate_not_run(&self) -> Style {
486 if !self.has_color() {
487 Style::default().add_modifier(Modifier::DIM)
488 } else {
489 Style::default().fg(self.palette.accent_dim)
490 }
491 }
492
493 pub fn gate_running(&self) -> Style {
495 if !self.has_color() {
496 Style::default().add_modifier(Modifier::BOLD)
497 } else {
498 Style::default()
499 .fg(self.palette.warning)
500 .add_modifier(Modifier::BOLD)
501 }
502 }
503
504 pub fn gate_passed(&self) -> Style {
506 if !self.has_color() {
507 Style::default()
508 } else {
509 Style::default().fg(self.palette.success)
510 }
511 }
512
513 pub fn gate_failed(&self) -> Style {
515 if !self.has_color() {
516 Style::default().add_modifier(Modifier::BOLD)
517 } else {
518 Style::default()
519 .fg(self.palette.error)
520 .add_modifier(Modifier::BOLD)
521 }
522 }
523
524 pub fn gate_timed_out(&self) -> Style {
526 if !self.has_color() {
527 Style::default().add_modifier(Modifier::BOLD)
528 } else {
529 Style::default()
530 .fg(self.palette.info)
531 .add_modifier(Modifier::BOLD)
532 }
533 }
534
535 pub fn filter_chip(&self) -> Style {
537 if !self.has_color() {
538 Style::default().add_modifier(Modifier::REVERSED)
539 } else {
540 Style::default()
541 .fg(self.palette.selection_fg)
542 .bg(self.palette.accent)
543 }
544 }
545
546 pub fn sparkline_filled(&self) -> Style {
548 if !self.has_color() {
549 Style::default()
550 } else {
551 Style::default().fg(self.palette.accent)
552 }
553 }
554
555 pub fn sparkline_empty(&self) -> Style {
557 if !self.has_color() {
558 Style::default().add_modifier(Modifier::DIM)
559 } else {
560 Style::default().fg(self.palette.accent_dim)
561 }
562 }
563
564 pub fn help_overlay_title(&self) -> Style {
566 if !self.has_color() {
567 Style::default().add_modifier(Modifier::BOLD)
568 } else {
569 Style::default()
570 .fg(self.palette.accent)
571 .add_modifier(Modifier::BOLD)
572 }
573 }
574
575 pub fn help_overlay_section(&self) -> Style {
577 if !self.has_color() {
578 Style::default().add_modifier(Modifier::BOLD)
579 } else {
580 Style::default()
581 .fg(self.palette.warning)
582 .add_modifier(Modifier::BOLD)
583 }
584 }
585
586 pub fn completion_highlight(&self) -> Style {
588 if !self.has_color() {
589 Style::default().add_modifier(Modifier::REVERSED)
590 } else {
591 Style::default()
592 .fg(self.palette.selection_fg)
593 .bg(self.palette.success)
594 }
595 }
596
597 pub fn completion_normal(&self) -> Style {
599 if !self.has_color() {
600 Style::default()
601 } else {
602 Style::default().fg(self.palette.fg)
603 }
604 }
605}
606
607#[cfg(test)]
608mod tests {
609 use super::*;
610 use proptest::prelude::*;
611
612 #[test]
613 fn no_color_mode_uses_no_fg_color() {
614 let theme = Theme::with_color_support(ColorSupport::None);
615 assert_eq!(theme.sidebar_style().fg, None);
616 assert_eq!(theme.list_item().fg, None);
617 assert_eq!(theme.detail_value().fg, None);
618 }
619
620 #[test]
621 fn color_mode_has_colors() {
622 let theme = Theme::with_color_support(ColorSupport::TrueColor256);
623 assert!(theme.sidebar_style().fg.is_some());
624 assert!(theme.list_selected().bg.is_some());
625 assert!(theme.detail_key().fg.is_some());
626 }
627
628 #[test]
629 fn eight_color_mode_has_colors() {
630 let theme = Theme::with_color_support(ColorSupport::Basic8);
631 assert!(theme.sidebar_style().fg.is_some());
632 assert!(theme.list_selected().bg.is_some());
633 }
634
635 #[test]
636 fn basic8_downgrades_bright_selection_bg() {
637 let basic8 = Theme::with_color_support_mode(ColorSupport::Basic8, ThemeMode::Dark);
642 assert_eq!(basic8.list_selected().bg, Some(Color::Blue));
643
644 let rich = Theme::with_color_support_mode(ColorSupport::TrueColor256, ThemeMode::Dark);
645 assert_eq!(rich.list_selected().bg, Some(Color::LightBlue));
646 }
647
648 #[test]
649 fn basic8_downgrades_indexed_warning() {
650 let basic8 = Theme::with_color_support_mode(ColorSupport::Basic8, ThemeMode::Light);
653 assert_eq!(basic8.detail_key().fg, Some(Color::Yellow));
654
655 let rich = Theme::with_color_support_mode(ColorSupport::TrueColor256, ThemeMode::Light);
656 assert_eq!(rich.detail_key().fg, Some(Color::Indexed(208)));
657 }
658
659 #[test]
660 fn dark_and_light_palettes_differ_in_color_mode() {
661 for support in [ColorSupport::TrueColor256, ColorSupport::Basic8] {
665 let dark = Theme::with_color_support_mode(support, ThemeMode::Dark);
666 let light = Theme::with_color_support_mode(support, ThemeMode::Light);
667 assert_ne!(
668 dark.sidebar_style().fg,
669 light.sidebar_style().fg,
670 "dark and light must differ for {support:?}"
671 );
672 assert_ne!(
673 dark.border_focused().fg,
674 light.border_focused().fg,
675 "dark and light accents must differ for {support:?}"
676 );
677 }
678 }
679
680 #[test]
681 fn focused_border_differs_from_unfocused() {
682 let theme = Theme::with_color_support(ColorSupport::TrueColor256);
683 let focused = theme.border_focused();
684 let unfocused = theme.border_unfocused();
685 assert_ne!(focused.fg, unfocused.fg);
686 }
687
688 #[test]
689 fn no_color_focused_uses_modifiers() {
690 let theme = Theme::with_color_support(ColorSupport::None);
691 let focused = theme.border_focused();
692 let unfocused = theme.border_unfocused();
693 assert!(focused.add_modifier.contains(Modifier::BOLD));
694 assert!(unfocused.add_modifier.contains(Modifier::DIM));
695 }
696
697 #[test]
698 fn color_support_detection_no_color_flag() {
699 assert_eq!(detect_color_support(true), ColorSupport::None);
700 }
701
702 #[test]
703 fn theme_deterministic_same_support_level() {
704 let theme1 = Theme::with_color_support(ColorSupport::TrueColor256);
706 let theme2 = Theme::with_color_support(ColorSupport::TrueColor256);
707 assert_eq!(theme1.sidebar_style(), theme2.sidebar_style());
708 assert_eq!(theme1.list_selected(), theme2.list_selected());
709 assert_eq!(theme1.error_style(), theme2.error_style());
710 assert_eq!(theme1.border_focused(), theme2.border_focused());
711 }
712
713 #[test]
714 fn with_color_support_sets_no_color_correctly() {
715 let theme_none = Theme::with_color_support(ColorSupport::None);
716 assert!(theme_none.no_color);
717
718 let theme_256 = Theme::with_color_support(ColorSupport::TrueColor256);
719 assert!(!theme_256.no_color);
720
721 let theme_8 = Theme::with_color_support(ColorSupport::Basic8);
722 assert!(!theme_8.no_color);
723 }
724
725 #[test]
726 fn gate_styles_have_color_in_color_mode() {
727 let theme = Theme::with_color_support(ColorSupport::TrueColor256);
728 assert!(theme.gate_passed().fg.is_some());
729 assert!(theme.gate_failed().fg.is_some());
730 assert!(theme.gate_running().fg.is_some());
731 assert!(theme.gate_timed_out().fg.is_some());
732 }
733
734 #[test]
735 fn no_color_help_bar_uses_dim() {
736 let theme = Theme::with_color_support(ColorSupport::None);
737 let style = theme.help_bar();
738 assert!(style.add_modifier.contains(Modifier::DIM));
739 }
740
741 #[test]
746 fn classify_falls_back_to_dark_when_detection_fails() {
747 assert_eq!(classify_theme(None, None), ThemeMode::Dark);
749 }
750
751 #[test]
752 fn classify_uses_luma_threshold() {
753 assert_eq!(classify_theme(Some(0.9), Some("15;0")), ThemeMode::Light);
756 assert_eq!(classify_theme(Some(0.61), None), ThemeMode::Light);
757 assert_eq!(classify_theme(Some(0.6), None), ThemeMode::Dark);
758 assert_eq!(classify_theme(Some(0.1), Some("0;15")), ThemeMode::Dark);
759 }
760
761 #[test]
762 fn colorfgbg_parsing() {
763 assert_eq!(parse_colorfgbg("0;15"), Some(ThemeMode::Light));
765 assert_eq!(parse_colorfgbg("15;0"), Some(ThemeMode::Dark));
766 assert_eq!(parse_colorfgbg("15;default;0"), Some(ThemeMode::Dark));
768 assert_eq!(parse_colorfgbg("0;default;15"), Some(ThemeMode::Light));
769 assert_eq!(parse_colorfgbg("0;7"), Some(ThemeMode::Light));
771 assert_eq!(parse_colorfgbg("0;6"), Some(ThemeMode::Dark));
772 assert_eq!(parse_colorfgbg("15;"), Some(ThemeMode::Light));
775 assert_eq!(parse_colorfgbg("0;15;"), Some(ThemeMode::Light));
776 assert_eq!(parse_colorfgbg(""), None);
778 assert_eq!(parse_colorfgbg("not;a;number"), None);
779 }
780
781 #[test]
782 fn colorfgbg_used_when_luma_missing() {
783 assert_eq!(classify_theme(None, Some("0;15")), ThemeMode::Light);
784 assert_eq!(classify_theme(None, Some("15;0")), ThemeMode::Dark);
785 assert_eq!(classify_theme(None, Some("garbage")), ThemeMode::Dark);
787 }
788
789 #[test]
790 fn resolve_theme_explicit_overrides_detection() {
791 assert_eq!(resolve_theme(ThemePreference::Dark, false), ThemeMode::Dark);
793 assert_eq!(
794 resolve_theme(ThemePreference::Light, false),
795 ThemeMode::Light
796 );
797 assert_eq!(resolve_theme(ThemePreference::Dark, true), ThemeMode::Dark);
798 assert_eq!(
799 resolve_theme(ThemePreference::Light, true),
800 ThemeMode::Light
801 );
802 }
803
804 #[test]
805 fn resolve_theme_auto_headless_defaults_dark() {
806 assert_eq!(resolve_theme(ThemePreference::Auto, true), ThemeMode::Dark);
808 }
809
810 #[test]
811 fn light_mode_uses_light_palette_colors() {
812 let theme = Theme::with_color_support_mode(ColorSupport::TrueColor256, ThemeMode::Light);
814 assert_eq!(theme.mode, ThemeMode::Light);
815 assert_eq!(theme.sidebar_style().fg, Some(Color::Black));
816 assert_eq!(theme.border_focused().fg, Some(Color::Blue));
817 assert_eq!(theme.list_selected().bg, Some(Color::Blue));
818 }
819
820 #[test]
821 fn dark_mode_uses_dark_palette_colors() {
822 let theme = Theme::with_color_support_mode(ColorSupport::TrueColor256, ThemeMode::Dark);
824 assert_eq!(theme.mode, ThemeMode::Dark);
825 assert_eq!(theme.sidebar_style().fg, Some(Color::White));
826 assert_eq!(theme.border_focused().fg, Some(Color::Cyan));
827 assert_eq!(theme.list_selected().bg, Some(Color::LightBlue));
828 }
829
830 #[test]
831 fn no_color_ignores_mode() {
832 for mode in [ThemeMode::Dark, ThemeMode::Light] {
834 let theme = Theme::with_color_support_mode(ColorSupport::None, mode);
835 assert_eq!(theme.sidebar_style().fg, None);
836 assert_eq!(theme.list_selected().fg, None);
837 assert_eq!(theme.list_selected().bg, None);
838 assert_eq!(theme.border_focused().fg, None);
839 assert_eq!(theme.gate_passed().fg, None);
840 }
841 }
842
843 #[test]
844 fn runtime_toggle_flips_mode_and_styles() {
845 let mut theme = Theme::with_color_support_mode(ColorSupport::TrueColor256, ThemeMode::Dark);
847 let dark_fg = theme.sidebar_style().fg;
848 let dark_border = theme.border_focused().fg;
849 theme.toggle_mode();
850 assert_eq!(theme.mode, ThemeMode::Light);
851 assert_ne!(theme.sidebar_style().fg, dark_fg);
852 assert_ne!(theme.border_focused().fg, dark_border);
853 theme.toggle_mode();
855 assert_eq!(theme.mode, ThemeMode::Dark);
856 assert_eq!(theme.sidebar_style().fg, dark_fg);
857 assert_eq!(theme.border_focused().fg, dark_border);
858 }
859
860 fn all_styles(theme: &Theme) -> Vec<Style> {
861 vec![
862 theme.sidebar_style(),
863 theme.sidebar_selected(),
864 theme.list_item(),
865 theme.list_selected(),
866 theme.detail_key(),
867 theme.detail_value(),
868 theme.status_bar(),
869 theme.help_bar(),
870 theme.error_style(),
871 theme.search_style(),
872 theme.border_style(),
873 theme.border_focused(),
874 theme.border_unfocused(),
875 theme.gate_not_run(),
876 theme.gate_running(),
877 theme.gate_passed(),
878 theme.gate_failed(),
879 theme.gate_timed_out(),
880 theme.filter_chip(),
881 theme.sparkline_filled(),
882 theme.sparkline_empty(),
883 theme.help_overlay_title(),
884 theme.help_overlay_section(),
885 theme.completion_highlight(),
886 theme.completion_normal(),
887 ]
888 }
889
890 fn mode_strategy() -> impl Strategy<Value = ThemeMode> {
891 prop_oneof![Just(ThemeMode::Dark), Just(ThemeMode::Light)]
892 }
893
894 fn support_strategy() -> impl Strategy<Value = ColorSupport> {
895 prop_oneof![
896 Just(ColorSupport::TrueColor256),
897 Just(ColorSupport::Basic8),
898 Just(ColorSupport::None),
899 ]
900 }
901
902 proptest! {
903 #[test]
907 fn prop34_theme_styles_deterministic(
908 mode in mode_strategy(),
909 support in support_strategy(),
910 ) {
911 let a = Theme::with_color_support_mode(support, mode);
912 let b = Theme::with_color_support_mode(support, mode);
913 prop_assert_eq!(all_styles(&a), all_styles(&b));
915 if support == ColorSupport::None {
917 for style in all_styles(&a) {
918 prop_assert_eq!(style.fg, None);
919 prop_assert_eq!(style.bg, None);
920 }
921 }
922 }
923 }
924}