use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
fn dampen_cmd() -> Command {
Command::cargo_bin("dampen").unwrap()
}
#[test]
fn test_new_creates_project_structure() {
let temp = TempDir::new().unwrap();
let project_name = "test-app";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Created new Dampen project"));
let project_path = temp.path().join(project_name);
assert!(project_path.exists(), "Project directory should exist");
assert!(project_path.is_dir(), "Project path should be a directory");
assert!(
project_path.join("Cargo.toml").exists(),
"Cargo.toml should exist"
);
assert!(
project_path.join("README.md").exists(),
"README.md should exist"
);
assert!(
project_path.join("src").is_dir(),
"src/ directory should exist"
);
assert!(
project_path.join("src/main.rs").exists(),
"src/main.rs should exist"
);
assert!(
project_path.join("src/ui").is_dir(),
"src/ui/ directory should exist"
);
assert!(
project_path.join("src/ui/mod.rs").exists(),
"src/ui/mod.rs should exist"
);
assert!(
project_path.join("src/ui/window.rs").exists(),
"src/ui/window.rs should exist"
);
assert!(
project_path.join("src/ui/window.dampen").exists(),
"src/ui/window.dampen should exist"
);
assert!(
project_path.join("tests").is_dir(),
"tests/ directory should exist"
);
assert!(
project_path.join("tests/integration.rs").exists(),
"tests/integration.rs should exist"
);
assert!(
project_path.join("build.rs").exists(),
"build.rs should exist for production builds"
);
}
#[test]
fn test_new_substitutes_project_name_in_cargo_toml() {
let temp = TempDir::new().unwrap();
let project_name = "my-cool-app";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let cargo_toml_path = temp.path().join(project_name).join("Cargo.toml");
let cargo_toml = fs::read_to_string(cargo_toml_path).unwrap();
assert!(cargo_toml.contains(&format!("name = \"{}\"", project_name)));
assert!(cargo_toml.contains("dampen-core"));
assert!(cargo_toml.contains("dampen-macros"));
assert!(cargo_toml.contains("dampen-iced"));
assert!(cargo_toml.contains("serde_json"));
assert!(
cargo_toml.contains("build = \"build.rs\""),
"Cargo.toml should reference build.rs"
);
}
#[test]
fn test_new_substitutes_project_name_in_readme() {
let temp = TempDir::new().unwrap();
let project_name = "readme-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let readme_path = temp.path().join(project_name).join("README.md");
let readme = fs::read_to_string(readme_path).unwrap();
assert!(readme.contains(&format!("# {}", project_name)));
assert!(readme.contains("Quick Start"));
assert!(readme.contains("dampen run"));
assert!(readme.contains("src/ui/window.dampen"));
}
#[test]
fn test_new_creates_valid_xml() {
let temp = TempDir::new().unwrap();
let project_name = "valid-xml-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let xml_file = temp.path().join(project_name).join("src/ui/window.dampen");
let xml_content = fs::read_to_string(xml_file).unwrap();
assert!(!xml_content.contains("<?xml"));
assert!(xml_content.contains(r#"<dampen version="1.1" encoding="utf-8">"#));
assert!(xml_content.contains("</dampen>"));
assert!(xml_content.contains("<column"));
assert!(xml_content.contains("</column>"));
assert!(xml_content.contains("Hello, Dampen!"));
}
#[test]
fn test_new_rejects_empty_name() {
let temp = TempDir::new().unwrap();
dampen_cmd()
.arg("new")
.arg("")
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains("Project name cannot be empty"));
}
#[test]
fn test_new_rejects_name_starting_with_number() {
let temp = TempDir::new().unwrap();
dampen_cmd()
.arg("new")
.arg("123invalid")
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains(
"must start with a letter or underscore",
));
}
#[test]
fn test_new_rejects_name_with_special_chars() {
let temp = TempDir::new().unwrap();
dampen_cmd()
.arg("new")
.arg("my@app")
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains(
"can only contain letters, numbers, hyphens, and underscores",
));
}
#[test]
fn test_new_rejects_name_with_spaces() {
let temp = TempDir::new().unwrap();
dampen_cmd()
.arg("new")
.arg("my app")
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains(
"can only contain letters, numbers, hyphens, and underscores",
));
}
#[test]
fn test_new_rejects_reserved_names() {
let temp = TempDir::new().unwrap();
for reserved in &["test", "build", "target", "src"] {
dampen_cmd()
.arg("new")
.arg(reserved)
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains("is a reserved name"));
}
}
#[test]
fn test_new_accepts_valid_names() {
let temp = TempDir::new().unwrap();
let valid_names = vec![
"my-app", "my_app", "MyApp", "app123", "_private", "a", "a-b-c",
];
for (i, name) in valid_names.iter().enumerate() {
let unique_name = format!("{}-{}", name, i);
dampen_cmd()
.arg("new")
.arg(&unique_name)
.current_dir(temp.path())
.assert()
.success();
}
}
#[test]
fn test_new_detects_existing_directory() {
let temp = TempDir::new().unwrap();
let project_name = "existing";
fs::create_dir(temp.path().join(project_name)).unwrap();
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.failure()
.stderr(predicate::str::contains("already exists"));
}
#[test]
fn test_new_creates_valid_rust_code() {
let temp = TempDir::new().unwrap();
let project_name = "valid-code-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let project_path = temp.path().join(project_name);
let main_rs = fs::read_to_string(project_path.join("src/main.rs")).unwrap();
assert!(main_rs.contains("mod ui;"));
assert!(main_rs.contains("#[dampen_app("));
assert!(main_rs.contains("DampenApp"));
assert!(main_rs.contains("pub fn main() -> iced::Result"));
let window_rs = fs::read_to_string(project_path.join("src/ui/window.rs")).unwrap();
assert!(window_rs.contains("pub struct Model"));
assert!(
window_rs.contains("#[derive(Default, UiModel, Serialize, Deserialize, Clone, Debug)]")
);
assert!(window_rs.contains("#[dampen_ui(\"window.dampen\")]"));
assert!(window_rs.contains("HandlerRegistry::new"));
}
#[test]
fn test_new_creates_ui_mod_exports() {
let temp = TempDir::new().unwrap();
let project_name = "ui-mod-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let project_path = temp.path().join(project_name);
let mod_rs = fs::read_to_string(project_path.join("src/ui/mod.rs")).unwrap();
assert!(mod_rs.contains("pub mod window;"));
}
#[test]
fn test_new_creates_integration_tests() {
let temp = TempDir::new().unwrap();
let project_name = "integration-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let project_path = temp.path().join(project_name);
let integration_rs = fs::read_to_string(project_path.join("tests/integration.rs")).unwrap();
assert!(integration_rs.contains("#[test]"));
assert!(integration_rs.contains("parse(xml)") || integration_rs.contains("parse("));
}
#[test]
fn test_new_output_messages() {
let temp = TempDir::new().unwrap();
let project_name = "output-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("Created new Dampen project"))
.stdout(predicate::str::contains("Next steps:"))
.stdout(predicate::str::contains("cd output-test"))
.stdout(predicate::str::contains("dampen run"));
}
#[test]
fn test_new_creates_build_rs_for_production() {
let temp = TempDir::new().unwrap();
let project_name = "prod-build-test";
dampen_cmd()
.arg("new")
.arg(project_name)
.current_dir(temp.path())
.assert()
.success();
let project_path = temp.path().join(project_name);
let build_rs_path = project_path.join("build.rs");
assert!(build_rs_path.exists(), "build.rs should exist");
let build_rs = fs::read_to_string(build_rs_path).unwrap();
assert!(
build_rs.contains("fn main()"),
"build.rs should have main function"
);
assert!(build_rs.contains("OUT_DIR"), "build.rs should use OUT_DIR");
assert!(
build_rs.contains(".dampen"),
"build.rs should reference .dampen files"
);
assert!(
build_rs.contains("src/ui/"),
"build.rs should look in src/ui/"
);
assert!(
build_rs.contains("cargo:rerun-if-changed"),
"build.rs should declare rerun triggers"
);
let cargo_toml = fs::read_to_string(project_path.join("Cargo.toml")).unwrap();
assert!(
cargo_toml.contains("build = \"build.rs\""),
"Cargo.toml should reference build.rs"
);
}