use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_glob_single_pattern() {
let temp_dir = TempDir::new().unwrap();
fs::write(
temp_dir.path().join("file1.ts"),
"function test1() { return 1; }",
)
.unwrap();
fs::write(
temp_dir.path().join("file2.ts"),
"function test2() { return 2; }",
)
.unwrap();
fs::write(
temp_dir.path().join("file3.ts"),
"function test3() { return 3; }",
)
.unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.current_dir(temp_dir.path())
.assert()
.success()
.stdout(predicate::str::contains("function test1"))
.stdout(predicate::str::contains("function test2"))
.stdout(predicate::str::contains("function test3"));
}
#[test]
fn test_glob_with_headers() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("a.ts"), "function a() {}").unwrap();
fs::write(temp_dir.path().join("b.ts"), "function b() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.current_dir(temp_dir.path())
.assert()
.success()
.stdout(predicate::str::contains("// === "))
.stdout(predicate::str::contains("a.ts"))
.stdout(predicate::str::contains("b.ts"));
}
#[test]
fn test_glob_no_header_flag() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("a.ts"), "function a() {}").unwrap();
fs::write(temp_dir.path().join("b.ts"), "function b() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.arg("--no-header")
.current_dir(temp_dir.path())
.assert()
.success()
.stdout(predicate::str::contains("// === ").not());
}
#[test]
fn test_glob_recursive_pattern() {
let temp_dir = TempDir::new().unwrap();
fs::create_dir_all(temp_dir.path().join("src/utils")).unwrap();
fs::write(temp_dir.path().join("src/main.ts"), "function main() {}").unwrap();
fs::write(
temp_dir.path().join("src/utils/helper.ts"),
"function helper() {}",
)
.unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("src/*.ts")
.current_dir(temp_dir.path())
.assert()
.success()
.stdout(predicate::str::contains("function main"));
}
#[test]
fn test_glob_no_matches() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("file.js"), "function test() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.current_dir(temp_dir.path())
.assert()
.failure()
.stderr(predicate::str::contains("No files found"));
}
#[test]
fn test_glob_absolute_path_rejected() {
Command::cargo_bin("skim")
.unwrap()
.arg("/etc/*.conf")
.assert()
.failure()
.stderr(predicate::str::contains("must be relative"))
.stderr(predicate::str::contains("cannot start with '/'"));
}
#[test]
fn test_glob_parent_traversal_rejected() {
Command::cargo_bin("skim")
.unwrap()
.arg("../*.ts")
.assert()
.failure()
.stderr(predicate::str::contains("cannot contain '..'"))
.stderr(predicate::str::contains("parent directory traversal"));
}
#[test]
fn test_glob_with_jobs_flag() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("a.ts"), "function a() {}").unwrap();
fs::write(temp_dir.path().join("b.ts"), "function b() {}").unwrap();
fs::write(temp_dir.path().join("c.ts"), "function c() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.arg("--jobs")
.arg("2")
.current_dir(temp_dir.path())
.assert()
.success()
.stdout(predicate::str::contains("function a"))
.stdout(predicate::str::contains("function b"))
.stdout(predicate::str::contains("function c"));
}
#[test]
fn test_glob_jobs_too_high() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("a.ts"), "function a() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.arg("--jobs")
.arg("200")
.current_dir(temp_dir.path())
.assert()
.failure()
.stderr(predicate::str::contains("--jobs value too high"))
.stderr(predicate::str::contains("maximum: 128"));
}
#[test]
fn test_glob_brace_expansion() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("file.js"), "function js() {}").unwrap();
fs::write(temp_dir.path().join("file.ts"), "function ts() {}").unwrap();
fs::write(temp_dir.path().join("file.py"), "def py(): pass").unwrap();
let output = Command::cargo_bin("skim")
.unwrap()
.arg("*.{js,ts}")
.arg("--no-header")
.current_dir(temp_dir.path())
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).unwrap();
assert!(
stdout.contains("function js"),
"brace expansion should match .js files"
);
assert!(
stdout.contains("function ts"),
"brace expansion should match .ts files"
);
assert!(
!stdout.contains("def py"),
"brace expansion should NOT match .py files"
);
}
fn init_fake_git_repo(dir: &std::path::Path) {
fs::create_dir_all(dir.join(".git")).unwrap();
}
#[test]
fn test_glob_respects_gitignore() {
let temp_dir = TempDir::new().unwrap();
init_fake_git_repo(temp_dir.path());
fs::write(temp_dir.path().join(".gitignore"), "build/\n").unwrap();
fs::write(temp_dir.path().join("visible.ts"), "function visible() {}").unwrap();
fs::create_dir_all(temp_dir.path().join("build")).unwrap();
fs::write(
temp_dir.path().join("build/output.ts"),
"function output() {}",
)
.unwrap();
let output = Command::cargo_bin("skim")
.unwrap()
.arg("**/*.ts")
.current_dir(temp_dir.path())
.arg("--no-header")
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).unwrap();
assert!(
stdout.contains("function visible"),
"visible file should be in output"
);
assert!(
!stdout.contains("function output"),
"gitignored file should NOT be in output"
);
}
#[test]
fn test_glob_no_ignore_includes_gitignored() {
let temp_dir = TempDir::new().unwrap();
init_fake_git_repo(temp_dir.path());
fs::write(temp_dir.path().join(".gitignore"), "build/\n").unwrap();
fs::write(temp_dir.path().join("visible.ts"), "function visible() {}").unwrap();
fs::create_dir_all(temp_dir.path().join("build")).unwrap();
fs::write(
temp_dir.path().join("build/output.ts"),
"function output() {}",
)
.unwrap();
let output = Command::cargo_bin("skim")
.unwrap()
.arg("**/*.ts")
.arg("--no-ignore")
.arg("--no-header")
.current_dir(temp_dir.path())
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).unwrap();
assert!(
stdout.contains("function visible"),
"visible file should be in output"
);
assert!(
stdout.contains("function output"),
"with --no-ignore, gitignored file SHOULD be in output"
);
}
#[test]
fn test_glob_skips_hidden_files() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("visible.ts"), "function visible() {}").unwrap();
fs::write(temp_dir.path().join(".hidden.ts"), "function hidden() {}").unwrap();
let output = Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.arg("--no-header")
.current_dir(temp_dir.path())
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).unwrap();
assert!(
stdout.contains("function visible"),
"visible file should be in output"
);
assert!(
!stdout.contains("function hidden"),
"hidden file should NOT be matched by glob"
);
}
#[test]
fn test_glob_respects_file_pattern_gitignore() {
let temp_dir = TempDir::new().unwrap();
init_fake_git_repo(temp_dir.path());
fs::write(temp_dir.path().join(".gitignore"), "*.log\n").unwrap();
fs::write(temp_dir.path().join("app.ts"), "function app() {}").unwrap();
fs::write(temp_dir.path().join("debug.log"), "some log content").unwrap();
fs::write(temp_dir.path().join("logger.ts"), "function logger() {}").unwrap();
let output = Command::cargo_bin("skim")
.unwrap()
.arg("*.*")
.arg("--no-header")
.current_dir(temp_dir.path())
.assert()
.success()
.get_output()
.stdout
.clone();
let stdout = String::from_utf8(output).unwrap();
assert!(
stdout.contains("function app"),
"app.ts should be in output"
);
assert!(
stdout.contains("function logger"),
"logger.ts should be in output (pattern is *.log, not *log*)"
);
assert!(
!stdout.contains("some log content"),
"debug.log should be excluded by .gitignore"
);
}
#[test]
fn test_glob_no_ignore_hint_in_error() {
let temp_dir = TempDir::new().unwrap();
init_fake_git_repo(temp_dir.path());
fs::write(temp_dir.path().join(".gitignore"), "*.ts\n").unwrap();
fs::write(temp_dir.path().join("only.ts"), "function only() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("*.ts")
.current_dir(temp_dir.path())
.assert()
.failure()
.stderr(predicate::str::contains("No files found"))
.stderr(predicate::str::contains("--no-ignore"));
}