# ugh

>**ugh**: [exclamation, informal]<br>
> used to express disgust or horror.<br>
> &nbsp;&nbsp;&nbsp;&nbsp;*"ugh - what's this disgusting object?"*

`ugh` is a Go testing library for driving [huh](https://charm.land/huh/v2) 
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:

```go
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:

```go
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`:

```go
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:

```go
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

```go
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.
