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) } } }) } }