-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add teatest/v2 to use bubbletea/v2 (#197)
* feat: add teatest/v2 to use bubbletea@v2-exp Will keep this open until we push a bubbletea/v2 release * feat(ci): add teatest-v2 workflow * fix: use bubbletea/v2
- Loading branch information
1 parent
9b0f832
commit 9350707
Showing
13 changed files
with
689 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# auto-generated by scripts/builds. DO NOT EDIT. | ||
name: teatest-v2 | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
paths: | ||
- exp/teatest/v2/** | ||
- .github/workflows/teatest-v2.yml | ||
|
||
jobs: | ||
build: | ||
strategy: | ||
matrix: | ||
os: [ubuntu-latest, macos-latest, windows-latest] | ||
runs-on: ${{ matrix.os }} | ||
defaults: | ||
run: | ||
working-directory: ./exp/teatest/v2 | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- uses: actions/setup-go@v5 | ||
with: | ||
go-version-file: ./exp/teatest/v2/go.mod | ||
cache: true | ||
cache-dependency-path: ./exp/teatest/go.sum | ||
- run: go build -v ./... | ||
- run: go test -race -v ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package teatest_test | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"regexp" | ||
"testing" | ||
"time" | ||
|
||
tea "github.com/charmbracelet/bubbletea/v2" | ||
"github.com/charmbracelet/x/exp/teatest/v2" | ||
) | ||
|
||
func TestApp(t *testing.T) { | ||
m := model(10) | ||
tm := teatest.NewTestModel( | ||
t, m, | ||
teatest.WithInitialTermSize(70, 30), | ||
) | ||
t.Cleanup(func() { | ||
if err := tm.Quit(); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
|
||
time.Sleep(time.Second + time.Millisecond*200) | ||
tm.Type("I'm typing things, but it'll be ignored by my program") | ||
tm.Send("ignored msg") | ||
tm.Send(tea.KeyPressMsg{ | ||
Code: tea.KeyEnter, | ||
}) | ||
|
||
if err := tm.Quit(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
out := readBts(t, tm.FinalOutput(t, teatest.WithFinalTimeout(time.Second))) | ||
if !regexp.MustCompile(`This program will exit in \d+ seconds`).Match(out) { | ||
t.Fatalf("output does not match the given regular expression: %s", string(out)) | ||
} | ||
teatest.RequireEqualOutput(t, out) | ||
|
||
if tm.FinalModel(t).(model) != 9 { | ||
t.Errorf("expected model to be 10, was %d", m) | ||
} | ||
} | ||
|
||
func TestAppInteractive(t *testing.T) { | ||
m := model(10) | ||
tm := teatest.NewTestModel( | ||
t, m, | ||
teatest.WithInitialTermSize(70, 30), | ||
) | ||
|
||
time.Sleep(time.Second + time.Millisecond*200) | ||
tm.Send("ignored msg") | ||
|
||
if bts := readBts(t, tm.Output()); !bytes.Contains(bts, []byte("This program will exit in 9 seconds")) { | ||
t.Fatalf("output does not match: expected %q", string(bts)) | ||
} | ||
|
||
teatest.WaitFor(t, tm.Output(), func(out []byte) bool { | ||
return bytes.Contains(out, []byte("This program will exit in 7 seconds")) | ||
}, teatest.WithDuration(5*time.Second), teatest.WithCheckInterval(time.Millisecond*10)) | ||
|
||
tm.Send(tea.KeyPressMsg{ | ||
Code: tea.KeyEnter, | ||
}) | ||
|
||
if err := tm.Quit(); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if tm.FinalModel(t).(model) != 7 { | ||
t.Errorf("expected model to be 7, was %d", m) | ||
} | ||
} | ||
|
||
func readBts(tb testing.TB, r io.Reader) []byte { | ||
tb.Helper() | ||
bts, err := io.ReadAll(r) | ||
if err != nil { | ||
tb.Fatal(err) | ||
} | ||
return bts | ||
} | ||
|
||
// A model can be more or less any type of data. It holds all the data for a | ||
// program, so often it's a struct. For this simple example, however, all | ||
// we'll need is a simple integer. | ||
type model int | ||
|
||
// Init optionally returns an initial command we should run. In this case we | ||
// want to start the timer. | ||
func (m model) Init() (tea.Model, tea.Cmd) { | ||
return m, tick | ||
} | ||
|
||
// Update is called when messages are received. The idea is that you inspect the | ||
// message and send back an updated model accordingly. You can also return | ||
// a command, which is a function that performs I/O and returns a message. | ||
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
switch msg.(type) { | ||
case tea.KeyMsg: | ||
return m, tea.Quit | ||
case tickMsg: | ||
m-- | ||
if m <= 0 { | ||
return m, tea.Quit | ||
} | ||
return m, tick | ||
} | ||
return m, nil | ||
} | ||
|
||
// View returns a string based on data in the model. That string which will be | ||
// rendered to the terminal. | ||
func (m model) View() string { | ||
return fmt.Sprintf("Hi. This program will exit in %d seconds. To quit sooner press any key.\n", m) | ||
} | ||
|
||
// Messages are events that we respond to in our Update function. This | ||
// particular one indicates that the timer has ticked. | ||
type tickMsg time.Time | ||
|
||
func tick() tea.Msg { | ||
time.Sleep(time.Second) | ||
return tickMsg{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
module github.com/charmbracelet/x/exp/teatest/v2 | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/charmbracelet/bubbletea/v2 v2.0.0-20240918154035-3313a4cfa033 | ||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a | ||
) | ||
|
||
require ( | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||
github.com/aymanbagabas/go-udiff v0.2.0 // indirect | ||
github.com/charmbracelet/lipgloss v0.13.0 // indirect | ||
github.com/charmbracelet/x/ansi v0.3.2 // indirect | ||
github.com/charmbracelet/x/term v0.2.0 // indirect | ||
github.com/charmbracelet/x/windows v0.2.0 // indirect | ||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||
github.com/mattn/go-isatty v0.0.20 // indirect | ||
github.com/mattn/go-runewidth v0.0.16 // indirect | ||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||
github.com/muesli/cancelreader v0.2.2 // indirect | ||
github.com/muesli/termenv v0.15.2 // indirect | ||
github.com/rivo/uniseg v0.4.7 // indirect | ||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||
golang.org/x/sync v0.8.0 // indirect | ||
golang.org/x/sys v0.25.0 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= | ||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= | ||
github.com/charmbracelet/bubbletea/v2 v2.0.0-20240918154035-3313a4cfa033 h1:eEQ1R5wHOfU53qifp4eNlRPfDwRcVGXmJVsBTpbr92Y= | ||
github.com/charmbracelet/bubbletea/v2 v2.0.0-20240918154035-3313a4cfa033/go.mod h1:j0gn4ft5CE7NDYNZjAA3hBM8t2OPjI8urxuAD0oR4w8= | ||
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= | ||
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= | ||
github.com/charmbracelet/x/ansi v0.3.2 h1:wsEwgAN+C9U06l9dCVMX0/L3x7ptvY1qmjMwyfE6USY= | ||
github.com/charmbracelet/x/ansi v0.3.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= | ||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= | ||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= | ||
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= | ||
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= | ||
github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= | ||
github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s= | ||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= | ||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= | ||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= | ||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | ||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= | ||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= | ||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= | ||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= | ||
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= | ||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= | ||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= | ||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= | ||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package teatest_test | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
"time" | ||
|
||
tea "github.com/charmbracelet/bubbletea/v2" | ||
"github.com/charmbracelet/x/exp/teatest/v2" | ||
) | ||
|
||
func TestAppSendToOtherProgram(t *testing.T) { | ||
m1 := &connectedModel{ | ||
name: "m1", | ||
} | ||
m2 := &connectedModel{ | ||
name: "m2", | ||
} | ||
|
||
tm1 := teatest.NewTestModel(t, m1, teatest.WithInitialTermSize(70, 30)) | ||
t.Cleanup(func() { | ||
if err := tm1.Quit(); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
tm2 := teatest.NewTestModel(t, m2, teatest.WithInitialTermSize(70, 30)) | ||
t.Cleanup(func() { | ||
if err := tm2.Quit(); err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
m1.programs = append(m1.programs, tm2) | ||
m2.programs = append(m2.programs, tm1) | ||
|
||
tm1.Type("pp") | ||
tm2.Type("pppp") | ||
|
||
tm1.Type("q") | ||
tm2.Type("q") | ||
|
||
out1 := readBts(t, tm1.FinalOutput(t, teatest.WithFinalTimeout(time.Second))) | ||
out2 := readBts(t, tm2.FinalOutput(t, teatest.WithFinalTimeout(time.Second))) | ||
|
||
if string(out1) != string(out2) { | ||
t.Errorf("output of both models should be the same, got:\n%v\nand:\n%v\n", string(out1), string(out2)) | ||
} | ||
|
||
teatest.RequireEqualOutput(t, out1) | ||
} | ||
|
||
type connectedModel struct { | ||
name string | ||
programs []interface{ Send(tea.Msg) } | ||
msgs []string | ||
} | ||
|
||
type ping string | ||
|
||
func (m *connectedModel) Init() (tea.Model, tea.Cmd) { | ||
return m, nil | ||
} | ||
|
||
func (m *connectedModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||
switch msg := msg.(type) { | ||
case tea.KeyMsg: | ||
switch msg.String() { | ||
case "p": | ||
send := ping("from " + m.name) | ||
m.msgs = append(m.msgs, string(send)) | ||
for _, p := range m.programs { | ||
p.Send(send) | ||
} | ||
fmt.Printf("sent ping %q to others\n", send) | ||
case "q": | ||
return m, tea.Quit | ||
} | ||
case ping: | ||
fmt.Printf("rcvd ping %q on %s\n", msg, m.name) | ||
m.msgs = append(m.msgs, string(msg)) | ||
} | ||
return m, nil | ||
} | ||
|
||
func (m *connectedModel) View() string { | ||
return "All pings:\n" + strings.Join(m.msgs, "\n") | ||
} |
Oops, something went wrong.