ugh

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Mar 19, 2026 License: MIT Imports: 9 Imported by: 0

README

ugh

ugh: [exclamation, informal]
used to express disgust or horror.
    "ugh - what's this disgusting object?"

ugh is a Go testing library for driving huh terminal UI forms. It uses a virtual terminal emulator to interpret rendered output and sends ANSI keystrokes as input — no real terminal needed.

Install

go get git.sr.ht/~timofurrer/ugh

Usage

Record expectations, then run them against a huh form's I/O:

func TestBurgerOrder(t *testing.T) {
    var burger string
    var confirmed bool

    c := ugh.New(t)
    c.Expect(ugh.Select("Choose your burger")).
        Do(ugh.SelectIndex(1)).
        Expect(ugh.Confirm("Place order?")).
        Do(ugh.Affirm)

    // wire to your form via io.Pipe + tea.WithWindowSize
    c.Run(ctx, appOutR, appInW)
}
Supported huh Field Types
Matcher Action Description
Confirm(title) Affirm, Reject Yes/no confirmation
Select(title) SelectIndex(n), SelectOption(label) Single selection
MultiSelect(title) ToggleIndices(i...), ToggleOptions(l...) Multi selection
Input(title) Type(text) Single-line text input
Text(title) TypeMultiline(text) Multi-line text input
Note(title) PressEnter Informational note

Regexp variants are available for title matching: ConfirmRegexp, SelectRegexp.

Custom Actions

Actions are plain func(*Response) — write your own:

c.Expect(ugh.Input("Name")).
    Do(func(r *ugh.Response) {
        if strings.Contains(r.Screen(), "required") {
            r.SendText("default")
        } else {
            r.SendText("custom")
        }
        r.Send(ugh.Enter)
    })
Custom Matchers

Implement the Matcher interface or use MatchScreen:

c.Expect(ugh.MatchScreen("loading done", func(screen string) bool {
    return strings.Contains(screen, "100%")
}))
Async Execution

Use Start when the form runs in the same goroutine as the test:

wait := c.Start(ctx, appOutR, appInW)
command.Execute() // blocks until form is answered
wait()
Expectation Verification

New registers a t.Cleanup hook that fails the test if any expectations were not executed — similar to gomock's unmet call detection.

Wiring to huh

appInR, appInW := io.Pipe()
appOutR, appOutW := io.Pipe()

form := huh.NewForm(groups...).
    WithProgramOptions(
        tea.WithInput(appInR),
        tea.WithOutput(appOutW),
        tea.WithWindowSize(120, 40),
    )

go func() {
    defer appOutW.Close()
    form.Run()
}()

c := ugh.New(t)
c.Expect(ugh.Confirm("Sure?")).
    Do(ugh.Affirm)
c.Run(ctx, appOutR, appInW)

tea.WithWindowSize is required — without it, bubbletea renders at 0x0 and the emulator sees an empty screen.

Documentation

Overview

Package ugh is a Go testing library for driving huh terminal UI forms through raw I/O streams using a virtual terminal emulator.

Index

Constants

This section is empty.

Variables

View Source
var (
	Enter     = Key{/* contains filtered or unexported fields */}
	Tab       = Key{/* contains filtered or unexported fields */}
	ShiftTab  = Key{/* contains filtered or unexported fields */}
	Up        = Key{/* contains filtered or unexported fields */}
	Down      = Key{/* contains filtered or unexported fields */}
	Right     = Key{/* contains filtered or unexported fields */}
	Left      = Key{/* contains filtered or unexported fields */}
	Space     = Key{/* contains filtered or unexported fields */}
	Escape    = Key{/* contains filtered or unexported fields */}
	Backspace = Key{/* contains filtered or unexported fields */}
	CtrlC     = Key{/* contains filtered or unexported fields */}
	CtrlA     = Key{/* contains filtered or unexported fields */}
	CtrlE     = Key{/* contains filtered or unexported fields */}
	CtrlU     = Key{/* contains filtered or unexported fields */}
	CtrlK     = Key{/* contains filtered or unexported fields */}
	CtrlW     = Key{/* contains filtered or unexported fields */}
	Home      = Key{/* contains filtered or unexported fields */}
	End       = Key{/* contains filtered or unexported fields */}
	Delete    = Key{/* contains filtered or unexported fields */}
)

Pre-defined key constants encoded as ANSI byte sequences compatible with bubbletea v2's ultraviolet input parser.

Functions

func Affirm

func Affirm(r *Response)

Affirm is an Action that sends "y" to affirm a huh Confirm field.

func PressEnter

func PressEnter(r *Response)

PressEnter is an Action that sends the Enter key.

func Reject

func Reject(r *Response)

Reject is an Action that sends "n" to reject a huh Confirm field.

Types

type Action

type Action func(r *Response)

Action is a function that runs at execution time after an expectation matches. It receives a *Response providing access to the screen state and the ability to send keystrokes.

func SelectIndex

func SelectIndex(index int) Action

SelectIndex returns an Action that sends Down×n then Enter. For Select fields, selecting by 0-based index.

func SelectOption

func SelectOption(label string) Action

SelectOption returns an Action that parses the screen to find the option label, navigates with Up/Down, then sends Enter. Uses Response.Text() as the field title for screen parsing.

func ToggleIndices

func ToggleIndices(indices ...int) Action

ToggleIndices returns an Action that navigates to each index and sends Space to toggle, then Enter to submit. For MultiSelect fields.

func ToggleOptions

func ToggleOptions(labels ...string) Action

ToggleOptions returns an Action that navigates to each label and sends Space to toggle, then Enter to submit. For MultiSelect fields.

func Type

func Type(text string) Action

Type returns an Action that types text and sends Enter. For Input fields.

func TypeMultiline

func TypeMultiline(text string) Action

TypeMultiline returns an Action that types text and sends Tab. For Text fields, where Enter inserts newlines and Tab advances to the next field.

type Console

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

Console drives a huh form through a virtual terminal emulator. Interactions are recorded as a chain of expectations and actions, then executed via Run or Start.

A t.Cleanup hook is registered automatically to verify that all recorded expectations were executed. If expectations are registered but Run/Start is never called, or if some expectations remain pending after execution, the test fails.

func New

func New(t testing.TB, opts ...Option) *Console

New creates a new test console. The testing.TB is used for failure reporting. A t.Cleanup hook is registered to verify all expectations are fulfilled.

func (*Console) Expect

func (c *Console) Expect(m Matcher) *Expectation

Expect records a screen expectation using the given Matcher and returns an *Expectation for attaching actions via Do or further chaining.

func (*Console) Run

func (c *Console) Run(ctx context.Context, appOutput io.Reader, appInput io.Writer)

Run executes all recorded steps synchronously against the application's I/O streams. Calls t.Fatalf on failure.

func (*Console) Screen

func (c *Console) Screen() string

Screen returns the current virtual terminal screen contents as plain text. Safe to call at any time, including during Run from other goroutines or after Run completes. Returns an empty string if called before Run.

func (*Console) Send

func (c *Console) Send(key Key) *Console

Send records a step that sends a single key's ANSI byte sequence to the application's stdin. Returns *Console for chaining.

func (*Console) SendText

func (c *Console) SendText(text string) *Console

SendText records a step that types text as individual characters with an inter-key delay. Returns *Console for chaining.

func (*Console) Start

func (c *Console) Start(ctx context.Context, appOutput io.Reader, appInput io.Writer) func()

Start executes all recorded steps asynchronously in a background goroutine. It returns a wait function that blocks until all steps complete and calls t.Fatalf on the caller's goroutine if any step failed.

type Expectation

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

Expectation represents a pending screen expectation that can be completed with Do.

func (*Expectation) Do

func (e *Expectation) Do(action Action) *Expectation

Do attaches an action to run when the expectation matches and returns the Expectation for further chaining.

func (*Expectation) Expect

func (e *Expectation) Expect(m Matcher) *Expectation

Expect chains another expectation after this one.

func (*Expectation) Send

func (e *Expectation) Send(key Key) *Expectation

Send records a keystroke step and returns the Expectation for chaining.

func (*Expectation) SendText

func (e *Expectation) SendText(text string) *Expectation

SendText records a text-typing step and returns the Expectation for chaining.

type Key

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

Key represents a keystroke as an ANSI byte sequence.

func (Key) Bytes

func (k Key) Bytes() []byte

Bytes returns the raw ANSI byte sequence for this key.

type Matcher

type Matcher interface {
	Match(screen string) bool
	String() string
}

Matcher describes what to wait for on screen. Implement this interface to create custom matchers for use with Console.Expect.

func Confirm

func Confirm(title string) Matcher

Confirm creates a substring Matcher for a huh Confirm field title.

func ConfirmRegexp

func ConfirmRegexp(pattern string) Matcher

ConfirmRegexp creates a regexp Matcher for a huh Confirm field title.

func Input

func Input(title string) Matcher

Input creates a substring Matcher for a huh Input field title.

func MatchScreen

func MatchScreen(desc string, fn func(screen string) bool) Matcher

MatchScreen creates a Matcher from a description and a predicate function.

func MultiSelect

func MultiSelect(title string) Matcher

MultiSelect creates a substring Matcher for a huh MultiSelect field title.

func Note

func Note(title string) Matcher

Note creates a substring Matcher for a huh Note field title.

func Regexp

func Regexp(pattern string) Matcher

Regexp creates a Matcher that waits for the screen to match the regexp pattern.

func Select

func Select(title string) Matcher

Select creates a substring Matcher for a huh Select field title.

func SelectRegexp

func SelectRegexp(pattern string) Matcher

SelectRegexp creates a regexp Matcher for a huh Select field title.

func String

func String(s string) Matcher

String creates a Matcher that waits for the screen to contain s.

func Text

func Text(title string) Matcher

Text creates a substring Matcher for a huh Text field title.

type Option

type Option func(*Console)

Option configures a Console.

func WithSize

func WithSize(width, height int) Option

WithSize sets the virtual terminal size. Default: 120x40.

func WithTimeout

func WithTimeout(d time.Duration) Option

WithTimeout sets the per-step timeout for expect operations. Default: 10s.

type Response

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

Response provides the action function with screen state and keystroke sending.

func (*Response) Screen

func (r *Response) Screen() string

Screen returns the virtual terminal screen contents at the moment the expectation matched.

func (*Response) Send

func (r *Response) Send(key Key)

Send writes a single key's ANSI byte sequence to the application's stdin.

func (*Response) SendText

func (r *Response) SendText(text string)

SendText types text as individual characters with an inter-key delay.

func (*Response) Text

func (r *Response) Text() string

Text returns Matcher.String() from the expectation that triggered this action.

Jump to

Keyboard shortcuts

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