standout_render/theme/
icon_def.rs1use std::collections::HashMap;
28
29use super::icon_mode::IconMode;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct IconDefinition {
56 pub classic: String,
58 pub nerdfont: Option<String>,
60}
61
62impl IconDefinition {
63 pub fn new(classic: impl Into<String>) -> Self {
65 Self {
66 classic: classic.into(),
67 nerdfont: None,
68 }
69 }
70
71 pub fn with_nerdfont(mut self, nerdfont: impl Into<String>) -> Self {
73 self.nerdfont = Some(nerdfont.into());
74 self
75 }
76
77 pub fn resolve(&self, mode: IconMode) -> &str {
84 match mode {
85 IconMode::NerdFont => self.nerdfont.as_deref().unwrap_or(&self.classic),
86 IconMode::Classic | IconMode::Auto => &self.classic,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Default)]
110pub struct IconSet {
111 icons: HashMap<String, IconDefinition>,
112}
113
114impl IconSet {
115 pub fn new() -> Self {
117 Self::default()
118 }
119
120 pub fn add(mut self, name: impl Into<String>, def: IconDefinition) -> Self {
122 self.icons.insert(name.into(), def);
123 self
124 }
125
126 pub fn insert(&mut self, name: impl Into<String>, def: IconDefinition) {
128 self.icons.insert(name.into(), def);
129 }
130
131 pub fn resolve(&self, mode: IconMode) -> HashMap<String, String> {
133 self.icons
134 .iter()
135 .map(|(name, def)| (name.clone(), def.resolve(mode).to_string()))
136 .collect()
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.icons.is_empty()
142 }
143
144 pub fn len(&self) -> usize {
146 self.icons.len()
147 }
148
149 pub fn merge(mut self, other: IconSet) -> Self {
153 self.icons.extend(other.icons);
154 self
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161
162 #[test]
167 fn test_icon_definition_classic_only() {
168 let icon = IconDefinition::new("⚪");
169 assert_eq!(icon.classic, "⚪");
170 assert_eq!(icon.nerdfont, None);
171 }
172
173 #[test]
174 fn test_icon_definition_with_nerdfont() {
175 let icon = IconDefinition::new("⚫").with_nerdfont("\u{f00c}");
176 assert_eq!(icon.classic, "⚫");
177 assert_eq!(icon.nerdfont, Some("\u{f00c}".to_string()));
178 }
179
180 #[test]
181 fn test_icon_definition_resolve_classic_mode() {
182 let icon = IconDefinition::new("⚫").with_nerdfont("\u{f00c}");
183 assert_eq!(icon.resolve(IconMode::Classic), "⚫");
184 }
185
186 #[test]
187 fn test_icon_definition_resolve_nerdfont_mode() {
188 let icon = IconDefinition::new("⚫").with_nerdfont("\u{f00c}");
189 assert_eq!(icon.resolve(IconMode::NerdFont), "\u{f00c}");
190 }
191
192 #[test]
193 fn test_icon_definition_resolve_nerdfont_fallback() {
194 let icon = IconDefinition::new("⚪");
196 assert_eq!(icon.resolve(IconMode::NerdFont), "⚪");
197 }
198
199 #[test]
200 fn test_icon_definition_resolve_auto_mode() {
201 let icon = IconDefinition::new("⚫").with_nerdfont("\u{f00c}");
202 assert_eq!(icon.resolve(IconMode::Auto), "⚫");
204 }
205
206 #[test]
207 fn test_icon_definition_multi_char() {
208 let icon = IconDefinition::new("[ok]").with_nerdfont("\u{f00c}");
209 assert_eq!(icon.resolve(IconMode::Classic), "[ok]");
210 assert_eq!(icon.resolve(IconMode::NerdFont), "\u{f00c}");
211 }
212
213 #[test]
214 fn test_icon_definition_empty_string() {
215 let icon = IconDefinition::new("");
216 assert_eq!(icon.resolve(IconMode::Classic), "");
217 }
218
219 #[test]
220 fn test_icon_definition_equality() {
221 let a = IconDefinition::new("⚪").with_nerdfont("nf");
222 let b = IconDefinition::new("⚪").with_nerdfont("nf");
223 assert_eq!(a, b);
224 }
225
226 #[test]
231 fn test_icon_set_new_is_empty() {
232 let set = IconSet::new();
233 assert!(set.is_empty());
234 assert_eq!(set.len(), 0);
235 }
236
237 #[test]
238 fn test_icon_set_add() {
239 let set = IconSet::new()
240 .add("pending", IconDefinition::new("⚪"))
241 .add("done", IconDefinition::new("⚫"));
242 assert_eq!(set.len(), 2);
243 assert!(!set.is_empty());
244 }
245
246 #[test]
247 fn test_icon_set_insert() {
248 let mut set = IconSet::new();
249 set.insert("pending", IconDefinition::new("⚪"));
250 assert_eq!(set.len(), 1);
251 }
252
253 #[test]
254 fn test_icon_set_resolve_classic() {
255 let set = IconSet::new()
256 .add("pending", IconDefinition::new("⚪"))
257 .add("done", IconDefinition::new("⚫").with_nerdfont("\u{f00c}"));
258
259 let resolved = set.resolve(IconMode::Classic);
260 assert_eq!(resolved.get("pending").unwrap(), "⚪");
261 assert_eq!(resolved.get("done").unwrap(), "⚫");
262 }
263
264 #[test]
265 fn test_icon_set_resolve_nerdfont() {
266 let set = IconSet::new()
267 .add("pending", IconDefinition::new("⚪"))
268 .add("done", IconDefinition::new("⚫").with_nerdfont("\u{f00c}"));
269
270 let resolved = set.resolve(IconMode::NerdFont);
271 assert_eq!(resolved.get("pending").unwrap(), "⚪"); assert_eq!(resolved.get("done").unwrap(), "\u{f00c}");
273 }
274
275 #[test]
276 fn test_icon_set_merge() {
277 let base = IconSet::new()
278 .add("keep", IconDefinition::new("K"))
279 .add("override", IconDefinition::new("OLD"));
280
281 let extension = IconSet::new()
282 .add("override", IconDefinition::new("NEW"))
283 .add("added", IconDefinition::new("A"));
284
285 let merged = base.merge(extension);
286
287 assert_eq!(merged.len(), 3);
288 let resolved = merged.resolve(IconMode::Classic);
289 assert_eq!(resolved.get("keep").unwrap(), "K");
290 assert_eq!(resolved.get("override").unwrap(), "NEW");
291 assert_eq!(resolved.get("added").unwrap(), "A");
292 }
293
294 #[test]
295 fn test_icon_set_resolve_empty() {
296 let set = IconSet::new();
297 let resolved = set.resolve(IconMode::Classic);
298 assert!(resolved.is_empty());
299 }
300}