feat: pantalla de tests en el dashboard TUI

Nueva seccion "Tests" en el menu principal del dashboard que permite
ejecutar Go tests, E2E tests (headless y headed), y todos secuencialmente.

- ScreenTests con menu de seleccion de tipo de test
- TestKind enum para identificar el tipo de test ejecutado
- Nuevos intents: IntentRunGoTests, IntentRunE2ETests, IntentRunE2EHeadTests, IntentRunAllTests
- LastTestKind en Model para re-ejecucion con "r"
- runGoTests, runE2ETests, runAllTests en adapter
- "Run Tests" en Server menu reemplazado por navegacion a ScreenTests
- Test output muestra tipo de test en titulo y vuelve a ScreenTests con "0"

Issue: 0023

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 15:43:51 +00:00
parent 89acbe02c8
commit 509d456275
5 changed files with 246 additions and 15 deletions
+101 -7
View File
@@ -23,6 +23,10 @@ const (
IntentKillLauncher IntentKind = "kill_launcher"
IntentRebuildRestart IntentKind = "rebuild_restart"
IntentRunTests IntentKind = "run_tests"
IntentRunGoTests IntentKind = "run_go_tests"
IntentRunE2ETests IntentKind = "run_e2e_tests"
IntentRunE2EHeadTests IntentKind = "run_e2e_head_tests"
IntentRunAllTests IntentKind = "run_all_tests"
)
// Intent is pure data describing a side effect to execute.
@@ -107,10 +111,12 @@ func Update(model Model, msg interface{}) (Model, []Intent) {
model.LogLines = m.Output
model.LogScroll = 0
model.Cursor = 0
model.LastTestKind = m.Kind
label := testKindLabel(m.Kind)
if m.Passed {
model.StatusMsg = "Tests PASSED"
model.StatusMsg = label + " PASSED"
} else {
model.StatusMsg = "Tests FAILED"
model.StatusMsg = label + " FAILED"
}
return model, nil
@@ -143,6 +149,8 @@ func updateKey(model Model, key KeyMsg) (Model, []Intent) {
return updateLogs(model, key)
case ScreenServer:
return updateServerScreen(model, key)
case ScreenTests:
return updateTestsScreen(model, key)
case ScreenTestOutput:
return updateTestOutput(model, key)
}
@@ -167,6 +175,11 @@ func updateMainScreen(model Model, key KeyMsg) (Model, []Intent) {
model.Cursor = 0
model.StatusMsg = ""
return model, []Intent{{Kind: IntentLoadAgents}}
case "Tests":
model.Screen = ScreenTests
model.Cursor = 0
model.StatusMsg = ""
return model, nil
case "Quit":
return model, []Intent{{Kind: IntentQuit}}
}
@@ -304,9 +317,11 @@ 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 "Tests":
model.Screen = ScreenTests
model.Cursor = 0
model.StatusMsg = ""
return model, nil
case "Logs":
model.Screen = ScreenLogs
model.LogLines = nil
@@ -321,7 +336,7 @@ func executeServerAction(model Model, action string) (Model, []Intent) {
func updateTestOutput(model Model, key KeyMsg) (Model, []Intent) {
switch key.Str {
case "0":
model.Screen = ScreenServer
model.Screen = ScreenTests
model.Cursor = 0
model.LogLines = nil
model.LogScroll = 0
@@ -332,12 +347,91 @@ func updateTestOutput(model Model, key KeyMsg) (Model, []Intent) {
maxScroll := max(0, len(model.LogLines)-visibleLogLines(model))
model.LogScroll = min(model.LogScroll+1, maxScroll)
case "r":
intent := testKindIntent(model.LastTestKind)
if intent == "" {
intent = IntentRunGoTests
}
model.StatusMsg = "Running tests..."
return model, []Intent{{Kind: IntentRunTests}}
model.LogLines = nil
model.LogScroll = 0
return model, []Intent{{Kind: intent}}
}
return model, nil
}
func updateTestsScreen(model Model, key KeyMsg) (Model, []Intent) {
opts := TestMenuOptions()
switch key.Str {
case "0":
model.Screen = ScreenMain
model.Cursor = 0
model.StatusMsg = ""
case "up", "k":
model.Cursor = clamp(model.Cursor-1, 0, len(opts)-1)
case "down", "j":
model.Cursor = clamp(model.Cursor+1, 0, len(opts)-1)
case "enter":
if model.Cursor < len(opts) {
return executeTestAction(model, opts[model.Cursor].Label)
}
}
return model, nil
}
func executeTestAction(model Model, action string) (Model, []Intent) {
model.StatusMsg = "Running tests..."
model.LogLines = nil
model.LogScroll = 0
switch action {
case "Go Tests":
model.LastTestKind = TestKindGo
return model, []Intent{{Kind: IntentRunGoTests}}
case "E2E Tests":
model.LastTestKind = TestKindE2E
return model, []Intent{{Kind: IntentRunE2ETests}}
case "E2E Tests (headed)":
model.LastTestKind = TestKindE2EHead
return model, []Intent{{Kind: IntentRunE2EHeadTests}}
case "All Tests":
model.LastTestKind = TestKindAll
return model, []Intent{{Kind: IntentRunAllTests}}
}
return model, nil
}
// testKindIntent maps a TestKind to its corresponding IntentKind.
func testKindIntent(k TestKind) IntentKind {
switch k {
case TestKindGo:
return IntentRunGoTests
case TestKindE2E:
return IntentRunE2ETests
case TestKindE2EHead:
return IntentRunE2EHeadTests
case TestKindAll:
return IntentRunAllTests
default:
return ""
}
}
// testKindLabel returns a human-readable label for a TestKind.
func testKindLabel(k TestKind) string {
switch k {
case TestKindGo:
return "Go Tests"
case TestKindE2E:
return "E2E Tests"
case TestKindE2EHead:
return "E2E Tests (headed)"
case TestKindAll:
return "All Tests"
default:
return "Tests"
}
}
// ── pure helpers ─────────────────────────────────────────────────────────
func visibleLogLines(m Model) int {