126 lines
4.2 KiB
Rust
126 lines
4.2 KiB
Rust
//! Palette contrast tests, mirroring bezel's `tests/theme.rs`: each light text
|
|
//! token must land within ~0.5 of its dark counterpart's contrast ratio
|
|
//! against its own background, and the primary text must keep the ratio the
|
|
//! palette was tuned for.
|
|
|
|
use gpui::Hsla;
|
|
use theme::{Brand, ThemeColors, ThemeMode, Tint, contrast_ratio, flatten};
|
|
|
|
// The shipped palette's measured floors (AppKit-style ink, per bezel):
|
|
// text/bg is 14.4:1 dark and 15.1:1 light, muted lands near 6.3 dark and
|
|
// 4.0 light, and faint sits below 3:1 in both appearances by design.
|
|
const TEXT_RATIO: f32 = 13.9;
|
|
const MUTED_RATIO: f32 = 3.9;
|
|
const FAINT_RATIO: f32 = 1.8;
|
|
const TEXT_PARITY: f32 = 0.7;
|
|
|
|
fn ratio(fg: Hsla, bg: Hsla) -> f32 {
|
|
contrast_ratio(flatten(fg, bg), bg)
|
|
}
|
|
|
|
#[test]
|
|
fn text_keeps_its_contrast() {
|
|
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
|
|
let colors = ThemeColors::for_appearance(appearance);
|
|
assert!(
|
|
ratio(colors.text, colors.bg) >= TEXT_RATIO,
|
|
"{appearance:?}: text on bg = {}",
|
|
ratio(colors.text, colors.bg)
|
|
);
|
|
assert!(
|
|
ratio(colors.text, colors.surface) >= TEXT_RATIO,
|
|
"{appearance:?}: text on surface"
|
|
);
|
|
assert!(
|
|
ratio(colors.text_muted, colors.bg) >= MUTED_RATIO,
|
|
"{appearance:?}: muted on bg = {}",
|
|
ratio(colors.text_muted, colors.bg)
|
|
);
|
|
assert!(
|
|
ratio(colors.text_faint, colors.bg) >= FAINT_RATIO,
|
|
"{appearance:?}: faint on bg = {}",
|
|
ratio(colors.text_faint, colors.bg)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn light_matches_dark_contrast() {
|
|
let dark = ThemeColors::for_appearance(ThemeMode::Dark);
|
|
let light = ThemeColors::for_appearance(ThemeMode::Light);
|
|
|
|
// Primary text keeps near-parity across appearances. Muted and faint do
|
|
// not (the light values sit further from the background by design, see
|
|
// Ink::APPKIT); their floors are asserted in `text_keeps_its_contrast`.
|
|
let pairs = [
|
|
("text/bg", dark.text, dark.bg, light.text, light.bg),
|
|
(
|
|
"text/surface",
|
|
dark.text,
|
|
dark.surface,
|
|
light.text,
|
|
light.surface,
|
|
),
|
|
];
|
|
for (name, dark_fg, dark_bg, light_fg, light_bg) in pairs {
|
|
let dark_ratio = ratio(dark_fg, dark_bg);
|
|
let light_ratio = ratio(light_fg, light_bg);
|
|
let delta = (dark_ratio - light_ratio).abs();
|
|
assert!(
|
|
delta <= TEXT_PARITY,
|
|
"{name}: light deviates by {delta} (dark {dark_ratio:.3}, light {light_ratio:.3})"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn default_brand_is_a_no_op() {
|
|
let brand = Brand::default();
|
|
assert!(brand.is_default());
|
|
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
|
|
let base = ThemeColors::for_appearance(appearance);
|
|
let branded = brand.branded(appearance);
|
|
assert_eq!(
|
|
base, branded,
|
|
"{appearance:?}: default brand must not move the palette"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn brand_tint_rotates_greys_and_keeps_semantics() {
|
|
let brand = Brand {
|
|
tint: Tint::new(257.417, 0.046),
|
|
accent: Tint::new(276.935, 0.182),
|
|
..Brand::default()
|
|
};
|
|
let base = ThemeColors::for_appearance(ThemeMode::Dark);
|
|
let branded = brand.branded(ThemeMode::Dark);
|
|
|
|
assert_ne!(branded.bg, base.bg, "bg takes the tint");
|
|
assert_ne!(branded.accent, base.accent, "accent takes the accent hue");
|
|
assert_eq!(
|
|
branded.danger, base.danger,
|
|
"danger is semantic and keeps its hue"
|
|
);
|
|
assert_eq!(
|
|
branded.warning, base.warning,
|
|
"warning is semantic and keeps its hue"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn warning_wash_carries_its_label() {
|
|
// warning_muted is a translucent amber wash, so the label pairing is
|
|
// measured against the plate flattened onto the content plane.
|
|
for appearance in [ThemeMode::Dark, ThemeMode::Light] {
|
|
let colors = ThemeColors::for_appearance(appearance);
|
|
let plate = flatten(colors.warning_muted, colors.bg);
|
|
assert!(
|
|
ratio(colors.warning, plate) >= 3.0,
|
|
"{appearance:?}: warning on warning_muted = {}",
|
|
ratio(colors.warning, plate)
|
|
);
|
|
}
|
|
}
|