1#[cfg(not(target_os = "linux"))]
4use crate::detect::system_is_dark;
5#[cfg(target_os = "linux")]
6use crate::detect::{LinuxDesktop, detect_linux_de, system_is_dark, xdg_current_desktop};
7
8use crate::SystemTheme;
9use crate::model::ThemeSpec;
10
11pub(crate) fn run_pipeline(
18 reader_output: ThemeSpec,
19 preset_name: &str,
20 is_dark: bool,
21) -> crate::Result<SystemTheme> {
22 let live_preset = ThemeSpec::preset(preset_name)?;
23
24 let full_preset_name = preset_name.strip_suffix("-live").unwrap_or(preset_name);
27 debug_assert!(
28 full_preset_name != preset_name || !preset_name.ends_with("-live"),
29 "live preset '{preset_name}' should have -live suffix stripped"
30 );
31 let full_preset = ThemeSpec::preset(full_preset_name)?;
32
33 let mut merged = full_preset.clone();
36 merged.merge(&live_preset);
37 merged.merge(&reader_output);
38
39 let name = if reader_output.name.is_empty() {
41 merged.name.clone()
42 } else {
43 reader_output.name.clone()
44 };
45
46 let mut light_variant = if reader_output.light.is_some() {
50 merged.light.unwrap_or_default()
51 } else {
52 full_preset.light.unwrap_or_default()
53 };
54
55 let mut dark_variant = if reader_output.dark.is_some() {
56 merged.dark.unwrap_or_default()
57 } else {
58 full_preset.dark.unwrap_or_default()
59 };
60
61 let reader_dpi = reader_output
66 .light
67 .as_ref()
68 .and_then(|v| v.defaults.font_dpi)
69 .or_else(|| {
70 reader_output
71 .dark
72 .as_ref()
73 .and_then(|v| v.defaults.font_dpi)
74 });
75 if let Some(dpi) = reader_dpi {
76 light_variant.defaults.font_dpi = light_variant.defaults.font_dpi.or(Some(dpi));
77 dark_variant.defaults.font_dpi = dark_variant.defaults.font_dpi.or(Some(dpi));
78 }
79
80 let light_variant_pre = light_variant.clone();
82 let dark_variant_pre = dark_variant.clone();
83
84 let light = light_variant.into_resolved()?;
85 let dark = dark_variant.into_resolved()?;
86
87 Ok(SystemTheme {
88 name,
89 is_dark,
90 light,
91 dark,
92 light_variant: light_variant_pre,
93 dark_variant: dark_variant_pre,
94 preset: full_preset_name.to_string(),
95 live_preset: preset_name.to_string(),
96 })
97}
98
99#[cfg(target_os = "linux")]
109fn linux_preset_for_de(de: LinuxDesktop) -> &'static str {
110 match de {
111 LinuxDesktop::Kde => "kde-breeze-live",
112 _ => "adwaita-live",
113 }
114}
115
116#[allow(unreachable_code)]
132#[must_use]
133pub fn platform_preset_name() -> &'static str {
134 #[cfg(target_os = "macos")]
135 {
136 return "macos-sonoma-live";
137 }
138 #[cfg(target_os = "windows")]
139 {
140 return "windows-11-live";
141 }
142 #[cfg(target_os = "linux")]
143 {
144 linux_preset_for_de(detect_linux_de(&xdg_current_desktop()))
145 }
146 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
147 {
148 "adwaita-live"
149 }
150}
151
152#[must_use]
176pub fn diagnose_platform_support() -> Vec<String> {
177 let mut diagnostics = Vec::new();
178
179 #[cfg(target_os = "linux")]
180 {
181 diagnostics.push("Platform: Linux".to_string());
182
183 match std::env::var("XDG_CURRENT_DESKTOP") {
185 Ok(val) if !val.is_empty() => {
186 let de = detect_linux_de(&val);
187 diagnostics.push(format!("XDG_CURRENT_DESKTOP: {val}"));
188 diagnostics.push(format!("Detected DE: {de:?}"));
189 }
190 _ => {
191 diagnostics.push("XDG_CURRENT_DESKTOP: not set".to_string());
192 diagnostics.push("Detected DE: Unknown (env var missing)".to_string());
193 }
194 }
195
196 match std::process::Command::new("gsettings")
198 .arg("--version")
199 .output()
200 {
201 Ok(output) if output.status.success() => {
202 let version = String::from_utf8_lossy(&output.stdout);
203 diagnostics.push(format!("gsettings: available ({})", version.trim()));
204 }
205 Ok(_) => {
206 diagnostics.push("gsettings: found but returned error".to_string());
207 }
208 Err(_) => {
209 diagnostics.push(
210 "gsettings: not found (dark mode and icon theme detection may be limited)"
211 .to_string(),
212 );
213 }
214 }
215
216 #[cfg(feature = "kde")]
218 {
219 let path = crate::kde::kdeglobals_path();
220 if path.exists() {
221 diagnostics.push(format!("KDE kdeglobals: found at {}", path.display()));
222 } else {
223 diagnostics.push(format!("KDE kdeglobals: not found at {}", path.display()));
224 }
225 }
226
227 #[cfg(not(feature = "kde"))]
228 {
229 diagnostics.push("KDE support: disabled (kde feature not enabled)".to_string());
230 }
231
232 #[cfg(feature = "portal")]
234 diagnostics.push("Portal support: enabled".to_string());
235
236 #[cfg(not(feature = "portal"))]
237 diagnostics.push("Portal support: disabled (portal feature not enabled)".to_string());
238 }
239
240 #[cfg(target_os = "macos")]
241 {
242 diagnostics.push("Platform: macOS".to_string());
243
244 #[cfg(feature = "macos")]
245 diagnostics.push("macOS theme detection: enabled (macos feature active)".to_string());
246
247 #[cfg(not(feature = "macos"))]
248 diagnostics.push(
249 "macOS theme detection: limited (macos feature not enabled, using subprocess fallback)"
250 .to_string(),
251 );
252 }
253
254 #[cfg(target_os = "windows")]
255 {
256 diagnostics.push("Platform: Windows".to_string());
257
258 #[cfg(feature = "windows")]
259 diagnostics.push("Windows theme detection: enabled (windows feature active)".to_string());
260
261 #[cfg(not(feature = "windows"))]
262 diagnostics
263 .push("Windows theme detection: disabled (windows feature not enabled)".to_string());
264 }
265
266 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
267 {
268 diagnostics.push("Platform: unsupported (no native theme detection available)".to_string());
269 }
270
271 diagnostics
272}
273
274#[allow(dead_code)]
282pub(crate) fn reader_is_dark(reader: &ThemeSpec) -> bool {
283 reader.dark.is_some() && reader.light.is_none()
284}
285
286#[cfg(target_os = "linux")]
292pub(crate) fn from_linux() -> crate::Result<SystemTheme> {
293 let is_dark = system_is_dark();
294 let de = detect_linux_de(&xdg_current_desktop());
295 let preset = linux_preset_for_de(de);
296 match de {
297 #[cfg(feature = "kde")]
298 LinuxDesktop::Kde => {
299 let reader = crate::kde::from_kde()?;
300 run_pipeline(reader, preset, is_dark)
301 }
302 #[cfg(not(feature = "kde"))]
303 LinuxDesktop::Kde => run_pipeline(ThemeSpec::preset("adwaita")?, "adwaita-live", is_dark),
304 LinuxDesktop::Gnome | LinuxDesktop::Budgie => {
305 run_pipeline(ThemeSpec::preset("adwaita")?, preset, is_dark)
307 }
308 LinuxDesktop::Xfce | LinuxDesktop::Cinnamon | LinuxDesktop::Mate | LinuxDesktop::LxQt => {
309 run_pipeline(ThemeSpec::preset("adwaita")?, preset, is_dark)
310 }
311 LinuxDesktop::Unknown => {
312 #[cfg(feature = "kde")]
313 {
314 let path = crate::kde::kdeglobals_path();
315 if path.exists() {
316 let reader = crate::kde::from_kde()?;
317 return run_pipeline(reader, linux_preset_for_de(LinuxDesktop::Kde), is_dark);
318 }
319 }
320 run_pipeline(ThemeSpec::preset("adwaita")?, preset, is_dark)
321 }
322 }
323}
324
325pub(crate) fn from_system_inner() -> crate::Result<SystemTheme> {
326 #[cfg(target_os = "macos")]
327 {
328 #[cfg(feature = "macos")]
329 {
330 let reader = crate::macos::from_macos()?;
331 let is_dark = reader_is_dark(&reader);
332 return run_pipeline(reader, "macos-sonoma-live", is_dark);
333 }
334
335 #[cfg(not(feature = "macos"))]
336 return Err(crate::Error::Unsupported(
337 "macOS theme detection requires the `macos` feature",
338 ));
339 }
340
341 #[cfg(target_os = "windows")]
342 {
343 #[cfg(feature = "windows")]
344 {
345 let reader = crate::windows::from_windows()?;
346 let is_dark = reader_is_dark(&reader);
347 return run_pipeline(reader, "windows-11-live", is_dark);
348 }
349
350 #[cfg(not(feature = "windows"))]
351 return Err(crate::Error::Unsupported(
352 "Windows theme detection requires the `windows` feature",
353 ));
354 }
355
356 #[cfg(target_os = "linux")]
357 {
358 from_linux()
359 }
360
361 #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
362 {
363 Err(crate::Error::Unsupported(
364 "no theme reader available for this platform",
365 ))
366 }
367}
368
369#[cfg(target_os = "linux")]
370pub(crate) async fn from_system_async_inner() -> crate::Result<SystemTheme> {
371 let is_dark = system_is_dark();
372 let de = detect_linux_de(&xdg_current_desktop());
373 let preset = linux_preset_for_de(de);
374 match de {
375 #[cfg(feature = "kde")]
376 LinuxDesktop::Kde => {
377 #[cfg(feature = "portal")]
378 {
379 let reader = crate::gnome::from_kde_with_portal().await?;
380 run_pipeline(reader, preset, is_dark)
381 }
382 #[cfg(not(feature = "portal"))]
383 {
384 let reader = crate::kde::from_kde()?;
385 run_pipeline(reader, preset, is_dark)
386 }
387 }
388 #[cfg(not(feature = "kde"))]
389 LinuxDesktop::Kde => run_pipeline(ThemeSpec::preset("adwaita")?, "adwaita-live", is_dark),
390 #[cfg(feature = "portal")]
391 LinuxDesktop::Gnome | LinuxDesktop::Budgie => {
392 let reader = crate::gnome::from_gnome().await?;
393 run_pipeline(reader, preset, is_dark)
394 }
395 #[cfg(not(feature = "portal"))]
396 LinuxDesktop::Gnome | LinuxDesktop::Budgie => {
397 run_pipeline(ThemeSpec::preset("adwaita")?, preset, is_dark)
398 }
399 LinuxDesktop::Xfce | LinuxDesktop::Cinnamon | LinuxDesktop::Mate | LinuxDesktop::LxQt => {
400 run_pipeline(ThemeSpec::preset("adwaita")?, preset, is_dark)
401 }
402 LinuxDesktop::Unknown => {
403 #[cfg(feature = "portal")]
405 {
406 if let Some(detected) = crate::gnome::detect_portal_backend().await {
407 let detected_preset = linux_preset_for_de(detected);
408 return match detected {
409 #[cfg(feature = "kde")]
410 LinuxDesktop::Kde => {
411 let reader = crate::gnome::from_kde_with_portal().await?;
412 run_pipeline(reader, detected_preset, is_dark)
413 }
414 #[cfg(not(feature = "kde"))]
415 LinuxDesktop::Kde => {
416 run_pipeline(ThemeSpec::preset("adwaita")?, "adwaita-live", is_dark)
417 }
418 LinuxDesktop::Gnome => {
419 let reader = crate::gnome::from_gnome().await?;
420 run_pipeline(reader, detected_preset, is_dark)
421 }
422 _ => {
423 run_pipeline(ThemeSpec::preset("adwaita")?, detected_preset, is_dark)
426 }
427 };
428 }
429 }
430 #[cfg(feature = "kde")]
432 {
433 let path = crate::kde::kdeglobals_path();
434 if path.exists() {
435 let reader = crate::kde::from_kde()?;
436 return run_pipeline(reader, linux_preset_for_de(LinuxDesktop::Kde), is_dark);
437 }
438 }
439 run_pipeline(ThemeSpec::preset("adwaita")?, preset, is_dark)
440 }
441 }
442}
443
444#[cfg(all(test, target_os = "linux"))]
449#[allow(clippy::unwrap_used, clippy::expect_used)]
450mod dispatch_tests {
451 use super::*;
452
453 #[test]
456 fn detect_kde_simple() {
457 assert_eq!(detect_linux_de("KDE"), LinuxDesktop::Kde);
458 }
459
460 #[test]
461 fn detect_kde_colon_separated_after() {
462 assert_eq!(detect_linux_de("ubuntu:KDE"), LinuxDesktop::Kde);
463 }
464
465 #[test]
466 fn detect_kde_colon_separated_before() {
467 assert_eq!(detect_linux_de("KDE:plasma"), LinuxDesktop::Kde);
468 }
469
470 #[test]
471 fn detect_gnome_simple() {
472 assert_eq!(detect_linux_de("GNOME"), LinuxDesktop::Gnome);
473 }
474
475 #[test]
476 fn detect_gnome_ubuntu() {
477 assert_eq!(detect_linux_de("ubuntu:GNOME"), LinuxDesktop::Gnome);
478 }
479
480 #[test]
481 fn detect_xfce() {
482 assert_eq!(detect_linux_de("XFCE"), LinuxDesktop::Xfce);
483 }
484
485 #[test]
486 fn detect_cinnamon() {
487 assert_eq!(detect_linux_de("X-Cinnamon"), LinuxDesktop::Cinnamon);
488 }
489
490 #[test]
491 fn detect_cinnamon_short() {
492 assert_eq!(detect_linux_de("Cinnamon"), LinuxDesktop::Cinnamon);
493 }
494
495 #[test]
496 fn detect_mate() {
497 assert_eq!(detect_linux_de("MATE"), LinuxDesktop::Mate);
498 }
499
500 #[test]
501 fn detect_lxqt() {
502 assert_eq!(detect_linux_de("LXQt"), LinuxDesktop::LxQt);
503 }
504
505 #[test]
506 fn detect_budgie() {
507 assert_eq!(detect_linux_de("Budgie:GNOME"), LinuxDesktop::Budgie);
508 }
509
510 #[test]
511 fn detect_empty_string() {
512 assert_eq!(detect_linux_de(""), LinuxDesktop::Unknown);
513 }
514
515 #[test]
518 #[allow(unsafe_code)]
519 fn from_linux_non_kde_returns_adwaita() {
520 let _guard = crate::test_util::ENV_MUTEX.lock().unwrap();
521 unsafe { std::env::set_var("XDG_CURRENT_DESKTOP", "GNOME") };
525 let result = from_linux();
526 unsafe { std::env::remove_var("XDG_CURRENT_DESKTOP") };
527
528 let theme = result.expect("from_linux() should return Ok for non-KDE desktop");
529 assert_eq!(theme.name, "Adwaita");
530 }
531
532 #[test]
535 #[cfg(feature = "kde")]
536 #[allow(unsafe_code)]
537 fn from_linux_unknown_de_with_kdeglobals_fallback() {
538 let _guard = crate::test_util::ENV_MUTEX.lock().unwrap();
539 use std::io::Write;
540
541 let tmp_dir = std::env::temp_dir().join("native_theme_test_kde_fallback");
543 std::fs::create_dir_all(&tmp_dir).unwrap();
544 let kdeglobals = tmp_dir.join("kdeglobals");
545 let mut f = std::fs::File::create(&kdeglobals).unwrap();
546 writeln!(
547 f,
548 "[General]\nColorScheme=TestTheme\n\n[Colors:Window]\nBackgroundNormal=239,240,241\n"
549 )
550 .unwrap();
551
552 let orig_xdg = std::env::var("XDG_CONFIG_HOME").ok();
554 let orig_desktop = std::env::var("XDG_CURRENT_DESKTOP").ok();
555
556 unsafe { std::env::set_var("XDG_CONFIG_HOME", &tmp_dir) };
557 unsafe { std::env::set_var("XDG_CURRENT_DESKTOP", "SomeUnknownDE") };
558
559 let result = from_linux();
560
561 match orig_xdg {
563 Some(val) => unsafe { std::env::set_var("XDG_CONFIG_HOME", val) },
564 None => unsafe { std::env::remove_var("XDG_CONFIG_HOME") },
565 }
566 match orig_desktop {
567 Some(val) => unsafe { std::env::set_var("XDG_CURRENT_DESKTOP", val) },
568 None => unsafe { std::env::remove_var("XDG_CURRENT_DESKTOP") },
569 }
570
571 let _ = std::fs::remove_dir_all(&tmp_dir);
573
574 let theme = result.expect("from_linux() should return Ok with kdeglobals fallback");
575 assert_eq!(
576 theme.name, "TestTheme",
577 "should use KDE theme name from kdeglobals"
578 );
579 }
580
581 #[test]
582 #[allow(unsafe_code)]
583 fn from_linux_unknown_de_without_kdeglobals_returns_adwaita() {
584 let _guard = crate::test_util::ENV_MUTEX.lock().unwrap();
585 let orig_xdg = std::env::var("XDG_CONFIG_HOME").ok();
587 let orig_desktop = std::env::var("XDG_CURRENT_DESKTOP").ok();
588
589 unsafe {
590 std::env::set_var(
591 "XDG_CONFIG_HOME",
592 "/tmp/nonexistent_native_theme_test_no_kde",
593 )
594 };
595 unsafe { std::env::set_var("XDG_CURRENT_DESKTOP", "SomeUnknownDE") };
596
597 let result = from_linux();
598
599 match orig_xdg {
601 Some(val) => unsafe { std::env::set_var("XDG_CONFIG_HOME", val) },
602 None => unsafe { std::env::remove_var("XDG_CONFIG_HOME") },
603 }
604 match orig_desktop {
605 Some(val) => unsafe { std::env::set_var("XDG_CURRENT_DESKTOP", val) },
606 None => unsafe { std::env::remove_var("XDG_CURRENT_DESKTOP") },
607 }
608
609 let theme = result.expect("from_linux() should return Ok (adwaita fallback)");
610 assert_eq!(
611 theme.name, "Adwaita",
612 "should fall back to Adwaita without kdeglobals"
613 );
614 }
615
616 #[test]
619 fn detect_hyprland_returns_unknown() {
620 assert_eq!(detect_linux_de("Hyprland"), LinuxDesktop::Unknown);
621 }
622
623 #[test]
624 fn detect_sway_returns_unknown() {
625 assert_eq!(detect_linux_de("sway"), LinuxDesktop::Unknown);
626 }
627
628 #[test]
629 fn detect_cosmic_returns_unknown() {
630 assert_eq!(detect_linux_de("COSMIC"), LinuxDesktop::Unknown);
631 }
632
633 #[test]
636 #[allow(unsafe_code)]
637 fn from_system_returns_result() {
638 let _guard = crate::test_util::ENV_MUTEX.lock().unwrap();
639 unsafe { std::env::set_var("XDG_CURRENT_DESKTOP", "GNOME") };
643 let result = crate::SystemTheme::from_system();
644 unsafe { std::env::remove_var("XDG_CURRENT_DESKTOP") };
645
646 let theme = result.expect("from_system() should return Ok on Linux");
647 assert_eq!(theme.name, "Adwaita");
648 }
649}
650
651#[cfg(test)]
655#[allow(
656 clippy::unwrap_used,
657 clippy::expect_used,
658 clippy::field_reassign_with_default
659)]
660mod pipeline_tests {
661 use crate::color::Rgba;
662 use crate::model::{ThemeDefaults, ThemeSpec, ThemeVariant};
663
664 use super::{reader_is_dark, run_pipeline};
665
666 #[test]
669 fn test_run_pipeline_produces_both_variants() {
670 let reader = ThemeSpec::preset("catppuccin-mocha").unwrap();
671 let result = run_pipeline(reader, "catppuccin-mocha", false);
672 assert!(result.is_ok(), "run_pipeline should succeed");
673 let st = result.unwrap();
674 assert!(!st.name.is_empty(), "name should be populated");
676 }
678
679 #[test]
680 fn test_run_pipeline_reader_values_win() {
681 let custom_accent = Rgba::rgb(42, 100, 200);
684 let mut reader = ThemeSpec::default();
685 reader.name = "CustomTheme".into();
686 let mut variant = ThemeVariant::default();
687 variant.defaults.accent_color = Some(custom_accent);
688 reader.light = Some(variant);
689
690 let result = run_pipeline(reader, "catppuccin-mocha", false);
691 assert!(result.is_ok(), "run_pipeline should succeed");
692 let st = result.unwrap();
693 assert_eq!(
695 st.light.defaults.accent_color, custom_accent,
696 "reader accent should win over preset accent"
697 );
698 assert_eq!(st.name, "CustomTheme", "reader name should win");
699 }
700
701 #[test]
702 fn test_run_pipeline_single_variant() {
703 let full = ThemeSpec::preset("kde-breeze").unwrap();
707 let mut reader = ThemeSpec::default();
708 let mut dark_v = full.dark.clone().unwrap();
709 dark_v.defaults.accent_color = Some(Rgba::rgb(200, 50, 50));
711 reader.dark = Some(dark_v);
712 reader.light = None;
713
714 let result = run_pipeline(reader, "kde-breeze-live", true);
715 assert!(
716 result.is_ok(),
717 "run_pipeline should succeed with single variant"
718 );
719 let st = result.unwrap();
720 assert_eq!(
722 st.dark.defaults.accent_color,
723 Rgba::rgb(200, 50, 50),
724 "dark variant should have reader accent"
725 );
726 assert_eq!(st.live_preset, "kde-breeze-live");
729 assert_eq!(st.preset, "kde-breeze");
730 }
731
732 #[test]
733 fn test_run_pipeline_inactive_variant_from_full_preset() {
734 let full = ThemeSpec::preset("kde-breeze").unwrap();
737 let mut reader = ThemeSpec::default();
738 reader.dark = Some(full.dark.clone().unwrap());
739 reader.light = None;
740
741 let st = run_pipeline(reader, "kde-breeze-live", true).unwrap();
742
743 let full_light = full.light.unwrap();
745 assert_eq!(
746 st.light.defaults.accent_color,
747 full_light.defaults.accent_color.unwrap(),
748 "inactive light variant should get accent from full preset"
749 );
750 assert_eq!(
751 st.light.defaults.background_color,
752 full_light.defaults.background_color.unwrap(),
753 "inactive light variant should get background from full preset"
754 );
755 }
756
757 #[test]
760 fn test_run_pipeline_with_preset_as_reader() {
761 let reader = ThemeSpec::preset("adwaita").unwrap();
764 let result = run_pipeline(reader, "adwaita", false);
765 assert!(
766 result.is_ok(),
767 "double-merge with same preset should succeed"
768 );
769 let st = result.unwrap();
770 assert_eq!(st.name, "Adwaita");
771 }
772
773 #[test]
776 fn test_run_pipeline_propagates_font_dpi_to_inactive_variant() {
777 let mut reader = ThemeSpec::default();
780 reader.dark = Some(ThemeVariant {
781 defaults: ThemeDefaults {
782 font_dpi: Some(120.0),
783 ..Default::default()
784 },
785 ..Default::default()
786 });
787
788 let st = run_pipeline(reader, "kde-breeze-live", true).unwrap();
789 let resolved_size = st.light.defaults.font.size;
802 assert!(
803 resolved_size > 10.0,
804 "inactive variant font size should be DPI-converted (got {resolved_size}, expected > 10.0)"
805 );
806 let expected = 10.0 * 120.0 / 72.0; assert!(
809 (resolved_size - expected).abs() < 0.1,
810 "font size should be 10pt * 120/72 = {expected:.1}px, got {resolved_size}"
811 );
812 }
813
814 #[test]
817 fn test_reader_is_dark_only_dark() {
818 let mut theme = ThemeSpec::default();
819 theme.dark = Some(ThemeVariant::default());
820 theme.light = None;
821 assert!(
822 reader_is_dark(&theme),
823 "should be true when only dark is set"
824 );
825 }
826
827 #[test]
828 fn test_reader_is_dark_only_light() {
829 let mut theme = ThemeSpec::default();
830 theme.light = Some(ThemeVariant::default());
831 theme.dark = None;
832 assert!(
833 !reader_is_dark(&theme),
834 "should be false when only light is set"
835 );
836 }
837
838 #[test]
839 fn test_reader_is_dark_both() {
840 let mut theme = ThemeSpec::default();
841 theme.light = Some(ThemeVariant::default());
842 theme.dark = Some(ThemeVariant::default());
843 assert!(
844 !reader_is_dark(&theme),
845 "should be false when both are set (macOS case)"
846 );
847 }
848
849 #[test]
850 fn test_reader_is_dark_neither() {
851 let theme = ThemeSpec::default();
852 assert!(
853 !reader_is_dark(&theme),
854 "should be false when neither is set"
855 );
856 }
857}