use glob::Pattern;
use rust_tree::rust_tree::options::TreeOptions;
use rust_tree::rust_tree::traversal::{list_directory, list_directory_as_string};
use std::fs;
use std::io::Write;
use tempfile::{tempdir, NamedTempFile};
fn create_test_directory() -> tempfile::TempDir {
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path();
fs::create_dir(temp_path.join("src")).unwrap();
fs::create_dir(temp_path.join("tests")).unwrap();
fs::create_dir(temp_path.join(".hidden")).unwrap();
fs::write(temp_path.join("README.md"), "# Test Project").unwrap();
fs::write(temp_path.join("Cargo.toml"), "[package]").unwrap();
fs::write(temp_path.join("src").join("main.rs"), "fn main() {}").unwrap();
fs::write(temp_path.join("src").join("lib.rs"), "// lib").unwrap();
fs::write(temp_path.join("tests").join("test.rs"), "#[test]").unwrap();
fs::write(temp_path.join(".hidden").join("secret.txt"), "secret").unwrap();
fs::write(temp_path.join(".gitignore"), "target/").unwrap();
temp_dir
}
fn create_default_options() -> TreeOptions {
TreeOptions {
all_files: false,
level: None,
full_path: false,
dir_only: false,
no_indent: false,
print_size: false,
human_readable: false,
pattern_glob: None,
exclude_patterns: vec![],
color: false,
no_color: false,
ascii: false,
sort_by_time: false,
reverse: false,
print_mod_date: false,
output_file: None,
file_limit: None,
dirs_first: false,
classify: false,
no_report: false,
print_permissions: false,
from_file: false,
icons: false,
prune: false,
match_dirs: false,
}
}
#[test]
fn test_list_directory_basic() {
let temp_dir = create_test_directory();
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_with_all_files() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.all_files = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_with_pattern() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("*.rs").unwrap());
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_exclude_pattern() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.exclude_patterns = vec![Pattern::new("target").unwrap()];
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_dirs_only() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.dir_only = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_with_depth_limit() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.level = Some(1);
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_with_sizes() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.print_size = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_human_readable_sizes() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.print_size = true;
options.human_readable = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_with_modification_dates() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.print_mod_date = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_sort_by_time() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.sort_by_time = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_reverse_sort() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.reverse = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_dirs_first() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.dirs_first = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_ascii_mode() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.ascii = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_full_path() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.full_path = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_no_indent() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.no_indent = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_classify() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.classify = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_no_report() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.no_report = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[cfg(unix)]
#[test]
fn test_list_directory_with_permissions() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.print_permissions = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_with_file_limit() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.file_limit = Some(3);
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_output_capture() {
let temp_dir = create_test_directory();
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_traverse_directory_with_output_file() {
let temp_dir = create_test_directory();
let mut temp_file = NamedTempFile::new().unwrap();
let mut options = create_default_options();
options.output_file = Some(temp_file.path().to_string_lossy().to_string());
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
temp_file.flush().unwrap();
let file_size = temp_file.as_file().metadata().unwrap().len();
assert!(file_size > 0);
}
#[test]
fn test_list_directory_nonexistent_path() {
let options = create_default_options();
let nonexistent_path = "/this/path/should/not/exist";
let result = list_directory(nonexistent_path, &options);
let _ = result;
}
#[test]
fn test_list_directory_combined_options() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.all_files = true;
options.print_size = true;
options.human_readable = true;
options.print_mod_date = true;
options.sort_by_time = true;
options.dirs_first = true;
options.level = Some(2);
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_multiple_pattern_combinations() {
let temp_dir = create_test_directory();
let patterns = vec![
"*.rs", "*.md", "Cargo.*", "*test*", ".*", ];
for pattern_str in patterns {
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new(pattern_str).unwrap());
options.all_files = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok(), "Failed with pattern: {}", pattern_str);
}
}
#[test]
fn test_exclude_pattern_combinations() {
let temp_dir = create_test_directory();
let exclude_patterns = vec![
"*.md", "test*", ".*", "src",
];
for pattern_str in exclude_patterns {
let mut options = create_default_options();
options.exclude_patterns = vec![Pattern::new(pattern_str).unwrap()];
options.all_files = true;
let result = list_directory(temp_dir.path(), &options);
assert!(
result.is_ok(),
"Failed with exclude pattern: {}",
pattern_str
);
}
}
#[test]
fn test_depth_level_variations() {
let temp_dir = create_test_directory();
for level in 0..=5 {
let mut options = create_default_options();
options.level = Some(level);
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok(), "Failed with depth level: {}", level);
}
}
#[test]
fn test_file_limit_variations() {
let temp_dir = create_test_directory();
for limit in 1..=10 {
let mut options = create_default_options();
options.file_limit = Some(limit);
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok(), "Failed with file limit: {}", limit);
}
}
#[test]
fn test_color_combinations() {
let temp_dir = create_test_directory();
let color_configs = vec![
(false, false), (true, false), (false, true), (true, true), ];
for (color, no_color) in color_configs {
let mut options = create_default_options();
options.color = color;
options.no_color = no_color;
let result = list_directory(temp_dir.path(), &options);
assert!(
result.is_ok(),
"Failed with color: {}, no_color: {}",
color,
no_color
);
}
}
#[test]
fn test_empty_directory() {
let temp_dir = tempdir().unwrap();
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_single_file_directory() {
let temp_dir = tempdir().unwrap();
fs::write(temp_dir.path().join("single.txt"), "content").unwrap();
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_deeply_nested_directory() {
let temp_dir = tempdir().unwrap();
let mut current_path = temp_dir.path().to_path_buf();
for i in 0..5 {
current_path.push(format!("level_{}", i));
fs::create_dir(¤t_path).unwrap();
}
fs::write(current_path.join("deep_file.txt"), "deep content").unwrap();
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_large_directory() {
let temp_dir = tempdir().unwrap();
for i in 0..50 {
fs::write(
temp_dir.path().join(format!("file_{:03}.txt", i)),
"content",
)
.unwrap();
}
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_special_characters_in_filenames() {
let temp_dir = tempdir().unwrap();
let special_files = vec![
"file with spaces.txt",
"file-with-dashes.txt",
"file_with_underscores.txt",
"file.with.dots.txt",
"file(with)parentheses.txt",
"file[with]brackets.txt",
];
for filename in &special_files {
fs::write(temp_dir.path().join(filename), "content").unwrap();
}
let options = create_default_options();
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_list_directory_as_string_basic() {
let temp_dir = create_test_directory();
let options = create_default_options();
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(!output.is_empty());
assert!(output.contains("src"));
assert!(output.contains("tests"));
assert!(output.contains("README.md"));
assert!(output.contains("Cargo.toml"));
assert!(!output.contains(".hidden"));
assert!(!output.contains(".gitignore"));
}
#[test]
fn test_list_directory_as_string_with_all_files() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.all_files = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains(".hidden"));
assert!(output.contains(".gitignore"));
}
#[test]
fn test_list_directory_as_string_with_tree_formatting() {
let temp_dir = create_test_directory();
let options = create_default_options();
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("├──") || output.contains("└──"));
assert!(output.contains("│") || output.contains("|"));
}
#[test]
fn test_list_directory_as_string_with_no_report() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.no_report = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(!output.contains("directories"));
assert!(!output.contains("files"));
}
#[test]
fn test_list_directory_as_string_nonexistent_path() {
let options = create_default_options();
let nonexistent_path = "/this/path/does/not/exist";
let result = list_directory_as_string(nonexistent_path, &options);
assert!(result.is_err());
}
fn create_prune_test_directory() -> tempfile::TempDir {
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path();
fs::create_dir(temp_path.join("empty_dir")).unwrap();
fs::create_dir(temp_path.join("has_txt")).unwrap();
fs::create_dir(temp_path.join("has_txt").join("subdir")).unwrap();
fs::create_dir(temp_path.join("no_txt")).unwrap();
fs::write(temp_path.join("has_txt").join("file.txt"), "content").unwrap();
fs::write(
temp_path.join("has_txt").join("subdir").join("nested.txt"),
"nested",
)
.unwrap();
fs::write(temp_path.join("no_txt").join("file.rs"), "fn main() {}").unwrap();
temp_dir
}
#[test]
fn test_prune_with_pattern() {
let temp_dir = create_prune_test_directory();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("*.txt").unwrap());
options.prune = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("has_txt"), "Should contain has_txt dir");
assert!(output.contains("file.txt"), "Should contain file.txt");
assert!(output.contains("subdir"), "Should contain subdir");
assert!(output.contains("nested.txt"), "Should contain nested.txt");
assert!(
!output.contains("empty_dir"),
"Should not contain empty_dir"
);
assert!(!output.contains("no_txt"), "Should not contain no_txt dir");
assert!(!output.contains("file.rs"), "Should not contain file.rs");
}
#[test]
fn test_prune_with_exclude() {
let temp_dir = create_prune_test_directory();
let mut options = create_default_options();
options.exclude_patterns = vec![Pattern::new("*.txt").unwrap()];
options.prune = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("no_txt"), "Should contain no_txt dir");
assert!(output.contains("file.rs"), "Should contain file.rs");
assert!(
!output.contains("empty_dir"),
"Should not contain empty_dir"
);
assert!(
!output.contains("has_txt"),
"Should not contain has_txt (all content excluded)"
);
}
#[test]
fn test_prune_without_filter_has_no_effect() {
let temp_dir = create_prune_test_directory();
let mut options = create_default_options();
options.prune = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(
output.contains("empty_dir"),
"Should contain empty_dir (no filter active)"
);
assert!(output.contains("has_txt"), "Should contain has_txt");
assert!(output.contains("no_txt"), "Should contain no_txt");
}
#[test]
fn test_prune_nested_directories() {
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path();
fs::create_dir_all(temp_path.join("a").join("b").join("c")).unwrap();
fs::write(
temp_path.join("a").join("b").join("c").join("deep.txt"),
"deep",
)
.unwrap();
fs::create_dir(temp_path.join("empty")).unwrap();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("*.txt").unwrap());
options.prune = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("deep.txt"), "Should contain deep.txt");
assert!(!output.contains("empty"), "Should not contain empty dir");
}
fn create_matchdirs_test_directory() -> tempfile::TempDir {
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path();
fs::create_dir_all(temp_path.join("fzf_root").join("sub").join("deep")).unwrap();
fs::create_dir_all(temp_path.join("project").join("fzf").join("sub")).unwrap();
fs::write(temp_path.join("fzf_root").join("init.lua"), "x").unwrap();
fs::write(
temp_path.join("fzf_root").join("sub").join("nested.lua"),
"x",
)
.unwrap();
fs::write(
temp_path
.join("fzf_root")
.join("sub")
.join("deep")
.join("verydeep.lua"),
"x",
)
.unwrap();
fs::write(
temp_path.join("project").join("fzf").join("plugin.lua"),
"x",
)
.unwrap();
fs::write(
temp_path
.join("project")
.join("fzf")
.join("sub")
.join("nested.lua"),
"x",
)
.unwrap();
temp_dir
}
#[test]
fn test_matchdirs_depth1_contents_shown() {
let temp_dir = create_matchdirs_test_directory();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("fzf_root"), "Should contain fzf_root");
assert!(
output.contains("init.lua"),
"Should contain init.lua (depth 2, parent matches at depth 1)"
);
}
#[test]
fn test_matchdirs_depth2_contents_not_shown() {
let temp_dir = create_matchdirs_test_directory();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(
!output.contains("nested.lua"),
"Should not contain nested.lua (depth 3)"
);
assert!(
!output.contains("verydeep.lua"),
"Should not contain verydeep.lua (depth 4)"
);
}
#[test]
fn test_matchdirs_nested_match_no_contents() {
let temp_dir = create_matchdirs_test_directory();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("fzf"), "Should contain fzf directory");
assert!(
!output.contains("plugin.lua"),
"Should not contain plugin.lua (fzf at depth 2, not depth 1)"
);
}
#[test]
fn test_matchdirs_all_directories_shown() {
let temp_dir = create_matchdirs_test_directory();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("fzf_root"), "Should contain fzf_root");
assert!(output.contains("project"), "Should contain project");
assert!(output.contains("sub"), "Should contain sub directories");
}
#[test]
fn test_matchdirs_with_prune_keeps_matched_empty() {
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path();
fs::create_dir_all(temp_path.join("fzf_empty")).unwrap();
fs::create_dir_all(temp_path.join("other")).unwrap();
fs::write(temp_path.join("other").join("file.rs"), "x").unwrap();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
options.prune = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(
output.contains("fzf_empty"),
"Should contain fzf_empty (matched, not pruned)"
);
assert!(
!output.contains("other"),
"Should not contain other (no matching content)"
);
}
#[test]
fn test_matchdirs_fromfile_depth1_contents_shown() {
let temp_dir = tempdir().unwrap();
let listing_file = temp_dir.path().join("listing.txt");
let content = "fzf_root/\nfzf_root/init.lua\nfzf_root/sub/\nfzf_root/sub/nested.lua\nproject/\nproject/fzf/\nproject/fzf/plugin.lua\n";
fs::write(&listing_file, content).unwrap();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
options.from_file = true;
let result = list_directory_as_string(&listing_file, &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(output.contains("fzf_root"), "Should contain fzf_root");
assert!(
output.contains("init.lua"),
"Should contain init.lua (parent matches at depth 1)"
);
}
#[test]
fn test_matchdirs_fromfile_nested_not_shown() {
let temp_dir = tempdir().unwrap();
let listing_file = temp_dir.path().join("listing.txt");
let content = "fzf_root/\nfzf_root/init.lua\nfzf_root/sub/\nfzf_root/sub/nested.lua\nproject/\nproject/fzf/\nproject/fzf/plugin.lua\n";
fs::write(&listing_file, content).unwrap();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
options.from_file = true;
let result = list_directory_as_string(&listing_file, &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(
!output.contains("nested.lua"),
"Should not contain nested.lua (parent at depth 2)"
);
assert!(
!output.contains("plugin.lua"),
"Should not contain plugin.lua (fzf at depth 2)"
);
}
#[test]
fn test_matchdirs_fromfile_prune_keeps_matched() {
let temp_dir = tempdir().unwrap();
let listing_file = temp_dir.path().join("listing.txt");
let content = "fzf_empty/\nother/\nother/file.rs\n";
fs::write(&listing_file, content).unwrap();
let mut options = create_default_options();
options.pattern_glob = Some(Pattern::new("fzf*").unwrap());
options.match_dirs = true;
options.prune = true;
options.from_file = true;
let result = list_directory_as_string(&listing_file, &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(
output.contains("fzf_empty"),
"Should contain fzf_empty (matched, not pruned)"
);
assert!(
!output.contains("other"),
"Should not contain other (no matching content)"
);
}
#[cfg(unix)]
#[test]
fn test_classify_symlink() {
use std::os::unix::fs::symlink;
let temp_dir = tempdir().unwrap();
let temp_path = temp_dir.path();
fs::write(temp_path.join("target.txt"), "content").unwrap();
symlink(temp_path.join("target.txt"), temp_path.join("link.txt")).unwrap();
let mut options = create_default_options();
options.classify = true;
let result = list_directory_as_string(temp_dir.path(), &options);
assert!(result.is_ok());
let output = result.unwrap();
assert!(
output.contains("link.txt@"),
"Symlink should have @ indicator"
);
}
#[test]
fn test_dirs_first_with_sort_by_time() {
let temp_dir = create_test_directory();
let mut options = create_default_options();
options.dirs_first = true;
options.sort_by_time = true;
let result = list_directory(temp_dir.path(), &options);
assert!(result.is_ok());
}
#[test]
fn test_fromfile_dirs_first() {
let temp_dir = tempdir().unwrap();
let listing_file = temp_dir.path().join("listing.txt");
let content = "file1.txt\ndir1/\nfile2.txt\ndir2/\ndir2/nested.txt\n";
fs::write(&listing_file, content).unwrap();
let mut options = create_default_options();
options.dirs_first = true;
options.from_file = true;
let result = list_directory_as_string(&listing_file, &options);
assert!(result.is_ok());
let output = result.unwrap();
let lines: Vec<&str> = output.lines().collect();
let dir1_pos = lines.iter().position(|l| l.contains("dir1"));
let dir2_pos = lines
.iter()
.position(|l| l.contains("dir2") && !l.contains("nested"));
let file1_pos = lines.iter().position(|l| l.contains("file1"));
if let (Some(d1), Some(d2), Some(f1)) = (dir1_pos, dir2_pos, file1_pos) {
assert!(d1 < f1 || d2 < f1, "Directories should appear before files");
}
}