Skip to content

Commit 86d876f

Browse files
BagToadCopilot
authored andcommitted
test(huh prompter): add table-driven tests for all prompt types
Extract build*Form() methods from each huhPrompter method, separating form construction from form.Run(). This enables testing the real form construction code by driving it with direct model updates, adapted from huh's own test patterns. Tests cover Input, Select, MultiSelect, Confirm, Password, MarkdownEditor, and MultiSelectWithSearch including a persistence test that verifies selections survive across search query changes. Also fixes a search cache initialization bug where the first buildOptions("") call would skip the searchFunc due to cachedSearchQuery defaulting to "". Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f294831 commit 86d876f

2 files changed

Lines changed: 599 additions & 45 deletions

File tree

internal/prompter/huh_prompter.go

Lines changed: 91 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (p *huhPrompter) newForm(groups ...*huh.Group) *huh.Form {
2424
WithOutput(p.stdout)
2525
}
2626

27-
func (p *huhPrompter) Select(prompt, defaultValue string, options []string) (int, error) {
27+
func (p *huhPrompter) buildSelectForm(prompt, defaultValue string, options []string) (*huh.Form, *int) {
2828
var result int
2929

3030
if !slices.Contains(options, defaultValue) {
@@ -39,19 +39,24 @@ func (p *huhPrompter) Select(prompt, defaultValue string, options []string) (int
3939
formOptions[i] = huh.NewOption(o, i)
4040
}
4141

42-
err := p.newForm(
42+
form := p.newForm(
4343
huh.NewGroup(
4444
huh.NewSelect[int]().
4545
Title(prompt).
4646
Value(&result).
4747
Options(formOptions...),
4848
),
49-
).Run()
49+
)
50+
return form, &result
51+
}
5052

51-
return result, err
53+
func (p *huhPrompter) Select(prompt, defaultValue string, options []string) (int, error) {
54+
form, result := p.buildSelectForm(prompt, defaultValue, options)
55+
err := form.Run()
56+
return *result, err
5257
}
5358

54-
func (p *huhPrompter) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) {
59+
func (p *huhPrompter) buildMultiSelectForm(prompt string, defaults []string, options []string) (*huh.Form, *[]int) {
5560
var result []int
5661

5762
defaults = slices.DeleteFunc(defaults, func(s string) bool {
@@ -66,20 +71,25 @@ func (p *huhPrompter) MultiSelect(prompt string, defaults []string, options []st
6671
formOptions[i] = huh.NewOption(o, i)
6772
}
6873

69-
err := p.newForm(
74+
form := p.newForm(
7075
huh.NewGroup(
7176
huh.NewMultiSelect[int]().
7277
Title(prompt).
7378
Value(&result).
7479
Limit(len(options)).
7580
Options(formOptions...),
7681
),
77-
).Run()
82+
)
83+
return form, &result
84+
}
7885

86+
func (p *huhPrompter) MultiSelect(prompt string, defaults []string, options []string) ([]int, error) {
87+
form, result := p.buildMultiSelectForm(prompt, defaults, options)
88+
err := form.Run()
7989
if err != nil {
8090
return nil, err
8191
}
82-
return result, nil
92+
return *result, nil
8393
}
8494

8595
// searchOptionsBinding is used as the OptionsFunc binding for MultiSelectWithSearch.
@@ -91,7 +101,7 @@ type searchOptionsBinding struct {
91101
Selected *[]string
92102
}
93103

94-
func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
104+
func (p *huhPrompter) buildMultiSelectWithSearchForm(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) (*huh.Form, *[]string) {
95105
selectedValues := make([]string, len(defaultValues))
96106
copy(selectedValues, defaultValues)
97107

@@ -103,13 +113,15 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
103113
// Cache searchFunc results locally keyed by query string.
104114
// This avoids redundant calls when the OptionsFunc binding hash changes
105115
// due to selection changes (not query changes).
116+
searchCacheValid := false
106117
var cachedSearchQuery string
107118
var cachedSearchResult MultiSelectSearchResult
108119

109120
buildOptions := func(query string) []huh.Option[string] {
110-
if query != cachedSearchQuery || cachedSearchResult.Err != nil {
121+
if !searchCacheValid || query != cachedSearchQuery {
111122
cachedSearchResult = searchFunc(query)
112123
cachedSearchQuery = query
124+
searchCacheValid = true
113125
}
114126
result := cachedSearchResult
115127

@@ -175,7 +187,7 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
175187
Selected: &selectedValues,
176188
}
177189

178-
err := p.newForm(
190+
form := p.newForm(
179191
huh.NewGroup(
180192
huh.NewInput().
181193
Title(searchPrompt).
@@ -189,67 +201,83 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
189201
Value(&selectedValues).
190202
Limit(0),
191203
),
192-
).Run()
204+
)
205+
return form, &selectedValues
206+
}
207+
208+
func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
209+
form, result := p.buildMultiSelectWithSearchForm(prompt, searchPrompt, defaultValues, persistentValues, searchFunc)
210+
err := form.Run()
193211
if err != nil {
194212
return nil, err
195213
}
196-
197-
return selectedValues, nil
214+
return *result, nil
198215
}
199216

200-
func (p *huhPrompter) Input(prompt, defaultValue string) (string, error) {
217+
func (p *huhPrompter) buildInputForm(prompt, defaultValue string) (*huh.Form, *string) {
201218
result := defaultValue
202-
203-
err := p.newForm(
219+
form := p.newForm(
204220
huh.NewGroup(
205221
huh.NewInput().
206222
Title(prompt).
207223
Value(&result),
208224
),
209-
).Run()
225+
)
226+
return form, &result
227+
}
210228

211-
return result, err
229+
func (p *huhPrompter) Input(prompt, defaultValue string) (string, error) {
230+
form, result := p.buildInputForm(prompt, defaultValue)
231+
err := form.Run()
232+
return *result, err
212233
}
213234

214-
func (p *huhPrompter) Password(prompt string) (string, error) {
235+
func (p *huhPrompter) buildPasswordForm(prompt string) (*huh.Form, *string) {
215236
var result string
216-
217-
err := p.newForm(
237+
form := p.newForm(
218238
huh.NewGroup(
219239
huh.NewInput().
220240
EchoMode(huh.EchoModePassword).
221241
Title(prompt).
222242
Value(&result),
223243
),
224-
).Run()
244+
)
245+
return form, &result
246+
}
225247

248+
func (p *huhPrompter) Password(prompt string) (string, error) {
249+
form, result := p.buildPasswordForm(prompt)
250+
err := form.Run()
226251
if err != nil {
227252
return "", err
228253
}
229-
return result, nil
254+
return *result, nil
230255
}
231256

232-
func (p *huhPrompter) Confirm(prompt string, defaultValue bool) (bool, error) {
257+
func (p *huhPrompter) buildConfirmForm(prompt string, defaultValue bool) (*huh.Form, *bool) {
233258
result := defaultValue
234-
235-
err := p.newForm(
259+
form := p.newForm(
236260
huh.NewGroup(
237261
huh.NewConfirm().
238262
Title(prompt).
239263
Value(&result),
240264
),
241-
).Run()
265+
)
266+
return form, &result
267+
}
242268

269+
func (p *huhPrompter) Confirm(prompt string, defaultValue bool) (bool, error) {
270+
form, result := p.buildConfirmForm(prompt, defaultValue)
271+
err := form.Run()
243272
if err != nil {
244273
return false, err
245274
}
246-
return result, nil
275+
return *result, nil
247276
}
248277

249-
func (p *huhPrompter) AuthToken() (string, error) {
278+
func (p *huhPrompter) buildAuthTokenForm() (*huh.Form, *string) {
250279
var result string
251-
252-
err := p.newForm(
280+
form := p.newForm(
253281
huh.NewGroup(
254282
huh.NewInput().
255283
EchoMode(huh.EchoModePassword).
@@ -262,12 +290,17 @@ func (p *huhPrompter) AuthToken() (string, error) {
262290
}).
263291
Value(&result),
264292
),
265-
).Run()
293+
)
294+
return form, &result
295+
}
266296

267-
return result, err
297+
func (p *huhPrompter) AuthToken() (string, error) {
298+
form, result := p.buildAuthTokenForm()
299+
err := form.Run()
300+
return *result, err
268301
}
269302

270-
func (p *huhPrompter) ConfirmDeletion(requiredValue string) error {
303+
func (p *huhPrompter) buildConfirmDeletionForm(requiredValue string) *huh.Form {
271304
return p.newForm(
272305
huh.NewGroup(
273306
huh.NewInput().
@@ -279,28 +312,36 @@ func (p *huhPrompter) ConfirmDeletion(requiredValue string) error {
279312
return nil
280313
}),
281314
),
282-
).Run()
315+
)
283316
}
284317

285-
func (p *huhPrompter) InputHostname() (string, error) {
286-
var result string
318+
func (p *huhPrompter) ConfirmDeletion(requiredValue string) error {
319+
return p.buildConfirmDeletionForm(requiredValue).Run()
320+
}
287321

288-
err := p.newForm(
322+
func (p *huhPrompter) buildInputHostnameForm() (*huh.Form, *string) {
323+
var result string
324+
form := p.newForm(
289325
huh.NewGroup(
290326
huh.NewInput().
291327
Title("Hostname:").
292328
Validate(ghinstance.HostnameValidator).
293329
Value(&result),
294330
),
295-
).Run()
331+
)
332+
return form, &result
333+
}
296334

335+
func (p *huhPrompter) InputHostname() (string, error) {
336+
form, result := p.buildInputHostnameForm()
337+
err := form.Run()
297338
if err != nil {
298339
return "", err
299340
}
300-
return result, nil
341+
return *result, nil
301342
}
302343

303-
func (p *huhPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) {
344+
func (p *huhPrompter) buildMarkdownEditorForm(prompt string, blankAllowed bool) (*huh.Form, *string) {
304345
var result string
305346
skipOption := "skip"
306347
launchOption := "launch"
@@ -311,20 +352,25 @@ func (p *huhPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed b
311352
options = append(options, huh.NewOption("Skip", skipOption))
312353
}
313354

314-
err := p.newForm(
355+
form := p.newForm(
315356
huh.NewGroup(
316357
huh.NewSelect[string]().
317358
Title(prompt).
318359
Options(options...).
319360
Value(&result),
320361
),
321-
).Run()
362+
)
363+
return form, &result
364+
}
322365

366+
func (p *huhPrompter) MarkdownEditor(prompt, defaultValue string, blankAllowed bool) (string, error) {
367+
form, result := p.buildMarkdownEditorForm(prompt, blankAllowed)
368+
err := form.Run()
323369
if err != nil {
324370
return "", err
325371
}
326372

327-
if result == skipOption {
373+
if *result == "skip" {
328374
return "", nil
329375
}
330376

0 commit comments

Comments
 (0)