Skip to content

Commit d439ec5

Browse files
committed
docs: add readme and license files
1 parent 6448f79 commit d439ec5

File tree

2 files changed

+251
-0
lines changed

2 files changed

+251
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Filip Mikina
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Teastraw
2+
3+
A Go package for end-to-end testing of Terminal User Interface (TUI) applications.
4+
5+
## Overview
6+
7+
`Teastraw` provides a test runner that can interact with TUI applications by:
8+
- Starting your TUI application in a pseudo-terminal (PTY)
9+
- Sending keyboard input and terminal events
10+
- Capturing and validating screen output
11+
- Managing application lifecycle and graceful shutdown
12+
13+
## Installation
14+
15+
```bash
16+
go get github.com/fiffeek/teastraw
17+
```
18+
19+
## Quick Start
20+
21+
The following example uses `stretchr/testify` for clarity, however, `teastraw` does not make
22+
any assumptions about the testing library. You get raw errors and can decide yourself what "failing" the test
23+
means exactly.
24+
25+
Here's a simple example of testing a list-based TUI application:
26+
27+
```go
28+
package main
29+
30+
import (
31+
"bytes"
32+
"context"
33+
"os"
34+
"os/exec"
35+
"testing"
36+
"time"
37+
38+
"github.com/fiffeek/teastraw/pkg/exp"
39+
"github.com/stretchr/testify/assert"
40+
"github.com/stretchr/testify/require"
41+
)
42+
43+
func TestMyTUIApp(t *testing.T) {
44+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
45+
defer cancel()
46+
47+
// Create command to run your TUI application
48+
cmd := exec.CommandContext(ctx, "./my-tui-app")
49+
cmd.Env = append(os.Environ(), "NO_COLOR=1") // Disable colors for consistent testing
50+
51+
// Create test runner with terminal dimensions
52+
runner, err := exp.NewTestRunner(
53+
exp.WithInitialTermSize(80, 24), // width, height
54+
exp.WithCommand(cmd),
55+
)
56+
require.NoError(t, err)
57+
58+
// Start the application
59+
require.NoError(t, runner.Start())
60+
61+
// Wait for initial screen to load
62+
err = runner.WaitFor(func(screen []byte) bool {
63+
return bytes.Contains(screen, []byte("Welcome"))
64+
}, exp.WithCheckInterval(50*time.Millisecond), exp.WithDuration(200*time.Millisecond))
65+
assert.NoError(t, err)
66+
67+
// Send keyboard input
68+
assert.NoError(t, runner.Send([]byte("j"))) // Move down
69+
70+
// Wait for screen to update
71+
err = runner.WaitFor(func(screen []byte) bool {
72+
return bytes.Contains(screen, []byte("Item 2"))
73+
}, exp.WithCheckInterval(50*time.Millisecond), exp.WithDuration(200*time.Millisecond))
74+
assert.NoError(t, err)
75+
76+
// Capture current screen state
77+
screen := runner.ScreenNow()
78+
// Validate screen content...
79+
80+
// Send quit command
81+
assert.NoError(t, runner.Send([]byte("\x03"))) // Ctrl+C
82+
83+
// Get final screen before exit
84+
finalScreen, err := runner.FinalScreen(exp.WithFinalTimeout(500*time.Millisecond))
85+
assert.NoError(t, err)
86+
87+
// Validate output.
88+
// Run the tests with `-update` to create the golden file.
89+
exp.RequireEqualSubtest(t, []byte(finalScreen), "__final_screen")
90+
91+
// Get complete output history
92+
output, err := runner.FinalOutput()
93+
assert.NoError(t, err)
94+
// Validate output...
95+
}
96+
```
97+
98+
## Key Features
99+
100+
### Screen Validation
101+
102+
Capture and validate screen content at any point:
103+
104+
```go
105+
// Get current screen state
106+
screen := runner.ScreenNow()
107+
108+
// Use golden file testing for screen validation
109+
exp.RequireEqualSubtest(t, screen, "expected_screen_state")
110+
```
111+
112+
### Waiting for Conditions
113+
114+
Wait for specific content to appear on screen:
115+
116+
```go
117+
err := runner.WaitFor(func(screen []byte) bool {
118+
return bytes.Contains(screen, []byte("Loading complete"))
119+
}, exp.WithCheckInterval(10*time.Millisecond), exp.WithDuration(1*time.Second))
120+
```
121+
122+
### Input Simulation
123+
124+
Send various types of input:
125+
126+
```go
127+
// Send regular keys
128+
runner.Send([]byte("hello"))
129+
130+
// Send special keys
131+
runner.Send([]byte("\x03")) // Ctrl+C
132+
runner.Send([]byte("\r")) // Enter
133+
runner.Send([]byte("\x1b[A")) // Up arrow
134+
```
135+
136+
> [!CAUTION]
137+
> It is recommended to grab output after each key press to let the app process it correctly,
138+
> see [run_test.go](https://github.com/fiffeek/teastraw/blob/main/test/run_test.go#L140) example.
139+
140+
141+
### Graceful Shutdown
142+
143+
Capture final state before application exits:
144+
145+
```go
146+
// Get the last screen before exit
147+
finalScreen, err := runner.FinalScreen(exp.WithFinalTimeout(500*time.Millisecond))
148+
149+
// Get complete output history
150+
allOutput, err := runner.FinalOutput()
151+
```
152+
153+
## Configuration Options
154+
155+
### Terminal Size
156+
157+
```go
158+
runner, err := exp.NewTestRunner(
159+
exp.WithInitialTermSize(120, 30), // Custom terminal dimensions
160+
exp.WithCommand(cmd),
161+
)
162+
```
163+
164+
### Exit Sequences
165+
166+
For applications with specific exit sequences:
167+
168+
```go
169+
runner, err := exp.NewTestRunner(
170+
exp.WithExitSequences([]string{"\x1b[?1049l"}), // Clear alternate screen
171+
exp.WithCommand(cmd),
172+
)
173+
174+
[A few common ones](https://github.com/fiffeek/teastraw/blob/main/pkg/exp/const.go#L3) are passed by default, you can also refer to them in your code.
175+
```
176+
177+
## Testing Patterns
178+
179+
### Golden File Testing
180+
181+
Teastraw includes utilities for golden file testing to validate screen output against known good states:
182+
183+
```go
184+
// This will create/update golden files in testdata/
185+
exp.RequireEqualSubtest(t, screenOutput, "test_name")
186+
```
187+
188+
If you prefer to handle errors yourself (e.g. do not fail a test when it doesn't match),
189+
you can use `exp.IsEqualToFixture` (`exp.RequireEqualSubtest` is just a wrapper).
190+
191+
### Time-based Testing
192+
193+
Set appropriate timeouts for your application's responsiveness:
194+
195+
```go
196+
// Quick interactions
197+
exp.WithDuration(100*time.Millisecond)
198+
199+
// Slower operations
200+
exp.WithDuration(2*time.Second)
201+
```
202+
203+
## Examples
204+
205+
See the `test/` directory for complete working examples of testing different types of TUI applications.
206+
207+
## Comparison to Teatest
208+
209+
Bubble Tea provides an experimental test framework called [teatest](https://github.com/charmbracelet/x/tree/main/exp/teatest). While `teastest` is great for testing your Bubble Tea models directly, `teastraw` takes a different approach by testing your complete TUI application end-to-end.
210+
211+
Key differences:
212+
- **teatest**: Tests your Bubble Tea model in isolation (unit/integration testing)
213+
- **teastraw**: Tests your compiled binary as a black box (end-to-end testing)
214+
215+
With `teatest`, you interact directly with your model, but external dependencies need to be mocked. With `teastraw`, you can test the real application behavior including CLI arguments, environment variables, file system interactions, and any other external integrations your app uses.
216+
217+
## Contributing
218+
219+
Contributions are welcome! Please feel free to submit a Pull Request.
220+
221+
## License
222+
223+
[See LICENSE](./LICENSE)
224+
225+
## Credits
226+
227+
The code was modeled and adapted from:
228+
- [charmbracelet's `x/exp/golden`](https://github.com/charmbracelet/x/tree/main/exp/golden)
229+
- [charmbracelet's `x/exp/teatest`](https://github.com/charmbracelet/x/tree/main/exp/teatest)
230+
- [bubbletea's examples](https://github.com/charmbracelet/bubbletea/tree/main/examples) in `./bin`

0 commit comments

Comments
 (0)