use std::path::Path;
use std::process::Command;
fn complete(line: &str, cwd: Option<&Path>) -> Vec<String> {
let mut c = Command::new(env!("CARGO_BIN_EXE_ct"));
c.env("_CT_COMPLETE", "bash").arg(line);
if let Some(dir) = cwd {
c.current_dir(dir);
}
let out = c.output().expect("run ct completion");
String::from_utf8_lossy(&out.stdout).lines().map(|s| s.trim_end().to_string()).collect()
}
fn run(args: &[&str]) -> (String, String, i32) {
let out = Command::new(env!("CARGO_BIN_EXE_ct")).args(args).output().expect("run ct");
(
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
out.status.code().unwrap_or(-1),
)
}
#[test]
fn completions_command_emits_wrapper_and_script() {
let (wrap, _, code) = run(&["completions"]);
assert_eq!(code, 0);
assert!(wrap.contains("completions --shell"), "wrapper must re-invoke --shell: {wrap:?}");
let (script, _, code) = run(&["completions", "--shell", "bash"]);
assert_eq!(code, 0);
assert!(
script.contains("complete -") && script.contains("_ct_complete"),
"registration script: {script:?}"
);
let (pos, _, _) = run(&["completions", "bash"]);
assert!(pos.contains("_ct_complete"), "positional shell: {pos:?}");
let (_, err, code) = run(&["completions", "--shell", "tcsh"]);
assert_eq!(code, 2);
assert!(err.contains("unknown shell"), "err: {err:?}");
}
#[test]
fn completes_subcommands_flags_and_enum_sets() {
let subs = complete("ct ", None);
for want in ["search", "check", "rules", "completions"] {
assert!(subs.iter().any(|s| s == want), "subcommand {want} missing from {subs:?}");
}
let flags = complete("ct search --", None);
assert!(flags.iter().any(|s| s == "--grep"), "flags: {flags:?}");
assert!(flags.iter().any(|s| s == "--no-ignore"), "flags: {flags:?}");
let modes = complete("ct search --mode ", None);
for want in ["literal", "glob", "regex"] {
assert!(modes.iter().any(|s| s == want), "--mode set missing {want}: {modes:?}");
}
}
#[test]
fn completes_ids_tags_and_defs_from_the_live_store() {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("target/test-tmp/completion");
std::fs::create_dir_all(dir.join(".ct")).unwrap();
std::fs::write(
dir.join(".ct/rules.jsonc"),
r#"{"defs":{"core-types":["X"]},"rules":[{"id":"abc-rule","question":"q","probe":["ls"],"tags":["hygiene"]}]}"#,
)
.unwrap();
let ids = complete("ct check --id ", Some(&dir));
assert!(ids.iter().any(|s| s == "abc-rule"), "dynamic rule id: {ids:?}");
let tags = complete("ct check --tag ", Some(&dir));
assert!(tags.iter().any(|s| s == "hygiene"), "dynamic tag: {tags:?}");
let defs = complete("ct rules --def ", Some(&dir));
assert!(defs.iter().any(|s| s == "core-types"), "dynamic def name: {defs:?}");
let promote = complete("ct rules --promote ", Some(&dir));
assert!(promote.iter().any(|s| s == "abc-rule"), "dynamic id for --promote: {promote:?}");
}