use makeover::{
DISTINCT, Rgb, STEP_FLOOR, embedded_themes, parse_theme_str, resolve, wcag_contrast,
};
const TEXT: f32 = 4.5;
const QUIET_TEXT: f32 = 3.0;
struct Floor {
fg: &'static str,
bg: &'static str,
min: f32,
known: &'static [&'static str],
}
const FLOORS: &[Floor] = &[
Floor {
fg: "content",
bg: "surface-page",
min: TEXT,
known: &[],
},
Floor {
fg: "content",
bg: "surface-raised",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "surface-well",
min: TEXT,
known: &[],
},
Floor {
fg: "content",
bg: "surface-sunken",
min: TEXT,
known: &["ayu-light", "solarized-dark"],
},
Floor {
fg: "content-secondary",
bg: "content",
min: STEP_FLOOR,
known: &[],
},
Floor {
fg: "content-secondary",
bg: "surface-page",
min: TEXT,
known: &["ayu-light", "solarized-dark"],
},
Floor {
fg: "content-secondary",
bg: "surface-sunken",
min: TEXT,
known: &[
"ayu-light",
"catppuccin-latte",
"dawnfox",
"flatwhite",
"rosepine-dawn",
"solarized-dark",
],
},
Floor {
fg: "content-secondary",
bg: "surface-well",
min: TEXT,
known: &[
"ayu-light",
"catppuccin-latte",
"dawnfox",
"flatwhite",
"rosepine-dawn",
"solarized-dark",
],
},
Floor {
fg: "content-muted",
bg: "surface-page",
min: QUIET_TEXT,
known: &[
"ayu-light",
"catppuccin-latte",
"dawnfox",
"flatwhite",
"rosepine-dawn",
"solarized-dark",
],
},
Floor {
fg: "content-muted",
bg: "surface-well",
min: QUIET_TEXT,
known: &[
"ayu-light",
"catppuccin-latte",
"dawnfox",
"flatwhite",
"gruvbox-light",
"rosepine-dawn",
"solarized-dark",
],
},
Floor {
fg: "content",
bg: "row-stripe",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "row-hover",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "row-selected",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "info-surface",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "success-surface",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "warning-surface",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content",
bg: "danger-surface",
min: TEXT,
known: &["solarized-dark"],
},
Floor {
fg: "content-on-action",
bg: "action",
min: TEXT,
known: &[],
},
Floor {
fg: "focus-ring",
bg: "surface-page",
min: DISTINCT,
known: &["ayu-light", "goingson"],
},
Floor {
fg: "focus-ring",
bg: "surface-raised",
min: DISTINCT,
known: &["ayu-light"],
},
];
#[test]
fn every_bundled_theme_clears_every_stated_floor() {
let themes: Vec<_> = embedded_themes()
.map(|(id, source)| (id, resolve(&parse_theme_str(id, source, false).unwrap())))
.collect();
assert!(
themes.len() >= 31,
"expected the full bundled corpus, got {}",
themes.len()
);
let mut problems = Vec::new();
for floor in FLOORS {
let mut failing: Vec<(&str, f32)> = Vec::new();
for (id, t) in &themes {
let rgb = |k: &str| t.hex(k).and_then(Rgb::from_hex);
let (Some(fg), Some(bg)) = (rgb(floor.fg), rgb(floor.bg)) else {
continue;
};
let ratio = wcag_contrast(fg, bg);
if ratio < floor.min {
failing.push((id, ratio));
}
}
let pair = format!("{} on {} (>= {})", floor.fg, floor.bg, floor.min);
for (id, ratio) in &failing {
if !floor.known.contains(id) {
problems.push(format!("{pair}: {id} is {ratio:.2}:1, a new failure"));
}
}
for id in floor.known {
if !failing.iter().any(|(f, _)| f == id) {
problems.push(format!("{pair}: {id} now clears it; take it off the list"));
}
}
}
assert!(
problems.is_empty(),
"contrast floors:\n {}",
problems.join("\n ")
);
}