|
1 | 1 | use std::env; |
2 | 2 | use std::fs; |
3 | | -use std::path::PathBuf; |
| 3 | +use std::path::{Path, PathBuf}; |
4 | 4 | use std::process::Command; |
5 | 5 |
|
| 6 | +/// Helper to run usage complete-word and return stdout |
| 7 | +fn run_complete_word(usage_bin: &Path, shell: &str, spec_file: &Path, words: &[&str]) -> String { |
| 8 | + let mut args = vec![ |
| 9 | + "complete-word".to_string(), |
| 10 | + "--shell".to_string(), |
| 11 | + shell.to_string(), |
| 12 | + "-f".to_string(), |
| 13 | + spec_file.to_str().unwrap().to_string(), |
| 14 | + "--".to_string(), |
| 15 | + ]; |
| 16 | + args.extend(words.iter().map(|w| w.to_string())); |
| 17 | + |
| 18 | + let output = Command::new(usage_bin) |
| 19 | + .args(&args) |
| 20 | + .output() |
| 21 | + .expect("Failed to run usage complete-word"); |
| 22 | + |
| 23 | + String::from_utf8_lossy(&output.stdout).to_string() |
| 24 | +} |
| 25 | + |
6 | 26 | /// Build the usage binary and return its path |
7 | 27 | fn build_usage_binary() -> PathBuf { |
8 | 28 | let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); |
@@ -673,3 +693,85 @@ Write-Host "COMPLETION_TEST_DONE" |
673 | 693 | // Cleanup |
674 | 694 | let _ = fs::remove_dir_all(&temp_dir); |
675 | 695 | } |
| 696 | + |
| 697 | +#[test] |
| 698 | +fn test_zsh_complete_word_output_format() { |
| 699 | + let usage_bin = build_usage_binary(); |
| 700 | + |
| 701 | + let temp_dir = env::temp_dir().join(format!("usage_zsh_fmt_test_{}", std::process::id())); |
| 702 | + fs::create_dir_all(&temp_dir).unwrap(); |
| 703 | + |
| 704 | + // Spec with subcommands (which have descriptions) |
| 705 | + let usage_spec = r#"name testcli |
| 706 | +bin testcli |
| 707 | +flag "-v --verbose" help="Verbose output" |
| 708 | +arg <file> help="Input file" |
| 709 | +cmd sub help="A subcommand" |
| 710 | +cmd other help="Another subcommand" |
| 711 | +"#; |
| 712 | + let spec_file = temp_dir.join("test.spec"); |
| 713 | + fs::write(&spec_file, usage_spec).unwrap(); |
| 714 | + |
| 715 | + // Test zsh output format: should be `name:description` for _describe |
| 716 | + let output = run_complete_word(&usage_bin, "zsh", &spec_file, &["testcli", ""]); |
| 717 | + let lines: Vec<&str> = output.lines().collect(); |
| 718 | + |
| 719 | + // Should have completions with description format "name:description" |
| 720 | + assert!( |
| 721 | + lines.iter().any(|l| l.contains("sub:A subcommand")), |
| 722 | + "Expected 'sub:A subcommand' in zsh output, got: {:?}", |
| 723 | + lines |
| 724 | + ); |
| 725 | + assert!( |
| 726 | + lines.iter().any(|l| l.contains("other:Another subcommand")), |
| 727 | + "Expected 'other:Another subcommand' in zsh output, got: {:?}", |
| 728 | + lines |
| 729 | + ); |
| 730 | + |
| 731 | + // Cleanup |
| 732 | + let _ = fs::remove_dir_all(&temp_dir); |
| 733 | +} |
| 734 | + |
| 735 | +#[test] |
| 736 | +fn test_complete_path_adds_trailing_slash_for_directories() { |
| 737 | + let usage_bin = build_usage_binary(); |
| 738 | + |
| 739 | + let temp_dir = env::temp_dir().join(format!("usage_path_test_{}", std::process::id())); |
| 740 | + fs::create_dir_all(&temp_dir).unwrap(); |
| 741 | + |
| 742 | + // Create a test directory structure |
| 743 | + let test_dir = temp_dir.join("testdir"); |
| 744 | + fs::create_dir_all(test_dir.join("subdir")).unwrap(); |
| 745 | + fs::write(test_dir.join("file.txt"), "hello").unwrap(); |
| 746 | + |
| 747 | + // Spec with a path-type arg |
| 748 | + let usage_spec = r#"name testcli |
| 749 | +bin testcli |
| 750 | +arg <path> |
| 751 | +complete path type="path" |
| 752 | +"#; |
| 753 | + let spec_file = temp_dir.join("test.spec"); |
| 754 | + fs::write(&spec_file, usage_spec).unwrap(); |
| 755 | + |
| 756 | + // Complete with a path prefix pointing to our test directory |
| 757 | + let test_dir_str = format!("{}/", test_dir.to_str().unwrap()); |
| 758 | + let output = run_complete_word(&usage_bin, "bash", &spec_file, &["testcli", &test_dir_str]); |
| 759 | + let lines: Vec<&str> = output.lines().collect(); |
| 760 | + |
| 761 | + // Directory should have trailing slash |
| 762 | + assert!( |
| 763 | + lines.iter().any(|l| l.ends_with("subdir/")), |
| 764 | + "Expected directory completion to end with '/', got: {:?}", |
| 765 | + lines |
| 766 | + ); |
| 767 | + |
| 768 | + // File should NOT have trailing slash |
| 769 | + assert!( |
| 770 | + lines.iter().any(|l| l.ends_with("file.txt")), |
| 771 | + "Expected file completion without trailing '/', got: {:?}", |
| 772 | + lines |
| 773 | + ); |
| 774 | + |
| 775 | + // Cleanup |
| 776 | + let _ = fs::remove_dir_all(&temp_dir); |
| 777 | +} |
0 commit comments