#[cfg(all(test, feature = "desktop"))]
mod tests {
use rust_widgets::theme::{Theme, ThemeManager};
const THEME_DIR: &str = "themes";
fn fixture_path(name: &str) -> String {
format!("{THEME_DIR}/{name}.json")
}
#[test]
fn fixtures_match_the_built_in_presets() {
let presets = [Theme::default(), Theme::dark()];
for preset in &presets {
let path = fixture_path(&preset.name);
let text = std::fs::read_to_string(&path).unwrap_or_else(|error| {
panic!(
"the preset '{}' has no checked-in fixture at {path}: {error} (regenerate \
with themes/generate.sh)",
preset.name
)
});
let parsed: Theme = serde_json::from_str(&text).unwrap_or_else(|error| {
panic!("{path} does not deserialize into a Theme: {error}")
});
assert_eq!(parsed.name, preset.name, "{path} declares the wrong theme name");
assert_eq!(
parsed.appearance, preset.appearance,
"{path} declares the wrong appearance"
);
assert_eq!(
parsed.colors.background, preset.colors.background,
"{path}: background colour drifted"
);
assert_eq!(
parsed.colors.foreground, preset.colors.foreground,
"{path}: foreground colour drifted"
);
assert_eq!(parsed.colors.primary, preset.colors.primary, "{path}: primary drifted");
assert_eq!(parsed.colors.accent, preset.colors.accent, "{path}: accent drifted");
assert_eq!(
parsed.spacing.medium, preset.spacing.medium,
"{path}: spacing scale drifted"
);
assert_eq!(parsed.borders.width, preset.borders.width, "{path}: border width drifted");
assert_eq!(
parsed.borders.radius, preset.borders.radius,
"{path}: border radius drifted"
);
assert_eq!(
parsed.fonts.body.family(),
preset.fonts.body.family(),
"{path}: body font family drifted"
);
assert_eq!(
parsed.fonts.body.size(),
preset.fonts.body.size(),
"{path}: body font size drifted"
);
assert_eq!(
parsed.fonts.monospace.family(),
preset.fonts.monospace.family(),
"{path}: monospace font family drifted"
);
}
}
#[test]
fn a_fixture_loads_and_resolves_a_style() {
let mut manager = ThemeManager::new();
let name = manager
.load_and_activate_theme(&fixture_path("default"))
.expect("the default fixture must load");
assert_eq!(name, "default");
assert_eq!(manager.current_theme_name(), "default");
let style = manager.resolve_style("button");
assert!(style.background_color.is_some(), "a loaded theme must resolve a background");
assert!(style.font.is_some(), "a loaded theme must resolve a font");
assert_eq!(
style.background_color,
Some(manager.current_theme().expect("active").colors.primary),
"a button's fill must come from the loaded theme's primary token"
);
}
#[test]
fn an_override_token_survives_the_round_trip() {
use rust_widgets::core::Color;
use rust_widgets::style::WidgetState;
use rust_widgets::theme::{ShadowOverride, ShadowToken, ThemeOverrides, ThemeStyleToken};
let theme = Theme {
overrides: ThemeOverrides {
styles: [
(
"button".to_string(),
ThemeStyleToken {
background: Some(Color::rgb(1, 2, 3)),
foreground: None,
border: Some(Color::rgb(4, 5, 6)),
border_width: Some(3),
radius: None,
font: None,
opacity: Some(0.5),
shadow: ShadowOverride::None,
touch_target: Some([44, 44]),
},
),
(
"panel".to_string(),
ThemeStyleToken {
shadow: ShadowOverride::Set(ShadowToken {
x: 1,
y: 2,
blur: 3,
color: Color::rgb(9, 9, 9),
}),
..Default::default()
},
),
(
"button:hover".to_string(),
ThemeStyleToken {
background: Some(Color::rgb(7, 8, 9)),
..Default::default()
},
),
]
.into_iter()
.collect(),
},
..Theme::default()
};
let json = serde_json::to_string(&theme).expect("serialize");
let parsed: Theme = serde_json::from_str(&json).expect("deserialize");
let token = parsed.overrides.styles.get("button").expect("the override token must survive");
assert_eq!(token.background, Some(Color::rgb(1, 2, 3)));
assert_eq!(token.foreground, None, "an unset field must stay unset");
assert_eq!(token.border, Some(Color::rgb(4, 5, 6)));
assert_eq!(token.border_width, Some(3));
assert_eq!(token.opacity, Some(0.5));
assert_eq!(
token.shadow,
ShadowOverride::None,
"\"clear the shadow\" must not collapse into \"do not mention it\""
);
assert_eq!(token.touch_target, Some([44, 44]));
let set_token =
parsed.overrides.styles.get("panel").expect("the second token must survive");
match set_token.shadow {
ShadowOverride::Set(shadow) => {
assert_eq!((shadow.x, shadow.y, shadow.blur), (1, 2, 3));
assert_eq!(shadow.color, Color::rgb(9, 9, 9));
}
other => panic!("expected a shadow to survive, got {other:?}"),
}
assert!(parsed.overrides.styles.contains_key("button:hover"));
let mut manager = ThemeManager::new();
manager.register_theme(parsed);
assert!(manager.set_theme("default"));
let hovered = manager.resolve_style_for_state("button", Some(WidgetState::Hover));
assert_eq!(hovered.background_color, Some(Color::rgb(7, 8, 9)));
let with_shadow = manager.resolve_style_for_state("panel", None);
assert!(with_shadow.shadow.is_some(), "`Set` must install a shadow");
let cleared = manager.resolve_style_for_state("button", None);
assert_eq!(cleared.shadow, None, "`None` must remove the shadow");
}
}