1package cobra
2
3import (
4	"bytes"
5	"context"
6	"strings"
7	"testing"
8)
9
10func validArgsFunc(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
11	if len(args) != 0 {
12		return nil, ShellCompDirectiveNoFileComp
13	}
14
15	var completions []string
16	for _, comp := range []string{"one\tThe first", "two\tThe second"} {
17		if strings.HasPrefix(comp, toComplete) {
18			completions = append(completions, comp)
19		}
20	}
21	return completions, ShellCompDirectiveDefault
22}
23
24func validArgsFunc2(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
25	if len(args) != 0 {
26		return nil, ShellCompDirectiveNoFileComp
27	}
28
29	var completions []string
30	for _, comp := range []string{"three\tThe third", "four\tThe fourth"} {
31		if strings.HasPrefix(comp, toComplete) {
32			completions = append(completions, comp)
33		}
34	}
35	return completions, ShellCompDirectiveDefault
36}
37
38func TestCmdNameCompletionInGo(t *testing.T) {
39	rootCmd := &Command{
40		Use: "root",
41		Run: emptyRun,
42	}
43	childCmd1 := &Command{
44		Use:   "firstChild",
45		Short: "First command",
46		Run:   emptyRun,
47	}
48	childCmd2 := &Command{
49		Use: "secondChild",
50		Run: emptyRun,
51	}
52	hiddenCmd := &Command{
53		Use:    "testHidden",
54		Hidden: true, // Not completed
55		Run:    emptyRun,
56	}
57	deprecatedCmd := &Command{
58		Use:        "testDeprecated",
59		Deprecated: "deprecated", // Not completed
60		Run:        emptyRun,
61	}
62	aliasedCmd := &Command{
63		Use:     "aliased",
64		Short:   "A command with aliases",
65		Aliases: []string{"testAlias", "testSynonym"}, // Not completed
66		Run:     emptyRun,
67	}
68
69	rootCmd.AddCommand(childCmd1, childCmd2, hiddenCmd, deprecatedCmd, aliasedCmd)
70
71	// Test that sub-command names are completed
72	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
73	if err != nil {
74		t.Errorf("Unexpected error: %v", err)
75	}
76
77	expected := strings.Join([]string{
78		"aliased",
79		"completion",
80		"firstChild",
81		"help",
82		"secondChild",
83		":4",
84		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
85
86	if output != expected {
87		t.Errorf("expected: %q, got: %q", expected, output)
88	}
89
90	// Test that sub-command names are completed with prefix
91	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "s")
92	if err != nil {
93		t.Errorf("Unexpected error: %v", err)
94	}
95
96	expected = strings.Join([]string{
97		"secondChild",
98		":4",
99		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
100
101	if output != expected {
102		t.Errorf("expected: %q, got: %q", expected, output)
103	}
104
105	// Test that even with no valid sub-command matches, hidden, deprecated and
106	// aliases are not completed
107	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "test")
108	if err != nil {
109		t.Errorf("Unexpected error: %v", err)
110	}
111
112	expected = strings.Join([]string{
113		":4",
114		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
115
116	if output != expected {
117		t.Errorf("expected: %q, got: %q", expected, output)
118	}
119
120	// Test that sub-command names are completed with description
121	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "")
122	if err != nil {
123		t.Errorf("Unexpected error: %v", err)
124	}
125
126	expected = strings.Join([]string{
127		"aliased\tA command with aliases",
128		"completion\tgenerate the autocompletion script for the specified shell",
129		"firstChild\tFirst command",
130		"help\tHelp about any command",
131		"secondChild",
132		":4",
133		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
134
135	if output != expected {
136		t.Errorf("expected: %q, got: %q", expected, output)
137	}
138}
139
140func TestNoCmdNameCompletionInGo(t *testing.T) {
141	rootCmd := &Command{
142		Use: "root",
143		Run: emptyRun,
144	}
145	rootCmd.Flags().String("localroot", "", "local root flag")
146
147	childCmd1 := &Command{
148		Use:   "childCmd1",
149		Short: "First command",
150		Args:  MinimumNArgs(0),
151		Run:   emptyRun,
152	}
153	rootCmd.AddCommand(childCmd1)
154	childCmd1.PersistentFlags().StringP("persistent", "p", "", "persistent flag")
155	persistentFlag := childCmd1.PersistentFlags().Lookup("persistent")
156	childCmd1.Flags().StringP("nonPersistent", "n", "", "non-persistent flag")
157	nonPersistentFlag := childCmd1.Flags().Lookup("nonPersistent")
158
159	childCmd2 := &Command{
160		Use: "childCmd2",
161		Run: emptyRun,
162	}
163	childCmd1.AddCommand(childCmd2)
164
165	// Test that sub-command names are not completed if there is an argument already
166	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "arg1", "")
167	if err != nil {
168		t.Errorf("Unexpected error: %v", err)
169	}
170
171	expected := strings.Join([]string{
172		":0",
173		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
174
175	if output != expected {
176		t.Errorf("expected: %q, got: %q", expected, output)
177	}
178
179	// Test that sub-command names are not completed if a local non-persistent flag is present
180	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--nonPersistent", "value", "")
181	if err != nil {
182		t.Errorf("Unexpected error: %v", err)
183	}
184	// Reset the flag for the next command
185	nonPersistentFlag.Changed = false
186
187	expected = strings.Join([]string{
188		":0",
189		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
190
191	if output != expected {
192		t.Errorf("expected: %q, got: %q", expected, output)
193	}
194
195	// Test that sub-command names are completed if a local non-persistent flag is present and TraverseChildren is set to true
196	// set TraverseChildren to true on the root cmd
197	rootCmd.TraverseChildren = true
198
199	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "")
200	if err != nil {
201		t.Errorf("Unexpected error: %v", err)
202	}
203	// Reset TraverseChildren for next command
204	rootCmd.TraverseChildren = false
205
206	expected = strings.Join([]string{
207		"childCmd1",
208		"completion",
209		"help",
210		":4",
211		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
212
213	if output != expected {
214		t.Errorf("expected: %q, got: %q", expected, output)
215	}
216
217	// Test that sub-command names from a child cmd are completed if a local non-persistent flag is present
218	// and TraverseChildren is set to true on the root cmd
219	rootCmd.TraverseChildren = true
220
221	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "--nonPersistent", "value", "")
222	if err != nil {
223		t.Errorf("Unexpected error: %v", err)
224	}
225	// Reset TraverseChildren for next command
226	rootCmd.TraverseChildren = false
227	// Reset the flag for the next command
228	nonPersistentFlag.Changed = false
229
230	expected = strings.Join([]string{
231		"childCmd2",
232		":4",
233		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
234
235	if output != expected {
236		t.Errorf("expected: %q, got: %q", expected, output)
237	}
238
239	// Test that we don't use Traverse when we shouldn't.
240	// This command should not return a completion since the command line is invalid without TraverseChildren.
241	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--localroot", "value", "childCmd1", "")
242	if err != nil {
243		t.Errorf("Unexpected error: %v", err)
244	}
245
246	expected = strings.Join([]string{
247		":0",
248		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
249
250	if output != expected {
251		t.Errorf("expected: %q, got: %q", expected, output)
252	}
253
254	// Test that sub-command names are not completed if a local non-persistent short flag is present
255	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-n", "value", "")
256	if err != nil {
257		t.Errorf("Unexpected error: %v", err)
258	}
259	// Reset the flag for the next command
260	nonPersistentFlag.Changed = false
261
262	expected = strings.Join([]string{
263		":0",
264		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
265
266	if output != expected {
267		t.Errorf("expected: %q, got: %q", expected, output)
268	}
269
270	// Test that sub-command names are completed with a persistent flag
271	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "--persistent", "value", "")
272	if err != nil {
273		t.Errorf("Unexpected error: %v", err)
274	}
275	// Reset the flag for the next command
276	persistentFlag.Changed = false
277
278	expected = strings.Join([]string{
279		"childCmd2",
280		":4",
281		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
282
283	if output != expected {
284		t.Errorf("expected: %q, got: %q", expected, output)
285	}
286
287	// Test that sub-command names are completed with a persistent short flag
288	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd1", "-p", "value", "")
289	if err != nil {
290		t.Errorf("Unexpected error: %v", err)
291	}
292	// Reset the flag for the next command
293	persistentFlag.Changed = false
294
295	expected = strings.Join([]string{
296		"childCmd2",
297		":4",
298		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
299
300	if output != expected {
301		t.Errorf("expected: %q, got: %q", expected, output)
302	}
303}
304
305func TestValidArgsCompletionInGo(t *testing.T) {
306	rootCmd := &Command{
307		Use:       "root",
308		ValidArgs: []string{"one", "two", "three"},
309		Args:      MinimumNArgs(1),
310	}
311
312	// Test that validArgs are completed
313	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
314	if err != nil {
315		t.Errorf("Unexpected error: %v", err)
316	}
317
318	expected := strings.Join([]string{
319		"one",
320		"two",
321		"three",
322		":4",
323		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
324
325	if output != expected {
326		t.Errorf("expected: %q, got: %q", expected, output)
327	}
328
329	// Test that validArgs are completed with prefix
330	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "o")
331	if err != nil {
332		t.Errorf("Unexpected error: %v", err)
333	}
334
335	expected = strings.Join([]string{
336		"one",
337		":4",
338		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
339
340	if output != expected {
341		t.Errorf("expected: %q, got: %q", expected, output)
342	}
343
344	// Test that validArgs don't repeat
345	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "one", "")
346	if err != nil {
347		t.Errorf("Unexpected error: %v", err)
348	}
349
350	expected = strings.Join([]string{
351		":0",
352		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
353
354	if output != expected {
355		t.Errorf("expected: %q, got: %q", expected, output)
356	}
357}
358
359func TestValidArgsAndCmdCompletionInGo(t *testing.T) {
360	rootCmd := &Command{
361		Use:       "root",
362		ValidArgs: []string{"one", "two"},
363		Run:       emptyRun,
364	}
365
366	childCmd := &Command{
367		Use: "thechild",
368		Run: emptyRun,
369	}
370
371	rootCmd.AddCommand(childCmd)
372
373	// Test that both sub-commands and validArgs are completed
374	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
375	if err != nil {
376		t.Errorf("Unexpected error: %v", err)
377	}
378
379	expected := strings.Join([]string{
380		"completion",
381		"help",
382		"thechild",
383		"one",
384		"two",
385		":4",
386		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
387
388	if output != expected {
389		t.Errorf("expected: %q, got: %q", expected, output)
390	}
391
392	// Test that both sub-commands and validArgs are completed with prefix
393	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
394	if err != nil {
395		t.Errorf("Unexpected error: %v", err)
396	}
397
398	expected = strings.Join([]string{
399		"thechild",
400		"two",
401		":4",
402		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
403
404	if output != expected {
405		t.Errorf("expected: %q, got: %q", expected, output)
406	}
407}
408
409func TestValidArgsFuncAndCmdCompletionInGo(t *testing.T) {
410	rootCmd := &Command{
411		Use:               "root",
412		ValidArgsFunction: validArgsFunc,
413		Run:               emptyRun,
414	}
415
416	childCmd := &Command{
417		Use:   "thechild",
418		Short: "The child command",
419		Run:   emptyRun,
420	}
421
422	rootCmd.AddCommand(childCmd)
423
424	// Test that both sub-commands and validArgsFunction are completed
425	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
426	if err != nil {
427		t.Errorf("Unexpected error: %v", err)
428	}
429
430	expected := strings.Join([]string{
431		"completion",
432		"help",
433		"thechild",
434		"one",
435		"two",
436		":0",
437		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
438
439	if output != expected {
440		t.Errorf("expected: %q, got: %q", expected, output)
441	}
442
443	// Test that both sub-commands and validArgs are completed with prefix
444	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
445	if err != nil {
446		t.Errorf("Unexpected error: %v", err)
447	}
448
449	expected = strings.Join([]string{
450		"thechild",
451		"two",
452		":0",
453		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
454
455	if output != expected {
456		t.Errorf("expected: %q, got: %q", expected, output)
457	}
458
459	// Test that both sub-commands and validArgs are completed with description
460	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "t")
461	if err != nil {
462		t.Errorf("Unexpected error: %v", err)
463	}
464
465	expected = strings.Join([]string{
466		"thechild\tThe child command",
467		"two\tThe second",
468		":0",
469		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
470
471	if output != expected {
472		t.Errorf("expected: %q, got: %q", expected, output)
473	}
474}
475
476func TestFlagNameCompletionInGo(t *testing.T) {
477	rootCmd := &Command{
478		Use: "root",
479		Run: emptyRun,
480	}
481	childCmd := &Command{
482		Use: "childCmd",
483		Run: emptyRun,
484	}
485	rootCmd.AddCommand(childCmd)
486
487	rootCmd.Flags().IntP("first", "f", -1, "first flag")
488	rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
489	childCmd.Flags().String("subFlag", "", "sub flag")
490
491	// Test that flag names are not shown if the user has not given the '-' prefix
492	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
493	if err != nil {
494		t.Errorf("Unexpected error: %v", err)
495	}
496
497	expected := strings.Join([]string{
498		"childCmd",
499		"completion",
500		"help",
501		":4",
502		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
503
504	if output != expected {
505		t.Errorf("expected: %q, got: %q", expected, output)
506	}
507
508	// Test that flag names are completed
509	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-")
510	if err != nil {
511		t.Errorf("Unexpected error: %v", err)
512	}
513
514	expected = strings.Join([]string{
515		"--first",
516		"-f",
517		"--second",
518		"-s",
519		":4",
520		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
521
522	if output != expected {
523		t.Errorf("expected: %q, got: %q", expected, output)
524	}
525
526	// Test that flag names are completed when a prefix is given
527	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--f")
528	if err != nil {
529		t.Errorf("Unexpected error: %v", err)
530	}
531
532	expected = strings.Join([]string{
533		"--first",
534		":4",
535		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
536
537	if output != expected {
538		t.Errorf("expected: %q, got: %q", expected, output)
539	}
540
541	// Test that flag names are completed in a sub-cmd
542	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-")
543	if err != nil {
544		t.Errorf("Unexpected error: %v", err)
545	}
546
547	expected = strings.Join([]string{
548		"--second",
549		"-s",
550		"--subFlag",
551		":4",
552		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
553
554	if output != expected {
555		t.Errorf("expected: %q, got: %q", expected, output)
556	}
557}
558
559func TestFlagNameCompletionInGoWithDesc(t *testing.T) {
560	rootCmd := &Command{
561		Use: "root",
562		Run: emptyRun,
563	}
564	childCmd := &Command{
565		Use:   "childCmd",
566		Short: "first command",
567		Run:   emptyRun,
568	}
569	rootCmd.AddCommand(childCmd)
570
571	rootCmd.Flags().IntP("first", "f", -1, "first flag\nlonger description for flag")
572	rootCmd.PersistentFlags().BoolP("second", "s", false, "second flag")
573	childCmd.Flags().String("subFlag", "", "sub flag")
574
575	// Test that flag names are not shown if the user has not given the '-' prefix
576	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "")
577	if err != nil {
578		t.Errorf("Unexpected error: %v", err)
579	}
580
581	expected := strings.Join([]string{
582		"childCmd\tfirst command",
583		"completion\tgenerate the autocompletion script for the specified shell",
584		"help\tHelp about any command",
585		":4",
586		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
587
588	if output != expected {
589		t.Errorf("expected: %q, got: %q", expected, output)
590	}
591
592	// Test that flag names are completed
593	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "-")
594	if err != nil {
595		t.Errorf("Unexpected error: %v", err)
596	}
597
598	expected = strings.Join([]string{
599		"--first\tfirst flag",
600		"-f\tfirst flag",
601		"--second\tsecond flag",
602		"-s\tsecond flag",
603		":4",
604		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
605
606	if output != expected {
607		t.Errorf("expected: %q, got: %q", expected, output)
608	}
609
610	// Test that flag names are completed when a prefix is given
611	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--f")
612	if err != nil {
613		t.Errorf("Unexpected error: %v", err)
614	}
615
616	expected = strings.Join([]string{
617		"--first\tfirst flag",
618		":4",
619		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
620
621	if output != expected {
622		t.Errorf("expected: %q, got: %q", expected, output)
623	}
624
625	// Test that flag names are completed in a sub-cmd
626	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "childCmd", "-")
627	if err != nil {
628		t.Errorf("Unexpected error: %v", err)
629	}
630
631	expected = strings.Join([]string{
632		"--second\tsecond flag",
633		"-s\tsecond flag",
634		"--subFlag\tsub flag",
635		":4",
636		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
637
638	if output != expected {
639		t.Errorf("expected: %q, got: %q", expected, output)
640	}
641}
642
643func TestFlagNameCompletionRepeat(t *testing.T) {
644	rootCmd := &Command{
645		Use: "root",
646		Run: emptyRun,
647	}
648	childCmd := &Command{
649		Use:   "childCmd",
650		Short: "first command",
651		Run:   emptyRun,
652	}
653	rootCmd.AddCommand(childCmd)
654
655	rootCmd.Flags().IntP("first", "f", -1, "first flag")
656	firstFlag := rootCmd.Flags().Lookup("first")
657	rootCmd.Flags().BoolP("second", "s", false, "second flag")
658	secondFlag := rootCmd.Flags().Lookup("second")
659	rootCmd.Flags().StringArrayP("array", "a", nil, "array flag")
660	arrayFlag := rootCmd.Flags().Lookup("array")
661	rootCmd.Flags().IntSliceP("slice", "l", nil, "slice flag")
662	sliceFlag := rootCmd.Flags().Lookup("slice")
663	rootCmd.Flags().BoolSliceP("bslice", "b", nil, "bool slice flag")
664	bsliceFlag := rootCmd.Flags().Lookup("bslice")
665
666	// Test that flag names are not repeated unless they are an array or slice
667	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--")
668	if err != nil {
669		t.Errorf("Unexpected error: %v", err)
670	}
671	// Reset the flag for the next command
672	firstFlag.Changed = false
673
674	expected := strings.Join([]string{
675		"--array",
676		"--bslice",
677		"--second",
678		"--slice",
679		":4",
680		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
681
682	if output != expected {
683		t.Errorf("expected: %q, got: %q", expected, output)
684	}
685
686	// Test that flag names are not repeated unless they are an array or slice
687	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--first", "1", "--second=false", "--")
688	if err != nil {
689		t.Errorf("Unexpected error: %v", err)
690	}
691	// Reset the flag for the next command
692	firstFlag.Changed = false
693	secondFlag.Changed = false
694
695	expected = strings.Join([]string{
696		"--array",
697		"--bslice",
698		"--slice",
699		":4",
700		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
701
702	if output != expected {
703		t.Errorf("expected: %q, got: %q", expected, output)
704	}
705
706	// Test that flag names are not repeated unless they are an array or slice
707	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--slice", "1", "--slice=2", "--array", "val", "--bslice", "true", "--")
708	if err != nil {
709		t.Errorf("Unexpected error: %v", err)
710	}
711	// Reset the flag for the next command
712	sliceFlag.Changed = false
713	arrayFlag.Changed = false
714	bsliceFlag.Changed = false
715
716	expected = strings.Join([]string{
717		"--array",
718		"--bslice",
719		"--first",
720		"--second",
721		"--slice",
722		":4",
723		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
724
725	if output != expected {
726		t.Errorf("expected: %q, got: %q", expected, output)
727	}
728
729	// Test that flag names are not repeated unless they are an array or slice, using shortname
730	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-")
731	if err != nil {
732		t.Errorf("Unexpected error: %v", err)
733	}
734	// Reset the flag for the next command
735	sliceFlag.Changed = false
736	arrayFlag.Changed = false
737
738	expected = strings.Join([]string{
739		"--array",
740		"-a",
741		"--bslice",
742		"-b",
743		"--first",
744		"-f",
745		"--second",
746		"-s",
747		"--slice",
748		"-l",
749		":4",
750		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
751
752	if output != expected {
753		t.Errorf("expected: %q, got: %q", expected, output)
754	}
755
756	// Test that flag names are not repeated unless they are an array or slice, using shortname with prefix
757	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-l", "1", "-l=2", "-a", "val", "-a")
758	if err != nil {
759		t.Errorf("Unexpected error: %v", err)
760	}
761	// Reset the flag for the next command
762	sliceFlag.Changed = false
763	arrayFlag.Changed = false
764
765	expected = strings.Join([]string{
766		"-a",
767		":4",
768		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
769
770	if output != expected {
771		t.Errorf("expected: %q, got: %q", expected, output)
772	}
773}
774
775func TestRequiredFlagNameCompletionInGo(t *testing.T) {
776	rootCmd := &Command{
777		Use:       "root",
778		ValidArgs: []string{"realArg"},
779		Run:       emptyRun,
780	}
781	childCmd := &Command{
782		Use: "childCmd",
783		ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
784			return []string{"subArg"}, ShellCompDirectiveNoFileComp
785		},
786		Run: emptyRun,
787	}
788	rootCmd.AddCommand(childCmd)
789
790	rootCmd.Flags().IntP("requiredFlag", "r", -1, "required flag")
791	assertNoErr(t, rootCmd.MarkFlagRequired("requiredFlag"))
792	requiredFlag := rootCmd.Flags().Lookup("requiredFlag")
793
794	rootCmd.PersistentFlags().IntP("requiredPersistent", "p", -1, "required persistent")
795	assertNoErr(t, rootCmd.MarkPersistentFlagRequired("requiredPersistent"))
796	requiredPersistent := rootCmd.PersistentFlags().Lookup("requiredPersistent")
797
798	rootCmd.Flags().StringP("release", "R", "", "Release name")
799
800	childCmd.Flags().BoolP("subRequired", "s", false, "sub required flag")
801	assertNoErr(t, childCmd.MarkFlagRequired("subRequired"))
802	childCmd.Flags().BoolP("subNotRequired", "n", false, "sub not required flag")
803
804	// Test that a required flag is suggested even without the - prefix
805	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
806	if err != nil {
807		t.Errorf("Unexpected error: %v", err)
808	}
809
810	expected := strings.Join([]string{
811		"childCmd",
812		"completion",
813		"help",
814		"--requiredFlag",
815		"-r",
816		"--requiredPersistent",
817		"-p",
818		"realArg",
819		":4",
820		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
821
822	if output != expected {
823		t.Errorf("expected: %q, got: %q", expected, output)
824	}
825
826	// Test that a required flag is suggested without other flags when using the '-' prefix
827	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-")
828	if err != nil {
829		t.Errorf("Unexpected error: %v", err)
830	}
831
832	expected = strings.Join([]string{
833		"--requiredFlag",
834		"-r",
835		"--requiredPersistent",
836		"-p",
837		":4",
838		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
839
840	if output != expected {
841		t.Errorf("expected: %q, got: %q", expected, output)
842	}
843
844	// Test that if no required flag matches, the normal flags are suggested
845	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--relea")
846	if err != nil {
847		t.Errorf("Unexpected error: %v", err)
848	}
849
850	expected = strings.Join([]string{
851		"--release",
852		":4",
853		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
854
855	if output != expected {
856		t.Errorf("expected: %q, got: %q", expected, output)
857	}
858
859	// Test required flags for sub-commands
860	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "")
861	if err != nil {
862		t.Errorf("Unexpected error: %v", err)
863	}
864
865	expected = strings.Join([]string{
866		"--requiredPersistent",
867		"-p",
868		"--subRequired",
869		"-s",
870		"subArg",
871		":4",
872		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
873
874	if output != expected {
875		t.Errorf("expected: %q, got: %q", expected, output)
876	}
877
878	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "-")
879	if err != nil {
880		t.Errorf("Unexpected error: %v", err)
881	}
882
883	expected = strings.Join([]string{
884		"--requiredPersistent",
885		"-p",
886		"--subRequired",
887		"-s",
888		":4",
889		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
890
891	if output != expected {
892		t.Errorf("expected: %q, got: %q", expected, output)
893	}
894
895	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "childCmd", "--subNot")
896	if err != nil {
897		t.Errorf("Unexpected error: %v", err)
898	}
899
900	expected = strings.Join([]string{
901		"--subNotRequired",
902		":4",
903		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
904
905	if output != expected {
906		t.Errorf("expected: %q, got: %q", expected, output)
907	}
908
909	// Test that when a required flag is present, it is not suggested anymore
910	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "")
911	if err != nil {
912		t.Errorf("Unexpected error: %v", err)
913	}
914	// Reset the flag for the next command
915	requiredFlag.Changed = false
916
917	expected = strings.Join([]string{
918		"--requiredPersistent",
919		"-p",
920		"realArg",
921		":4",
922		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
923
924	if output != expected {
925		t.Errorf("expected: %q, got: %q", expected, output)
926	}
927
928	// Test that when a persistent required flag is present, it is not suggested anymore
929	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredPersistent", "1", "")
930	if err != nil {
931		t.Errorf("Unexpected error: %v", err)
932	}
933	// Reset the flag for the next command
934	requiredPersistent.Changed = false
935
936	expected = strings.Join([]string{
937		"childCmd",
938		"completion",
939		"help",
940		"--requiredFlag",
941		"-r",
942		"realArg",
943		":4",
944		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
945
946	if output != expected {
947		t.Errorf("expected: %q, got: %q", expected, output)
948	}
949
950	// Test that when all required flags are present, normal completion is done
951	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--requiredFlag", "1", "--requiredPersistent", "1", "")
952	if err != nil {
953		t.Errorf("Unexpected error: %v", err)
954	}
955	// Reset the flags for the next command
956	requiredFlag.Changed = false
957	requiredPersistent.Changed = false
958
959	expected = strings.Join([]string{
960		"realArg",
961		":4",
962		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
963
964	if output != expected {
965		t.Errorf("expected: %q, got: %q", expected, output)
966	}
967}
968
969func TestFlagFileExtFilterCompletionInGo(t *testing.T) {
970	rootCmd := &Command{
971		Use: "root",
972		Run: emptyRun,
973	}
974
975	// No extensions.  Should be ignored.
976	rootCmd.Flags().StringP("file", "f", "", "file flag")
977	assertNoErr(t, rootCmd.MarkFlagFilename("file"))
978
979	// Single extension
980	rootCmd.Flags().StringP("log", "l", "", "log flag")
981	assertNoErr(t, rootCmd.MarkFlagFilename("log", "log"))
982
983	// Multiple extensions
984	rootCmd.Flags().StringP("yaml", "y", "", "yaml flag")
985	assertNoErr(t, rootCmd.MarkFlagFilename("yaml", "yaml", "yml"))
986
987	// Directly using annotation
988	rootCmd.Flags().StringP("text", "t", "", "text flag")
989	assertNoErr(t, rootCmd.Flags().SetAnnotation("text", BashCompFilenameExt, []string{"txt"}))
990
991	// Test that the completion logic returns the proper info for the completion
992	// script to handle the file filtering
993	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--file", "")
994	if err != nil {
995		t.Errorf("Unexpected error: %v", err)
996	}
997
998	expected := strings.Join([]string{
999		":0",
1000		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1001
1002	if output != expected {
1003		t.Errorf("expected: %q, got: %q", expected, output)
1004	}
1005
1006	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--log", "")
1007	if err != nil {
1008		t.Errorf("Unexpected error: %v", err)
1009	}
1010
1011	expected = strings.Join([]string{
1012		"log",
1013		":8",
1014		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
1015
1016	if output != expected {
1017		t.Errorf("expected: %q, got: %q", expected, output)
1018	}
1019
1020	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml", "")
1021	if err != nil {
1022		t.Errorf("Unexpected error: %v", err)
1023	}
1024
1025	expected = strings.Join([]string{
1026		"yaml", "yml",
1027		":8",
1028		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
1029
1030	if output != expected {
1031		t.Errorf("expected: %q, got: %q", expected, output)
1032	}
1033
1034	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--yaml=")
1035	if err != nil {
1036		t.Errorf("Unexpected error: %v", err)
1037	}
1038
1039	expected = strings.Join([]string{
1040		"yaml", "yml",
1041		":8",
1042		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
1043
1044	if output != expected {
1045		t.Errorf("expected: %q, got: %q", expected, output)
1046	}
1047
1048	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y", "")
1049	if err != nil {
1050		t.Errorf("Unexpected error: %v", err)
1051	}
1052
1053	expected = strings.Join([]string{
1054		"yaml", "yml",
1055		":8",
1056		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
1057
1058	if output != expected {
1059		t.Errorf("expected: %q, got: %q", expected, output)
1060	}
1061
1062	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-y=")
1063	if err != nil {
1064		t.Errorf("Unexpected error: %v", err)
1065	}
1066
1067	expected = strings.Join([]string{
1068		"yaml", "yml",
1069		":8",
1070		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
1071
1072	if output != expected {
1073		t.Errorf("expected: %q, got: %q", expected, output)
1074	}
1075
1076	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--text", "")
1077	if err != nil {
1078		t.Errorf("Unexpected error: %v", err)
1079	}
1080
1081	expected = strings.Join([]string{
1082		"txt",
1083		":8",
1084		"Completion ended with directive: ShellCompDirectiveFilterFileExt", ""}, "\n")
1085
1086	if output != expected {
1087		t.Errorf("expected: %q, got: %q", expected, output)
1088	}
1089}
1090
1091func TestFlagDirFilterCompletionInGo(t *testing.T) {
1092	rootCmd := &Command{
1093		Use: "root",
1094		Run: emptyRun,
1095	}
1096
1097	// Filter directories
1098	rootCmd.Flags().StringP("dir", "d", "", "dir flag")
1099	assertNoErr(t, rootCmd.MarkFlagDirname("dir"))
1100
1101	// Filter directories within a directory
1102	rootCmd.Flags().StringP("subdir", "s", "", "subdir")
1103	assertNoErr(t, rootCmd.Flags().SetAnnotation("subdir", BashCompSubdirsInDir, []string{"themes"}))
1104
1105	// Multiple directory specification get ignored
1106	rootCmd.Flags().StringP("manydir", "m", "", "manydir")
1107	assertNoErr(t, rootCmd.Flags().SetAnnotation("manydir", BashCompSubdirsInDir, []string{"themes", "colors"}))
1108
1109	// Test that the completion logic returns the proper info for the completion
1110	// script to handle the directory filtering
1111	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--dir", "")
1112	if err != nil {
1113		t.Errorf("Unexpected error: %v", err)
1114	}
1115
1116	expected := strings.Join([]string{
1117		":16",
1118		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1119
1120	if output != expected {
1121		t.Errorf("expected: %q, got: %q", expected, output)
1122	}
1123
1124	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-d", "")
1125	if err != nil {
1126		t.Errorf("Unexpected error: %v", err)
1127	}
1128
1129	expected = strings.Join([]string{
1130		":16",
1131		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1132
1133	if output != expected {
1134		t.Errorf("expected: %q, got: %q", expected, output)
1135	}
1136
1137	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir", "")
1138	if err != nil {
1139		t.Errorf("Unexpected error: %v", err)
1140	}
1141
1142	expected = strings.Join([]string{
1143		"themes",
1144		":16",
1145		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1146
1147	if output != expected {
1148		t.Errorf("expected: %q, got: %q", expected, output)
1149	}
1150
1151	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--subdir=")
1152	if err != nil {
1153		t.Errorf("Unexpected error: %v", err)
1154	}
1155
1156	expected = strings.Join([]string{
1157		"themes",
1158		":16",
1159		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1160
1161	if output != expected {
1162		t.Errorf("expected: %q, got: %q", expected, output)
1163	}
1164
1165	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "")
1166	if err != nil {
1167		t.Errorf("Unexpected error: %v", err)
1168	}
1169
1170	expected = strings.Join([]string{
1171		"themes",
1172		":16",
1173		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1174
1175	if output != expected {
1176		t.Errorf("expected: %q, got: %q", expected, output)
1177	}
1178
1179	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s=")
1180	if err != nil {
1181		t.Errorf("Unexpected error: %v", err)
1182	}
1183
1184	expected = strings.Join([]string{
1185		"themes",
1186		":16",
1187		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1188
1189	if output != expected {
1190		t.Errorf("expected: %q, got: %q", expected, output)
1191	}
1192
1193	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--manydir", "")
1194	if err != nil {
1195		t.Errorf("Unexpected error: %v", err)
1196	}
1197
1198	expected = strings.Join([]string{
1199		":16",
1200		"Completion ended with directive: ShellCompDirectiveFilterDirs", ""}, "\n")
1201
1202	if output != expected {
1203		t.Errorf("expected: %q, got: %q", expected, output)
1204	}
1205}
1206
1207func TestValidArgsFuncCmdContext(t *testing.T) {
1208	validArgsFunc := func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1209		ctx := cmd.Context()
1210
1211		if ctx == nil {
1212			t.Error("Received nil context in completion func")
1213		} else if ctx.Value("testKey") != "123" {
1214			t.Error("Received invalid context")
1215		}
1216
1217		return nil, ShellCompDirectiveDefault
1218	}
1219
1220	rootCmd := &Command{
1221		Use: "root",
1222		Run: emptyRun,
1223	}
1224	childCmd := &Command{
1225		Use:               "childCmd",
1226		ValidArgsFunction: validArgsFunc,
1227		Run:               emptyRun,
1228	}
1229	rootCmd.AddCommand(childCmd)
1230
1231	//nolint:golint,staticcheck // We can safely use a basic type as key in tests.
1232	ctx := context.WithValue(context.Background(), "testKey", "123")
1233
1234	// Test completing an empty string on the childCmd
1235	_, output, err := executeCommandWithContextC(ctx, rootCmd, ShellCompNoDescRequestCmd, "childCmd", "")
1236	if err != nil {
1237		t.Errorf("Unexpected error: %v", err)
1238	}
1239
1240	expected := strings.Join([]string{
1241		":0",
1242		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1243
1244	if output != expected {
1245		t.Errorf("expected: %q, got: %q", expected, output)
1246	}
1247}
1248
1249func TestValidArgsFuncSingleCmd(t *testing.T) {
1250	rootCmd := &Command{
1251		Use:               "root",
1252		ValidArgsFunction: validArgsFunc,
1253		Run:               emptyRun,
1254	}
1255
1256	// Test completing an empty string
1257	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
1258	if err != nil {
1259		t.Errorf("Unexpected error: %v", err)
1260	}
1261
1262	expected := strings.Join([]string{
1263		"one",
1264		"two",
1265		":0",
1266		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1267
1268	if output != expected {
1269		t.Errorf("expected: %q, got: %q", expected, output)
1270	}
1271
1272	// Check completing with a prefix
1273	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
1274	if err != nil {
1275		t.Errorf("Unexpected error: %v", err)
1276	}
1277
1278	expected = strings.Join([]string{
1279		"two",
1280		":0",
1281		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1282
1283	if output != expected {
1284		t.Errorf("expected: %q, got: %q", expected, output)
1285	}
1286}
1287
1288func TestValidArgsFuncSingleCmdInvalidArg(t *testing.T) {
1289	rootCmd := &Command{
1290		Use: "root",
1291		// If we don't specify a value for Args, this test fails.
1292		// This is only true for a root command without any subcommands, and is caused
1293		// by the fact that the __complete command becomes a subcommand when there should not be one.
1294		// The problem is in the implementation of legacyArgs().
1295		Args:              MinimumNArgs(1),
1296		ValidArgsFunction: validArgsFunc,
1297		Run:               emptyRun,
1298	}
1299
1300	// Check completing with wrong number of args
1301	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "unexpectedArg", "t")
1302	if err != nil {
1303		t.Errorf("Unexpected error: %v", err)
1304	}
1305
1306	expected := strings.Join([]string{
1307		":4",
1308		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1309
1310	if output != expected {
1311		t.Errorf("expected: %q, got: %q", expected, output)
1312	}
1313}
1314
1315func TestValidArgsFuncChildCmds(t *testing.T) {
1316	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1317	child1Cmd := &Command{
1318		Use:               "child1",
1319		ValidArgsFunction: validArgsFunc,
1320		Run:               emptyRun,
1321	}
1322	child2Cmd := &Command{
1323		Use:               "child2",
1324		ValidArgsFunction: validArgsFunc2,
1325		Run:               emptyRun,
1326	}
1327	rootCmd.AddCommand(child1Cmd, child2Cmd)
1328
1329	// Test completion of first sub-command with empty argument
1330	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "")
1331	if err != nil {
1332		t.Errorf("Unexpected error: %v", err)
1333	}
1334
1335	expected := strings.Join([]string{
1336		"one",
1337		"two",
1338		":0",
1339		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1340
1341	if output != expected {
1342		t.Errorf("expected: %q, got: %q", expected, output)
1343	}
1344
1345	// Test completion of first sub-command with a prefix to complete
1346	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "t")
1347	if err != nil {
1348		t.Errorf("Unexpected error: %v", err)
1349	}
1350
1351	expected = strings.Join([]string{
1352		"two",
1353		":0",
1354		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1355
1356	if output != expected {
1357		t.Errorf("expected: %q, got: %q", expected, output)
1358	}
1359
1360	// Check completing with wrong number of args
1361	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child1", "unexpectedArg", "t")
1362	if err != nil {
1363		t.Errorf("Unexpected error: %v", err)
1364	}
1365
1366	expected = strings.Join([]string{
1367		":4",
1368		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1369
1370	if output != expected {
1371		t.Errorf("expected: %q, got: %q", expected, output)
1372	}
1373
1374	// Test completion of second sub-command with empty argument
1375	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "")
1376	if err != nil {
1377		t.Errorf("Unexpected error: %v", err)
1378	}
1379
1380	expected = strings.Join([]string{
1381		"three",
1382		"four",
1383		":0",
1384		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1385
1386	if output != expected {
1387		t.Errorf("expected: %q, got: %q", expected, output)
1388	}
1389
1390	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "t")
1391	if err != nil {
1392		t.Errorf("Unexpected error: %v", err)
1393	}
1394
1395	expected = strings.Join([]string{
1396		"three",
1397		":0",
1398		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1399
1400	if output != expected {
1401		t.Errorf("expected: %q, got: %q", expected, output)
1402	}
1403
1404	// Check completing with wrong number of args
1405	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "child2", "unexpectedArg", "t")
1406	if err != nil {
1407		t.Errorf("Unexpected error: %v", err)
1408	}
1409
1410	expected = strings.Join([]string{
1411		":4",
1412		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1413
1414	if output != expected {
1415		t.Errorf("expected: %q, got: %q", expected, output)
1416	}
1417}
1418
1419func TestValidArgsFuncAliases(t *testing.T) {
1420	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1421	child := &Command{
1422		Use:               "child",
1423		Aliases:           []string{"son", "daughter"},
1424		ValidArgsFunction: validArgsFunc,
1425		Run:               emptyRun,
1426	}
1427	rootCmd.AddCommand(child)
1428
1429	// Test completion of first sub-command with empty argument
1430	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "")
1431	if err != nil {
1432		t.Errorf("Unexpected error: %v", err)
1433	}
1434
1435	expected := strings.Join([]string{
1436		"one",
1437		"two",
1438		":0",
1439		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1440
1441	if output != expected {
1442		t.Errorf("expected: %q, got: %q", expected, output)
1443	}
1444
1445	// Test completion of first sub-command with a prefix to complete
1446	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "daughter", "t")
1447	if err != nil {
1448		t.Errorf("Unexpected error: %v", err)
1449	}
1450
1451	expected = strings.Join([]string{
1452		"two",
1453		":0",
1454		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1455
1456	if output != expected {
1457		t.Errorf("expected: %q, got: %q", expected, output)
1458	}
1459
1460	// Check completing with wrong number of args
1461	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "son", "unexpectedArg", "t")
1462	if err != nil {
1463		t.Errorf("Unexpected error: %v", err)
1464	}
1465
1466	expected = strings.Join([]string{
1467		":4",
1468		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1469
1470	if output != expected {
1471		t.Errorf("expected: %q, got: %q", expected, output)
1472	}
1473}
1474
1475func TestValidArgsFuncInBashScript(t *testing.T) {
1476	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1477	child := &Command{
1478		Use:               "child",
1479		ValidArgsFunction: validArgsFunc,
1480		Run:               emptyRun,
1481	}
1482	rootCmd.AddCommand(child)
1483
1484	buf := new(bytes.Buffer)
1485	assertNoErr(t, rootCmd.GenBashCompletion(buf))
1486	output := buf.String()
1487
1488	check(t, output, "has_completion_function=1")
1489}
1490
1491func TestNoValidArgsFuncInBashScript(t *testing.T) {
1492	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1493	child := &Command{
1494		Use: "child",
1495		Run: emptyRun,
1496	}
1497	rootCmd.AddCommand(child)
1498
1499	buf := new(bytes.Buffer)
1500	assertNoErr(t, rootCmd.GenBashCompletion(buf))
1501	output := buf.String()
1502
1503	checkOmit(t, output, "has_completion_function=1")
1504}
1505
1506func TestCompleteCmdInBashScript(t *testing.T) {
1507	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1508	child := &Command{
1509		Use:               "child",
1510		ValidArgsFunction: validArgsFunc,
1511		Run:               emptyRun,
1512	}
1513	rootCmd.AddCommand(child)
1514
1515	buf := new(bytes.Buffer)
1516	assertNoErr(t, rootCmd.GenBashCompletion(buf))
1517	output := buf.String()
1518
1519	check(t, output, ShellCompNoDescRequestCmd)
1520}
1521
1522func TestCompleteNoDesCmdInZshScript(t *testing.T) {
1523	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1524	child := &Command{
1525		Use:               "child",
1526		ValidArgsFunction: validArgsFunc,
1527		Run:               emptyRun,
1528	}
1529	rootCmd.AddCommand(child)
1530
1531	buf := new(bytes.Buffer)
1532	assertNoErr(t, rootCmd.GenZshCompletionNoDesc(buf))
1533	output := buf.String()
1534
1535	check(t, output, ShellCompNoDescRequestCmd)
1536}
1537
1538func TestCompleteCmdInZshScript(t *testing.T) {
1539	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1540	child := &Command{
1541		Use:               "child",
1542		ValidArgsFunction: validArgsFunc,
1543		Run:               emptyRun,
1544	}
1545	rootCmd.AddCommand(child)
1546
1547	buf := new(bytes.Buffer)
1548	assertNoErr(t, rootCmd.GenZshCompletion(buf))
1549	output := buf.String()
1550
1551	check(t, output, ShellCompRequestCmd)
1552	checkOmit(t, output, ShellCompNoDescRequestCmd)
1553}
1554
1555func TestFlagCompletionInGo(t *testing.T) {
1556	rootCmd := &Command{
1557		Use: "root",
1558		Run: emptyRun,
1559	}
1560	rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
1561	assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1562		completions := []string{}
1563		for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
1564			if strings.HasPrefix(comp, toComplete) {
1565				completions = append(completions, comp)
1566			}
1567		}
1568		return completions, ShellCompDirectiveDefault
1569	}))
1570	rootCmd.Flags().String("filename", "", "Enter a filename")
1571	assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1572		completions := []string{}
1573		for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
1574			if strings.HasPrefix(comp, toComplete) {
1575				completions = append(completions, comp)
1576			}
1577		}
1578		return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp
1579	}))
1580
1581	// Test completing an empty string
1582	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "")
1583	if err != nil {
1584		t.Errorf("Unexpected error: %v", err)
1585	}
1586
1587	expected := strings.Join([]string{
1588		"1",
1589		"2",
1590		"10",
1591		":0",
1592		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1593
1594	if output != expected {
1595		t.Errorf("expected: %q, got: %q", expected, output)
1596	}
1597
1598	// Check completing with a prefix
1599	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--introot", "1")
1600	if err != nil {
1601		t.Errorf("Unexpected error: %v", err)
1602	}
1603
1604	expected = strings.Join([]string{
1605		"1",
1606		"10",
1607		":0",
1608		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1609
1610	if output != expected {
1611		t.Errorf("expected: %q, got: %q", expected, output)
1612	}
1613
1614	// Test completing an empty string
1615	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "")
1616	if err != nil {
1617		t.Errorf("Unexpected error: %v", err)
1618	}
1619
1620	expected = strings.Join([]string{
1621		"file.yaml",
1622		"myfile.json",
1623		"file.xml",
1624		":6",
1625		"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
1626
1627	if output != expected {
1628		t.Errorf("expected: %q, got: %q", expected, output)
1629	}
1630
1631	// Check completing with a prefix
1632	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "--filename", "f")
1633	if err != nil {
1634		t.Errorf("Unexpected error: %v", err)
1635	}
1636
1637	expected = strings.Join([]string{
1638		"file.yaml",
1639		"file.xml",
1640		":6",
1641		"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
1642
1643	if output != expected {
1644		t.Errorf("expected: %q, got: %q", expected, output)
1645	}
1646}
1647
1648func TestValidArgsFuncChildCmdsWithDesc(t *testing.T) {
1649	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
1650	child1Cmd := &Command{
1651		Use:               "child1",
1652		ValidArgsFunction: validArgsFunc,
1653		Run:               emptyRun,
1654	}
1655	child2Cmd := &Command{
1656		Use:               "child2",
1657		ValidArgsFunction: validArgsFunc2,
1658		Run:               emptyRun,
1659	}
1660	rootCmd.AddCommand(child1Cmd, child2Cmd)
1661
1662	// Test completion of first sub-command with empty argument
1663	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child1", "")
1664	if err != nil {
1665		t.Errorf("Unexpected error: %v", err)
1666	}
1667
1668	expected := strings.Join([]string{
1669		"one\tThe first",
1670		"two\tThe second",
1671		":0",
1672		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1673
1674	if output != expected {
1675		t.Errorf("expected: %q, got: %q", expected, output)
1676	}
1677
1678	// Test completion of first sub-command with a prefix to complete
1679	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "t")
1680	if err != nil {
1681		t.Errorf("Unexpected error: %v", err)
1682	}
1683
1684	expected = strings.Join([]string{
1685		"two\tThe second",
1686		":0",
1687		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1688
1689	if output != expected {
1690		t.Errorf("expected: %q, got: %q", expected, output)
1691	}
1692
1693	// Check completing with wrong number of args
1694	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child1", "unexpectedArg", "t")
1695	if err != nil {
1696		t.Errorf("Unexpected error: %v", err)
1697	}
1698
1699	expected = strings.Join([]string{
1700		":4",
1701		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1702
1703	if output != expected {
1704		t.Errorf("expected: %q, got: %q", expected, output)
1705	}
1706
1707	// Test completion of second sub-command with empty argument
1708	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "")
1709	if err != nil {
1710		t.Errorf("Unexpected error: %v", err)
1711	}
1712
1713	expected = strings.Join([]string{
1714		"three\tThe third",
1715		"four\tThe fourth",
1716		":0",
1717		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1718
1719	if output != expected {
1720		t.Errorf("expected: %q, got: %q", expected, output)
1721	}
1722
1723	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "t")
1724	if err != nil {
1725		t.Errorf("Unexpected error: %v", err)
1726	}
1727
1728	expected = strings.Join([]string{
1729		"three\tThe third",
1730		":0",
1731		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1732
1733	if output != expected {
1734		t.Errorf("expected: %q, got: %q", expected, output)
1735	}
1736
1737	// Check completing with wrong number of args
1738	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "unexpectedArg", "t")
1739	if err != nil {
1740		t.Errorf("Unexpected error: %v", err)
1741	}
1742
1743	expected = strings.Join([]string{
1744		":4",
1745		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1746
1747	if output != expected {
1748		t.Errorf("expected: %q, got: %q", expected, output)
1749	}
1750}
1751
1752func TestFlagCompletionWithNotInterspersedArgs(t *testing.T) {
1753	rootCmd := &Command{Use: "root", Run: emptyRun}
1754	childCmd := &Command{
1755		Use: "child",
1756		Run: emptyRun,
1757		ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1758			return []string{"--validarg", "test"}, ShellCompDirectiveDefault
1759		},
1760	}
1761	childCmd2 := &Command{
1762		Use:       "child2",
1763		Run:       emptyRun,
1764		ValidArgs: []string{"arg1", "arg2"},
1765	}
1766	rootCmd.AddCommand(childCmd, childCmd2)
1767	childCmd.Flags().Bool("bool", false, "test bool flag")
1768	childCmd.Flags().String("string", "", "test string flag")
1769	_ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1770		return []string{"myval"}, ShellCompDirectiveDefault
1771	})
1772
1773	// Test flag completion with no argument
1774	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--")
1775	if err != nil {
1776		t.Errorf("Unexpected error: %v", err)
1777	}
1778
1779	expected := strings.Join([]string{
1780		"--bool\ttest bool flag",
1781		"--string\ttest string flag",
1782		":4",
1783		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1784
1785	if output != expected {
1786		t.Errorf("expected: %q, got: %q", expected, output)
1787	}
1788
1789	// Test that no flags are completed after the -- arg
1790	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "-")
1791	if err != nil {
1792		t.Errorf("Unexpected error: %v", err)
1793	}
1794
1795	expected = strings.Join([]string{
1796		"--validarg",
1797		"test",
1798		":0",
1799		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1800
1801	if output != expected {
1802		t.Errorf("expected: %q, got: %q", expected, output)
1803	}
1804
1805	// Test that no flags are completed after the -- arg with a flag set
1806	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--bool", "--", "-")
1807	if err != nil {
1808		t.Errorf("Unexpected error: %v", err)
1809	}
1810
1811	expected = strings.Join([]string{
1812		"--validarg",
1813		"test",
1814		":0",
1815		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1816
1817	if output != expected {
1818		t.Errorf("expected: %q, got: %q", expected, output)
1819	}
1820
1821	// set Interspersed to false which means that no flags should be completed after the first arg
1822	childCmd.Flags().SetInterspersed(false)
1823
1824	// Test that no flags are completed after the first arg
1825	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--")
1826	if err != nil {
1827		t.Errorf("Unexpected error: %v", err)
1828	}
1829
1830	expected = strings.Join([]string{
1831		"--validarg",
1832		"test",
1833		":0",
1834		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1835
1836	if output != expected {
1837		t.Errorf("expected: %q, got: %q", expected, output)
1838	}
1839
1840	// Test that no flags are completed after the fist arg with a flag set
1841	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "t", "arg", "--")
1842	if err != nil {
1843		t.Errorf("Unexpected error: %v", err)
1844	}
1845
1846	expected = strings.Join([]string{
1847		"--validarg",
1848		"test",
1849		":0",
1850		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1851
1852	if output != expected {
1853		t.Errorf("expected: %q, got: %q", expected, output)
1854	}
1855
1856	// Check that args are still completed after --
1857	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "")
1858	if err != nil {
1859		t.Errorf("Unexpected error: %v", err)
1860	}
1861
1862	expected = strings.Join([]string{
1863		"--validarg",
1864		"test",
1865		":0",
1866		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1867
1868	if output != expected {
1869		t.Errorf("expected: %q, got: %q", expected, output)
1870	}
1871
1872	// Check that args are still completed even if flagname with ValidArgsFunction exists
1873	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--string", "")
1874	if err != nil {
1875		t.Errorf("Unexpected error: %v", err)
1876	}
1877
1878	expected = strings.Join([]string{
1879		"--validarg",
1880		"test",
1881		":0",
1882		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1883
1884	if output != expected {
1885		t.Errorf("expected: %q, got: %q", expected, output)
1886	}
1887
1888	// Check that args are still completed even if flagname with ValidArgsFunction exists
1889	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child2", "--", "a")
1890	if err != nil {
1891		t.Errorf("Unexpected error: %v", err)
1892	}
1893
1894	expected = strings.Join([]string{
1895		"arg1",
1896		"arg2",
1897		":4",
1898		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
1899
1900	if output != expected {
1901		t.Errorf("expected: %q, got: %q", expected, output)
1902	}
1903
1904	// Check that --validarg is not parsed as flag after --
1905	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "")
1906	if err != nil {
1907		t.Errorf("Unexpected error: %v", err)
1908	}
1909
1910	expected = strings.Join([]string{
1911		"--validarg",
1912		"test",
1913		":0",
1914		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1915
1916	if output != expected {
1917		t.Errorf("expected: %q, got: %q", expected, output)
1918	}
1919
1920	// Check that --validarg is not parsed as flag after an arg
1921	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "arg", "--validarg", "")
1922	if err != nil {
1923		t.Errorf("Unexpected error: %v", err)
1924	}
1925
1926	expected = strings.Join([]string{
1927		"--validarg",
1928		"test",
1929		":0",
1930		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1931
1932	if output != expected {
1933		t.Errorf("expected: %q, got: %q", expected, output)
1934	}
1935
1936	// Check that --validarg is added to args for the ValidArgsFunction
1937	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1938		return args, ShellCompDirectiveDefault
1939	}
1940	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "")
1941	if err != nil {
1942		t.Errorf("Unexpected error: %v", err)
1943	}
1944
1945	expected = strings.Join([]string{
1946		"--validarg",
1947		":0",
1948		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1949
1950	if output != expected {
1951		t.Errorf("expected: %q, got: %q", expected, output)
1952	}
1953
1954	// Check that --validarg is added to args for the ValidArgsFunction and toComplete is also set correctly
1955	childCmd.ValidArgsFunction = func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1956		return append(args, toComplete), ShellCompDirectiveDefault
1957	}
1958	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "child", "--", "--validarg", "--toComp=ab")
1959	if err != nil {
1960		t.Errorf("Unexpected error: %v", err)
1961	}
1962
1963	expected = strings.Join([]string{
1964		"--validarg",
1965		"--toComp=ab",
1966		":0",
1967		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
1968
1969	if output != expected {
1970		t.Errorf("expected: %q, got: %q", expected, output)
1971	}
1972}
1973
1974func TestFlagCompletionWorksRootCommandAddedAfterFlags(t *testing.T) {
1975	rootCmd := &Command{Use: "root", Run: emptyRun}
1976	childCmd := &Command{
1977		Use: "child",
1978		Run: emptyRun,
1979		ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1980			return []string{"--validarg", "test"}, ShellCompDirectiveDefault
1981		},
1982	}
1983	childCmd.Flags().Bool("bool", false, "test bool flag")
1984	childCmd.Flags().String("string", "", "test string flag")
1985	_ = childCmd.RegisterFlagCompletionFunc("string", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
1986		return []string{"myval"}, ShellCompDirectiveDefault
1987	})
1988
1989	// Important: This is a test for https://github.com/spf13/cobra/issues/1437
1990	// Only add the subcommand after RegisterFlagCompletionFunc was called, do not change this order!
1991	rootCmd.AddCommand(childCmd)
1992
1993	// Test that flag completion works for the subcmd
1994	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "child", "--string", "")
1995	if err != nil {
1996		t.Errorf("Unexpected error: %v", err)
1997	}
1998
1999	expected := strings.Join([]string{
2000		"myval",
2001		":0",
2002		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
2003
2004	if output != expected {
2005		t.Errorf("expected: %q, got: %q", expected, output)
2006	}
2007}
2008
2009func TestFlagCompletionInGoWithDesc(t *testing.T) {
2010	rootCmd := &Command{
2011		Use: "root",
2012		Run: emptyRun,
2013	}
2014	rootCmd.Flags().IntP("introot", "i", -1, "help message for flag introot")
2015	assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("introot", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2016		completions := []string{}
2017		for _, comp := range []string{"1\tThe first", "2\tThe second", "10\tThe tenth"} {
2018			if strings.HasPrefix(comp, toComplete) {
2019				completions = append(completions, comp)
2020			}
2021		}
2022		return completions, ShellCompDirectiveDefault
2023	}))
2024	rootCmd.Flags().String("filename", "", "Enter a filename")
2025	assertNoErr(t, rootCmd.RegisterFlagCompletionFunc("filename", func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2026		completions := []string{}
2027		for _, comp := range []string{"file.yaml\tYAML format", "myfile.json\tJSON format", "file.xml\tXML format"} {
2028			if strings.HasPrefix(comp, toComplete) {
2029				completions = append(completions, comp)
2030			}
2031		}
2032		return completions, ShellCompDirectiveNoSpace | ShellCompDirectiveNoFileComp
2033	}))
2034
2035	// Test completing an empty string
2036	output, err := executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "")
2037	if err != nil {
2038		t.Errorf("Unexpected error: %v", err)
2039	}
2040
2041	expected := strings.Join([]string{
2042		"1\tThe first",
2043		"2\tThe second",
2044		"10\tThe tenth",
2045		":0",
2046		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
2047
2048	if output != expected {
2049		t.Errorf("expected: %q, got: %q", expected, output)
2050	}
2051
2052	// Check completing with a prefix
2053	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--introot", "1")
2054	if err != nil {
2055		t.Errorf("Unexpected error: %v", err)
2056	}
2057
2058	expected = strings.Join([]string{
2059		"1\tThe first",
2060		"10\tThe tenth",
2061		":0",
2062		"Completion ended with directive: ShellCompDirectiveDefault", ""}, "\n")
2063
2064	if output != expected {
2065		t.Errorf("expected: %q, got: %q", expected, output)
2066	}
2067
2068	// Test completing an empty string
2069	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "")
2070	if err != nil {
2071		t.Errorf("Unexpected error: %v", err)
2072	}
2073
2074	expected = strings.Join([]string{
2075		"file.yaml\tYAML format",
2076		"myfile.json\tJSON format",
2077		"file.xml\tXML format",
2078		":6",
2079		"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
2080
2081	if output != expected {
2082		t.Errorf("expected: %q, got: %q", expected, output)
2083	}
2084
2085	// Check completing with a prefix
2086	output, err = executeCommand(rootCmd, ShellCompRequestCmd, "--filename", "f")
2087	if err != nil {
2088		t.Errorf("Unexpected error: %v", err)
2089	}
2090
2091	expected = strings.Join([]string{
2092		"file.yaml\tYAML format",
2093		"file.xml\tXML format",
2094		":6",
2095		"Completion ended with directive: ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp", ""}, "\n")
2096
2097	if output != expected {
2098		t.Errorf("expected: %q, got: %q", expected, output)
2099	}
2100}
2101
2102func TestValidArgsNotValidArgsFunc(t *testing.T) {
2103	rootCmd := &Command{
2104		Use:       "root",
2105		ValidArgs: []string{"one", "two"},
2106		ValidArgsFunction: func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) {
2107			return []string{"three", "four"}, ShellCompDirectiveNoFileComp
2108		},
2109		Run: emptyRun,
2110	}
2111
2112	// Test that if both ValidArgs and ValidArgsFunction are present
2113	// only ValidArgs is considered
2114	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
2115	if err != nil {
2116		t.Errorf("Unexpected error: %v", err)
2117	}
2118
2119	expected := strings.Join([]string{
2120		"one",
2121		"two",
2122		":4",
2123		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2124
2125	if output != expected {
2126		t.Errorf("expected: %q, got: %q", expected, output)
2127	}
2128
2129	// Check completing with a prefix
2130	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
2131	if err != nil {
2132		t.Errorf("Unexpected error: %v", err)
2133	}
2134
2135	expected = strings.Join([]string{
2136		"two",
2137		":4",
2138		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2139
2140	if output != expected {
2141		t.Errorf("expected: %q, got: %q", expected, output)
2142	}
2143}
2144
2145func TestArgAliasesCompletionInGo(t *testing.T) {
2146	rootCmd := &Command{
2147		Use:        "root",
2148		Args:       OnlyValidArgs,
2149		ValidArgs:  []string{"one", "two", "three"},
2150		ArgAliases: []string{"un", "deux", "trois"},
2151		Run:        emptyRun,
2152	}
2153
2154	// Test that argaliases are not completed when there are validargs that match
2155	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
2156	if err != nil {
2157		t.Errorf("Unexpected error: %v", err)
2158	}
2159
2160	expected := strings.Join([]string{
2161		"one",
2162		"two",
2163		"three",
2164		":4",
2165		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2166
2167	if output != expected {
2168		t.Errorf("expected: %q, got: %q", expected, output)
2169	}
2170
2171	// Test that argaliases are not completed when there are validargs that match using a prefix
2172	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "t")
2173	if err != nil {
2174		t.Errorf("Unexpected error: %v", err)
2175	}
2176
2177	expected = strings.Join([]string{
2178		"two",
2179		"three",
2180		":4",
2181		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2182
2183	if output != expected {
2184		t.Errorf("expected: %q, got: %q", expected, output)
2185	}
2186
2187	// Test that argaliases are completed when there are no validargs that match
2188	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "tr")
2189	if err != nil {
2190		t.Errorf("Unexpected error: %v", err)
2191	}
2192
2193	expected = strings.Join([]string{
2194		"trois",
2195		":4",
2196		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2197
2198	if output != expected {
2199		t.Errorf("expected: %q, got: %q", expected, output)
2200	}
2201}
2202
2203func TestCompleteHelp(t *testing.T) {
2204	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
2205	child1Cmd := &Command{
2206		Use: "child1",
2207		Run: emptyRun,
2208	}
2209	child2Cmd := &Command{
2210		Use: "child2",
2211		Run: emptyRun,
2212	}
2213	rootCmd.AddCommand(child1Cmd, child2Cmd)
2214
2215	child3Cmd := &Command{
2216		Use: "child3",
2217		Run: emptyRun,
2218	}
2219	child1Cmd.AddCommand(child3Cmd)
2220
2221	// Test that completion includes the help command
2222	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "")
2223	if err != nil {
2224		t.Errorf("Unexpected error: %v", err)
2225	}
2226
2227	expected := strings.Join([]string{
2228		"child1",
2229		"child2",
2230		"completion",
2231		"help",
2232		":4",
2233		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2234
2235	if output != expected {
2236		t.Errorf("expected: %q, got: %q", expected, output)
2237	}
2238
2239	// Test sub-commands are completed on first level of help command
2240	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "")
2241	if err != nil {
2242		t.Errorf("Unexpected error: %v", err)
2243	}
2244
2245	expected = strings.Join([]string{
2246		"child1",
2247		"child2",
2248		"completion",
2249		"help", // "<program> help help" is a valid command, so should be completed
2250		":4",
2251		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2252
2253	if output != expected {
2254		t.Errorf("expected: %q, got: %q", expected, output)
2255	}
2256
2257	// Test sub-commands are completed on first level of help command
2258	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "help", "child1", "")
2259	if err != nil {
2260		t.Errorf("Unexpected error: %v", err)
2261	}
2262
2263	expected = strings.Join([]string{
2264		"child3",
2265		":4",
2266		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2267
2268	if output != expected {
2269		t.Errorf("expected: %q, got: %q", expected, output)
2270	}
2271}
2272
2273func removeCompCmd(rootCmd *Command) {
2274	// Remove completion command for the next test
2275	for _, cmd := range rootCmd.commands {
2276		if cmd.Name() == compCmdName {
2277			rootCmd.RemoveCommand(cmd)
2278			return
2279		}
2280	}
2281}
2282
2283func TestDefaultCompletionCmd(t *testing.T) {
2284	rootCmd := &Command{
2285		Use:  "root",
2286		Args: NoArgs,
2287		Run:  emptyRun,
2288	}
2289
2290	// Test that no completion command is created if there are not other sub-commands
2291	assertNoErr(t, rootCmd.Execute())
2292	for _, cmd := range rootCmd.commands {
2293		if cmd.Name() == compCmdName {
2294			t.Errorf("Should not have a 'completion' command when there are no other sub-commands of root")
2295			break
2296		}
2297	}
2298
2299	subCmd := &Command{
2300		Use: "sub",
2301		Run: emptyRun,
2302	}
2303	rootCmd.AddCommand(subCmd)
2304
2305	// Test that a completion command is created if there are other sub-commands
2306	found := false
2307	assertNoErr(t, rootCmd.Execute())
2308	for _, cmd := range rootCmd.commands {
2309		if cmd.Name() == compCmdName {
2310			found = true
2311			break
2312		}
2313	}
2314	if !found {
2315		t.Errorf("Should have a 'completion' command when there are other sub-commands of root")
2316	}
2317	// Remove completion command for the next test
2318	removeCompCmd(rootCmd)
2319
2320	// Test that the default completion command can be disabled
2321	rootCmd.CompletionOptions.DisableDefaultCmd = true
2322	assertNoErr(t, rootCmd.Execute())
2323	for _, cmd := range rootCmd.commands {
2324		if cmd.Name() == compCmdName {
2325			t.Errorf("Should not have a 'completion' command when the feature is disabled")
2326			break
2327		}
2328	}
2329	// Re-enable for next test
2330	rootCmd.CompletionOptions.DisableDefaultCmd = false
2331
2332	// Test that completion descriptions are enabled by default
2333	output, err := executeCommand(rootCmd, compCmdName, "zsh")
2334	if err != nil {
2335		t.Errorf("Unexpected error: %v", err)
2336	}
2337
2338	check(t, output, ShellCompRequestCmd)
2339	checkOmit(t, output, ShellCompNoDescRequestCmd)
2340	// Remove completion command for the next test
2341	removeCompCmd(rootCmd)
2342
2343	// Test that completion descriptions can be disabled completely
2344	rootCmd.CompletionOptions.DisableDescriptions = true
2345	output, err = executeCommand(rootCmd, compCmdName, "zsh")
2346	if err != nil {
2347		t.Errorf("Unexpected error: %v", err)
2348	}
2349
2350	check(t, output, ShellCompNoDescRequestCmd)
2351	// Re-enable for next test
2352	rootCmd.CompletionOptions.DisableDescriptions = false
2353	// Remove completion command for the next test
2354	removeCompCmd(rootCmd)
2355
2356	var compCmd *Command
2357	// Test that the --no-descriptions flag is present on all shells
2358	assertNoErr(t, rootCmd.Execute())
2359	for _, shell := range []string{"bash", "fish", "powershell", "zsh"} {
2360		if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
2361			t.Errorf("Unexpected error: %v", err)
2362		}
2363		if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag == nil {
2364			t.Errorf("Missing --%s flag for %s shell", compCmdNoDescFlagName, shell)
2365		}
2366	}
2367	// Remove completion command for the next test
2368	removeCompCmd(rootCmd)
2369
2370	// Test that the '--no-descriptions' flag can be disabled
2371	rootCmd.CompletionOptions.DisableNoDescFlag = true
2372	assertNoErr(t, rootCmd.Execute())
2373	for _, shell := range []string{"fish", "zsh", "bash", "powershell"} {
2374		if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
2375			t.Errorf("Unexpected error: %v", err)
2376		}
2377		if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil {
2378			t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell)
2379		}
2380	}
2381	// Re-enable for next test
2382	rootCmd.CompletionOptions.DisableNoDescFlag = false
2383	// Remove completion command for the next test
2384	removeCompCmd(rootCmd)
2385
2386	// Test that the '--no-descriptions' flag is disabled when descriptions are disabled
2387	rootCmd.CompletionOptions.DisableDescriptions = true
2388	assertNoErr(t, rootCmd.Execute())
2389	for _, shell := range []string{"fish", "zsh", "bash", "powershell"} {
2390		if compCmd, _, err = rootCmd.Find([]string{compCmdName, shell}); err != nil {
2391			t.Errorf("Unexpected error: %v", err)
2392		}
2393		if flag := compCmd.Flags().Lookup(compCmdNoDescFlagName); flag != nil {
2394			t.Errorf("Unexpected --%s flag for %s shell", compCmdNoDescFlagName, shell)
2395		}
2396	}
2397	// Re-enable for next test
2398	rootCmd.CompletionOptions.DisableDescriptions = false
2399	// Remove completion command for the next test
2400	removeCompCmd(rootCmd)
2401}
2402
2403func TestCompleteCompletion(t *testing.T) {
2404	rootCmd := &Command{Use: "root", Args: NoArgs, Run: emptyRun}
2405	subCmd := &Command{
2406		Use: "sub",
2407		Run: emptyRun,
2408	}
2409	rootCmd.AddCommand(subCmd)
2410
2411	// Test sub-commands of the completion command
2412	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "completion", "")
2413	if err != nil {
2414		t.Errorf("Unexpected error: %v", err)
2415	}
2416
2417	expected := strings.Join([]string{
2418		"bash",
2419		"fish",
2420		"powershell",
2421		"zsh",
2422		":4",
2423		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2424
2425	if output != expected {
2426		t.Errorf("expected: %q, got: %q", expected, output)
2427	}
2428
2429	// Test there are no completions for the sub-commands of the completion command
2430	var compCmd *Command
2431	for _, cmd := range rootCmd.Commands() {
2432		if cmd.Name() == compCmdName {
2433			compCmd = cmd
2434			break
2435		}
2436	}
2437
2438	for _, shell := range compCmd.Commands() {
2439		output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, compCmdName, shell.Name(), "")
2440		if err != nil {
2441			t.Errorf("Unexpected error: %v", err)
2442		}
2443
2444		expected = strings.Join([]string{
2445			":4",
2446			"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2447
2448		if output != expected {
2449			t.Errorf("expected: %q, got: %q", expected, output)
2450		}
2451	}
2452}
2453
2454func TestMultipleShorthandFlagCompletion(t *testing.T) {
2455	rootCmd := &Command{
2456		Use:       "root",
2457		ValidArgs: []string{"foo", "bar"},
2458		Run:       emptyRun,
2459	}
2460	f := rootCmd.Flags()
2461	f.BoolP("short", "s", false, "short flag 1")
2462	f.BoolP("short2", "d", false, "short flag 2")
2463	f.StringP("short3", "f", "", "short flag 3")
2464	_ = rootCmd.RegisterFlagCompletionFunc("short3", func(*Command, []string, string) ([]string, ShellCompDirective) {
2465		return []string{"works"}, ShellCompDirectiveNoFileComp
2466	})
2467
2468	// Test that a single shorthand flag works
2469	output, err := executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-s", "")
2470	if err != nil {
2471		t.Errorf("Unexpected error: %v", err)
2472	}
2473
2474	expected := strings.Join([]string{
2475		"foo",
2476		"bar",
2477		":4",
2478		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2479
2480	if output != expected {
2481		t.Errorf("expected: %q, got: %q", expected, output)
2482	}
2483
2484	// Test that multiple boolean shorthand flags work
2485	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sd", "")
2486	if err != nil {
2487		t.Errorf("Unexpected error: %v", err)
2488	}
2489
2490	expected = strings.Join([]string{
2491		"foo",
2492		"bar",
2493		":4",
2494		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2495
2496	if output != expected {
2497		t.Errorf("expected: %q, got: %q", expected, output)
2498	}
2499
2500	// Test that multiple boolean + string shorthand flags work
2501	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf", "")
2502	if err != nil {
2503		t.Errorf("Unexpected error: %v", err)
2504	}
2505
2506	expected = strings.Join([]string{
2507		"works",
2508		":4",
2509		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2510
2511	if output != expected {
2512		t.Errorf("expected: %q, got: %q", expected, output)
2513	}
2514
2515	// Test that multiple boolean + string with equal sign shorthand flags work
2516	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=")
2517	if err != nil {
2518		t.Errorf("Unexpected error: %v", err)
2519	}
2520
2521	expected = strings.Join([]string{
2522		"works",
2523		":4",
2524		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2525
2526	if output != expected {
2527		t.Errorf("expected: %q, got: %q", expected, output)
2528	}
2529
2530	// Test that multiple boolean + string with equal sign with value shorthand flags work
2531	output, err = executeCommand(rootCmd, ShellCompNoDescRequestCmd, "-sdf=abc", "")
2532	if err != nil {
2533		t.Errorf("Unexpected error: %v", err)
2534	}
2535
2536	expected = strings.Join([]string{
2537		"foo",
2538		"bar",
2539		":4",
2540		"Completion ended with directive: ShellCompDirectiveNoFileComp", ""}, "\n")
2541
2542	if output != expected {
2543		t.Errorf("expected: %q, got: %q", expected, output)
2544	}
2545}
2546