From ea5667e040eaf644b7f7dea9b05da0752bccdaa2 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Mon, 13 Apr 2026 22:20:08 +0200 Subject: [PATCH] =?UTF-8?q?test(tui):=20integration=20smoke=20=E2=80=94=20?= =?UTF-8?q?dashboard=20runs=20hello=20module=20end-to-end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/tui/dashboard/integration_test.go | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 internal/tui/dashboard/integration_test.go diff --git a/internal/tui/dashboard/integration_test.go b/internal/tui/dashboard/integration_test.go new file mode 100644 index 0000000..034abe8 --- /dev/null +++ b/internal/tui/dashboard/integration_test.go @@ -0,0 +1,79 @@ +package dashboard + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + tea "github.com/charmbracelet/bubbletea" + + "github.com/MDietermann/ArduinoArchSetup/internal/engine" + "github.com/MDietermann/ArduinoArchSetup/internal/module" + "github.com/MDietermann/ArduinoArchSetup/internal/modules/hello" + "github.com/MDietermann/ArduinoArchSetup/internal/tui" +) + +// TestDashboardRunsHelloModule exercises the whole reporter → model loop +// without a real TTY: we run engine.Run synchronously while routing events +// through a TeaReporter bound to a recorder, then replay every recorded +// message into the Model. The marker file written by the hello module +// confirms the pipeline is end-to-end. +func TestDashboardRunsHelloModule(t *testing.T) { + markerPath := filepath.Join(t.TempDir(), "hello.ok") + reg := module.NewRegistry() + if err := reg.Register(hello.Module{Path: markerPath}); err != nil { + t.Fatal(err) + } + + state := tui.WizardState{ + Modules: []tui.ModuleChoice{{ID: "hello", Name: "Hello", Selected: true}}, + } + + m := NewModel(state.SelectedIDs(), map[string]string{"hello": "Hello"}) + rec := &recorderSender{} + reporter := tui.NewTeaReporter(rec) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + sum, err := engine.Run(ctx, reg, engine.RunOptions{ + Selected: state.SelectedIDs(), + Reporter: reporter, + }) + if err != nil { + t.Fatalf("engine.Run: %v", err) + } + + for _, msg := range rec.drained() { + m.Update(msg) + } + m.Update(DoneMsg{Summary: sum}) + + if _, err := os.Stat(markerPath); err != nil { + t.Fatalf("marker not written: %v", err) + } + if m.Summary() == nil { + t.Fatal("dashboard summary is nil") + } + ok, fail, skip := m.Summary().Counts() + if ok != 1 || fail != 0 || skip != 0 { + t.Fatalf("counts = (%d,%d,%d)", ok, fail, skip) + } + if out := m.View(); out == "" { + t.Fatal("empty view") + } +} + +// recorderSender buffers tea.Msg values for replay. Matches the unexported +// `sender` interface in the tui package via its Send method signature. +type recorderSender struct{ msgs []tea.Msg } + +func (r *recorderSender) Send(msg tea.Msg) { r.msgs = append(r.msgs, msg) } + +func (r *recorderSender) drained() []tea.Msg { + out := make([]tea.Msg, len(r.msgs)) + copy(out, r.msgs) + return out +}