use oxiui_core::{Color, FontSpec, Palette, Theme};
use oxiui_theme::high_contrast::{wcag_contrast, wcag_luminance};
use oxiui_theme::{cooljapan_default, cooljapan_high_contrast, dark, light};
use oxiui_theme::{
os_prefers_high_contrast, os_prefers_reduced_motion, CooljapanTheme, DesignTokens, RadiusStep,
SpacingStep, ThemeExt, TypographyScale,
};
#[test]
fn dark_and_light_palettes_differ() {
let d = dark();
let l = light();
assert_ne!(d.palette().background, l.palette().background);
}
#[test]
fn cooljapan_default_is_dark() {
let default_theme = cooljapan_default();
let dark_theme = dark();
assert_eq!(
default_theme.palette().primary,
dark_theme.palette().primary
);
}
#[test]
fn font_spec_has_positive_size_and_weight() {
let theme = cooljapan_default();
let font = theme.font();
assert!(font.size > 0.0, "font size must be positive");
assert!(font.weight > 0, "font weight must be positive");
}
#[test]
fn dark_palette_is_tokyo_night() {
let theme = dark();
let palette = theme.palette();
assert_eq!(palette.background.0, 26);
assert_eq!(palette.background.1, 27);
assert_eq!(palette.background.2, 38);
}
#[test]
fn light_palette_has_white_surface() {
let theme = light();
let palette = theme.palette();
assert_eq!(palette.surface.0, 255);
assert_eq!(palette.surface.1, 255);
assert_eq!(palette.surface.2, 255);
}
#[test]
fn wcag_luminance_black_is_zero() {
let lum = wcag_luminance(0, 0, 0);
assert!(
(lum - 0.0).abs() < 1e-10,
"black luminance must be 0.0, got {lum}"
);
}
#[test]
fn wcag_luminance_white_is_one() {
let lum = wcag_luminance(255, 255, 255);
assert!(
(lum - 1.0).abs() < 1e-6,
"white luminance must be ~1.0, got {lum}"
);
}
#[test]
fn wcag_contrast_white_on_black_is_21() {
let ratio = wcag_contrast((255, 255, 255), (0, 0, 0));
assert!(
(ratio - 21.0).abs() < 0.1,
"white-on-black contrast should be ~21.0, got {ratio:.4}"
);
}
#[test]
fn wcag_contrast_yellow_on_black_exceeds_aaa() {
let ratio = wcag_contrast((255, 255, 0), (0, 0, 0));
assert!(
ratio > 7.0,
"yellow-on-black contrast should exceed WCAG AAA 7.0, got {ratio:.4}"
);
}
#[test]
fn wcag_contrast_is_symmetric() {
let fg_on_bg = wcag_contrast((255, 255, 255), (0, 0, 0));
let bg_on_fg = wcag_contrast((0, 0, 0), (255, 255, 255));
assert!(
(fg_on_bg - bg_on_fg).abs() < 1e-10,
"contrast ratio must be symmetric"
);
}
#[test]
fn high_contrast_palette_foreground_vs_background_exceeds_aaa() {
let p = cooljapan_high_contrast();
let fg = (p.text.0, p.text.1, p.text.2);
let bg = (p.background.0, p.background.1, p.background.2);
let ratio = wcag_contrast(fg, bg);
assert!(
ratio > 7.0,
"high-contrast text-on-background must exceed WCAG AAA 7.0, got {ratio:.4}"
);
}
#[test]
fn high_contrast_palette_primary_vs_background_exceeds_aaa() {
let p = cooljapan_high_contrast();
let primary = (p.primary.0, p.primary.1, p.primary.2);
let bg = (p.background.0, p.background.1, p.background.2);
let ratio = wcag_contrast(primary, bg);
assert!(
ratio > 7.0,
"high-contrast primary-on-background must exceed WCAG AAA 7.0, got {ratio:.4}"
);
}
#[test]
fn high_contrast_palette_background_is_black() {
let p = cooljapan_high_contrast();
assert_eq!(p.background.0, 0, "background R");
assert_eq!(p.background.1, 0, "background G");
assert_eq!(p.background.2, 0, "background B");
}
#[test]
fn high_contrast_palette_text_is_white() {
let p = cooljapan_high_contrast();
assert_eq!(p.text.0, 255, "text R");
assert_eq!(p.text.1, 255, "text G");
assert_eq!(p.text.2, 255, "text B");
}
#[test]
fn theme_ext_works_on_boxed_dyn_theme() {
let theme = cooljapan_default();
let tokens = theme.tokens();
assert_eq!(tokens.spacing(SpacingStep::Xs), 4.0);
assert_eq!(tokens.radius(RadiusStep::None), 0.0);
let typ = theme.typography();
assert!(typ.body.size > 0.0);
}
#[test]
fn extended_palette_inferred_dark_for_dark_theme() {
let theme = dark();
let ext = theme.extended_palette();
assert_eq!(ext.base.background, theme.palette().background);
assert_eq!(ext.error, Color(247, 118, 142, 255));
}
#[test]
fn extended_palette_inferred_light_for_light_theme() {
let theme = light();
let ext = theme.extended_palette();
assert_ne!(ext.error, Color(247, 118, 142, 255));
}
#[test]
fn custom_cooljapan_theme_constructible() {
let palette = Palette::new(
Color(10, 10, 10, 255),
Color(20, 20, 20, 255),
Color(0, 120, 255, 255),
Color(255, 255, 255, 255),
Color(230, 230, 230, 255),
Color(120, 120, 120, 255),
);
let theme = CooljapanTheme::new(palette, FontSpec::new("Inter", 16.0, 500));
assert_eq!(theme.font().size, 16.0);
assert_eq!(theme.palette().primary, Color(0, 120, 255, 255));
assert!(theme.typography().display.size > theme.typography().body.size);
}
#[test]
fn test_design_tokens_from_dark_theme() {
let theme = dark();
let dt: &DesignTokens = theme.design_tokens();
assert!(dt.spacing(SpacingStep::Xs) > 0.0);
assert_eq!(dt.radius(RadiusStep::None), 0.0);
assert_eq!(dt.elevation(0), 0.0);
}
#[test]
fn test_typography_ref_from_dark_theme() {
let theme = dark();
let ts: &TypographyScale = theme.typography_ref();
assert!(ts.body.size > 0.0);
assert!(ts.display.size > ts.body.size);
}
#[test]
fn test_effective_palette_no_override_by_default() {
if std::env::var("OXIUI_HIGH_CONTRAST").is_ok() {
return;
}
let theme = light();
let palette = theme.palette().clone();
let effective = theme.effective_palette();
assert_eq!(effective.background, palette.background);
assert_eq!(effective.surface, palette.surface);
assert_eq!(effective.primary, palette.primary);
assert_eq!(effective.text, palette.text);
}
#[test]
fn test_effective_palette_boosts_contrast_when_env_set() {
let key = "OXIUI_HIGH_CONTRAST_TEST_TEMP_KEY_DP92";
let bg = Color(216, 218, 228, 255); let expected_r = (bg.0 as f32 * 0.9).round() as u8;
let expected_g = (bg.1 as f32 * 0.9).round() as u8;
let expected_b = (bg.2 as f32 * 0.9).round() as u8;
assert!(
expected_r < bg.0 || expected_g < bg.1 || expected_b < bg.2,
"blending toward black must darken at least one channel"
);
if std::env::var("OXIUI_HIGH_CONTRAST").is_ok() {
return;
}
unsafe {
std::env::set_var("OXIUI_HIGH_CONTRAST", "1");
}
let theme = light();
let effective = theme.effective_palette();
unsafe {
std::env::remove_var("OXIUI_HIGH_CONTRAST");
}
assert!(effective.background.0 <= bg.0);
assert!(effective.background.1 <= bg.1);
assert!(effective.background.2 <= bg.2);
assert_eq!(effective.background.3, 255);
assert_ne!(effective.background, bg);
let _ = key;
}
#[test]
fn test_high_contrast_auto_detect_env_var_one() {
if std::env::var("OXIUI_HIGH_CONTRAST").is_ok() {
return;
}
unsafe {
std::env::set_var("OXIUI_HIGH_CONTRAST", "1");
}
let result = os_prefers_high_contrast();
unsafe {
std::env::remove_var("OXIUI_HIGH_CONTRAST");
}
assert!(
result,
"os_prefers_high_contrast() must be true when OXIUI_HIGH_CONTRAST=1"
);
}
#[test]
fn test_high_contrast_auto_detect_env_var_true() {
if std::env::var("OXIUI_HIGH_CONTRAST").is_ok() {
return;
}
unsafe {
std::env::set_var("OXIUI_HIGH_CONTRAST", "True");
}
let result = os_prefers_high_contrast();
unsafe {
std::env::remove_var("OXIUI_HIGH_CONTRAST");
}
assert!(
result,
"os_prefers_high_contrast() must be true when OXIUI_HIGH_CONTRAST=True"
);
}
#[test]
fn test_high_contrast_env_absent_returns_false() {
if std::env::var("OXIUI_HIGH_CONTRAST").is_ok() {
return;
}
assert!(!os_prefers_high_contrast(), "no env var => false");
}
#[test]
fn test_reduced_motion_env_var() {
if std::env::var("OXIUI_REDUCED_MOTION").is_ok() {
return;
}
assert!(!os_prefers_reduced_motion(), "no env var => false");
unsafe {
std::env::set_var("OXIUI_REDUCED_MOTION", "1");
}
let result = os_prefers_reduced_motion();
unsafe {
std::env::remove_var("OXIUI_REDUCED_MOTION");
}
assert!(result);
}
#[test]
fn test_theme_trait_object_safe() {
let palette = Palette::new(
Color(0, 0, 0, 255),
Color(20, 20, 20, 255),
Color(0, 100, 200, 255),
Color(255, 255, 255, 255),
Color(220, 220, 220, 255),
Color(128, 128, 128, 255),
);
let _: Box<dyn Theme> = Box::new(CooljapanTheme::new(palette, FontSpec::default()));
}
#[test]
fn test_is_high_contrast_default_false() {
let theme = dark();
assert!(!theme.is_high_contrast());
let theme = light();
assert!(!theme.is_high_contrast());
}
#[test]
fn test_design_tokens_spacing_non_zero() {
let theme = cooljapan_default();
let dt = theme.design_tokens();
assert!(
dt.spacing(SpacingStep::Xs) > 0.0,
"Xs spacing must be positive"
);
}
#[test]
fn test_typography_ref_body_size_positive() {
let theme = cooljapan_default();
let ts = theme.typography_ref();
assert!(ts.body.size > 0.0, "body font size must be positive");
}