1pub mod builders;
19mod resolve;
20mod theme_trait;
21mod themes;
22
23pub use builders::ThemeExt;
26pub use builders::{
27 GaugeStyles, InputStyles, ListStyles, NotificationStyles, ScrollbarStyles, StateStyles,
28 TabStyles, TableStyles, ThemedBar, ThemedBlock, ThemedLine, ThemedSpan, ThemedStatusLine,
29 zebra_rows,
30};
31pub use theme_trait::Theme;
32pub use themes::CustomTheme;
33pub use themes::ThemeData;
34
35pub use resolve::{
38 available_theme_ids, builtin_themes, default_theme, no_color_active, resolve_theme,
39};
40
41pub use themes::{
44 BUILTIN_THEMES, CATPPUCCIN_MOCHA, DRACULA, GRUVBOX_DARK, NO_COLOR, NORD, ONE_DARK, ROSE_PINE,
45 SOLARIZED_DARK, TAILWIND_DARK, TERMINAL_NATIVE, TOKYO_NIGHT,
46};
47
48#[allow(non_upper_case_globals, missing_docs, clippy::wildcard_imports)]
51mod aliases {
52 use super::themes::*;
53 pub const CatppuccinMocha: ThemeData = CATPPUCCIN_MOCHA;
54 pub const Dracula: ThemeData = DRACULA;
55 pub const Nord: ThemeData = NORD;
56 pub const GruvboxDark: ThemeData = GRUVBOX_DARK;
57 pub const OneDark: ThemeData = ONE_DARK;
58 pub const SolarizedDark: ThemeData = SOLARIZED_DARK;
59 pub const TailwindDark: ThemeData = TAILWIND_DARK;
60 pub const TokyoNight: ThemeData = TOKYO_NIGHT;
61 pub const RosePine: ThemeData = ROSE_PINE;
62 pub const TerminalNative: ThemeData = TERMINAL_NATIVE;
63 pub const NoColor: ThemeData = NO_COLOR;
64}
65pub use aliases::*;
66
67#[cfg(test)]
70mod tests {
71 use ratatui::style::Color;
72
73 use super::*;
74
75 #[test]
76 fn all_builtin_themes_have_unique_ids() {
77 let ids: Vec<&str> = BUILTIN_THEMES.iter().map(|t| t.id).collect();
78 let mut dedup = ids.clone();
79 dedup.sort_unstable();
80 dedup.dedup();
81 assert_eq!(ids.len(), dedup.len());
82 }
83
84 #[test]
85 fn every_builtin_has_non_empty_name_and_id() {
86 for theme in BUILTIN_THEMES {
87 assert!(!theme.name.is_empty());
88 assert!(!theme.id.is_empty());
89 assert!(!theme.id.contains(' '));
90 }
91 }
92
93 #[test]
94 fn every_builtin_has_distinct_status_colors() {
95 for theme in BUILTIN_THEMES {
96 assert_ne!(theme.success, theme.error, "{}: success == error", theme.id);
97 }
98 }
99
100 #[test]
101 fn every_builtin_has_distinct_diff_colors() {
102 for theme in BUILTIN_THEMES {
103 assert_ne!(
104 theme.diff_added, theme.diff_removed,
105 "{}: added == removed",
106 theme.id
107 );
108 }
109 }
110
111 #[test]
112 fn every_builtin_surface_differs_from_text() {
113 for theme in BUILTIN_THEMES {
114 assert_ne!(theme.surface, theme.text, "{}: surface == text", theme.id);
115 }
116 }
117
118 #[test]
119 fn derived_methods_use_core_slots() {
120 let t = CatppuccinMocha;
121 assert_eq!(t.block_pass(), t.success());
122 assert_eq!(t.block_fail(), t.error());
123 assert_eq!(t.indicator_passed(), t.success());
124 }
125
126 #[test]
127 fn no_color_is_all_reset() {
128 assert_eq!(NoColor.accent, Color::Reset);
129 assert_eq!(NoColor.text, Color::Reset);
130 assert_eq!(NoColor.error, Color::Reset);
131 }
132
133 #[test]
134 fn tokyo_night_has_blue_accent() {
135 assert_eq!(TokyoNight.accent, Color::Rgb(122, 162, 247));
136 }
137
138 #[test]
139 fn rose_pine_has_rose_accent() {
140 assert_eq!(RosePine.accent, Color::Rgb(235, 188, 186));
141 }
142
143 #[test]
144 fn theme_data_display() {
145 assert_eq!(CatppuccinMocha.to_string(), "Catppuccin Mocha (catppuccin)");
146 }
147
148 #[test]
149 fn theme_data_equality() {
150 assert_eq!(CatppuccinMocha, CATPPUCCIN_MOCHA);
151 assert_ne!(CatppuccinMocha, Dracula);
152 }
153
154 #[test]
155 fn theme_data_copy() {
156 let a = CatppuccinMocha;
157 let b = a; assert_eq!(a, b);
159 }
160
161 #[test]
162 fn custom_theme_with_trait() {
163 let t = CustomTheme {
164 name: "Test".to_owned(),
165 id: "test".to_owned(),
166 accent: Color::Magenta,
167 accent_dim: Color::DarkGray,
168 text: Color::White,
169 text_dim: Color::Gray,
170 text_bright: Color::White,
171 success: Color::Green,
172 error: Color::Red,
173 warning: Color::Yellow,
174 info: Color::Cyan,
175 diff_added: Color::Green,
176 diff_removed: Color::Red,
177 diff_context: Color::DarkGray,
178 border: Color::DarkGray,
179 surface: Color::Black,
180 background: Color::Reset,
181 };
182 let theme: &dyn Theme = &t;
183 assert_eq!(theme.accent(), Color::Magenta);
184 assert_eq!(theme.block_pass(), theme.success());
185 }
186
187 #[test]
188 fn background_has_real_color() {
189 assert_eq!(CatppuccinMocha.background(), Color::Rgb(30, 30, 46));
190 assert_eq!(Dracula.background(), Color::Rgb(40, 42, 54));
191 assert_eq!(TerminalNative.background(), Color::Reset);
193 assert_eq!(NoColor.background(), Color::Reset);
194 }
195
196 #[test]
197 fn style_base_combines_bg_and_fg() {
198 let t = CatppuccinMocha;
199 let base = t.style_base();
200 assert_eq!(base.bg, Some(Color::Rgb(30, 30, 46)));
201 assert_eq!(base.fg, Some(t.text()));
202 }
203
204 #[test]
205 fn stripe_blends_background_and_surface() {
206 let t = CatppuccinMocha;
208 let stripe = t.stripe();
209 assert_ne!(stripe, t.background(), "stripe must differ from background");
210 assert_ne!(stripe, t.surface(), "stripe must differ from surface");
211 assert_eq!(stripe, Color::Rgb(43, 44, 61));
212 }
213
214 #[test]
215 fn stripe_falls_back_to_surface_for_reset_bg() {
216 assert_eq!(NoColor.stripe(), NoColor.surface());
218 assert_eq!(TerminalNative.stripe(), TerminalNative.surface());
220 }
221}