use pixelsrc::explain::{
explain_animation, explain_composition, explain_palette, explain_sprite, format_explanation,
resolve_palette_colors, Explanation,
};
use pixelsrc::models::{Animation, Composition, PaletteRef, Sprite};
use std::collections::HashMap;
fn make_sprite(name: &str, grid: Vec<&str>, palette: PaletteRef) -> Sprite {
Sprite {
name: name.to_string(),
size: None,
palette,
grid: grid.into_iter().map(String::from).collect(),
source: None,
transform: None,
metadata: None,
}
}
#[test]
fn test_explain_basic_sprite() {
let sprite = make_sprite(
"star",
vec!["{_}{y}{_}", "{y}{y}{y}", "{_}{y}{_}"],
PaletteRef::Inline(HashMap::from([
("{_}".to_string(), "#00000000".to_string()),
("{y}".to_string(), "#FFD700".to_string()),
])),
);
let palette_colors = resolve_palette_colors(&sprite.palette, &HashMap::new());
let explanation = explain_sprite(&sprite, palette_colors.as_ref());
assert_eq!(explanation.name, "star");
assert_eq!(explanation.width, 3);
assert_eq!(explanation.height, 3);
assert_eq!(explanation.total_cells, 9);
assert!(
explanation.tokens.len() >= 2,
"Should list all tokens used"
);
}
#[test]
fn test_explain_token_usage() {
let sprite = make_sprite(
"checker",
vec![
"{a}{b}{a}{b}",
"{b}{a}{b}{a}",
"{a}{b}{a}{b}",
"{b}{a}{b}{a}",
],
PaletteRef::Inline(HashMap::from([
("{a}".to_string(), "#FF0000".to_string()),
("{b}".to_string(), "#00FF00".to_string()),
])),
);
let palette_colors = resolve_palette_colors(&sprite.palette, &HashMap::new());
let explanation = explain_sprite(&sprite, palette_colors.as_ref());
for token in &explanation.tokens {
assert!(
(token.percentage - 50.0).abs() < 1.0,
"Each token should be ~50% in a checkerboard"
);
assert_eq!(token.count, 8, "Each token should appear 8 times in 4x4");
}
}
#[test]
fn test_explain_transparency_ratio() {
let sprite = make_sprite(
"diamond",
vec!["{_}{x}{_}", "{x}{x}{x}", "{_}{x}{_}"],
PaletteRef::Inline(HashMap::from([
("{_}".to_string(), "#00000000".to_string()),
("{x}".to_string(), "#FF0000".to_string()),
])),
);
let palette_colors = resolve_palette_colors(&sprite.palette, &HashMap::new());
let explanation = explain_sprite(&sprite, palette_colors.as_ref());
assert_eq!(explanation.transparent_count, 4);
let expected_ratio = 4.0 / 9.0 * 100.0;
assert!(
(explanation.transparency_ratio - expected_ratio).abs() < 1.0,
"Transparency ratio should be ~44.4%"
);
}
#[test]
fn test_explain_palette() {
let colors = HashMap::from([
("{_}".to_string(), "#00000000".to_string()),
("{skin}".to_string(), "#FFD5B4".to_string()),
("{hair}".to_string(), "#8B4513".to_string()),
("{shirt}".to_string(), "#4169E1".to_string()),
]);
let explanation = explain_palette("hero_colors", &colors);
assert_eq!(explanation.name, "hero_colors");
assert_eq!(explanation.color_count, 4);
assert!(!explanation.is_builtin);
let token_names: Vec<_> = explanation.colors.iter().map(|(t, _, _)| t.clone()).collect();
assert!(token_names.contains(&"{skin}".to_string()));
assert!(token_names.contains(&"{hair}".to_string()));
}
#[test]
fn test_explain_animation() {
let animation = Animation {
name: "walk_cycle".to_string(),
frames: vec![
"walk_1".to_string(),
"walk_2".to_string(),
"walk_3".to_string(),
"walk_4".to_string(),
],
keyframes: None,
source: None,
transform: None,
duration: Some(pixelsrc::models::Duration::Milliseconds(150)),
timing_function: None,
r#loop: None,
palette_cycle: None,
tags: None,
frame_metadata: None,
attachments: None,
};
let explanation = explain_animation(&animation);
assert_eq!(explanation.name, "walk_cycle");
assert_eq!(explanation.frame_count, 4);
assert_eq!(explanation.duration_ms, 150);
assert_eq!(explanation.frames, vec!["walk_1", "walk_2", "walk_3", "walk_4"]);
}
#[test]
fn test_explain_composition() {
let composition = Composition {
name: "scene".to_string(),
base: Some("background".to_string()),
size: Some([64, 64]),
cell_size: Some([8, 8]),
sprites: HashMap::from([
("a".to_string(), Some("player".to_string())),
("b".to_string(), Some("enemy".to_string())),
]),
layers: vec![],
};
let explanation = explain_composition(&composition);
assert_eq!(explanation.name, "scene");
assert_eq!(explanation.base, Some("background".to_string()));
assert_eq!(explanation.size, Some([64, 64]));
assert_eq!(explanation.sprite_count, 2);
}
#[test]
fn test_format_explanation() {
let sprite = make_sprite(
"coin",
vec![
"{_}{g}{g}{_}",
"{g}{g}{g}{g}",
"{g}{g}{g}{g}",
"{_}{g}{g}{_}",
],
PaletteRef::Inline(HashMap::from([
("{_}".to_string(), "#0000".to_string()),
("{g}".to_string(), "#FFD700".to_string()),
])),
);
let palette_colors = resolve_palette_colors(&sprite.palette, &HashMap::new());
let explanation = explain_sprite(&sprite, palette_colors.as_ref());
let formatted = format_explanation(&Explanation::Sprite(explanation));
assert!(formatted.contains("coin"), "Should mention sprite name");
assert!(formatted.contains("4") || formatted.contains("4x4"), "Should show dimensions");
}
#[test]
fn test_explain_inline_vs_named_palette() {
let sprite_inline = make_sprite(
"inline_test",
vec!["{x}"],
PaletteRef::Inline(HashMap::from([
("{x}".to_string(), "#FF0000".to_string()),
])),
);
let palette_colors = resolve_palette_colors(&sprite_inline.palette, &HashMap::new());
let explanation = explain_sprite(&sprite_inline, palette_colors.as_ref());
assert_eq!(explanation.palette_ref, "inline");
let sprite_named = make_sprite(
"named_test",
vec!["{x}"],
PaletteRef::Named("my_palette".to_string()),
);
let empty_palettes = HashMap::new();
let palette_colors = resolve_palette_colors(&sprite_named.palette, &empty_palettes);
let explanation = explain_sprite(&sprite_named, palette_colors.as_ref());
assert_eq!(explanation.palette_ref, "my_palette");
}
#[test]
fn test_explain_inconsistent_rows() {
let sprite = make_sprite(
"uneven",
vec![
"{x}{x}{x}", "{x}{x}", "{x}{x}{x}", ],
PaletteRef::Inline(HashMap::from([
("{x}".to_string(), "#FF0000".to_string()),
])),
);
let palette_colors = resolve_palette_colors(&sprite.palette, &HashMap::new());
let explanation = explain_sprite(&sprite, palette_colors.as_ref());
assert!(
!explanation.consistent_rows,
"Should detect inconsistent row widths"
);
}