use crate::theme::registry::{themes, Theme};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ThemeMode {
Dark,
Light,
}
impl ThemeMode {
pub fn is_dark(self) -> bool {
self == ThemeMode::Dark
}
pub fn other(self) -> ThemeMode {
match self {
ThemeMode::Dark => ThemeMode::Light,
ThemeMode::Light => ThemeMode::Dark,
}
}
pub fn label(self) -> &'static str {
match self {
ThemeMode::Dark => "Dark",
ThemeMode::Light => "Light",
}
}
}
impl Theme {
pub fn mode(&self) -> ThemeMode {
if self.dark {
ThemeMode::Dark
} else {
ThemeMode::Light
}
}
}
pub fn themes_by_mode(mode: ThemeMode) -> Vec<&'static Theme> {
themes()
.iter()
.chain(crate::theme::register::user_list())
.filter(|t| t.mode() == mode)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::theme::registry::get;
#[test]
fn every_theme_classifies_and_the_partition_is_exact() {
let dark = themes_by_mode(ThemeMode::Dark);
let light = themes_by_mode(ThemeMode::Light);
assert!(!dark.is_empty(), "dark mode must never be empty");
assert!(!light.is_empty(), "light mode must never be empty");
let builtin_dark: Vec<&str> = dark
.iter()
.filter(|t| themes().iter().any(|b| b.id == t.id))
.map(|t| t.id)
.collect();
let builtin_light: Vec<&str> = light
.iter()
.filter(|t| themes().iter().any(|b| b.id == t.id))
.map(|t| t.id)
.collect();
assert_eq!(builtin_dark.len() + builtin_light.len(), themes().len());
for t in themes() {
assert_eq!(t.mode().is_dark(), t.is_dark(), "[{}] mode vs flag", t.id);
let group = if t.is_dark() {
&builtin_dark
} else {
&builtin_light
};
assert!(group.contains(&t.id), "[{}] missing from its group", t.id);
}
}
#[test]
fn known_themes_pin_their_modes() {
for (id, mode) in [
("abstract-dark", ThemeMode::Dark),
("tokyo-night", ThemeMode::Dark),
("dracula", ThemeMode::Dark),
("observer-night", ThemeMode::Dark),
("abstract-midnight", ThemeMode::Dark),
("abstract-light", ThemeMode::Light),
("abstract-dawn", ThemeMode::Light),
("abstract-paper", ThemeMode::Light),
("rose-pine-dawn", ThemeMode::Light),
("catppuccin-latte", ThemeMode::Light),
("one-light", ThemeMode::Light),
("everforest-light", ThemeMode::Light),
("solarized-light", ThemeMode::Light),
] {
assert_eq!(get(id).expect(id).mode(), mode, "{id}");
}
}
#[test]
fn ordering_is_registry_order_and_house_themes_lead() {
assert_eq!(themes_by_mode(ThemeMode::Dark)[0].id, "abstract-dark");
assert_eq!(themes_by_mode(ThemeMode::Light)[0].id, "abstract-light");
for mode in [ThemeMode::Dark, ThemeMode::Light] {
let ids: Vec<&str> = themes_by_mode(mode)
.iter()
.filter(|t| themes().iter().any(|b| b.id == t.id))
.map(|t| t.id)
.collect();
let expected: Vec<&str> = themes()
.iter()
.filter(|t| t.mode() == mode)
.map(|t| t.id)
.collect();
assert_eq!(ids, expected, "{mode:?} order drifted from registry");
}
}
#[test]
fn mode_helpers_round_trip() {
assert_eq!(ThemeMode::Dark.other(), ThemeMode::Light);
assert_eq!(ThemeMode::Light.other(), ThemeMode::Dark);
assert_eq!(ThemeMode::Dark.other().other(), ThemeMode::Dark);
assert!(ThemeMode::Dark.is_dark());
assert!(!ThemeMode::Light.is_dark());
assert_eq!(ThemeMode::Dark.label(), "Dark");
assert_eq!(ThemeMode::Light.label(), "Light");
}
#[test]
fn runtime_registrations_join_their_mode_group() {
use crate::theme::register::{register, RegisterMode, ThemeCandidate};
let base = get("abstract-light").expect("house light");
let reg = register(
ThemeCandidate {
id: "mode-test-light".into(),
label: "Mode Test Light".into(),
dark: false,
tokens: base.tokens,
ground_intent: Vec::new(),
},
RegisterMode::Strict,
)
.expect("clean candidate registers");
let light = themes_by_mode(ThemeMode::Light);
assert!(
light.iter().any(|t| t.id == reg.theme.id),
"user theme visible in its mode listing"
);
let pos_user = light.iter().position(|t| t.id == reg.theme.id).unwrap();
let pos_house = light.iter().position(|t| t.id == "abstract-light").unwrap();
assert!(pos_house < pos_user, "built-ins lead, registrations trail");
assert!(
!themes_by_mode(ThemeMode::Dark)
.iter()
.any(|t| t.id == reg.theme.id),
"a light registration never appears in the dark group"
);
}
}