use pixelsrc::scaffold::{new_animation, new_palette, new_sprite, ScaffoldError};
use serial_test::serial;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
fn setup_test_project(temp: &TempDir) -> PathBuf {
let project_path = temp.path().to_path_buf();
let config = r#"[project]
name = "test"
version = "0.1.0"
"#;
fs::write(project_path.join("pxl.toml"), config).unwrap();
fs::create_dir_all(project_path.join("src/pxl/sprites")).unwrap();
fs::create_dir_all(project_path.join("src/pxl/animations")).unwrap();
fs::create_dir_all(project_path.join("src/pxl/palettes")).unwrap();
project_path
}
fn in_project<F, R>(temp: &TempDir, f: F) -> R
where
F: FnOnce() -> R,
{
let project_path = setup_test_project(temp);
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&project_path).unwrap();
let result = f();
std::env::set_current_dir(original_dir).unwrap();
result
}
#[test]
#[serial]
fn test_new_sprite_basic() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_sprite("hero", None));
assert!(result.is_ok(), "Should create sprite: {:?}", result.err());
let path = result.unwrap();
assert!(path.exists(), "Sprite file should exist");
assert!(
path.to_string_lossy().contains("sprites/hero.pxl"),
"Path should contain sprites/hero.pxl"
);
let content = fs::read_to_string(&path).unwrap();
assert!(content.contains("\"type\": \"sprite\""));
assert!(content.contains("\"name\": \"hero\""));
assert!(content.contains("\"grid\""));
}
#[test]
#[serial]
fn test_new_sprite_with_palette() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_sprite("enemy", Some("enemies")));
assert!(result.is_ok(), "Should create sprite: {:?}", result.err());
let path = result.unwrap();
let content = fs::read_to_string(&path).unwrap();
assert!(
content.contains("\"palette\": \"enemies\""),
"Should reference enemies palette"
);
}
#[test]
#[serial]
fn test_new_animation_basic() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_animation("walk", None));
assert!(result.is_ok(), "Should create animation: {:?}", result.err());
let path = result.unwrap();
assert!(path.exists(), "Animation file should exist");
assert!(
path.to_string_lossy().contains("animations/walk.pxl"),
"Path should contain animations/walk.pxl"
);
let content = fs::read_to_string(&path).unwrap();
assert!(content.contains("\"type\": \"animation\""));
assert!(content.contains("\"name\": \"walk\""));
assert!(content.contains("\"frames\""));
assert!(content.contains("\"duration\""));
}
#[test]
#[serial]
fn test_new_animation_includes_frames() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_animation("run", Some("player")));
assert!(result.is_ok());
let path = result.unwrap();
let content = fs::read_to_string(&path).unwrap();
assert!(
content.contains("\"name\": \"run_1\""),
"Should include run_1 frame"
);
assert!(
content.contains("\"name\": \"run_2\""),
"Should include run_2 frame"
);
assert!(content.contains("\"run_1\""));
assert!(content.contains("\"run_2\""));
}
#[test]
#[serial]
fn test_new_palette_basic() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_palette("forest"));
assert!(result.is_ok(), "Should create palette: {:?}", result.err());
let path = result.unwrap();
assert!(path.exists(), "Palette file should exist");
assert!(
path.to_string_lossy().contains("palettes/forest.pxl"),
"Path should contain palettes/forest.pxl"
);
let content = fs::read_to_string(&path).unwrap();
assert!(content.contains("\"type\": \"palette\""));
assert!(content.contains("\"name\": \"forest\""));
assert!(content.contains("\"colors\""));
}
#[test]
#[serial]
fn test_new_palette_starter_colors() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_palette("ui"));
assert!(result.is_ok());
let path = result.unwrap();
let content = fs::read_to_string(&path).unwrap();
assert!(content.contains("\"{_}\""), "Should include transparent");
assert!(content.contains("\"{black}\""), "Should include black");
assert!(content.contains("\"{white}\""), "Should include white");
}
#[test]
#[serial]
fn test_new_not_in_project() {
let temp = TempDir::new().unwrap();
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(temp.path()).unwrap();
let result = new_sprite("test", None);
std::env::set_current_dir(original_dir).unwrap();
match result {
Err(ScaffoldError::NotInProject) => {}
other => panic!("Expected NotInProject error, got: {:?}", other),
}
}
#[test]
#[serial]
fn test_new_file_exists() {
let temp = TempDir::new().unwrap();
let project_path = setup_test_project(&temp);
let sprite_path = project_path.join("src/pxl/sprites/existing.pxl");
fs::write(&sprite_path, "existing content").unwrap();
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&project_path).unwrap();
let result = new_sprite("existing", None);
std::env::set_current_dir(original_dir).unwrap();
match result {
Err(ScaffoldError::FileExists(_)) => {}
other => panic!("Expected FileExists error, got: {:?}", other),
}
}
#[test]
#[serial]
fn test_new_invalid_name() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_sprite("Invalid-Name", None));
match result {
Err(ScaffoldError::InvalidName(name)) => {
assert_eq!(name, "Invalid-Name");
}
other => panic!("Expected InvalidName error, got: {:?}", other),
}
}
#[test]
#[serial]
fn test_valid_asset_names() {
let valid_names = ["hero", "player_idle", "sprite1", "a", "walk_cycle_1"];
for name in valid_names.iter() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_sprite(name, None));
assert!(
result.is_ok(),
"Name '{}' should be valid: {:?}",
name,
result.err()
);
}
}
#[test]
#[serial]
fn test_invalid_asset_names() {
let invalid_names = [
("", "empty"),
("Hero", "uppercase"),
("1sprite", "starts with number"),
("_underscore", "starts with underscore"),
("my-sprite", "contains hyphen"),
("my sprite", "contains space"),
];
for (name, reason) in invalid_names.iter() {
let temp = TempDir::new().unwrap();
let result = in_project(&temp, || new_sprite(name, None));
assert!(
matches!(result, Err(ScaffoldError::InvalidName(_))),
"Name '{}' ({}) should be invalid",
name,
reason
);
}
}