Files
commandkit/completion_context_test.go

90 lines
4.0 KiB
Go

package commandkit
import (
"cmp"
"reflect"
"slices"
"testing"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
)
func TestCompletionCommandContext(t *testing.T) {
parse := func(value string) (string, error) { return value, nil }
mixed := NewCommand(CommandSpec[struct{}]{
Name: "mixed", Children: []Command{simpleRunnable("child")},
Arguments: []Argument[struct{}]{ValueArgument(ArgumentMeta{Name: "item"}, false, parse, func(*struct{}, Supplied[string]) {})},
Run: func(Invocation, struct{}) Outcome { return Success() },
})
app, err := New(Identity{Name: "example"},
Group("workspace", "", Group("list", "", simpleRunnable("shadow"), simpleRunnable("show"))),
simpleRunnable("other"), mixed, Completion("example"),
Alias("ws", "workspace"), Alias("w", "ws"), Alias("ws list shadow", "other"),
Alias("legacy inspect", "workspace list"), FlagAlias("--silent", "--quiet"),
FlagAliasFor("workspace list", "--detail", "--verbose"),
)
if err != nil {
t.Fatal(err)
}
snapshot := app.routeSnapshot()
for _, tc := range []struct {
name string
completed []string
current string
path string
position bool
choices []string
kind routeKind
}{
{"root", nil, "work", "", true, []string{"workspace"}, routeCanonical},
{"group", []string{"workspace"}, "li", "workspace", true, []string{"list"}, routeCanonical},
{"entered alias", []string{"ws", "list"}, "sh", "workspace list", true, []string{"show"}, routeCanonical},
{"alias chain", []string{"w", "list"}, "sh", "workspace list", true, []string{"show"}, routeCanonical},
{"discovery", []string{"legacy"}, "in", "", false, nil, routeDiscovery},
{"complete alias", []string{"legacy", "inspect"}, "sh", "workspace list", true, []string{"shadow", "show"}, routeCanonical},
{"runnable child", []string{"mixed"}, "ch", "mixed", true, []string{"child"}, routeCanonical},
{"unmatched child", []string{"mixed"}, "other", "mixed", true, nil, routeCanonical},
{"runnable operand", []string{"mixed", "value"}, "ch", "mixed", false, nil, routeCanonical},
{"flag ends path", []string{"mixed", "--quiet"}, "ch", "mixed", false, nil, routeCanonical},
{"flag before child", []string{"workspace", "--quiet"}, "li", "workspace", false, nil, routeCanonical},
{"unknown", []string{"missing"}, "x", "", false, nil, routeUnknown},
{"hidden endpoint", nil, "__comp", "", true, nil, routeCanonical},
} {
t.Run(tc.name, func(t *testing.T) {
original := append([]string(nil), tc.completed...)
context := resolveCompletionContext(snapshot, tc.completed, tc.current)
if context.route.kind != tc.kind || context.route.canonicalPath.String() != tc.path || context.commandPosition != tc.position || !reflect.DeepEqual(tc.completed, original) {
t.Fatalf("context=%#v completed=%q", context, tc.completed)
}
var names []string
for _, choice := range completionChildren(snapshot, context) {
names = append(names, choice.Value)
}
if !reflect.DeepEqual(names, tc.choices) {
t.Fatalf("children=%q, want %q", names, tc.choices)
}
if tc.kind == routeDiscovery && !reflect.DeepEqual(context.aliases, []commandmeta.FlagAlias{{Source: "--silent", Target: "--quiet"}}) {
t.Fatalf("discovery aliases=%#v", context.aliases)
}
if (tc.name == "runnable child" || tc.name == "unmatched child") && (context.command == nil || len(context.command.Arguments) != 1) {
t.Fatalf("runnable operand metadata missing: %#v", context.command)
}
if tc.name == "entered alias" {
actual := slices.Clone(context.aliases)
expected := []commandmeta.FlagAlias{{Source: "--detail", Target: "--verbose"}, {Source: "--silent", Target: "--quiet"}}
compare := func(a, b commandmeta.FlagAlias) int {
if result := cmp.Compare(a.Source, b.Source); result != 0 {
return result
}
return cmp.Compare(a.Target, b.Target)
}
slices.SortFunc(actual, compare)
slices.SortFunc(expected, compare)
if !slices.Equal(actual, expected) {
t.Fatalf("canonical aliases=%#v, want %#v", actual, expected)
}
}
})
}
}