feat(tui): add TeaReporter forwarding engine events to bubbletea

This commit is contained in:
MDietermann
2026-04-13 21:49:14 +02:00
parent 258b53b6a0
commit c0983284b3
2 changed files with 111 additions and 0 deletions

60
internal/tui/reporter.go Normal file
View File

@@ -0,0 +1,60 @@
package tui
import (
"sync"
tea "github.com/charmbracelet/bubbletea"
"github.com/MDietermann/ArduinoArchSetup/internal/module"
)
// ProgressMsg is emitted when a module reports progress during Apply.
type ProgressMsg struct {
ModuleID string
Current int
Total int
Message string
}
// LogMsg is emitted when a module emits a log line during Apply.
type LogMsg struct {
ModuleID string
Line string
}
// sender abstracts tea.Program.Send so tests can inject a recorder.
type sender interface {
Send(msg tea.Msg)
}
// TeaReporter implements module.Reporter and forwards every event to a
// sender (typically a *tea.Program). Concurrency-safe.
type TeaReporter struct {
mu sync.Mutex
s sender
}
// NewTeaReporter wraps a sender. Passing nil is allowed — events are dropped.
func NewTeaReporter(s sender) *TeaReporter {
return &TeaReporter{s: s}
}
func (r *TeaReporter) Progress(id string, current, total int, msg string) {
r.mu.Lock()
defer r.mu.Unlock()
if r.s == nil {
return
}
r.s.Send(ProgressMsg{ModuleID: id, Current: current, Total: total, Message: msg})
}
func (r *TeaReporter) Log(id, line string) {
r.mu.Lock()
defer r.mu.Unlock()
if r.s == nil {
return
}
r.s.Send(LogMsg{ModuleID: id, Line: line})
}
var _ module.Reporter = (*TeaReporter)(nil)

View File

@@ -0,0 +1,51 @@
package tui
import (
"sync"
"testing"
tea "github.com/charmbracelet/bubbletea"
)
type recorder struct {
mu sync.Mutex
msgs []tea.Msg
}
func (r *recorder) Send(m tea.Msg) {
r.mu.Lock()
defer r.mu.Unlock()
r.msgs = append(r.msgs, m)
}
func (r *recorder) get() []tea.Msg {
r.mu.Lock()
defer r.mu.Unlock()
out := make([]tea.Msg, len(r.msgs))
copy(out, r.msgs)
return out
}
func TestTeaReporterForwardsProgressAndLog(t *testing.T) {
rec := &recorder{}
r := NewTeaReporter(rec)
r.Progress("mod", 1, 4, "step one")
r.Log("mod", "hello world")
msgs := rec.get()
if len(msgs) != 2 {
t.Fatalf("got %d msgs", len(msgs))
}
if p, ok := msgs[0].(ProgressMsg); !ok || p.ModuleID != "mod" || p.Current != 1 || p.Total != 4 {
t.Errorf("msg[0] = %+v", msgs[0])
}
if l, ok := msgs[1].(LogMsg); !ok || l.Line != "hello world" {
t.Errorf("msg[1] = %+v", msgs[1])
}
}
func TestTeaReporterNilSenderIsSafe(t *testing.T) {
r := NewTeaReporter(nil)
r.Progress("x", 1, 1, "y") // must not panic
r.Log("x", "y")
}