feat: ejecutar tests desde el dashboard TUI
Se añade opción "Run Tests" al menú del servidor en el dashboard TUI. Ejecuta `go test -tags goolm -count=1 ./...` y muestra los resultados en una pantalla dedicada (ScreenTestOutput) con scroll y opción de re-ejecutar. Cambios: - pkg/tui: nuevo MsgTestsDone, ScreenTestOutput, IntentRunTests, updateTestOutput - pkg/tui/view.go: viewTestOutput con scroll y controles (↑↓ r 0) - shell/tui/adapter.go: runTests() ejecuta go test con el env del manager - shell/process/manager.go: buildEnv → BuildEnv (exportado) para que el adapter pueda construir el env completo con las variables de .env Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -38,5 +38,11 @@ type MsgRebuildDone struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// MsgTestsDone reports the result of running tests.
|
||||
type MsgTestsDone struct {
|
||||
Passed bool
|
||||
Output []string // lines of test output
|
||||
}
|
||||
|
||||
// MsgTick triggers a periodic refresh.
|
||||
type MsgTick struct{}
|
||||
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
ScreenAgentActions // actions for a selected agent
|
||||
ScreenLogs // tail log output
|
||||
ScreenServer // server-wide process management
|
||||
ScreenTestOutput // test run output
|
||||
)
|
||||
|
||||
// Model is the complete TUI state — pure data.
|
||||
@@ -74,11 +75,13 @@ func ServerMenuOptions(running bool) []MenuOption {
|
||||
{Label: "Kill", Desc: "SIGKILL forzado"},
|
||||
{Label: "Rebuild & Restart", Desc: "Build + reiniciar"},
|
||||
{Label: "Logs", Desc: "Ver log del launcher"},
|
||||
{Label: "Run Tests", Desc: "Ejecutar todos los tests"},
|
||||
}
|
||||
}
|
||||
return []MenuOption{
|
||||
{Label: "Start", Desc: "Iniciar el launcher unificado"},
|
||||
{Label: "Rebuild & Restart", Desc: "Build + iniciar"},
|
||||
{Label: "Run Tests", Desc: "Ejecutar todos los tests"},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
IntentRestartLauncher IntentKind = "restart_launcher"
|
||||
IntentKillLauncher IntentKind = "kill_launcher"
|
||||
IntentRebuildRestart IntentKind = "rebuild_restart"
|
||||
IntentRunTests IntentKind = "run_tests"
|
||||
)
|
||||
|
||||
// Intent is pure data describing a side effect to execute.
|
||||
@@ -98,6 +99,18 @@ func Update(model Model, msg interface{}) (Model, []Intent) {
|
||||
model.LogScroll = max(0, len(m.Lines)-visibleLogLines(model))
|
||||
return model, nil
|
||||
|
||||
case MsgTestsDone:
|
||||
model.Screen = ScreenTestOutput
|
||||
model.LogLines = m.Output
|
||||
model.LogScroll = 0
|
||||
model.Cursor = 0
|
||||
if m.Passed {
|
||||
model.StatusMsg = "Tests PASSED"
|
||||
} else {
|
||||
model.StatusMsg = "Tests FAILED"
|
||||
}
|
||||
return model, nil
|
||||
|
||||
case MsgTick:
|
||||
return model, []Intent{{Kind: IntentLoadAgents}}
|
||||
|
||||
@@ -127,6 +140,8 @@ func updateKey(model Model, key KeyMsg) (Model, []Intent) {
|
||||
return updateLogs(model, key)
|
||||
case ScreenServer:
|
||||
return updateServerScreen(model, key)
|
||||
case ScreenTestOutput:
|
||||
return updateTestOutput(model, key)
|
||||
}
|
||||
return model, nil
|
||||
}
|
||||
@@ -283,6 +298,9 @@ func executeServerAction(model Model, action string) (Model, []Intent) {
|
||||
case "Rebuild & Restart":
|
||||
model.StatusMsg = "Building & restarting..."
|
||||
return model, []Intent{{Kind: IntentRebuildRestart}}
|
||||
case "Run Tests":
|
||||
model.StatusMsg = "Running tests..."
|
||||
return model, []Intent{{Kind: IntentRunTests}}
|
||||
case "Logs":
|
||||
model.Screen = ScreenLogs
|
||||
model.LogLines = nil
|
||||
@@ -294,6 +312,26 @@ func executeServerAction(model Model, action string) (Model, []Intent) {
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func updateTestOutput(model Model, key KeyMsg) (Model, []Intent) {
|
||||
switch key.Str {
|
||||
case "0":
|
||||
model.Screen = ScreenServer
|
||||
model.Cursor = 0
|
||||
model.LogLines = nil
|
||||
model.LogScroll = 0
|
||||
model.StatusMsg = ""
|
||||
case "up", "k":
|
||||
model.LogScroll = max(0, model.LogScroll-1)
|
||||
case "down", "j":
|
||||
maxScroll := max(0, len(model.LogLines)-visibleLogLines(model))
|
||||
model.LogScroll = min(model.LogScroll+1, maxScroll)
|
||||
case "r":
|
||||
model.StatusMsg = "Running tests..."
|
||||
return model, []Intent{{Kind: IntentRunTests}}
|
||||
}
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// ── pure helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
func visibleLogLines(m Model) int {
|
||||
|
||||
@@ -18,6 +18,8 @@ func View(model Model) string {
|
||||
return viewLogs(model)
|
||||
case ScreenServer:
|
||||
return viewServer(model)
|
||||
case ScreenTestOutput:
|
||||
return viewTestOutput(model)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -236,6 +238,40 @@ func viewServer(m Model) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func viewTestOutput(m Model) string {
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString("\n Test Results\n")
|
||||
b.WriteString(" " + strings.Repeat("─", 60) + "\n")
|
||||
|
||||
if m.StatusMsg != "" {
|
||||
b.WriteString(" " + m.StatusMsg + "\n\n")
|
||||
}
|
||||
|
||||
if len(m.LogLines) == 0 {
|
||||
b.WriteString(" Running tests...\n")
|
||||
} else {
|
||||
visible := visibleLogLines(m)
|
||||
end := m.LogScroll + visible
|
||||
if end > len(m.LogLines) {
|
||||
end = len(m.LogLines)
|
||||
}
|
||||
start := m.LogScroll
|
||||
if start >= len(m.LogLines) {
|
||||
start = max(0, len(m.LogLines)-1)
|
||||
}
|
||||
for _, line := range m.LogLines[start:end] {
|
||||
if len(line) > m.WindowWidth-4 && m.WindowWidth > 10 {
|
||||
line = line[:m.WindowWidth-7] + "..."
|
||||
}
|
||||
b.WriteString(" " + line + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("\n ↑↓ scroll r re-ejecutar 0 volver\n")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func countStatuses(agents []AgentView) (running, stopped, disabled int) {
|
||||
for _, a := range agents {
|
||||
switch {
|
||||
|
||||
Reference in New Issue
Block a user