feat(tui): add TeaReporter forwarding engine events to bubbletea
This commit is contained in:
60
internal/tui/reporter.go
Normal file
60
internal/tui/reporter.go
Normal 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)
|
||||||
51
internal/tui/reporter_test.go
Normal file
51
internal/tui/reporter_test.go
Normal 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")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user