From bc6939f6c40a3ea9ec0c76f5fc217e7a7b061892 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 13 Nov 2024 10:45:34 -0300 Subject: [PATCH 01/10] feat: use vt on teatest/v2 Signed-off-by: Carlos Alexandro Becker --- exp/teatest/v2/app_test.go | 16 +++---- exp/teatest/v2/go.mod | 25 +++++----- exp/teatest/v2/go.sum | 37 ++++++--------- exp/teatest/v2/send_test.go | 4 +- exp/teatest/v2/teatest.go | 84 +++++++++++----------------------- exp/teatest/v2/teatest_test.go | 47 +++++++++---------- go.work | 2 +- go.work.sum | 3 +- 8 files changed, 91 insertions(+), 127 deletions(-) diff --git a/exp/teatest/v2/app_test.go b/exp/teatest/v2/app_test.go index 93fbe2c4..1ac0500c 100644 --- a/exp/teatest/v2/app_test.go +++ b/exp/teatest/v2/app_test.go @@ -1,10 +1,10 @@ package teatest_test import ( - "bytes" "fmt" "io" "regexp" + "strings" "testing" "time" @@ -35,9 +35,9 @@ func TestApp(t *testing.T) { 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)) + out := tm.FinalOutput(t, teatest.WithFinalTimeout(time.Second)) + if !regexp.MustCompile(`This program will exit in \d+ seconds`).MatchString(out) { + t.Fatalf("output does not match the given regular expression: %q", out) } teatest.RequireEqualOutput(t, out) @@ -56,12 +56,12 @@ func TestAppInteractive(t *testing.T) { 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)) + if s := tm.Output(); !strings.Contains(s, "This program will exit in 9 seconds") { + t.Fatalf("output does not match: expected %q", string(s)) } - teatest.WaitFor(t, tm.Output(), func(out []byte) bool { - return bytes.Contains(out, []byte("This program will exit in 7 seconds")) + teatest.WaitForOutput(t, tm, func(s string) bool { + return strings.Contains(s, "This program will exit in 7 seconds") }, teatest.WithDuration(5*time.Second), teatest.WithCheckInterval(time.Millisecond*10)) tm.Send(tea.KeyPressMsg{ diff --git a/exp/teatest/v2/go.mod b/exp/teatest/v2/go.mod index d978c6d2..a3f8376b 100644 --- a/exp/teatest/v2/go.mod +++ b/exp/teatest/v2/go.mod @@ -1,27 +1,30 @@ module github.com/charmbracelet/x/exp/teatest/v2 -go 1.19 +go 1.22.8 require ( - github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.1 + github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23d6 github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a + github.com/charmbracelet/x/vt v0.0.0-20241017211702-84fa5b7bb18e ) 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/colorprofile v0.1.7 // indirect + github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2 // indirect + github.com/charmbracelet/x/cellbuf v0.0.6-0.20241110171603-a30b032a5ae2 // indirect github.com/charmbracelet/x/term v0.2.0 // indirect + github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 // 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 + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect ) + +replace github.com/charmbracelet/x/vt => ../../../vt + +replace github.com/charmbracelet/x/cellbuf => ../../../cellbuf diff --git a/exp/teatest/v2/go.sum b/exp/teatest/v2/go.sum index 41c5d247..e2958a42 100644 --- a/exp/teatest/v2/go.sum +++ b/exp/teatest/v2/go.sum @@ -1,39 +1,32 @@ -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-alpha.1 h1:OZtpLCsuuPplC+1oyUo+/eAN7e9MC2UyZWKlKrVlUnw= -github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.1/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/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23d6 h1:kRj022q2jfr69oRZNnhev/Em44M7/TjV7jvWpyQ9PMo= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23d6/go.mod h1:Az92EQe8w9w+TgIPiTjbZtVohnlxwHiVDNJMPUTSg2o= +github.com/charmbracelet/colorprofile v0.1.7 h1:q7PtMQrRBBnLNE2EbtbNUtouu979EivKcDGGaimhyO8= +github.com/charmbracelet/colorprofile v0.1.7/go.mod h1:d3UYToTrNmsD2p9/lbiya16H1WahndM0miDlJWXWf4U= +github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2 h1:iW1rX9FDCWBSIusGCmCLQdd2f9gN9c88KJyvCt1EsRA= +github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2/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/wcwidth v0.0.0-20241011142426-46044092ad91 h1:D5OO0lVavz7A+Swdhp62F9gbkibxmz9B2hZ/jVdMPf0= +github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91/go.mod h1:Ey8PFmYwH+/td9bpiEx07Fdx9ZVkxfIjWXxBluxF4Nw= 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= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= diff --git a/exp/teatest/v2/send_test.go b/exp/teatest/v2/send_test.go index 8879f5e8..31f42ba1 100644 --- a/exp/teatest/v2/send_test.go +++ b/exp/teatest/v2/send_test.go @@ -39,8 +39,8 @@ func TestAppSendToOtherProgram(t *testing.T) { 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))) + out1 := tm1.FinalOutput(t, teatest.WithFinalTimeout(time.Second)) + out2 := 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)) diff --git a/exp/teatest/v2/teatest.go b/exp/teatest/v2/teatest.go index f277f067..90023f9b 100644 --- a/exp/teatest/v2/teatest.go +++ b/exp/teatest/v2/teatest.go @@ -2,9 +2,7 @@ package teatest import ( - "bytes" "fmt" - "io" "os" "os/signal" "sync" @@ -14,6 +12,7 @@ import ( tea "github.com/charmbracelet/bubbletea/v2" "github.com/charmbracelet/x/exp/golden" + "github.com/charmbracelet/x/vt" ) // Program defines the subset of the tea.Program API we need for testing. @@ -63,22 +62,22 @@ func WithDuration(d time.Duration) WaitForOption { } } -// WaitFor keeps reading from r until the condition matches. +// WaitForOutput keeps reading from r until the condition matches. // Default duration is 1s, default check interval is 50ms. // These defaults can be changed with WithDuration and WithCheckInterval. -func WaitFor( +func WaitForOutput( tb testing.TB, - r io.Reader, - condition func(bts []byte) bool, + tm *TestModel, + condition func(string) bool, options ...WaitForOption, ) { tb.Helper() - if err := doWaitFor(r, condition, options...); err != nil { + if err := doWaitFor(tm, condition, options...); err != nil { tb.Fatal(err) } } -func doWaitFor(r io.Reader, condition func(bts []byte) bool, options ...WaitForOption) error { +func doWaitFor(tm *TestModel, condition func(string) bool, options ...WaitForOption) error { wf := WaitingForContext{ Duration: time.Second, CheckInterval: 50 * time.Millisecond, //nolint: gomnd @@ -88,26 +87,21 @@ func doWaitFor(r io.Reader, condition func(bts []byte) bool, options ...WaitForO opt(&wf) } - var b bytes.Buffer start := time.Now() for time.Since(start) <= wf.Duration { - if _, err := io.ReadAll(io.TeeReader(r, &b)); err != nil { - return fmt.Errorf("WaitFor: %w", err) - } - if condition(b.Bytes()) { + if condition(tm.Output()) { return nil } time.Sleep(wf.CheckInterval) } - return fmt.Errorf("WaitFor: condition not met after %s. Last output:\n%s", wf.Duration, b.String()) + return fmt.Errorf("WaitFor: condition not met after %s. Last output:\n%q", wf.Duration, tm.Output()) } // TestModel is a model that is being tested. type TestModel struct { program *tea.Program - in *bytes.Buffer - out io.ReadWriter + term *vt.Terminal modelCh chan tea.Model model tea.Model @@ -118,17 +112,24 @@ type TestModel struct { // NewTestModel makes a new TestModel which can be used for tests. func NewTestModel(tb testing.TB, m tea.Model, options ...TestOption) *TestModel { + var opts TestModelOptions + for _, opt := range options { + opt(&opts) + } + if opts.size.Width == 0 { + opts.size.Width, opts.size.Height = 70, 40 + } + tm := &TestModel{ - in: bytes.NewBuffer(nil), - out: safe(bytes.NewBuffer(nil)), + term: vt.NewTerminal(opts.size.Width, opts.size.Height), modelCh: make(chan tea.Model, 1), doneCh: make(chan bool, 1), } tm.program = tea.NewProgram( m, - tea.WithInput(tm.in), - tea.WithOutput(tm.out), + tea.WithInput(tm.term), + tea.WithOutput(tm.term), tea.WithoutSignals(), ) @@ -149,14 +150,7 @@ func NewTestModel(tb testing.TB, m tea.Model, options ...TestOption) *TestModel tm.program.Kill() }() - var opts TestModelOptions - for _, opt := range options { - opt(&opts) - } - - if opts.size.Width != 0 { - tm.program.Send(opts.size) - } + tm.program.Send(opts.size) return tm } @@ -229,14 +223,14 @@ func (tm *TestModel) FinalModel(tb testing.TB, opts ...FinalOpt) tea.Model { // FinalOutput returns the program's final output io.Reader. // This method only returns once the program has finished running or when it // times out. -func (tm *TestModel) FinalOutput(tb testing.TB, opts ...FinalOpt) io.Reader { +func (tm *TestModel) FinalOutput(tb testing.TB, opts ...FinalOpt) string { tm.waitDone(tb, opts) return tm.Output() } // Output returns the program's current output io.Reader. -func (tm *TestModel) Output() io.Reader { - return tm.out +func (tm *TestModel) Output() string { + return tm.term.String() } // Send sends messages to the underlying program. @@ -271,31 +265,7 @@ func (tm *TestModel) GetProgram() *tea.Program { // Important: this uses the system `diff` tool. // // You can update the golden files by running your tests with the -update flag. -func RequireEqualOutput(tb testing.TB, out []byte) { +func RequireEqualOutput(tb testing.TB, out string) { tb.Helper() - golden.RequireEqualEscape(tb, out, true) -} - -func safe(rw io.ReadWriter) io.ReadWriter { - return &safeReadWriter{rw: rw} -} - -// safeReadWriter implements io.ReadWriter, but locks reads and writes. -type safeReadWriter struct { - rw io.ReadWriter - m sync.RWMutex -} - -// Read implements io.ReadWriter. -func (s *safeReadWriter) Read(p []byte) (n int, err error) { - s.m.RLock() - defer s.m.RUnlock() - return s.rw.Read(p) //nolint: wrapcheck -} - -// Write implements io.ReadWriter. -func (s *safeReadWriter) Write(p []byte) (int, error) { - s.m.Lock() - defer s.m.Unlock() - return s.rw.Write(p) //nolint: wrapcheck + golden.RequireEqualEscape(tb, []byte(out), true) } diff --git a/exp/teatest/v2/teatest_test.go b/exp/teatest/v2/teatest_test.go index 1ccb7fb9..6635ba22 100644 --- a/exp/teatest/v2/teatest_test.go +++ b/exp/teatest/v2/teatest_test.go @@ -1,38 +1,35 @@ package teatest import ( - "fmt" - "strings" "testing" - "testing/iotest" "time" tea "github.com/charmbracelet/bubbletea/v2" ) -func TestWaitForErrorReader(t *testing.T) { - err := doWaitFor(iotest.ErrReader(fmt.Errorf("fake")), func(bts []byte) bool { - return true - }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) - if err == nil { - t.Fatal("expected an error, got nil") - } - if err.Error() != "WaitFor: fake" { - t.Fatalf("unexpected error: %s", err.Error()) - } -} +// func TestWaitForErrorReader(t *testing.T) { +// err := doWaitFor(iotest.ErrReader(fmt.Errorf("fake")), func(bts []byte) bool { +// return true +// }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) +// if err == nil { +// t.Fatal("expected an error, got nil") +// } +// if err.Error() != "WaitFor: fake" { +// t.Fatalf("unexpected error: %s", err.Error()) +// } +// } -func TestWaitForTimeout(t *testing.T) { - err := doWaitFor(strings.NewReader("nope"), func(bts []byte) bool { - return false - }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) - if err == nil { - t.Fatal("expected an error, got nil") - } - if err.Error() != "WaitFor: condition not met after 1ms. Last output:\nnope" { - t.Fatalf("unexpected error: %s", err.Error()) - } -} +// func TestWaitForTimeout(t *testing.T) { +// err := doWaitFor(strings.NewReader("nope"), func(bts []byte) bool { +// return false +// }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) +// if err == nil { +// t.Fatal("expected an error, got nil") +// } +// if err.Error() != "WaitFor: condition not met after 1ms. Last output:\nnope" { +// t.Fatalf("unexpected error: %s", err.Error()) +// } +// } type m string diff --git a/go.work b/go.work index 35619e9d..f004d319 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.21 +go 1.22.8 use ( ./ansi diff --git a/go.work.sum b/go.work.sum index a02fd1fa..61c6a7a1 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,3 +1,5 @@ +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113133413-59e87bcc9af8 h1:inszS2eSOR1E7sBGDQL58o9cvENODJkQB3VeJd2JKtk= +github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113133413-59e87bcc9af8/go.mod h1:wqWnIdpxvHw+HS1Q0Oy2/QMBUKTGYZeP9Y2xyRSchtQ= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= @@ -10,7 +12,6 @@ golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= From c0895f91c8763e8202fb6b6aa44f6a405672a17d Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 13 Nov 2024 11:05:18 -0300 Subject: [PATCH 02/10] feat: updates Signed-off-by: Carlos Alexandro Becker --- exp/teatest/v2/app_test.go | 2 +- exp/teatest/v2/send_test.go | 4 +- exp/teatest/v2/teatest.go | 45 +++++++++++++------ exp/teatest/v2/testdata/TestApp.golden | 4 +- .../testdata/TestAppSendToOtherProgram.golden | 5 +-- 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/exp/teatest/v2/app_test.go b/exp/teatest/v2/app_test.go index 1ac0500c..d071ff55 100644 --- a/exp/teatest/v2/app_test.go +++ b/exp/teatest/v2/app_test.go @@ -35,7 +35,7 @@ func TestApp(t *testing.T) { t.Fatal(err) } - out := tm.FinalOutput(t, teatest.WithFinalTimeout(time.Second)) + out := teatest.TrimEmptyLines(tm.FinalOutput(t, teatest.WithFinalTimeout(time.Second))) if !regexp.MustCompile(`This program will exit in \d+ seconds`).MatchString(out) { t.Fatalf("output does not match the given regular expression: %q", out) } diff --git a/exp/teatest/v2/send_test.go b/exp/teatest/v2/send_test.go index 31f42ba1..4667f465 100644 --- a/exp/teatest/v2/send_test.go +++ b/exp/teatest/v2/send_test.go @@ -39,8 +39,8 @@ func TestAppSendToOtherProgram(t *testing.T) { tm1.Type("q") tm2.Type("q") - out1 := tm1.FinalOutput(t, teatest.WithFinalTimeout(time.Second)) - out2 := tm2.FinalOutput(t, teatest.WithFinalTimeout(time.Second)) + out1 := teatest.TrimEmptyLines(tm1.FinalOutput(t, teatest.WithFinalTimeout(time.Second))) + out2 := teatest.TrimEmptyLines(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)) diff --git a/exp/teatest/v2/teatest.go b/exp/teatest/v2/teatest.go index 90023f9b..f390fabe 100644 --- a/exp/teatest/v2/teatest.go +++ b/exp/teatest/v2/teatest.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "os/signal" + "strings" "sync" "syscall" "testing" @@ -154,19 +155,23 @@ func NewTestModel(tb testing.TB, m tea.Model, options ...TestOption) *TestModel return tm } -func (tm *TestModel) waitDone(tb testing.TB, opts []FinalOpt) { +func mergeOpts(opts []FinalOpt) FinalOpts { + r := FinalOpts{} + for _, opt := range opts { + opt(&r) + } + return r +} + +func (tm *TestModel) waitDone(tb testing.TB, opts FinalOpts) { tm.done.Do(func() { - fopts := FinalOpts{} - for _, opt := range opts { - opt(&fopts) - } - if fopts.timeout > 0 { + if opts.timeout > 0 { select { - case <-time.After(fopts.timeout): - if fopts.onTimeout == nil { - tb.Fatalf("timeout after %s", fopts.timeout) + case <-time.After(opts.timeout): + if opts.onTimeout == nil { + tb.Fatalf("timeout after %s", opts.timeout) } - fopts.onTimeout(tb) + opts.onTimeout(tb) case <-tm.doneCh: } } else { @@ -179,6 +184,7 @@ func (tm *TestModel) waitDone(tb testing.TB, opts []FinalOpt) { type FinalOpts struct { timeout time.Duration onTimeout func(tb testing.TB) + trim bool } // FinalOpt changes FinalOpts. @@ -203,14 +209,14 @@ func WithFinalTimeout(d time.Duration) FinalOpt { // This method only returns once the program has finished running or when it // times out. func (tm *TestModel) WaitFinished(tb testing.TB, opts ...FinalOpt) { - tm.waitDone(tb, opts) + tm.waitDone(tb, mergeOpts(opts)) } // FinalModel returns the resulting model, resulting from program.Run(). // This method only returns once the program has finished running or when it // times out. func (tm *TestModel) FinalModel(tb testing.TB, opts ...FinalOpt) tea.Model { - tm.waitDone(tb, opts) + tm.waitDone(tb, mergeOpts(opts)) select { case m := <-tm.modelCh: tm.model = m @@ -224,7 +230,8 @@ func (tm *TestModel) FinalModel(tb testing.TB, opts ...FinalOpt) tea.Model { // This method only returns once the program has finished running or when it // times out. func (tm *TestModel) FinalOutput(tb testing.TB, opts ...FinalOpt) string { - tm.waitDone(tb, opts) + opt := mergeOpts(opts) + tm.waitDone(tb, opt) return tm.Output() } @@ -269,3 +276,15 @@ func RequireEqualOutput(tb testing.TB, out string) { tb.Helper() golden.RequireEqualEscape(tb, []byte(out), true) } + +// TrimEmptyLines removes trailing empty lines from the given output. +func TrimEmptyLines(out string) string { + // trim empty trailing lines from the output + lines := strings.Split(out, "\n") + for i := len(lines) - 1; i >= 0; i-- { + if strings.TrimSpace(lines[i]) != "" { + return strings.Join(lines[:i], "\n") + } + } + return out +} diff --git a/exp/teatest/v2/testdata/TestApp.golden b/exp/teatest/v2/testdata/TestApp.golden index 12b60376..a728107f 100644 --- a/exp/teatest/v2/testdata/TestApp.golden +++ b/exp/teatest/v2/testdata/TestApp.golden @@ -1,3 +1 @@ -[?25l[?2004h[?2027h[?2027$p Hi. This program will exit in 10 seconds. To quit sooner press any key -Hi. This program will exit in 9 seconds. To quit sooner press any key. - [?2004l[?25h \ No newline at end of file +Hi. This program will exit in 10 seconds. To quit sooner press any key \ No newline at end of file diff --git a/exp/teatest/v2/testdata/TestAppSendToOtherProgram.golden b/exp/teatest/v2/testdata/TestAppSendToOtherProgram.golden index 0e196c51..6ba5f64c 100644 --- a/exp/teatest/v2/testdata/TestAppSendToOtherProgram.golden +++ b/exp/teatest/v2/testdata/TestAppSendToOtherProgram.golden @@ -1,7 +1,6 @@ -[?25l[?2004h[?2027h[?2027$p All pings: +All pings: from m1 from m1 from m2 from m2 -from m2 -from m2 [?2004l[?25h \ No newline at end of file +from m2 \ No newline at end of file From 5fde6e5380e42347eb9523e5f90fdfdf4d40b389 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 13 Nov 2024 11:07:26 -0300 Subject: [PATCH 03/10] fix: cleanup --- exp/teatest/v2/teatest_test.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/exp/teatest/v2/teatest_test.go b/exp/teatest/v2/teatest_test.go index 6635ba22..f359188a 100644 --- a/exp/teatest/v2/teatest_test.go +++ b/exp/teatest/v2/teatest_test.go @@ -7,30 +7,6 @@ import ( tea "github.com/charmbracelet/bubbletea/v2" ) -// func TestWaitForErrorReader(t *testing.T) { -// err := doWaitFor(iotest.ErrReader(fmt.Errorf("fake")), func(bts []byte) bool { -// return true -// }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) -// if err == nil { -// t.Fatal("expected an error, got nil") -// } -// if err.Error() != "WaitFor: fake" { -// t.Fatalf("unexpected error: %s", err.Error()) -// } -// } - -// func TestWaitForTimeout(t *testing.T) { -// err := doWaitFor(strings.NewReader("nope"), func(bts []byte) bool { -// return false -// }, WithDuration(time.Millisecond), WithCheckInterval(10*time.Microsecond)) -// if err == nil { -// t.Fatal("expected an error, got nil") -// } -// if err.Error() != "WaitFor: condition not met after 1ms. Last output:\nnope" { -// t.Fatalf("unexpected error: %s", err.Error()) -// } -// } - type m string func (m m) Init() (tea.Model, tea.Cmd) { return m, nil } From 7f9ecdc83d6279261dc133e14531dc1ceafe269d Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 13 Nov 2024 11:10:46 -0300 Subject: [PATCH 04/10] fix: deps Signed-off-by: Carlos Alexandro Becker --- exp/teatest/v2/go.mod | 8 ++------ exp/teatest/v2/go.sum | 5 ++++- go.work | 4 +++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/exp/teatest/v2/go.mod b/exp/teatest/v2/go.mod index a3f8376b..8f8558a5 100644 --- a/exp/teatest/v2/go.mod +++ b/exp/teatest/v2/go.mod @@ -1,11 +1,11 @@ module github.com/charmbracelet/x/exp/teatest/v2 -go 1.22.8 +go 1.19 require ( github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23d6 github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a - github.com/charmbracelet/x/vt v0.0.0-20241017211702-84fa5b7bb18e + github.com/charmbracelet/x/vt v0.0.0-20241110171603-a30b032a5ae2 ) require ( @@ -24,7 +24,3 @@ require ( golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect ) - -replace github.com/charmbracelet/x/vt => ../../../vt - -replace github.com/charmbracelet/x/cellbuf => ../../../cellbuf diff --git a/exp/teatest/v2/go.sum b/exp/teatest/v2/go.sum index e2958a42..c8b88ff5 100644 --- a/exp/teatest/v2/go.sum +++ b/exp/teatest/v2/go.sum @@ -6,10 +6,14 @@ github.com/charmbracelet/colorprofile v0.1.7 h1:q7PtMQrRBBnLNE2EbtbNUtouu979EivK github.com/charmbracelet/colorprofile v0.1.7/go.mod h1:d3UYToTrNmsD2p9/lbiya16H1WahndM0miDlJWXWf4U= github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2 h1:iW1rX9FDCWBSIusGCmCLQdd2f9gN9c88KJyvCt1EsRA= github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/cellbuf v0.0.6-0.20241110171603-a30b032a5ae2 h1:Tya33mYdX3mi/BAWaw3M2CB8gq6OoZOEiDCTYW25lqg= +github.com/charmbracelet/x/cellbuf v0.0.6-0.20241110171603-a30b032a5ae2/go.mod h1:+a10wKGeO6TKYBbvxCfQEEgn5knWzoZtLf00oiXgvBg= 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/vt v0.0.0-20241110171603-a30b032a5ae2 h1:wc9NwiloOJukjTlz0GdOncgg/JlwWL4W3+AwrBS+7wQ= +github.com/charmbracelet/x/vt v0.0.0-20241110171603-a30b032a5ae2/go.mod h1:+CYC0tzYqYMtIryA0lcGQgCUaAiRLaS7Rxi9R+PFii8= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 h1:D5OO0lVavz7A+Swdhp62F9gbkibxmz9B2hZ/jVdMPf0= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91/go.mod h1:Ey8PFmYwH+/td9bpiEx07Fdx9ZVkxfIjWXxBluxF4Nw= github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= @@ -23,7 +27,6 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc 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/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= diff --git a/go.work b/go.work index f004d319..85ecaaf4 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,6 @@ -go 1.22.8 +go 1.23.1 + +toolchain go1.23.3 use ( ./ansi From 3fad95d08b9c9e1503c08f0e36446669a8306273 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 13 Nov 2024 11:16:26 -0300 Subject: [PATCH 05/10] docs: update --- exp/teatest/v2/teatest.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/exp/teatest/v2/teatest.go b/exp/teatest/v2/teatest.go index f390fabe..d4d1eadc 100644 --- a/exp/teatest/v2/teatest.go +++ b/exp/teatest/v2/teatest.go @@ -216,7 +216,7 @@ func (tm *TestModel) WaitFinished(tb testing.TB, opts ...FinalOpt) { // This method only returns once the program has finished running or when it // times out. func (tm *TestModel) FinalModel(tb testing.TB, opts ...FinalOpt) tea.Model { - tm.waitDone(tb, mergeOpts(opts)) + tm.WaitFinished(tb, opts...) select { case m := <-tm.modelCh: tm.model = m @@ -226,16 +226,16 @@ func (tm *TestModel) FinalModel(tb testing.TB, opts ...FinalOpt) tea.Model { } } -// FinalOutput returns the program's final output io.Reader. +// FinalOutput returns the program's final output. // This method only returns once the program has finished running or when it // times out. +// It's the equivalent of calling both `tm.WaitFinished` and `tm.Output()`. func (tm *TestModel) FinalOutput(tb testing.TB, opts ...FinalOpt) string { - opt := mergeOpts(opts) - tm.waitDone(tb, opt) + tm.WaitFinished(tb, opts...) return tm.Output() } -// Output returns the program's current output io.Reader. +// Output returns the program's current output. func (tm *TestModel) Output() string { return tm.term.String() } From 11445948497cea54f382f985c40fd634a4eef427 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Wed, 13 Nov 2024 11:16:36 -0300 Subject: [PATCH 06/10] fix: go work --- go.work | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/go.work b/go.work index 85ecaaf4..35619e9d 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,4 @@ -go 1.23.1 - -toolchain go1.23.3 +go 1.21 use ( ./ansi From 54292aa632e2bfe2c329327a4633cb1dc18dee95 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 14 Nov 2024 08:40:09 -0300 Subject: [PATCH 07/10] chore: update vt Signed-off-by: Carlos Alexandro Becker --- exp/teatest/v2/go.mod | 2 +- exp/teatest/v2/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/exp/teatest/v2/go.mod b/exp/teatest/v2/go.mod index 8f8558a5..e9fcad75 100644 --- a/exp/teatest/v2/go.mod +++ b/exp/teatest/v2/go.mod @@ -5,7 +5,7 @@ go 1.19 require ( github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23d6 github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a - github.com/charmbracelet/x/vt v0.0.0-20241110171603-a30b032a5ae2 + github.com/charmbracelet/x/vt v0.0.0-20241113201228-12f9eedf8aa6 ) require ( diff --git a/exp/teatest/v2/go.sum b/exp/teatest/v2/go.sum index c8b88ff5..4a4b4071 100644 --- a/exp/teatest/v2/go.sum +++ b/exp/teatest/v2/go.sum @@ -12,8 +12,8 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99k 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/vt v0.0.0-20241110171603-a30b032a5ae2 h1:wc9NwiloOJukjTlz0GdOncgg/JlwWL4W3+AwrBS+7wQ= -github.com/charmbracelet/x/vt v0.0.0-20241110171603-a30b032a5ae2/go.mod h1:+CYC0tzYqYMtIryA0lcGQgCUaAiRLaS7Rxi9R+PFii8= +github.com/charmbracelet/x/vt v0.0.0-20241113201228-12f9eedf8aa6 h1:tIcT/TeXoDcmyMKE0x0hh2wE0SCpev6Q2mEACNwXRKI= +github.com/charmbracelet/x/vt v0.0.0-20241113201228-12f9eedf8aa6/go.mod h1:+CYC0tzYqYMtIryA0lcGQgCUaAiRLaS7Rxi9R+PFii8= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 h1:D5OO0lVavz7A+Swdhp62F9gbkibxmz9B2hZ/jVdMPf0= github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91/go.mod h1:Ey8PFmYwH+/td9bpiEx07Fdx9ZVkxfIjWXxBluxF4Nw= github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= From f24243f08415e884d99b101e2b91b7aa9a5034db Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 14 Nov 2024 11:45:21 -0300 Subject: [PATCH 08/10] chore: ansi and cellbuf /vt Signed-off-by: Carlos Alexandro Becker --- exp/teatest/v2/go.mod | 8 ++++---- exp/teatest/v2/go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/exp/teatest/v2/go.mod b/exp/teatest/v2/go.mod index e9fcad75..517a536f 100644 --- a/exp/teatest/v2/go.mod +++ b/exp/teatest/v2/go.mod @@ -11,10 +11,10 @@ require ( require ( github.com/aymanbagabas/go-udiff v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.1.7 // indirect - github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2 // indirect - github.com/charmbracelet/x/cellbuf v0.0.6-0.20241110171603-a30b032a5ae2 // indirect - github.com/charmbracelet/x/term v0.2.0 // indirect - github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 // indirect + github.com/charmbracelet/x/ansi v0.4.6-0.20241113201228-12f9eedf8aa6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.6-0.20241113201228-12f9eedf8aa6 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect + github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 // indirect github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/muesli/cancelreader v0.2.2 // indirect diff --git a/exp/teatest/v2/go.sum b/exp/teatest/v2/go.sum index 4a4b4071..da9ca12c 100644 --- a/exp/teatest/v2/go.sum +++ b/exp/teatest/v2/go.sum @@ -4,18 +4,18 @@ github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23 github.com/charmbracelet/bubbletea/v2 v2.0.0-alpha.2.0.20241113134142-c71ad13e23d6/go.mod h1:Az92EQe8w9w+TgIPiTjbZtVohnlxwHiVDNJMPUTSg2o= github.com/charmbracelet/colorprofile v0.1.7 h1:q7PtMQrRBBnLNE2EbtbNUtouu979EivKcDGGaimhyO8= github.com/charmbracelet/colorprofile v0.1.7/go.mod h1:d3UYToTrNmsD2p9/lbiya16H1WahndM0miDlJWXWf4U= -github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2 h1:iW1rX9FDCWBSIusGCmCLQdd2f9gN9c88KJyvCt1EsRA= -github.com/charmbracelet/x/ansi v0.4.6-0.20241110171603-a30b032a5ae2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/cellbuf v0.0.6-0.20241110171603-a30b032a5ae2 h1:Tya33mYdX3mi/BAWaw3M2CB8gq6OoZOEiDCTYW25lqg= -github.com/charmbracelet/x/cellbuf v0.0.6-0.20241110171603-a30b032a5ae2/go.mod h1:+a10wKGeO6TKYBbvxCfQEEgn5knWzoZtLf00oiXgvBg= +github.com/charmbracelet/x/ansi v0.4.6-0.20241113201228-12f9eedf8aa6 h1:4OG3+4CUSzOK0vdg6kkXQksLoh1d3zOnBfHKTOnnnBg= +github.com/charmbracelet/x/ansi v0.4.6-0.20241113201228-12f9eedf8aa6/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/cellbuf v0.0.6-0.20241113201228-12f9eedf8aa6 h1:7YvO1RgEvUkX0gx5++X/lG+DoU8MPYjjPYJKb6b7tFk= +github.com/charmbracelet/x/cellbuf v0.0.6-0.20241113201228-12f9eedf8aa6/go.mod h1:+a10wKGeO6TKYBbvxCfQEEgn5knWzoZtLf00oiXgvBg= 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/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/charmbracelet/x/vt v0.0.0-20241113201228-12f9eedf8aa6 h1:tIcT/TeXoDcmyMKE0x0hh2wE0SCpev6Q2mEACNwXRKI= github.com/charmbracelet/x/vt v0.0.0-20241113201228-12f9eedf8aa6/go.mod h1:+CYC0tzYqYMtIryA0lcGQgCUaAiRLaS7Rxi9R+PFii8= -github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91 h1:D5OO0lVavz7A+Swdhp62F9gbkibxmz9B2hZ/jVdMPf0= -github.com/charmbracelet/x/wcwidth v0.0.0-20241011142426-46044092ad91/go.mod h1:Ey8PFmYwH+/td9bpiEx07Fdx9ZVkxfIjWXxBluxF4Nw= +github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32 h1:14czE6R5CgOlvONsJYa2B1uTyLvXzGXpBqw2AyZeTh4= +github.com/charmbracelet/x/wcwidth v0.0.0-20241113152101-0af7d04e9f32/go.mod h1:hyua5CY63kyl7IfyIxv1SjVEqoKze/XmDkEglItuVjA= 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= From eed8e1e924a78c0f11a2190b593340e7cd996c37 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 14 Nov 2024 13:40:21 -0300 Subject: [PATCH 09/10] feat: pass program options Signed-off-by: Carlos Alexandro Becker --- exp/teatest/v2/app_test.go | 36 ++++++++++++++++++++++++++++++++++++ exp/teatest/v2/teatest.go | 20 +++++++++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/exp/teatest/v2/app_test.go b/exp/teatest/v2/app_test.go index d071ff55..c3e65b61 100644 --- a/exp/teatest/v2/app_test.go +++ b/exp/teatest/v2/app_test.go @@ -46,6 +46,42 @@ func TestApp(t *testing.T) { } } +func TestAppAltScreen(t *testing.T) { + t.Skip("needs changes in /vt") + m := model(10) + tm := teatest.NewTestModel( + t, m, + teatest.WithInitialTermSize(70, 30), + teatest.WithProgramOptions(tea.WithAltScreen()), + ) + 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 := teatest.TrimEmptyLines(tm.FinalOutput(t, teatest.WithFinalTimeout(time.Second))) + if !regexp.MustCompile(`This program will exit in \d+ seconds`).MatchString(out) { + t.Fatalf("output does not match the given regular expression: %q", 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( diff --git a/exp/teatest/v2/teatest.go b/exp/teatest/v2/teatest.go index d4d1eadc..9d843c39 100644 --- a/exp/teatest/v2/teatest.go +++ b/exp/teatest/v2/teatest.go @@ -23,12 +23,21 @@ type Program interface { // TestModelOptions defines all options available to the test function. type TestModelOptions struct { - size tea.WindowSizeMsg + size tea.WindowSizeMsg + topts []tea.ProgramOption } // TestOption is a functional option. type TestOption func(opts *TestModelOptions) +// WithProgramOptions allows to give the program additional +// [tea.ProgramOption]s. +func WithProgramOptions(topts ...tea.ProgramOption) TestOption { + return func(opts *TestModelOptions) { + opts.topts = append(opts.topts, topts...) + } +} + // WithInitialTermSize ... func WithInitialTermSize(x, y int) TestOption { return func(opts *TestModelOptions) { @@ -127,11 +136,14 @@ func NewTestModel(tb testing.TB, m tea.Model, options ...TestOption) *TestModel doneCh: make(chan bool, 1), } - tm.program = tea.NewProgram( - m, + topts := []tea.ProgramOption{ tea.WithInput(tm.term), tea.WithOutput(tm.term), tea.WithoutSignals(), + } + tm.program = tea.NewProgram( + m, + append(topts, opts.topts...)..., ) interruptions := make(chan os.Signal, 1) @@ -232,6 +244,8 @@ func (tm *TestModel) FinalModel(tb testing.TB, opts ...FinalOpt) tea.Model { // It's the equivalent of calling both `tm.WaitFinished` and `tm.Output()`. func (tm *TestModel) FinalOutput(tb testing.TB, opts ...FinalOpt) string { tm.WaitFinished(tb, opts...) + // FIXME: need to check here if term was using alt screen and get that + // output instead. return tm.Output() } From 08fcc97770c5ab430fdfa8a34323a25b70301d1e Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 14 Nov 2024 22:20:27 -0300 Subject: [PATCH 10/10] Update exp/teatest/v2/teatest.go Co-authored-by: Ayman Bagabas --- exp/teatest/v2/teatest.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exp/teatest/v2/teatest.go b/exp/teatest/v2/teatest.go index 9d843c39..0a223411 100644 --- a/exp/teatest/v2/teatest.go +++ b/exp/teatest/v2/teatest.go @@ -127,7 +127,7 @@ func NewTestModel(tb testing.TB, m tea.Model, options ...TestOption) *TestModel opt(&opts) } if opts.size.Width == 0 { - opts.size.Width, opts.size.Height = 70, 40 + opts.size.Width, opts.size.Height = 80, 24 } tm := &TestModel{