package commandkit import ( "context" "reflect" "testing" commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta" ) // Existing consumer code can compare these metadata values or use them as keys. var ( _ map[FlagMeta]struct{} _ map[ArgumentMeta]struct{} _ = (FlagMeta{}) == (FlagMeta{}) _ = (ArgumentMeta{}) == (ArgumentMeta{}) ) func TestCompletionHintsFollowCapturedBindings(t *testing.T) { type inner struct{ values Supplied[[]string] } type outer struct{ flags FlagValues[inner] } choices := []CompletionChoice{{Value: "alpha", Description: "first"}} hint := CompleteChoices(choices...) choices[0] = CompletionChoice{Value: "changed"} parse := func(value string) (string, error) { return value, nil } repeated := RepeatFlag(FlagMeta{Name: "tag", Completion: hint}, nil, parse, func(input *inner, value Supplied[[]string]) { input.values = value }) captured := CaptureFlags([]Flag[inner]{repeated}, func(input *outer) *FlagValues[inner] { return &input.flags }) command := NewCommand(CommandSpec[outer]{ Name: "run", Flags: append(captured, ValueFlag(FlagMeta{Name: "file", Completion: CompleteFiles()}, (*string)(nil), parse, func(*outer, Supplied[string]) {}), BoolFlag(FlagMeta{Name: "enabled"}, false, func(*outer, Supplied[bool]) {}), ValueFlag(FlagMeta{Name: "empty", Completion: CompleteChoices()}, (*string)(nil), parse, func(*outer, Supplied[string]) {})), Arguments: []Argument[outer]{ ValueArgument(ArgumentMeta{Name: "directory", Completion: CompleteDirectories()}, false, parse, func(*outer, Supplied[string]) {}), RepeatArgument(ArgumentMeta{Name: "values", Completion: hint}, 0, -1, parse, func(*outer, Supplied[[]string]) {}), }, }) flags := command.record.payload.bridgeFlags() arguments := command.record.payload.bridgeArguments() want := commandmeta.CompletionHint{Kind: commandmeta.CompletionChoices, Choices: []commandmeta.CompletionChoice{{Value: "alpha", Description: "first"}}} if !flags[0].Repeated || !reflect.DeepEqual(flags[0].Completion, want) || !reflect.DeepEqual(arguments[1].Completion, want) { t.Fatalf("captured hints: flags=%#v arguments=%#v", flags, arguments) } if flags[1].Completion.Kind != commandmeta.CompletionFiles || flags[2].Completion.Kind != commandmeta.CompletionNone || flags[3].Completion.Kind != commandmeta.CompletionChoices || len(flags[3].Completion.Choices) != 0 || arguments[0].Completion.Kind != commandmeta.CompletionDirectories { t.Fatalf("hint strategies: flags=%#v arguments=%#v", flags, arguments) } flags[0].Completion.Choices[0].Value = "mutated" if got := command.record.payload.bridgeFlags()[0].Completion; !reflect.DeepEqual(got, want) { t.Fatalf("bridged choices changed captured hint: %#v", got) } if got := bridgeCompletion(NoCompletion()); got.Kind != commandmeta.CompletionNone || !reflect.DeepEqual(got, bridgeCompletion(CompletionHint{})) { t.Fatalf("no completion: %#v", got) } for _, flag := range append(commandmeta.ReportingFlags(), commandmeta.HelpFlag()) { if !reflect.DeepEqual(flag.Completion.Choices, []commandmeta.CompletionChoice{{Value: "true"}, {Value: "false"}}) { t.Fatalf("reporting flag choices: %#v", flag) } } } func TestCompletionChoicesDoNotRestrictExecution(t *testing.T) { type input struct{ mode Supplied[string] } var received input command := NewCommand(CommandSpec[input]{ Name: "run", Flags: []Flag[input]{ValueFlag(FlagMeta{Name: "mode", Completion: CompleteChoices(CompletionChoice{Value: "suggested"})}, (*string)(nil), func(value string) (string, error) { return value, nil }, func(i *input, value Supplied[string]) { i.mode = value })}, Run: func(_ Invocation, value input) Outcome { received = value; return Success() }, }) app, err := New(Identity{Name: "example"}, command) if err != nil { t.Fatal(err) } outcome := app.Execute(context.Background(), []string{"run", "--mode=other"}, Streams{}) if outcome.Status != 0 || !received.mode.Set || received.mode.Value != "other" { t.Fatalf("outcome=%#v input=%#v", outcome, received) } }