use crate::tui::render::presets::{
ALUCARD, CATPPUCCIN_LATTE, CATPPUCCIN_MOCHA, DRACULA, MONOKAI, SOLARIZED_DARK, SOLARIZED_LIGHT,
built_ins,
};
use crate::tui::render::theme::{self, rgb_to_ansi256};
use crate::tui::render::user_themes::build_theme;
use crate::tui::theme_catalog::theme_pack::sources;
use ratatui::style::Color;
const fn rgb(hex: u32) -> Color {
Color::Rgb(
((hex >> 16) & 0xFF) as u8,
((hex >> 8) & 0xFF) as u8,
(hex & 0xFF) as u8,
)
}
#[test]
fn crab_dark_declares_no_canvas() {
assert_eq!(
theme::CRAB_DARK.colors.background,
None,
"crab-dark must not paint a canvas: terminal transparency depends on it"
);
assert_eq!(theme::CRAB_DARK.colors.ansi.background, None);
}
#[test]
fn presets_declare_their_upstream_background() {
let cases = [
("dracula", &DRACULA, 0x282A36u32),
("alucard", &ALUCARD, 0xFFFBEB),
("monokai", &MONOKAI, 0x272822),
("catppuccin-mocha", &CATPPUCCIN_MOCHA, 0x1E1E2E),
("catppuccin-latte", &CATPPUCCIN_LATTE, 0xEFF1F5),
("solarized-light", &SOLARIZED_LIGHT, 0xFDF6E3),
("solarized-dark", &SOLARIZED_DARK, 0x002B36),
];
for (name, theme, hex) in cases {
assert_eq!(
theme.colors.background,
Some(rgb(hex)),
"{name} background drifted from its upstream spec"
);
}
}
#[test]
fn preset_ansi_background_is_derived_from_the_rgb() {
let cases = [
(&DRACULA, 0x282A36u32),
(&ALUCARD, 0xFFFBEB),
(&MONOKAI, 0x272822),
(&CATPPUCCIN_MOCHA, 0x1E1E2E),
(&CATPPUCCIN_LATTE, 0xEFF1F5),
(&SOLARIZED_LIGHT, 0xFDF6E3),
(&SOLARIZED_DARK, 0x002B36),
];
for (theme, hex) in cases {
let (r, g, b) = (
((hex >> 16) & 0xFF) as u8,
((hex >> 8) & 0xFF) as u8,
(hex & 0xFF) as u8,
);
assert_eq!(
theme.colors.ansi.background,
Some(rgb_to_ansi256(r, g, b)),
"{} ansi background is not the quantized rgb",
theme.name
);
}
}
#[test]
fn user_theme_without_background_still_loads_and_declares_none() {
let toml = pack_toml_without_background();
let theme = build_theme("bgtest-absent", &toml).expect("legacy theme file must still load");
assert_eq!(theme.colors.background, None);
assert_eq!(theme.colors.ansi.background, None);
}
#[test]
fn user_theme_with_background_declares_it() {
let toml = format!(
"{}\nbackground = \"#123456\"\n",
pack_toml_without_background()
);
let theme = build_theme("bgtest-present", &toml).expect("theme with background must load");
assert_eq!(theme.colors.background, Some(Color::Rgb(0x12, 0x34, 0x56)));
assert_eq!(
theme.colors.ansi.background,
Some(rgb_to_ansi256(0x12, 0x34, 0x56))
);
}
#[test]
fn user_theme_with_invalid_background_is_rejected() {
let toml = format!(
"{}\nbackground = \"not-a-hex\"\n",
pack_toml_without_background()
);
let err = build_theme("bgtest-bad", &toml).expect_err("invalid hex must be rejected");
assert!(
err.contains("background"),
"error must name the offending key, got: {err}"
);
}
#[test]
fn every_pack_theme_declares_a_canvas_matching_its_ink() {
assert!(!sources().is_empty(), "pack is empty");
for (name, toml) in sources() {
let ink =
toml_value(toml, "ink").unwrap_or_else(|| panic!("pack theme {name} has no ink key"));
let bg = toml_value(toml, "background")
.unwrap_or_else(|| panic!("pack theme {name} declares no canvas"));
assert_eq!(
bg, ink,
"pack theme {name}: canvas {bg} does not match its source background {ink}"
);
}
}
#[test]
fn every_pack_canvas_survives_the_validator() {
for (name, toml) in sources() {
let theme = build_theme(&format!("bgcheck-{name}"), toml)
.unwrap_or_else(|e| panic!("pack theme {name} rejected: {e}"));
assert!(
theme.colors.background.is_some(),
"pack theme {name} parsed with no canvas"
);
}
}
#[test]
fn built_ins_are_mixed_on_canvas_declaration() {
let mut with = 0usize;
let mut without: Vec<&str> = Vec::new();
for t in built_ins() {
if t.colors.background.is_some() {
with += 1;
} else {
without.push(t.name);
}
}
assert!(with > 0, "no built-in declares a canvas");
assert_eq!(
without,
vec!["crab-dark"],
"crab-dark must be the only built-in without a canvas"
);
}
fn toml_value(text: &str, key: &str) -> Option<String> {
text.lines()
.find_map(|l| l.strip_prefix(&format!("{key} = ")))
.map(|v| v.trim().trim_matches('"').to_string())
}
fn pack_toml_without_background() -> String {
let (_, toml) = sources().first().expect("pack is empty");
toml.lines()
.filter(|l| !l.starts_with("background = "))
.collect::<Vec<_>>()
.join("\n")
}