use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
fn basefmt() -> Command {
Command::new(env!("CARGO_BIN_EXE_basefmt"))
}
fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else {
fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}
fn setup_test_file(temp_dir: &TempDir, fixture_name: &str) -> PathBuf {
let input_path = PathBuf::from("tests/fixtures/input").join(fixture_name);
let temp_file = temp_dir.path().join(fixture_name);
fs::copy(&input_path, &temp_file).unwrap();
temp_file
}
fn create_default_editorconfig(dir: &TempDir) {
let config_path = dir.path().join(".editorconfig");
fs::write(
config_path,
r#"root = true
[*]
insert_final_newline = true
trim_trailing_whitespace = true
trim_leading_newlines = true
"#,
)
.unwrap();
}
fn read_expected(fixture_name: &str) -> String {
let expected_path = PathBuf::from("tests/fixtures/expected").join(fixture_name);
fs::read_to_string(expected_path).unwrap()
}
#[test]
fn test_format_single_files() {
let test_cases = [
"leading_newlines.txt",
"no_final_newline.txt",
"trailing_space.txt",
"multiple_final_newlines.txt",
];
for fixture_name in test_cases {
let temp_dir = TempDir::new().unwrap();
create_default_editorconfig(&temp_dir);
let test_file = setup_test_file(&temp_dir, fixture_name);
let status = basefmt().arg(test_file.to_str().unwrap()).status().unwrap();
assert!(status.success(), "Failed to format {fixture_name}");
let actual = fs::read_to_string(&test_file).unwrap();
let expected = read_expected(fixture_name);
assert_eq!(
actual, expected,
"File {fixture_name} was not formatted correctly"
);
}
}
#[test]
fn test_format_directory() {
let temp_dir = TempDir::new().unwrap();
create_default_editorconfig(&temp_dir);
setup_test_file(&temp_dir, "leading_newlines.txt");
setup_test_file(&temp_dir, "no_final_newline.txt");
setup_test_file(&temp_dir, "trailing_space.txt");
setup_test_file(&temp_dir, "multiple_final_newlines.txt");
let status = basefmt()
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
for fixture_name in [
"leading_newlines.txt",
"no_final_newline.txt",
"trailing_space.txt",
"multiple_final_newlines.txt",
] {
let actual = fs::read_to_string(temp_dir.path().join(fixture_name)).unwrap();
let expected = read_expected(fixture_name);
assert_eq!(
actual, expected,
"File {fixture_name} was not formatted correctly"
);
}
}
#[test]
fn test_check_mode_clean_file() {
let temp_dir = TempDir::new().unwrap();
create_default_editorconfig(&temp_dir);
let expected_path = PathBuf::from("tests/fixtures/expected/leading_newlines.txt");
let test_file = temp_dir.path().join("leading_newlines.txt");
fs::copy(&expected_path, &test_file).unwrap();
let status = basefmt()
.arg("--check")
.arg(test_file.to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
}
#[test]
fn test_check_mode_dirty_file() {
let temp_dir = TempDir::new().unwrap();
create_default_editorconfig(&temp_dir);
let test_file = setup_test_file(&temp_dir, "leading_newlines.txt");
let original_content = fs::read_to_string(&test_file).unwrap();
let status = basefmt()
.arg("--check")
.arg(test_file.to_str().unwrap())
.status()
.unwrap();
assert!(!status.success());
let after_check = fs::read_to_string(&test_file).unwrap();
assert_eq!(original_content, after_check);
}
#[test]
fn test_format_skips_binary_file() {
let temp_dir = TempDir::new().unwrap();
let binary_file = temp_dir.path().join("binary.bin");
fs::write(&binary_file, &[0xFF, 0xFE, 0xFD]).unwrap();
let status = basefmt()
.arg(binary_file.to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
assert_eq!(status.code(), Some(0));
let content = fs::read(&binary_file).unwrap();
assert_eq!(content, vec![0xFF, 0xFE, 0xFD]);
}
#[test]
fn test_format_directory_with_binary_file() {
let temp_dir = TempDir::new().unwrap();
let text_file = temp_dir.path().join("text.txt");
fs::write(&text_file, "\n\ntest content \n\n").unwrap();
let binary_file = temp_dir.path().join("binary.bin");
fs::write(&binary_file, &[0xFF, 0xFE, 0xFD]).unwrap();
let status = basefmt()
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
assert_eq!(status.code(), Some(0));
let text_content = fs::read_to_string(&text_file).unwrap();
assert_eq!(text_content, "test content\n");
let binary_content = fs::read(&binary_file).unwrap();
assert_eq!(binary_content, vec![0xFF, 0xFE, 0xFD]);
}
#[test]
fn test_check_skips_binary_file() {
let temp_dir = TempDir::new().unwrap();
let binary_file = temp_dir.path().join("binary.bin");
fs::write(&binary_file, &[0xFF, 0xFE, 0xFD]).unwrap();
let status = basefmt()
.arg("--check")
.arg(binary_file.to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
assert_eq!(status.code(), Some(0));
let content = fs::read(&binary_file).unwrap();
assert_eq!(content, vec![0xFF, 0xFE, 0xFD]);
}
#[test]
fn test_editorconfig_and_exclude_integration() {
let temp_dir = TempDir::new().unwrap();
let fixture_src = PathBuf::from("tests/fixtures/config");
fs::copy(
fixture_src.join(".editorconfig"),
temp_dir.path().join(".editorconfig"),
)
.unwrap();
fs::copy(
fixture_src.join(".basefmt.toml"),
temp_dir.path().join(".basefmt.toml"),
)
.unwrap();
fs::create_dir_all(temp_dir.path().join("test/fixtures")).unwrap();
fs::create_dir_all(temp_dir.path().join("vendor")).unwrap();
fs::create_dir_all(temp_dir.path().join("generated")).unwrap();
fs::write(
temp_dir.path().join("normal.txt"),
"normal file with trailing spaces \n\n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("markdown.md"),
"# Markdown\nTrailing spaces \n \n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("test/fixtures/data.txt"),
"test data with trailing spaces \n\n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("vendor/lib.js"),
"// vendor library with trailing spaces \n\n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("generated/output.rs"),
"// generated code with trailing spaces \n\n\n",
)
.unwrap();
let status = basefmt()
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
let normal_content = fs::read_to_string(temp_dir.path().join("normal.txt")).unwrap();
assert_eq!(
normal_content, "normal file with trailing spaces\n",
"normal.txt should have been formatted"
);
let md_content = fs::read_to_string(temp_dir.path().join("markdown.md")).unwrap();
assert!(
md_content.ends_with(" \n"),
"markdown.md should preserve trailing spaces due to EditorConfig. Got: {md_content:?}"
);
assert_eq!(
md_content, "# Markdown\nTrailing spaces \n \n",
"markdown.md formatting incorrect"
);
let test_fixture_content =
fs::read_to_string(temp_dir.path().join("test/fixtures/data.txt")).unwrap();
assert!(
test_fixture_content.ends_with(" \n\n\n"),
"test/fixtures/data.txt should not be formatted (EditorConfig unset)"
);
let vendor_content = fs::read_to_string(temp_dir.path().join("vendor/lib.js")).unwrap();
assert!(
vendor_content.ends_with(" \n\n\n"),
"vendor/lib.js should not be formatted (EditorConfig unset)"
);
let generated_content =
fs::read_to_string(temp_dir.path().join("generated/output.rs")).unwrap();
assert!(
generated_content.ends_with(" \n\n\n"),
"generated/output.rs should not be formatted (.basefmt.toml exclude)"
);
}
#[test]
fn test_editorconfig_disables_formatting() {
let temp_dir = TempDir::new().unwrap();
let fixture_src = PathBuf::from("tests/fixtures/config");
copy_dir_recursive(&fixture_src, temp_dir.path()).unwrap();
let md_path = temp_dir.path().join("markdown.md");
fs::write(&md_path, "# Test\ntrailing spaces \n").unwrap();
let status = basefmt().arg(md_path.to_str().unwrap()).status().unwrap();
assert!(status.success());
let formatted_content = fs::read_to_string(&md_path).unwrap();
assert!(
formatted_content.contains("trailing spaces "),
"Markdown file should preserve trailing spaces"
);
}
#[test]
fn test_basefmt_exclude_overrides_editorconfig() {
let temp_dir = TempDir::new().unwrap();
let fixture_src = PathBuf::from("tests/fixtures/config");
copy_dir_recursive(&fixture_src, temp_dir.path()).unwrap();
fs::create_dir_all(temp_dir.path().join("generated")).unwrap();
let generated_path = temp_dir.path().join("generated/output.rs");
let original_content = "// generated code with trailing spaces \n\n\n";
fs::write(&generated_path, original_content).unwrap();
let status = basefmt()
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
let after_content = fs::read_to_string(&generated_path).unwrap();
assert_eq!(
original_content, after_content,
"generated/output.rs should not be formatted (excluded by .basefmt.toml)"
);
}
#[test]
fn test_check_mode_with_config() {
let temp_dir = TempDir::new().unwrap();
let fixture_src = PathBuf::from("tests/fixtures/config");
copy_dir_recursive(&fixture_src, temp_dir.path()).unwrap();
let normal_path = temp_dir.path().join("normal.txt");
fs::write(&normal_path, "normal file with trailing spaces \n\n\n").unwrap();
let status = basefmt()
.arg("--check")
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(!status.success());
let normal_content = fs::read_to_string(&normal_path).unwrap();
assert!(
normal_content.ends_with(" \n\n\n"),
"check mode should not modify files"
);
}
#[test]
fn test_editorconfig_unset_disables_formatting() {
let temp_dir = TempDir::new().unwrap();
let fixture_src = PathBuf::from("tests/fixtures/config");
copy_dir_recursive(&fixture_src, temp_dir.path()).unwrap();
fs::create_dir_all(temp_dir.path().join("vendor")).unwrap();
let vendor_path = temp_dir.path().join("vendor/lib.js");
let original_content = "// vendor library with trailing spaces \n\n\n";
fs::write(&vendor_path, original_content).unwrap();
let status = basefmt()
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
let after_content = fs::read_to_string(&vendor_path).unwrap();
assert_eq!(
original_content, after_content,
"vendor/lib.js should not be formatted (EditorConfig unset)"
);
}
#[test]
fn test_multiple_exclusion_patterns() {
let temp_dir = TempDir::new().unwrap();
let fixture_src = PathBuf::from("tests/fixtures/config");
copy_dir_recursive(&fixture_src, temp_dir.path()).unwrap();
fs::create_dir_all(temp_dir.path().join("test/fixtures")).unwrap();
fs::create_dir_all(temp_dir.path().join("generated")).unwrap();
fs::write(
temp_dir.path().join("test/fixtures/data.txt"),
"test data with trailing spaces \n\n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("markdown.md"),
"# Markdown\ntrailing spaces \n\n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("generated/output.rs"),
"// generated code with trailing spaces \n\n\n",
)
.unwrap();
fs::write(
temp_dir.path().join("normal.txt"),
"normal file with trailing spaces \n\n\n",
)
.unwrap();
let status = basefmt()
.arg(temp_dir.path().to_str().unwrap())
.status()
.unwrap();
assert!(status.success());
let test_fixture = fs::read_to_string(temp_dir.path().join("test/fixtures/data.txt")).unwrap();
assert!(
test_fixture.ends_with(" \n\n\n"),
"test/fixtures/** excluded by EditorConfig"
);
let markdown = fs::read_to_string(temp_dir.path().join("markdown.md")).unwrap();
assert!(
markdown.contains("trailing spaces "),
"*.md excluded by EditorConfig"
);
let generated = fs::read_to_string(temp_dir.path().join("generated/output.rs")).unwrap();
assert!(
generated.ends_with(" \n\n\n"),
"generated/** excluded by .basefmt.toml"
);
let normal = fs::read_to_string(temp_dir.path().join("normal.txt")).unwrap();
assert_eq!(normal, "normal file with trailing spaces\n");
}