huhtest

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 10, 2025 License: MIT Imports: 9 Imported by: 0

README

🤔 Huh Test

huhtest is a work-in-progress test library for your huh forms. If you're - for some reason - eager to test your huh-based interactive CLI applications then you've come to the right place. It works by matching messages in a form's output (stdout) and then sending pre-programmed text to the form's input (stdin).

It's not 100% bug-free, as some combinations of groups and selects seem to have off-by-one errors.

⬇️ Installation

go get github.com/survivorbat/huhtest

📋 Usage

Check out this example

🧪 Testing

To make sure this thing actually works, we have both unit tests and integration tests, the former checks the output of huhtest directly, the latter actually uses huh to check whether the inputs are properly processed.

🐞 Debugging

There's a .Debug() method available that enabled extra logging in the Responser. If you encounter a bug or are suspicious about something not working, turn it on to see exactly what it's doing.

🔭 Plans

  • Custom keymap support for select fields
  • Select fields should preferably be selected by output text and not index

Documentation

Index

Examples

Constants

View Source
const (
	// ConfirmAffirm is the 'yes' answer in a Confirm question
	ConfirmAffirm confirmResponse = "yes"

	// ConfirmAffirm is the 'no' answer in a Confirm question
	ConfirmNegative confirmResponse = "no"
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Closer

type Closer func()

Closer is returned from Start and should be called in a defer after calling Start

type Responder

type Responder struct {
	// contains filtered or unexported fields
}

Responder is a builder that allows you to put together a list of responses to questions asked in a form. Check out NewResponder for more information.

func NewResponder

func NewResponder() *Responder

NewResponder instantiates a Responder that allows you to build responses to a commandline application. The Start() command should be called at the end of a chain to start a goroutine that will read and write using the returned io.Pipe objects.

For example:

stdIn, stdOut, cancel := NewResponder().
  AddResponse(...),
  AddConfirm(...),
  Start()
defer cancel()

myForm.WithInput(stdIn).WithOutput(stdOut).Run()

Check out the individual method descriptions to learn more.

Example
package main

import (
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/charmbracelet/huh"
	"github.com/stretchr/testify/require"
)

var t = new(testing.T)

func main() {
	// Arrange
	var (
		howAreYouFeelingAnswer string
		areYouReadyAnswer      bool
		sleptWellAnswer        string
		activitiesAnswer       []string
	)

	myForm := huh.NewForm(
		huh.NewGroup(
			huh.NewInput().
				Title("How Are You Feeling?").
				Value(&howAreYouFeelingAnswer),
			huh.NewConfirm().
				Title("Are you ready?").
				Value(&areYouReadyAnswer),
			huh.NewSelect[string]().
				Title("Have you slept well?").
				Options(
					huh.NewOption("Well!", "well"),
					huh.NewOption("Terribly!", "terrible"),
					huh.NewOption("It was OK!", "ok"),
				).
				Value(&sleptWellAnswer),
		),
		huh.NewGroup(
			huh.NewMultiSelect[string]().
				Title("What are your favourite activities?").
				Options(
					huh.NewOption("Cycling", "bike"),
					huh.NewOption("Sleeping", "sleep"),
					huh.NewOption("Boating", "boat"),
					huh.NewOption("Gaming", "game"),
					huh.NewOption("Flying", "fly"),
				).
				Value(&activitiesAnswer),
		),
	)

	stdin, stdout, cancel := NewResponder().
		AddResponse("How Are You Feeling?", "Great").
		AddConfirm("Are you ready?", ConfirmAffirm).
		AddMultiSelect("activities", []int{1, 2, 4}).
		AddSelect("Have you slept well", 2).
		Start(t, 1*time.Second)

	defer cancel()

	// Act
	err := myForm.WithInput(stdin).WithOutput(stdout).Run()

	// Assert
	require.NoError(t, err)

	fmt.Println("How are you feeling?", howAreYouFeelingAnswer)
	fmt.Println("Are you ready?", areYouReadyAnswer)
	fmt.Println("Have you slept well?", sleptWellAnswer)
	fmt.Println("What are your favourite activities?", strings.Join(activitiesAnswer, ", "))

}
Output:
How are you feeling? Great
Are you ready? true
Have you slept well? ok
What are your favourite activities? sleep, boat, fly

func NewResponderWith

func NewResponderWith(questionResponses map[string][]string) *Responder

NewResponderWith is a lot like NewResponder, but initialises the given questions and responses automatically. This only works for simple responses, select, multi-select and confirms have to be added manually afterwards. Please refer to the documentation of NewResponder to learn more about its usages.

func (*Responder) AddConfirm

func (r *Responder) AddConfirm(question string, answer confirmResponse) *Responder

AddConfirm adds a confirm response to the Responder. If the same question comes up multiple times, the same response will be returned by default. Use Times() or Once() to modify this behaviour and register an error.

Multiple answers to the same question can be added by repeating this call.

func (*Responder) AddMultiSelect

func (r *Responder) AddMultiSelect(question string, options []int) *Responder

AddMultiSelect adds a response that will navigate a multiple-choice list and pick the indexes of the given options. If the same question comes up multiple times, the same response will be returned by default. Use Times() or Once() to modify this behaviour and register an error.

Multiple answers to the same question can be added by repeating this call.

func (*Responder) AddResponse

func (r *Responder) AddResponse(question string, answer string) *Responder

AddResponse adds a text-based response to the responder that will be returned if the question matches. If the same question comes up multiple times, the same response will be returned by default. Use Times() or Once() to modify this behaviour and register an error.

Multiple answers to the same question can be added by repeating this call.

func (*Responder) AddSelect

func (r *Responder) AddSelect(question string, option int) *Responder

AddSelect adds a response that will navigate a multiple-choice list and pick the index of the given option. If the same question comes up multiple times, the same response will be returned by default. Use Times() or Once() to modify this behaviour and register an error.

Multiple answers to the same question can be added by repeating this call.

func (*Responder) Debug

func (r *Responder) Debug() *Responder

Debug turns on logging for debugging forms

func (*Responder) MatchExact

func (r *Responder) MatchExact() *Responder

MatchExact changes question matching to exactly match the output. This is only useful if you are 100% sure that the output line won't contain any formatting or flair,

func (*Responder) MatchRegexp

func (r *Responder) MatchRegexp() *Responder

MatchRegexp changes question matching to treat the question as a regex

func (*Responder) RespondOnce

func (r *Responder) RespondOnce() *Responder

RespondOnce will make the test error if the question is posed more than 1 once, but it will still return an answer.

func (*Responder) RespondTimes

func (r *Responder) RespondTimes(times int) *Responder

RespondOnce will make the test error if the question is posed more than the specified amount of times, but it will still return an answer.

func (*Responder) Start

func (r *Responder) Start(t testingi.T, timeout time.Duration) (*io.PipeReader, *io.PipeWriter, Closer)

Start will kick off the goroutine that will listen for inputs in the returned io.PipeWriter. It will then attempt to answer the incoming line with a registered response on the io.PipeReader. You're required to provide a timeout that will stop the reader and writer to prevent it from locking forever.

To stop the responder, you can call the returned cancel/close function that will close the readers and writers

Usage:

stdIn, stdOut, cancel := NewResponder().
  AddResponse(...),
  AddConfirms(...),
  Start()
defer cancel()

myForm.WithInput(stdIn).WithOutput(stdOut).Run()

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL