with one click
teatest-v2
Test Bubble Tea v2 TUI applications using teatest v2 library
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Test Bubble Tea v2 TUI applications using teatest v2 library
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | teatest-v2 |
| description | Test Bubble Tea v2 TUI applications using teatest v2 library |
| license | MIT |
| compatibility | opencode |
| metadata | {"language":"go","domain":"testing"} |
Testing library for Bubble Tea v2 applications.
import "charm.land/x/exp/teatest/v2"
Requires github.com/charmbracelet/bubbletea/v2.
tm := teatest.NewTestModel(t, model,
teatest.WithInitialTermSize(80, 24),
teatest.WithProgramOptions(tea.WithContext(ctx)),
)
// Send any tea.Msg
tm.Send(tea.KeyMsg{Type: tea.KeyEnter})
// Type text (sends individual key messages)
tm.Type("hello")
// Current output (non-blocking)
reader := tm.Output()
// Final output (blocks until program exits)
reader := tm.FinalOutput(t, teatest.WithFinalTimeout(5*time.Second))
// Blocks until program exits, then returns final model state
fm := tm.FinalModel(t, teatest.WithFinalTimeout(5*time.Second))
m := fm.(MyModel) // type assert to concrete type
// Wait for output to contain specific text
teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("expected text"))
}, teatest.WithDuration(3*time.Second), teatest.WithCheckInterval(100*time.Millisecond))
tm.Quit() // quit program
tm.WaitFinished(t, teatest.WithFinalTimeout(time.Second)) // wait for exit
tm.GetProgram() // access underlying *tea.Program
func TestOutput(t *testing.T) {
tm := teatest.NewTestModel(t, initialModel(), teatest.WithInitialTermSize(80, 24))
out, _ := io.ReadAll(tm.FinalOutput(t))
teatest.RequireEqualOutput(t, out) // compares to testdata/TestOutput.golden
}
Update golden files: go test -update
func TestFullOutput(t *testing.T) {
tm := teatest.NewTestModel(t, initialModel(), teatest.WithInitialTermSize(80, 24))
out, err := io.ReadAll(tm.FinalOutput(t))
require.NoError(t, err)
teatest.RequireEqualOutput(t, out)
}
func TestFinalModel(t *testing.T) {
tm := teatest.NewTestModel(t, initialModel(), teatest.WithInitialTermSize(80, 24))
fm := tm.FinalModel(t)
m, ok := fm.(model)
require.True(t, ok)
assert.Equal(t, expected, m.field)
}
func TestInteraction(t *testing.T) {
tm := teatest.NewTestModel(t, initialModel(), teatest.WithInitialTermSize(80, 24))
// Wait for initial render
teatest.WaitFor(t, tm.Output(), func(bts []byte) bool {
return bytes.Contains(bts, []byte("ready"))
})
// Send input
tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("q")})
// Wait for program to finish
tm.WaitFinished(t, teatest.WithFinalTimeout(time.Second))
}
func init() {
lipgloss.SetColorProfile(termenv.Ascii) // disable colors for reproducible output
}
Add to .gitattributes:
*.golden -text
| Type | Purpose |
|---|---|
TestModel | Wrapper for testing a tea.Model |
TestOption | Options for NewTestModel |
FinalOpt | Options for Final*/WaitFinished methods |
WaitForOption | Options for WaitFor |
| Function | Purpose |
|---|---|
WithInitialTermSize(x, y) | Set terminal dimensions |
WithProgramOptions(...) | Pass tea.ProgramOptions |
WithFinalTimeout(d) | Timeout for final operations |
WithDuration(d) | Total wait duration |
WithCheckInterval(d) | Check frequency for WaitFor |