use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
const FIND_FIXTURE: &str = include_str!("fixtures/cmd/file/find_small.txt");
const GH_FIXTURE: &str = include_str!("fixtures/cmd/infra/gh_pr_list.json");
const LOG_FIXTURE: &str = include_str!("fixtures/cmd/log/plaintext_mixed.txt");
fn skim_cmd() -> Command {
Command::cargo_bin("skim").unwrap()
}
#[test]
fn test_file_with_extension_routes_to_file_operation() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.ts");
fs::write(&file, "function add(a: number): number { return a; }").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg(&file)
.assert()
.success()
.stdout(predicate::str::contains("function add"));
}
#[test]
fn test_file_named_init_py_routes_to_file_operation() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("init.py");
fs::write(&file, "def hello(): pass").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg(&file)
.assert()
.success()
.stdout(predicate::str::contains("def hello"));
}
#[test]
fn test_path_with_separator_routes_to_file_operation() {
let dir = TempDir::new().unwrap();
let subdir = dir.path().join("src");
fs::create_dir(&subdir).unwrap();
let file = subdir.join("test.rs");
fs::write(&file, "fn main() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg(&file)
.arg("--mode=signatures")
.assert()
.success();
}
#[test]
fn test_stdin_dash_routes_to_file_operation() {
Command::cargo_bin("skim")
.unwrap()
.arg("-")
.arg("-l")
.arg("rust")
.write_stdin("fn main() {}")
.assert()
.success()
.stdout(predicate::str::contains("fn main"));
}
#[test]
fn test_glob_pattern_routes_to_file_operation() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("foo.ts");
fs::write(&file, "const x = 1;").unwrap();
Command::cargo_bin("skim")
.unwrap()
.current_dir(dir.path())
.arg("*.ts")
.assert()
.success();
}
#[test]
fn test_dot_routes_to_file_operation() {
Command::cargo_bin("skim")
.unwrap()
.arg(".")
.assert()
.success();
}
#[test]
fn test_no_positional_routes_to_file_operation() {
Command::cargo_bin("skim")
.unwrap()
.arg("--clear-cache")
.assert()
.success();
}
#[test]
fn test_double_dash_before_subcommand_name_routes_to_file_operation() {
let output = Command::cargo_bin("skim")
.unwrap()
.arg("--")
.arg("test")
.assert()
.failure();
output.stderr(predicate::str::contains("not yet implemented").not());
}
#[test]
fn test_known_subcommand_init_is_implemented() {
Command::cargo_bin("skim")
.unwrap()
.arg("init")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("skim init"));
}
#[test]
fn test_subcommand_init_with_unknown_flag_fails() {
Command::cargo_bin("skim")
.unwrap()
.arg("init")
.arg("--nonexistent-flag")
.assert()
.failure()
.stderr(predicate::str::contains("unknown flag"));
}
#[test]
fn test_subcommand_help_exits_zero() {
Command::cargo_bin("skim")
.unwrap()
.arg("init")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("skim init"))
.stdout(predicate::str::contains("Install skim"));
}
#[test]
fn test_subcommand_short_help_exits_zero() {
Command::cargo_bin("skim")
.unwrap()
.arg("build")
.arg("-h")
.assert()
.success()
.stdout(predicate::str::contains("skim build"));
}
#[test]
fn test_full_path_to_file_named_as_subcommand_uses_separator_heuristic() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("init");
fs::write(&file, "fn setup() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg(&file)
.arg("-l")
.arg("rust")
.assert()
.success()
.stdout(predicate::str::contains("fn setup"));
}
#[test]
fn test_bare_file_named_as_subcommand_routes_to_subcommand() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("init");
fs::write(&file, "fn setup() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.current_dir(dir.path())
.arg("init")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("skim init"))
.stdout(predicate::str::contains("fn setup").not());
}
#[test]
fn test_full_path_to_dir_named_as_subcommand_uses_separator_heuristic() {
let dir = TempDir::new().unwrap();
let build_dir = dir.path().join("build");
fs::create_dir(&build_dir).unwrap();
let file = build_dir.join("main.rs");
fs::write(&file, "fn main() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg(&build_dir)
.assert()
.success();
}
#[test]
fn test_bare_dir_named_as_subcommand_routes_to_subcommand() {
let dir = TempDir::new().unwrap();
let build_dir = dir.path().join("build");
fs::create_dir(&build_dir).unwrap();
let file = build_dir.join("main.rs");
fs::write(&file, "fn main() {}").unwrap();
Command::cargo_bin("skim")
.unwrap()
.current_dir(dir.path())
.arg("build")
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("skim build"));
}
#[test]
fn test_mode_flag_consumes_next_token() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.ts");
fs::write(&file, "function f(): void { return; }").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("--mode")
.arg("signatures")
.arg(&file)
.assert()
.success();
}
#[test]
fn test_mode_equals_syntax_is_single_token() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.ts");
fs::write(&file, "function f(): void { return; }").unwrap();
Command::cargo_bin("skim")
.unwrap()
.arg("--mode=signatures")
.arg(&file)
.assert()
.success();
}
#[test]
fn test_help_lists_subcommands() {
Command::cargo_bin("skim")
.unwrap()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("SUBCOMMANDS"))
.stdout(predicate::str::contains("init"))
.stdout(predicate::str::contains("test"))
.stdout(predicate::str::contains("build"))
.stdout(predicate::str::contains("completions"));
}
#[test]
fn test_unknown_word_routes_to_file_operation() {
Command::cargo_bin("skim")
.unwrap()
.arg("foobar")
.assert()
.failure()
.stderr(predicate::str::contains("not yet implemented").not());
}
#[test]
fn test_subcommand_file_help() {
skim_cmd()
.args(["file", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("find"));
}
#[test]
fn test_subcommand_infra_help() {
skim_cmd()
.args(["infra", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("gh"));
}
#[test]
fn test_subcommand_log_help() {
skim_cmd()
.args(["log", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("dedup"));
}
#[test]
fn test_subcommand_file_unknown_tool() {
skim_cmd()
.args(["file", "unknown-tool-xyz"])
.assert()
.failure();
}
#[test]
fn test_subcommand_log_empty_stdin() {
skim_cmd().arg("log").write_stdin("").assert().success();
}
#[test]
fn test_log_conflicting_flags() {
skim_cmd()
.args(["log", "--debug-only", "--keep-debug"])
.write_stdin(LOG_FIXTURE)
.assert()
.success();
}
#[test]
fn test_file_find_empty_stdin() {
skim_cmd()
.args(["file", "find"])
.write_stdin("")
.assert()
.success();
}
#[test]
fn test_subcommand_file_json() {
let output = skim_cmd()
.args(["file", "find", "--json"])
.write_stdin(FIND_FIXTURE)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(stdout.trim()).unwrap();
assert!(json.get("tool").is_some(), "JSON should have 'tool' field");
}
#[test]
fn test_subcommand_infra_json() {
let output = skim_cmd()
.args(["infra", "gh", "--json"])
.write_stdin(GH_FIXTURE)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(stdout.trim()).unwrap();
assert!(json.get("tool").is_some(), "JSON should have 'tool' field");
}
#[test]
fn test_subcommand_log_json() {
let output = skim_cmd()
.args(["log", "--json"])
.write_stdin(LOG_FIXTURE)
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).unwrap();
let json: serde_json::Value = serde_json::from_str(stdout.trim()).unwrap();
let has_total_lines = json
.get("result")
.and_then(|r| r.get("total_lines"))
.is_some();
assert!(
has_total_lines,
"JSON should have 'result.total_lines' field"
);
}
#[test]
fn test_subcommand_file_show_stats() {
let output = skim_cmd()
.args(["file", "find", "--show-stats"])
.write_stdin(FIND_FIXTURE)
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.to_lowercase().contains("token"),
"stderr should contain token stats, got: {stderr}"
);
}
#[test]
fn test_subcommand_infra_show_stats() {
let output = skim_cmd()
.args(["infra", "gh", "--show-stats"])
.write_stdin(GH_FIXTURE)
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.to_lowercase().contains("token"),
"stderr should contain token stats, got: {stderr}"
);
}
#[test]
fn test_subcommand_log_show_stats() {
let output = skim_cmd()
.args(["log", "--show-stats"])
.write_stdin(LOG_FIXTURE)
.output()
.unwrap();
assert!(output.status.success());
let stderr = String::from_utf8(output.stderr).unwrap();
assert!(
stderr.to_lowercase().contains("token"),
"stderr should contain token stats, got: {stderr}"
);
}