Files
archvillainette f257672427
sync-wrappers / sync (push) Successful in 16s
test / test (push) Successful in 1m22s
build-binaries / build-binaries (push) Successful in 2m7s
build-image / publish (push) Successful in 38s
command and help ux pass (#21)
Reviewed-on: #21
Co-authored-by: vickydotbat <vickydotbat@tutamail.com>
Co-committed-by: vickydotbat <vickydotbat@tutamail.com>
2026-06-25 09:29:39 +00:00

105 lines
3.0 KiB
Go

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