From 5c8f08b60ee27af1469bfbc4be72f5c1f189fb9b Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 06:34:25 +0000 Subject: [PATCH] fix(cli): harden legacy select helpers Co-Authored-By: Virgil --- pkg/cli/prompt.go | 23 ++++++++++++++++++++--- pkg/cli/prompt_test.go | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/pkg/cli/prompt.go b/pkg/cli/prompt.go index 0b1aa7f..9c3e207 100644 --- a/pkg/cli/prompt.go +++ b/pkg/cli/prompt.go @@ -55,6 +55,10 @@ func Prompt(label, defaultVal string) (string, error) { // Select presents numbered options and returns the selected value. func Select(label string, options []string) (string, error) { + if len(options) == 0 { + return "", nil + } + fmt.Println(compileGlyphs(label)) for i, opt := range options { fmt.Printf(" %d. %s\n", i+1, compileGlyphs(opt)) @@ -76,6 +80,10 @@ func Select(label string, options []string) (string, error) { // MultiSelect presents checkboxes (space-separated numbers). func MultiSelect(label string, options []string) ([]string, error) { + if len(options) == 0 { + return []string{}, nil + } + fmt.Println(compileGlyphs(label)) for i, opt := range options { fmt.Printf(" %d. %s\n", i+1, compileGlyphs(opt)) @@ -91,7 +99,10 @@ func MultiSelect(label string, options []string) ([]string, error) { return nil, err } - var selected []string + var ( + selected []string + seen = make(map[int]bool, len(options)) + ) normalized := strings.NewReplacer(",", " ").Replace(input) for _, s := range strings.Fields(normalized) { if strings.Contains(s, "-") { @@ -105,7 +116,10 @@ func MultiSelect(label string, options []string) ([]string, error) { continue } for n := start; n <= end; n++ { - selected = append(selected, options[n-1]) + if !seen[n-1] { + seen[n-1] = true + selected = append(selected, options[n-1]) + } } continue } @@ -114,7 +128,10 @@ func MultiSelect(label string, options []string) ([]string, error) { if convErr != nil || n < 1 || n > len(options) { continue } - selected = append(selected, options[n-1]) + if !seen[n-1] { + seen[n-1] = true + selected = append(selected, options[n-1]) + } } return selected, nil } diff --git a/pkg/cli/prompt_test.go b/pkg/cli/prompt_test.go index 9ffc0bc..7459e65 100644 --- a/pkg/cli/prompt_test.go +++ b/pkg/cli/prompt_test.go @@ -61,6 +61,12 @@ func TestSelect_Bad_EOF(t *testing.T) { assert.ErrorIs(t, err, io.EOF) } +func TestSelect_Good_EmptyOptions(t *testing.T) { + val, err := Select("Pick", nil) + assert.NoError(t, err) + assert.Empty(t, val) +} + func TestMultiSelect_Good(t *testing.T) { SetStdin(strings.NewReader("1 3\n")) defer SetStdin(nil) @@ -88,6 +94,21 @@ func TestMultiSelect_Bad_EOFReturnsEmptySelection(t *testing.T) { assert.Empty(t, vals) } +func TestMultiSelect_Good_DedupesSelections(t *testing.T) { + SetStdin(strings.NewReader("1 1 2-3 2\n")) + defer SetStdin(nil) + + vals, err := MultiSelect("Pick", []string{"a", "b", "c"}) + assert.NoError(t, err) + assert.Equal(t, []string{"a", "b", "c"}, vals) +} + +func TestMultiSelect_Good_EmptyOptions(t *testing.T) { + vals, err := MultiSelect("Pick", nil) + assert.NoError(t, err) + assert.Empty(t, vals) +} + func TestConfirm_Good(t *testing.T) { SetStdin(strings.NewReader("y\n")) defer SetStdin(nil)