use flag_rs::{CommandBuilder, CompletionResult, Flag, FlagType};
fn create_test_cli() -> flag_rs::Command {
CommandBuilder::new("testcli")
.short("A test CLI for completion testing")
.flag(
Flag::new("verbose")
.short('v')
.usage("Enable verbose output")
.value_type(FlagType::Bool),
)
.flag(
Flag::new("config")
.short('c')
.usage("Config file path")
.value_type(FlagType::File),
)
.subcommand(
CommandBuilder::new("server")
.short("Server management commands")
.flag(
Flag::new("port")
.short('p')
.usage("Port to listen on")
.value_type(FlagType::Int)
.default(flag_rs::FlagValue::Int(8080)),
)
.subcommand(
CommandBuilder::new("start")
.short("Start the server")
.flag(
Flag::new("daemon")
.short('d')
.usage("Run as daemon")
.value_type(FlagType::Bool),
)
.arg_completion(|_ctx, prefix| {
let profiles = vec!["development", "staging", "production"];
Ok(CompletionResult::new().extend(
profiles
.into_iter()
.filter(|p| p.starts_with(prefix))
.map(String::from),
))
})
.build(),
)
.subcommand(
CommandBuilder::new("stop")
.short("Stop the server")
.flag(
Flag::new("force")
.short('f')
.usage("Force stop")
.value_type(FlagType::Bool),
)
.build(),
)
.build(),
)
.subcommand(
CommandBuilder::new("database")
.short("Database operations")
.alias("db")
.flag(
Flag::new("host")
.short('h')
.usage("Database host")
.value_type(FlagType::String)
.default(flag_rs::FlagValue::String("localhost".to_string())),
)
.flag_completion("host", |_ctx, prefix| {
let hosts = vec!["localhost", "db.example.com", "192.168.1.100"];
Ok(CompletionResult::new().extend(
hosts
.into_iter()
.filter(|h| h.starts_with(prefix))
.map(String::from),
))
})
.subcommand(
CommandBuilder::new("migrate")
.short("Run database migrations")
.arg_completion(|_ctx, prefix| {
let migrations = vec![
"001_initial_schema",
"002_add_users_table",
"003_add_indexes",
];
Ok(CompletionResult::new().extend(
migrations
.into_iter()
.filter(|m| m.starts_with(prefix))
.map(String::from),
))
})
.build(),
)
.build(),
)
.build()
}
#[test]
fn test_basic_command_structure() {
let app = create_test_cli();
assert_eq!(app.name(), "testcli");
assert_eq!(app.short(), "A test CLI for completion testing");
assert!(app.flags().contains_key("verbose"));
assert!(app.flags().contains_key("config"));
assert!(app.subcommands().contains_key("server"));
assert!(app.subcommands().contains_key("database"));
}
#[test]
fn test_completion_with_aliases() {
let app = create_test_cli();
let db_cmd = app.find_subcommand("db");
assert!(db_cmd.is_some());
assert_eq!(db_cmd.unwrap().name(), "database");
}
#[test]
fn test_dynamic_arg_completion() {
let app = create_test_cli();
let ctx = flag_rs::Context::new(vec![
"testcli".to_string(),
"server".to_string(),
"start".to_string(),
]);
let server_cmd = app.find_subcommand("server").unwrap();
let start_cmd = server_cmd.find_subcommand("start").unwrap();
let result = start_cmd.get_completions(&ctx, "dev", None).unwrap();
assert_eq!(result.values, vec!["development"]);
let result = start_cmd.get_completions(&ctx, "prod", None).unwrap();
assert_eq!(result.values, vec!["production"]);
let result = start_cmd.get_completions(&ctx, "", None).unwrap();
assert_eq!(result.values.len(), 3);
}
#[test]
fn test_dynamic_flag_completion() {
let app = create_test_cli();
let ctx = flag_rs::Context::new(vec!["testcli".to_string(), "database".to_string()]);
let db_cmd = app.find_subcommand("database").unwrap();
let result = db_cmd.get_completions(&ctx, "local", Some("host")).unwrap();
assert_eq!(result.values, vec!["localhost"]);
let result = db_cmd.get_completions(&ctx, "db.", Some("host")).unwrap();
assert_eq!(result.values, vec!["db.example.com"]);
}
#[test]
fn test_nested_subcommand_structure() {
let app = create_test_cli();
let server = app.find_subcommand("server").unwrap();
assert!(server.subcommands().contains_key("start"));
assert!(server.subcommands().contains_key("stop"));
let database = app.find_subcommand("database").unwrap();
assert!(database.subcommands().contains_key("migrate"));
}
#[test]
fn test_flag_types() {
let app = CommandBuilder::new("typetest")
.flag(Flag::new("choice").value_type(FlagType::Choice(vec![
"option1".to_string(),
"option2".to_string(),
"option3".to_string(),
])))
.flag(Flag::new("file").value_type(FlagType::File))
.flag(Flag::new("dir").value_type(FlagType::Directory))
.build();
let choice_flag = app.flags().get("choice").unwrap();
match &choice_flag.value_type {
FlagType::Choice(choices) => {
assert_eq!(choices.len(), 3);
assert!(choices.contains(&"option1".to_string()));
}
_ => panic!("Expected Choice type"),
}
}
#[test]
fn test_completion_with_global_flags() {
let app = CommandBuilder::new("globaltest")
.flag(
Flag::new("global")
.usage("A global flag")
.value_type(FlagType::Bool),
)
.subcommand(
CommandBuilder::new("sub")
.flag(
Flag::new("local")
.usage("A local flag")
.value_type(FlagType::Bool),
)
.build(),
)
.build();
assert!(app.flags().contains_key("global"));
let sub = app.find_subcommand("sub").unwrap();
assert!(sub.flags().contains_key("local"));
}
#[test]
fn test_completion_special_characters() {
let app = CommandBuilder::new("special-test")
.subcommand(
CommandBuilder::new("sub-command")
.flag(
Flag::new("with-dash")
.usage("Flag with dash")
.value_type(FlagType::Bool),
)
.build(),
)
.build();
let sub = app.find_subcommand("sub-command");
assert!(sub.is_some());
assert!(sub.unwrap().flags().contains_key("with-dash"));
}
#[test]
fn test_empty_completion_result() {
let app = CommandBuilder::new("emptytest")
.arg_completion(|_ctx, prefix| {
if prefix.starts_with('z') {
Ok(CompletionResult::new())
} else {
Ok(CompletionResult::new().add("something"))
}
})
.build();
let ctx = flag_rs::Context::new(vec!["emptytest".to_string()]);
let result = app.get_completions(&ctx, "z", None).unwrap();
assert!(result.values.is_empty());
let result = app.get_completions(&ctx, "s", None).unwrap();
assert_eq!(result.values, vec!["something"]);
}
#[test]
fn test_completion_with_descriptions() {
let app = CommandBuilder::new("desctest")
.arg_completion(|_ctx, _prefix| {
Ok(CompletionResult::new()
.add_with_description("option1", "First option")
.add_with_description("option2", "Second option")
.add("option3"))
})
.build();
let ctx = flag_rs::Context::new(vec!["desctest".to_string()]);
let result = app.get_completions(&ctx, "", None).unwrap();
assert_eq!(result.values.len(), 3);
assert_eq!(result.descriptions[0], "First option");
assert_eq!(result.descriptions[1], "Second option");
assert_eq!(result.descriptions[2], "");
}