use std::fs;
use std::path::Path;
use thiserror::Error;
use crate::templates::justfile::{generate_justfile, JustfileTemplate};
#[derive(Debug, Error)]
pub enum InitError {
#[error("Directory already exists: {0}")]
DirectoryExists(String),
#[error("Failed to create directory: {0}")]
CreateDir(std::io::Error),
#[error("Failed to write file: {0}")]
WriteFile(std::io::Error),
#[error("Unknown preset '{0}'. Available: minimal, artist, animator, game")]
UnknownPreset(String),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Preset {
Minimal,
Artist,
Animator,
Game,
}
impl Preset {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"minimal" => Some(Preset::Minimal),
"artist" => Some(Preset::Artist),
"animator" => Some(Preset::Animator),
"game" => Some(Preset::Game),
_ => None,
}
}
}
pub fn init_project(path: &Path, name: &str, preset: &str) -> Result<(), InitError> {
let preset =
Preset::from_str(preset).ok_or_else(|| InitError::UnknownPreset(preset.to_string()))?;
if path.exists() {
let is_empty = path.read_dir().map(|mut d| d.next().is_none()).unwrap_or(false);
if !is_empty {
return Err(InitError::DirectoryExists(path.display().to_string()));
}
}
match preset {
Preset::Minimal => init_minimal(path, name),
Preset::Artist => init_artist(path, name),
Preset::Animator => init_animator(path, name),
Preset::Game => init_game(path, name),
}
}
fn init_minimal(path: &Path, name: &str) -> Result<(), InitError> {
create_dir(path)?;
create_dir(&path.join("src/pxl/palettes"))?;
create_dir(&path.join("src/pxl/sprites"))?;
create_dir(&path.join("build"))?;
let pxl_toml = generate_minimal_config(name);
write_file(&path.join("pxl.toml"), &pxl_toml)?;
let gitignore = generate_gitignore();
write_file(&path.join(".gitignore"), &gitignore)?;
let justfile = generate_justfile(JustfileTemplate::Minimal, name);
write_file(&path.join("justfile"), &justfile)?;
let palette = generate_main_palette();
write_file(&path.join("src/pxl/palettes/main.pxl"), &palette)?;
let sprite = generate_example_sprite();
write_file(&path.join("src/pxl/sprites/example.pxl"), &sprite)?;
write_file(&path.join("build/.gitkeep"), "")?;
Ok(())
}
fn create_dir(path: &Path) -> Result<(), InitError> {
fs::create_dir_all(path).map_err(InitError::CreateDir)
}
fn write_file(path: &Path, content: &str) -> Result<(), InitError> {
fs::write(path, content).map_err(InitError::WriteFile)
}
fn generate_minimal_config(name: &str) -> String {
format!(
r#"[project]
name = "{}"
version = "0.1.0"
"#,
name
)
}
fn generate_gitignore() -> String {
r#"# Pixelsrc build output
build/
# OS files
.DS_Store
Thumbs.db
"#
.to_string()
}
fn generate_main_palette() -> String {
r##"{"type": "palette", "name": "main", "colors": {"{_}": "#00000000", "{black}": "#000000", "{white}": "#FFFFFF", "{primary}": "#4A90D9", "{secondary}": "#D94A4A"}}"##.to_string()
}
fn generate_example_sprite() -> String {
r#"{"type": "sprite", "name": "example", "palette": "main", "grid": [
"{_}{primary}{primary}{_}",
"{primary}{white}{white}{primary}",
"{primary}{white}{white}{primary}",
"{_}{primary}{primary}{_}"
]}"#
.to_string()
}
fn init_artist(path: &Path, name: &str) -> Result<(), InitError> {
create_dir(path)?;
create_dir(&path.join("src/pxl/palettes"))?;
create_dir(&path.join("src/pxl/sprites"))?;
create_dir(&path.join("src/pxl/variants"))?;
create_dir(&path.join("build"))?;
let pxl_toml = generate_artist_config(name);
write_file(&path.join("pxl.toml"), &pxl_toml)?;
let gitignore = generate_gitignore();
write_file(&path.join(".gitignore"), &gitignore)?;
let justfile = generate_justfile(JustfileTemplate::Artist, name);
write_file(&path.join("justfile"), &justfile)?;
let main_palette = generate_main_palette();
write_file(&path.join("src/pxl/palettes/main.pxl"), &main_palette)?;
let alt_palette = generate_alt_palette();
write_file(&path.join("src/pxl/palettes/alt.pxl"), &alt_palette)?;
let sprite = generate_character_sprite();
write_file(&path.join("src/pxl/sprites/character.pxl"), &sprite)?;
let variant = generate_variant_example();
write_file(&path.join("src/pxl/variants/character_alt.pxl"), &variant)?;
write_file(&path.join("build/.gitkeep"), "")?;
Ok(())
}
fn generate_artist_config(name: &str) -> String {
format!(
r#"[project]
name = "{}"
version = "0.1.0"
[defaults]
scale = 1
padding = 0
[validate]
strict = false
unused_palettes = "warn"
"#,
name
)
}
fn generate_alt_palette() -> String {
r##"{"type": "palette", "name": "alt", "colors": {"{_}": "#00000000", "{black}": "#1a1a2e", "{white}": "#eaeaea", "{primary}": "#e94560", "{secondary}": "#533483"}}"##.to_string()
}
fn generate_character_sprite() -> String {
r#"{"type": "sprite", "name": "character", "palette": "main", "grid": [
"{_}{_}{primary}{primary}{_}{_}",
"{_}{primary}{white}{white}{primary}{_}",
"{primary}{white}{black}{black}{white}{primary}",
"{primary}{white}{white}{white}{white}{primary}",
"{_}{primary}{secondary}{secondary}{primary}{_}",
"{_}{_}{primary}{primary}{_}{_}"
]}"#
.to_string()
}
fn generate_variant_example() -> String {
r#"{"type": "sprite", "name": "character_alt", "palette": "alt", "grid": [
"{_}{_}{primary}{primary}{_}{_}",
"{_}{primary}{white}{white}{primary}{_}",
"{primary}{white}{black}{black}{white}{primary}",
"{primary}{white}{white}{white}{white}{primary}",
"{_}{primary}{secondary}{secondary}{primary}{_}",
"{_}{_}{primary}{primary}{_}{_}"
]}"#
.to_string()
}
fn init_animator(path: &Path, name: &str) -> Result<(), InitError> {
create_dir(path)?;
create_dir(&path.join("src/pxl/palettes"))?;
create_dir(&path.join("src/pxl/sprites"))?;
create_dir(&path.join("src/pxl/animations"))?;
create_dir(&path.join("build/preview"))?;
let pxl_toml = generate_animator_config(name);
write_file(&path.join("pxl.toml"), &pxl_toml)?;
let gitignore = generate_gitignore();
write_file(&path.join(".gitignore"), &gitignore)?;
let justfile = generate_justfile(JustfileTemplate::Animator, name);
write_file(&path.join("justfile"), &justfile)?;
let palette = generate_main_palette();
write_file(&path.join("src/pxl/palettes/main.pxl"), &palette)?;
let frames = generate_animation_frames();
write_file(&path.join("src/pxl/sprites/walk.pxl"), &frames)?;
let animation = generate_animation_definition();
write_file(&path.join("src/pxl/animations/walk.pxl"), &animation)?;
write_file(&path.join("build/.gitkeep"), "")?;
Ok(())
}
fn generate_animator_config(name: &str) -> String {
format!(
r#"[project]
name = "{}"
version = "0.1.0"
[defaults]
scale = 2
padding = 0
[animations]
sources = ["animations/**"]
preview = true
preview_scale = 4
[watch]
debounce_ms = 100
clear_screen = true
"#,
name
)
}
fn generate_animation_frames() -> String {
r##"{"type": "palette", "name": "walk", "colors": {"{_}": "#00000000", "{b}": "#2d2d2d", "{s}": "#4a90d9", "{h}": "#ffcc00"}}
{"type": "sprite", "name": "walk_1", "palette": "walk", "grid": ["{_}{h}{h}{_}", "{_}{s}{s}{_}", "{b}{s}{s}{b}", "{_}{b}{b}{_}"]}
{"type": "sprite", "name": "walk_2", "palette": "walk", "grid": ["{_}{h}{h}{_}", "{_}{s}{s}{_}", "{_}{s}{s}{_}", "{b}{_}{_}{b}"]}
{"type": "sprite", "name": "walk_3", "palette": "walk", "grid": ["{_}{h}{h}{_}", "{_}{s}{s}{_}", "{b}{s}{s}{b}", "{_}{b}{b}{_}"]}
{"type": "sprite", "name": "walk_4", "palette": "walk", "grid": ["{_}{h}{h}{_}", "{_}{s}{s}{_}", "{_}{s}{s}{_}", "{b}{_}{_}{b}"]}"##
.to_string()
}
fn generate_animation_definition() -> String {
r#"{"type": "include", "path": "../sprites/walk.pxl"}
{"type": "animation", "name": "walk_cycle", "frames": ["walk_1", "walk_2", "walk_3", "walk_4"], "duration": 150}"#
.to_string()
}
fn init_game(path: &Path, name: &str) -> Result<(), InitError> {
create_dir(path)?;
create_dir(&path.join("src/pxl/palettes"))?;
create_dir(&path.join("src/pxl/sprites/player"))?;
create_dir(&path.join("src/pxl/sprites/items"))?;
create_dir(&path.join("src/pxl/animations"))?;
create_dir(&path.join("src/pxl/ui"))?;
create_dir(&path.join("build/atlases"))?;
create_dir(&path.join("build/preview"))?;
let pxl_toml = generate_game_config(name);
write_file(&path.join("pxl.toml"), &pxl_toml)?;
let gitignore = generate_gitignore();
write_file(&path.join(".gitignore"), &gitignore)?;
let justfile = generate_justfile(JustfileTemplate::Game, name);
write_file(&path.join("justfile"), &justfile)?;
let main_palette = generate_main_palette();
write_file(&path.join("src/pxl/palettes/main.pxl"), &main_palette)?;
let ui_palette = generate_ui_palette();
write_file(&path.join("src/pxl/palettes/ui.pxl"), &ui_palette)?;
let player = generate_player_sprites();
write_file(&path.join("src/pxl/sprites/player/idle.pxl"), &player)?;
let items = generate_item_sprites();
write_file(&path.join("src/pxl/sprites/items/coin.pxl"), &items)?;
let animation = generate_player_animation();
write_file(&path.join("src/pxl/animations/player.pxl"), &animation)?;
let ui = generate_ui_elements();
write_file(&path.join("src/pxl/ui/button.pxl"), &ui)?;
write_file(&path.join("build/.gitkeep"), "")?;
Ok(())
}
fn generate_game_config(name: &str) -> String {
format!(
r#"[project]
name = "{}"
version = "0.1.0"
src = "src/pxl"
out = "build"
[defaults]
scale = 1
padding = 1
[atlases.sprites]
sources = ["sprites/**"]
max_size = [1024, 1024]
padding = 1
power_of_two = true
[atlases.ui]
sources = ["ui/**"]
max_size = [512, 512]
padding = 0
[animations]
sources = ["animations/**"]
preview = true
preview_scale = 4
sheet_layout = "horizontal"
[export.generic]
enabled = true
[validate]
strict = true
unused_palettes = "warn"
missing_refs = "error"
[watch]
debounce_ms = 100
clear_screen = true
"#,
name
)
}
fn generate_ui_palette() -> String {
r##"{"type": "palette", "name": "ui", "colors": {"{_}": "#00000000", "{bg}": "#1a1a2e", "{border}": "#4a4a6a", "{text}": "#ffffff", "{accent}": "#e94560"}}"##.to_string()
}
fn generate_player_sprites() -> String {
r#"{"type": "sprite", "name": "player_idle_1", "palette": "main", "grid": [
"{_}{primary}{primary}{_}",
"{primary}{white}{white}{primary}",
"{_}{primary}{primary}{_}",
"{_}{black}{black}{_}"
]}
{"type": "sprite", "name": "player_idle_2", "palette": "main", "grid": [
"{_}{primary}{primary}{_}",
"{primary}{white}{white}{primary}",
"{_}{primary}{primary}{_}",
"{black}{_}{_}{black}"
]}"#
.to_string()
}
fn generate_item_sprites() -> String {
r##"{"type": "palette", "name": "items", "colors": {"{_}": "#00000000", "{gold}": "#FFD700", "{shine}": "#FFFFE0"}}
{"type": "sprite", "name": "coin", "palette": "items", "grid": ["{_}{gold}{gold}{_}", "{gold}{shine}{gold}{gold}", "{gold}{gold}{shine}{gold}", "{_}{gold}{gold}{_}"]}"##
.to_string()
}
fn generate_player_animation() -> String {
r#"{"type": "include", "path": "../sprites/player/idle.pxl"}
{"type": "animation", "name": "player_idle", "frames": ["player_idle_1", "player_idle_2"], "duration": 500}"#
.to_string()
}
fn generate_ui_elements() -> String {
r#"{"type": "sprite", "name": "button_normal", "palette": "ui", "grid": [
"{border}{border}{border}{border}{border}{border}",
"{border}{bg}{bg}{bg}{bg}{border}",
"{border}{bg}{text}{text}{bg}{border}",
"{border}{bg}{bg}{bg}{bg}{border}",
"{border}{border}{border}{border}{border}{border}"
]}
{"type": "sprite", "name": "button_hover", "palette": "ui", "grid": [
"{accent}{accent}{accent}{accent}{accent}{accent}",
"{accent}{bg}{bg}{bg}{bg}{accent}",
"{accent}{bg}{text}{text}{bg}{accent}",
"{accent}{bg}{bg}{bg}{bg}{accent}",
"{accent}{accent}{accent}{accent}{accent}{accent}"
]}"#
.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_preset_from_str() {
assert_eq!(Preset::from_str("minimal"), Some(Preset::Minimal));
assert_eq!(Preset::from_str("MINIMAL"), Some(Preset::Minimal));
assert_eq!(Preset::from_str("artist"), Some(Preset::Artist));
assert_eq!(Preset::from_str("animator"), Some(Preset::Animator));
assert_eq!(Preset::from_str("game"), Some(Preset::Game));
assert_eq!(Preset::from_str("unknown"), None);
}
#[test]
fn test_init_minimal_creates_structure() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("test-project");
init_project(&project_path, "test-project", "minimal").unwrap();
assert!(project_path.join("src/pxl/palettes").exists());
assert!(project_path.join("src/pxl/sprites").exists());
assert!(project_path.join("build").exists());
assert!(project_path.join("pxl.toml").exists());
assert!(project_path.join(".gitignore").exists());
assert!(project_path.join("justfile").exists());
assert!(project_path.join("src/pxl/palettes/main.pxl").exists());
assert!(project_path.join("src/pxl/sprites/example.pxl").exists());
assert!(project_path.join("build/.gitkeep").exists());
}
#[test]
fn test_init_minimal_justfile_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("test");
init_project(&project_path, "test", "minimal").unwrap();
let justfile = fs::read_to_string(project_path.join("justfile")).unwrap();
assert!(justfile.contains("render:"));
assert!(justfile.contains("validate:"));
assert!(justfile.contains("clean:"));
}
#[test]
fn test_init_minimal_config_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("my-game");
init_project(&project_path, "my-game", "minimal").unwrap();
let config = fs::read_to_string(project_path.join("pxl.toml")).unwrap();
assert!(config.contains("name = \"my-game\""));
assert!(config.contains("version = \"0.1.0\""));
}
#[test]
fn test_init_minimal_palette_valid_json() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("test");
init_project(&project_path, "test", "minimal").unwrap();
let palette = fs::read_to_string(project_path.join("src/pxl/palettes/main.pxl")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&palette).unwrap();
assert_eq!(parsed["type"], "palette");
assert_eq!(parsed["name"], "main");
}
#[test]
fn test_init_minimal_sprite_valid_json() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("test");
init_project(&project_path, "test", "minimal").unwrap();
let sprite = fs::read_to_string(project_path.join("src/pxl/sprites/example.pxl")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&sprite).unwrap();
assert_eq!(parsed["type"], "sprite");
assert_eq!(parsed["name"], "example");
assert_eq!(parsed["palette"], "main");
}
#[test]
fn test_init_gitignore_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("test");
init_project(&project_path, "test", "minimal").unwrap();
let gitignore = fs::read_to_string(project_path.join(".gitignore")).unwrap();
assert!(gitignore.contains("build/"));
assert!(gitignore.contains(".DS_Store"));
}
#[test]
fn test_init_existing_non_empty_dir_fails() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("existing");
fs::create_dir_all(&project_path).unwrap();
fs::write(project_path.join("some-file.txt"), "content").unwrap();
let result = init_project(&project_path, "existing", "minimal");
assert!(matches!(result, Err(InitError::DirectoryExists(_))));
}
#[test]
fn test_init_existing_empty_dir_succeeds() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("empty");
fs::create_dir_all(&project_path).unwrap();
let result = init_project(&project_path, "empty", "minimal");
assert!(result.is_ok());
}
#[test]
fn test_init_unknown_preset_fails() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("test");
let result = init_project(&project_path, "test", "nonexistent");
assert!(matches!(result, Err(InitError::UnknownPreset(_))));
}
#[test]
fn test_init_in_current_dir() {
let temp = TempDir::new().unwrap();
init_project(temp.path(), "current", "minimal").unwrap();
assert!(temp.path().join("pxl.toml").exists());
assert!(temp.path().join("src/pxl").exists());
}
#[test]
fn test_init_artist_creates_structure() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("art-project");
init_project(&project_path, "art-project", "artist").unwrap();
assert!(project_path.join("src/pxl/palettes").exists());
assert!(project_path.join("src/pxl/sprites").exists());
assert!(project_path.join("src/pxl/variants").exists());
assert!(project_path.join("build").exists());
assert!(project_path.join("pxl.toml").exists());
assert!(project_path.join(".gitignore").exists());
assert!(project_path.join("justfile").exists());
assert!(project_path.join("src/pxl/palettes/main.pxl").exists());
assert!(project_path.join("src/pxl/palettes/alt.pxl").exists());
assert!(project_path.join("src/pxl/sprites/character.pxl").exists());
assert!(project_path.join("src/pxl/variants/character_alt.pxl").exists());
}
#[test]
fn test_init_artist_justfile_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("art");
init_project(&project_path, "art", "artist").unwrap();
let justfile = fs::read_to_string(project_path.join("justfile")).unwrap();
assert!(justfile.contains("render:"));
assert!(justfile.contains("variants:"));
assert!(justfile.contains("scales:"));
assert!(justfile.contains("clean:"));
}
#[test]
fn test_init_artist_config_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("art");
init_project(&project_path, "art", "artist").unwrap();
let config = fs::read_to_string(project_path.join("pxl.toml")).unwrap();
assert!(config.contains("name = \"art\""));
assert!(config.contains("[defaults]"));
assert!(config.contains("[validate]"));
}
#[test]
fn test_init_artist_palettes_valid_json() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("art");
init_project(&project_path, "art", "artist").unwrap();
let main = fs::read_to_string(project_path.join("src/pxl/palettes/main.pxl")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&main).unwrap();
assert_eq!(parsed["type"], "palette");
assert_eq!(parsed["name"], "main");
let alt = fs::read_to_string(project_path.join("src/pxl/palettes/alt.pxl")).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&alt).unwrap();
assert_eq!(parsed["type"], "palette");
assert_eq!(parsed["name"], "alt");
}
#[test]
fn test_init_animator_creates_structure() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("anim-project");
init_project(&project_path, "anim-project", "animator").unwrap();
assert!(project_path.join("src/pxl/palettes").exists());
assert!(project_path.join("src/pxl/sprites").exists());
assert!(project_path.join("src/pxl/animations").exists());
assert!(project_path.join("build/preview").exists());
assert!(project_path.join("pxl.toml").exists());
assert!(project_path.join(".gitignore").exists());
assert!(project_path.join("justfile").exists());
assert!(project_path.join("src/pxl/palettes/main.pxl").exists());
assert!(project_path.join("src/pxl/sprites/walk.pxl").exists());
assert!(project_path.join("src/pxl/animations/walk.pxl").exists());
}
#[test]
fn test_init_animator_config_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("anim");
init_project(&project_path, "anim", "animator").unwrap();
let config = fs::read_to_string(project_path.join("pxl.toml")).unwrap();
assert!(config.contains("name = \"anim\""));
assert!(config.contains("[animations]"));
assert!(config.contains("preview = true"));
assert!(config.contains("[watch]"));
}
#[test]
fn test_init_animator_justfile_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("anim");
init_project(&project_path, "anim", "animator").unwrap();
let justfile = fs::read_to_string(project_path.join("justfile")).unwrap();
assert!(justfile.contains("preview:"));
assert!(justfile.contains("--gif"));
assert!(justfile.contains("watch:"));
}
#[test]
fn test_init_animator_animation_valid() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("anim");
init_project(&project_path, "anim", "animator").unwrap();
let anim = fs::read_to_string(project_path.join("src/pxl/animations/walk.pxl")).unwrap();
assert!(anim.contains("\"type\": \"include\""));
assert!(anim.contains("\"type\": \"animation\""));
assert!(anim.contains("walk_cycle"));
}
#[test]
fn test_init_game_creates_structure() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("game-project");
init_project(&project_path, "game-project", "game").unwrap();
assert!(project_path.join("src/pxl/palettes").exists());
assert!(project_path.join("src/pxl/sprites/player").exists());
assert!(project_path.join("src/pxl/sprites/items").exists());
assert!(project_path.join("src/pxl/animations").exists());
assert!(project_path.join("src/pxl/ui").exists());
assert!(project_path.join("build/atlases").exists());
assert!(project_path.join("build/preview").exists());
assert!(project_path.join("pxl.toml").exists());
assert!(project_path.join(".gitignore").exists());
assert!(project_path.join("justfile").exists());
assert!(project_path.join("src/pxl/palettes/main.pxl").exists());
assert!(project_path.join("src/pxl/palettes/ui.pxl").exists());
assert!(project_path.join("src/pxl/sprites/player/idle.pxl").exists());
assert!(project_path.join("src/pxl/sprites/items/coin.pxl").exists());
assert!(project_path.join("src/pxl/animations/player.pxl").exists());
assert!(project_path.join("src/pxl/ui/button.pxl").exists());
}
#[test]
fn test_init_game_config_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("game");
init_project(&project_path, "game", "game").unwrap();
let config = fs::read_to_string(project_path.join("pxl.toml")).unwrap();
assert!(config.contains("name = \"game\""));
assert!(config.contains("[atlases.sprites]"));
assert!(config.contains("[atlases.ui]"));
assert!(config.contains("[animations]"));
assert!(config.contains("[export.generic]"));
assert!(config.contains("[validate]"));
assert!(config.contains("strict = true"));
}
#[test]
fn test_init_game_justfile_content() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("game");
init_project(&project_path, "game", "game").unwrap();
let justfile = fs::read_to_string(project_path.join("justfile")).unwrap();
assert!(justfile.contains("build: validate"));
assert!(justfile.contains("atlas-sprites:"));
assert!(justfile.contains("atlas-ui:"));
assert!(justfile.contains("ci: fmt-check validate build"));
}
#[test]
fn test_init_game_sprites_valid_json() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("game");
init_project(&project_path, "game", "game").unwrap();
let player =
fs::read_to_string(project_path.join("src/pxl/sprites/player/idle.pxl")).unwrap();
assert!(player.contains("\"type\": \"sprite\""));
assert!(player.contains("player_idle_1"));
assert!(player.contains("player_idle_2"));
let coin = fs::read_to_string(project_path.join("src/pxl/sprites/items/coin.pxl")).unwrap();
assert!(coin.contains("\"type\": \"palette\""));
assert!(coin.contains("\"type\": \"sprite\""));
assert!(coin.contains("coin"));
}
#[test]
fn test_init_game_ui_valid_json() {
let temp = TempDir::new().unwrap();
let project_path = temp.path().join("game");
init_project(&project_path, "game", "game").unwrap();
let ui = fs::read_to_string(project_path.join("src/pxl/ui/button.pxl")).unwrap();
assert!(ui.contains("button_normal"));
assert!(ui.contains("button_hover"));
}
}