Files
commandkit/completion_flags_test.go

147 lines
7.4 KiB
Go
Raw Normal View History

package commandkit
import (
"reflect"
"testing"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
)
func TestCompletionFlagAndValuePositions(t *testing.T) {
parse := func(value string) (string, error) { return value, nil }
choice := CompleteChoices(CompletionChoice{Value: "blue"}, CompletionChoice{Value: "green"})
command := NewCommand(CommandSpec[struct{}]{
Name: "run",
Flags: []Flag[struct{}]{
ValueFlag(FlagMeta{Name: "color", Short: "c", Completion: choice}, (*string)(nil), parse, func(*struct{}, Supplied[string]) {}),
RepeatFlag(FlagMeta{Name: "tag", Short: "t", Completion: choice}, nil, parse, func(*struct{}, Supplied[[]string]) {}),
BoolFlag(FlagMeta{Name: "switch", Short: "s"}, false, func(*struct{}, Supplied[bool]) {}),
},
Run: func(Invocation, struct{}) Outcome { return Success() },
})
app, err := New(Identity{Name: "example"}, command,
FlagAliasFor("run", "--colour", "--color"), FlagAliasFor("run", "-C", "--color"), FlagAlias("-v", "--verbose"))
if err != nil {
t.Fatal(err)
}
snapshot := app.routeSnapshot()
for _, tc := range []struct {
name, current, prefix, value string
completed []string
kind completionTargetKind
hint commandmeta.CompletionKind
}{
{"pending takes option", "--quiet", "", "--quiet", []string{"run", "--color"}, completionTargetValue, commandmeta.CompletionChoices},
{"terminator consumed as value", "-x", "", "-x", []string{"run", "--color", "--", "--tag"}, completionTargetValue, commandmeta.CompletionChoices},
{"repeated singleton value", "--color=gr", "--color=", "gr", []string{"run", "--color=blue"}, completionTargetValue, commandmeta.CompletionChoices},
{"long alias value", "--colour=bl", "--colour=", "bl", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
{"long empty value", "--color=", "--color=", "", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
{"short alias value", "-sCbl", "-sC", "bl", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
{"short equals", "-sC=bl", "-sC=", "bl", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
{"reporting boolean", "--verbose=f", "--verbose=", "f", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
{"short boolean alias", "-v=f", "-v=", "f", []string{"run"}, completionTargetValue, commandmeta.CompletionChoices},
{"application boolean", "--switch=f", "--switch=", "f", []string{"run"}, completionTargetValue, commandmeta.CompletionNone},
{"scoped alias unavailable at root", "--colour=bl", "", "", nil, completionTargetNone, commandmeta.CompletionNone},
{"unknown completed", "--c", "", "", []string{"run", "--unknown"}, completionTargetNone, commandmeta.CompletionNone},
{"unknown attached", "--unknown=x", "", "", []string{"run"}, completionTargetNone, commandmeta.CompletionNone},
{"terminator", "--color", "", "", []string{"run", "--"}, completionTargetNone, commandmeta.CompletionNone},
} {
t.Run(tc.name, func(t *testing.T) {
target := completionTargetFor(snapshot, resolveCompletionContext(snapshot, tc.completed, tc.current))
if target.kind != tc.kind || target.hint.Kind != tc.hint || target.prefix != tc.prefix || target.valuePrefix != tc.value {
t.Fatalf("target=%#v", target)
}
})
}
context := resolveCompletionContext(snapshot, []string{"run", "--colour=blue", "--tag=one", "--verbose"}, "--")
target := completionTargetFor(snapshot, context)
names := completionChoiceNames(target.choices)
if containsCompletionName(names, "--color") || !containsCompletionName(names, "--tag") || !containsCompletionName(names, "--verbose") {
t.Fatalf("flag suggestions=%q", names)
}
cluster := completionTargetFor(snapshot, resolveCompletionContext(snapshot, []string{"run"}, "-sv"))
if cluster.kind != completionTargetFlags || !containsCompletionName(completionChoiceNames(cluster.choices), "-svc") || containsCompletionName(completionChoiceNames(cluster.choices), "-svs") {
t.Fatalf("short cluster=%#v", cluster)
}
dash := completionTargetFor(snapshot, resolveCompletionContext(snapshot, []string{"run"}, "-"))
if !containsCompletionName(completionChoiceNames(dash.choices), "--color") || !containsCompletionName(completionChoiceNames(dash.choices), "-c") {
t.Fatalf("dash suggestions=%#v", dash)
}
}
func TestCompletionOperandAllocation(t *testing.T) {
arguments := []commandmeta.Argument{
{Name: "optional", Min: 0, Max: 1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionFiles}},
{Name: "repeated", Min: 0, Max: -1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionDirectories}},
{Name: "required", Min: 1, Max: 1, Completion: commandmeta.BooleanCompletion()},
}
for _, tc := range []struct {
completed int
want commandmeta.CompletionKind
}{
{0, commandmeta.CompletionChoices},
{1, commandmeta.CompletionChoices},
{2, commandmeta.CompletionChoices},
} {
hint, ok := completionOperandHint(arguments, tc.completed)
if !ok || hint.Kind != tc.want {
t.Fatalf("%d operands: %#v, %v", tc.completed, hint, ok)
}
}
bounded := []commandmeta.Argument{{Name: "only", Max: 1, Completion: commandmeta.BooleanCompletion()}}
if _, ok := completionOperandHint(bounded, 1); ok {
t.Fatal("excess operand received a hint")
}
trailing := []commandmeta.Argument{{Name: "first", Min: 1, Max: 1, Completion: commandmeta.BooleanCompletion()}, {Name: "rest", Min: 0, Max: -1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionDirectories}}}
if hint, ok := completionOperandHint(trailing, 1); !ok || hint.Kind != commandmeta.CompletionDirectories {
t.Fatalf("repeated trailing operand: %#v, %v", hint, ok)
}
if got, want := completionChoiceNames(commandmeta.BooleanCompletion().Choices), []string{"true", "false"}; !reflect.DeepEqual(got, want) {
t.Fatalf("boolean choices=%q", got)
}
}
func TestCompletionOperandAndFlagContexts(t *testing.T) {
command := &commandmeta.Command{
Arguments: []commandmeta.Argument{{Name: "item", Min: 0, Max: 1, Completion: commandmeta.CompletionHint{Kind: commandmeta.CompletionFiles}}},
Flags: []commandmeta.Flag{{Name: "name", Completion: commandmeta.BooleanCompletion()}},
}
for _, tc := range []struct {
name, current string
tail []string
kind completionTargetKind
hint commandmeta.CompletionKind
}{
{"empty chooses operand", "", nil, completionTargetOperand, commandmeta.CompletionFiles},
{"dash chooses flag", "--n", nil, completionTargetFlags, commandmeta.CompletionNone},
{"after terminator", "--n", []string{"--"}, completionTargetOperand, commandmeta.CompletionFiles},
{"interspersed flag value", "value", []string{"--name", "--quiet"}, completionTargetOperand, commandmeta.CompletionFiles},
{"excess operand", "value", []string{"already"}, completionTargetNone, commandmeta.CompletionNone},
} {
t.Run(tc.name, func(t *testing.T) {
target := completionTargetFor(nil, completionContext{command: command, tail: tc.tail, current: tc.current})
if target.kind != tc.kind || target.hint.Kind != tc.hint {
t.Fatalf("target=%#v", target)
}
})
}
}
func completionChoiceNames(choices []commandmeta.CompletionChoice) []string {
var names []string
for _, choice := range choices {
names = append(names, choice.Value)
}
return names
}
func containsCompletionName(names []string, want string) bool {
for _, name := range names {
if name == want {
return true
}
}
return false
}