package menu import ( "bytes" "io" "reflect" "regexp" "strings" "testing" ) var sample = []Item{ {Label: "module build", Desc: "build the .mod", Args: []string{"module", "build"}}, {Label: "topdata validate-topdata", Desc: "validate topdata", Args: []string{"topdata", "validate-topdata"}}, } func TestSelectReturnsChosenArgs(t *testing.T) { got, err := Select(io.Discard, strings.NewReader("1\n\n"), sample) if err != nil { t.Fatalf("unexpected error: %v", err) } if want := []string{"module", "build"}; !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } } func TestSelectAppendsExtraArgs(t *testing.T) { got, err := Select(io.Discard, strings.NewReader("2\n--dry-run foo\n"), sample) if err != nil { t.Fatalf("unexpected error: %v", err) } want := []string{"topdata", "validate-topdata", "--dry-run", "foo"} if !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } } func TestSelectQuitReturnsNil(t *testing.T) { got, err := Select(io.Discard, strings.NewReader("q\n"), sample) if err != nil || got != nil { t.Fatalf("quit should return (nil,nil); got (%v,%v)", got, err) } } func TestSelectInvalidErrors(t *testing.T) { if _, err := Select(io.Discard, strings.NewReader("99\n"), sample); err == nil { t.Fatal("out-of-range selection should error") } } func TestSelectAlignsDescriptionsAfterLongestLabel(t *testing.T) { items := []Item{ {Label: "hak build", Desc: "build haks", Args: []string{"hak", "build"}}, {Label: "topdata exceptionally-long-command", Desc: "long command", Args: []string{"topdata", "long"}}, } var out bytes.Buffer if _, err := Select(&out, strings.NewReader("q\n"), items); err != nil { t.Fatalf("select: %v", err) } text := stripANSI(out.String()) var positions []int for _, line := range strings.Split(text, "\n") { for _, desc := range []string{"build haks", "long command"} { if index := strings.Index(line, desc); index >= 0 { positions = append(positions, index) } } } if len(positions) != 2 { t.Fatalf("expected two menu descriptions, got %d:\n%s", len(positions), text) } if positions[0] != positions[1] { t.Fatalf("descriptions are not aligned: columns %v\n%s", positions, text) } } func TestSelectShowsCommandGuidanceBeforeArgumentsPrompt(t *testing.T) { items := []Item{{ Label: "topdata build", Desc: "compile topdata", Args: []string{"topdata", "build"}, Usage: "crucible topdata build [--force]", Options: []string{"--force rebuild outputs"}, }} var out bytes.Buffer if _, err := Select(&out, strings.NewReader("1\n\n"), items); err != nil { t.Fatalf("select: %v", err) } text := stripANSI(out.String()) for _, want := range []string{ "usage: crucible topdata build [--force]", "options:", "--force rebuild outputs", "arguments (press Enter to use defaults):", } { if !strings.Contains(text, want) { t.Errorf("selected command guidance missing %q:\n%s", want, text) } } } func stripANSI(value string) string { return regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(value, "") }