From 2d0643bb74b466b6df1288248e4bd4681e760f80 Mon Sep 17 00:00:00 2001 From: yutasb Date: Sun, 28 Jul 2024 01:38:04 +0900 Subject: [PATCH 1/4] feat: at least option to multi select --- field_multiselect.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/field_multiselect.go b/field_multiselect.go index 7a217aa3..9e87f95c 100644 --- a/field_multiselect.go +++ b/field_multiselect.go @@ -27,6 +27,7 @@ type MultiSelect[T comparable] struct { filterable bool filteredOptions []Option[T] limit int + atLeast int height int // error handling @@ -174,6 +175,12 @@ func (m *MultiSelect[T]) Limit(limit int) *MultiSelect[T] { return m } +// AtLeast sets the minimum number of options that must be selected. +func (m *MultiSelect[T]) AtLeast(atLeast int) *MultiSelect[T] { + m.atLeast = atLeast + return m +} + // Height sets the height of the multi-select field. func (m *MultiSelect[T]) Height(height int) *MultiSelect[T] { // What we really want to do is set the height of the viewport, but we @@ -379,6 +386,10 @@ func (m *MultiSelect[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.updateValue() case key.Matches(msg, m.keymap.Prev): m.updateValue() + if m.atLeast > 0 && m.numSelected() < m.atLeast { + m.err = fmt.Errorf("you must select at least %d options", m.atLeast) + return m, nil + } m.err = m.validate(m.accessor.Get()) if m.err != nil { return m, nil @@ -386,6 +397,10 @@ func (m *MultiSelect[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, PrevField case key.Matches(msg, m.keymap.Next, m.keymap.Submit): m.updateValue() + if m.atLeast > 0 && m.numSelected() < m.atLeast { + m.err = fmt.Errorf("you must select at least %d options", m.atLeast) + return m, nil + } m.err = m.validate(m.accessor.Get()) if m.err != nil { return m, nil From 225db9bcde8dbbf1b5e74007be0fa827949b945e Mon Sep 17 00:00:00 2001 From: yutasb Date: Sun, 28 Jul 2024 01:41:16 +0900 Subject: [PATCH 2/4] add: huh test for at least option --- huh_test.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/huh_test.go b/huh_test.go index 7e16a15a..b06e8b19 100644 --- a/huh_test.go +++ b/huh_test.go @@ -28,8 +28,13 @@ func TestForm(t *testing.T) { Toppings []string } + type Beverages struct { + Name []string + } + type Order struct { Taco Taco + Beverages Beverages Name string Instructions string Discount bool @@ -83,6 +88,24 @@ func TestForm(t *testing.T) { Limit(4), ), + // Prompt for toppings and special instructions. + // The customer can ask for at least two toppings. + NewGroup( + NewMultiSelect[string](). + Title("Beverages"). + Description("Choose at least two."). + Options( + NewOption("Coke", "coke").Selected(true), + NewOption("Pepsi", "pepsi").Selected(true), + NewOption("Sprite", "sprite"), + NewOption("Fanta", "fanta"), + NewOption("Dr. Pepper", "dr. pepper"), + ). + Value(&order.Beverages.Name). + Filterable(true). + AtLeast(2), + ), + // Gather final details for the order. NewGroup( NewInput(). @@ -219,6 +242,40 @@ func TestForm(t *testing.T) { m = batchUpdate(m.Update(tea.KeyMsg{Type: tea.KeyEnter})) view = ansi.Strip(m.View()) + // + // ┃ Beverages + // ┃ Choose at least two. + // ┃ > ✓ Coke + // ┃ ✓ Pepsi + // ┃ • Sprite + // ┃ • Fanta + // ┃ • Dr. Pepper + // + // x toggle • ↑ up • ↓ down • enter confirm • shift+tab back + // + if !strings.Contains(view, "Beverages") { + t.Log(pretty.Render(view)) + t.Fatal("Expected form to show beverages group") + } + + if !strings.Contains(view, "Choose at least two.") { + t.Log(pretty.Render(view)) + t.Error("Expected form to show beverages description") + } + + if !strings.Contains(view, "> ✓ Coke") { + t.Log(pretty.Render(view)) + t.Error("Expected form to preselect coke") + } + + if !strings.Contains(view, " ✓ Pepsi") { + t.Log(pretty.Render(view)) + t.Error("Expected form to preselect pepsi") + } + + m = batchUpdate(m.Update(tea.KeyMsg{Type: tea.KeyEnter})) + view = ansi.Strip(m.View()) + if !strings.Contains(view, "What's your name?") { t.Log(pretty.Render(view)) t.Error("Expected form to prompt for name") From 4b07d985a08478ea34c73487c9a0797b0ec07bdd Mon Sep 17 00:00:00 2001 From: yutasb Date: Sun, 28 Jul 2024 01:50:49 +0900 Subject: [PATCH 3/4] fix: code comment --- huh_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/huh_test.go b/huh_test.go index b06e8b19..014fbb01 100644 --- a/huh_test.go +++ b/huh_test.go @@ -88,8 +88,8 @@ func TestForm(t *testing.T) { Limit(4), ), - // Prompt for toppings and special instructions. - // The customer can ask for at least two toppings. + // Prompt for beverages. + // The customer can ask for at least two beverages. NewGroup( NewMultiSelect[string](). Title("Beverages"). From 51081594b024a9ddd1a22eb146ac4af979ae2217 Mon Sep 17 00:00:00 2001 From: yutasb Date: Sun, 28 Jul 2024 12:02:08 +0900 Subject: [PATCH 4/4] refactor: rename AtLeast to Minumum --- field_multiselect.go | 16 ++++++++-------- huh_test.go | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/field_multiselect.go b/field_multiselect.go index 9e87f95c..8801276e 100644 --- a/field_multiselect.go +++ b/field_multiselect.go @@ -27,7 +27,7 @@ type MultiSelect[T comparable] struct { filterable bool filteredOptions []Option[T] limit int - atLeast int + minimum int height int // error handling @@ -175,9 +175,9 @@ func (m *MultiSelect[T]) Limit(limit int) *MultiSelect[T] { return m } -// AtLeast sets the minimum number of options that must be selected. -func (m *MultiSelect[T]) AtLeast(atLeast int) *MultiSelect[T] { - m.atLeast = atLeast +// Minimum sets the minimum of the multi-select field. +func (m *MultiSelect[T]) Minimum(minimum int) *MultiSelect[T] { + m.minimum = minimum return m } @@ -386,8 +386,8 @@ func (m *MultiSelect[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.updateValue() case key.Matches(msg, m.keymap.Prev): m.updateValue() - if m.atLeast > 0 && m.numSelected() < m.atLeast { - m.err = fmt.Errorf("you must select at least %d options", m.atLeast) + if m.minimum > 0 && m.numSelected() < m.minimum { + m.err = fmt.Errorf("you must select at least %d options", m.minimum) return m, nil } m.err = m.validate(m.accessor.Get()) @@ -397,8 +397,8 @@ func (m *MultiSelect[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, PrevField case key.Matches(msg, m.keymap.Next, m.keymap.Submit): m.updateValue() - if m.atLeast > 0 && m.numSelected() < m.atLeast { - m.err = fmt.Errorf("you must select at least %d options", m.atLeast) + if m.minimum > 0 && m.numSelected() < m.minimum { + m.err = fmt.Errorf("you must select at least %d options", m.minimum) return m, nil } m.err = m.validate(m.accessor.Get()) diff --git a/huh_test.go b/huh_test.go index 014fbb01..44a364dd 100644 --- a/huh_test.go +++ b/huh_test.go @@ -103,7 +103,7 @@ func TestForm(t *testing.T) { ). Value(&order.Beverages.Name). Filterable(true). - AtLeast(2), + Minimum(2), ), // Gather final details for the order.