From 1abc1ef50900a88a21c3b51a8a5b13df0ff7f023 Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Sun, 8 Mar 2026 15:43:56 +0000 Subject: [PATCH] test: tests unitarios para pantalla de tests del dashboard Tests para TestMenuOptions, updateTestsScreen (navegacion, seleccion, generacion de intents), viewTests (render, cursor, last run), testKindLabel, testKindIntent, y navegacion desde main/server menus. Issue: 0023 Co-Authored-By: Claude Opus 4.6 --- pkg/tui/tui_test.go | 347 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 pkg/tui/tui_test.go diff --git a/pkg/tui/tui_test.go b/pkg/tui/tui_test.go new file mode 100644 index 0000000..b9083fb --- /dev/null +++ b/pkg/tui/tui_test.go @@ -0,0 +1,347 @@ +package tui + +import ( + "strings" + "testing" +) + +// ── TestMenuOptions ───────────────────────────────────────────────────── + +func TestTestMenuOptions_Count(t *testing.T) { + opts := TestMenuOptions() + if len(opts) != 4 { + t.Fatalf("expected 4 test menu options, got %d", len(opts)) + } +} + +func TestTestMenuOptions_Labels(t *testing.T) { + opts := TestMenuOptions() + expected := []string{"Go Tests", "E2E Tests", "E2E Tests (headed)", "All Tests"} + for i, want := range expected { + if opts[i].Label != want { + t.Errorf("option[%d]: expected label %q, got %q", i, want, opts[i].Label) + } + } +} + +func TestMainMenuOptions_IncludesTests(t *testing.T) { + opts := MainMenuOptions() + found := false + for _, opt := range opts { + if opt.Label == "Tests" { + found = true + break + } + } + if !found { + t.Error("MainMenuOptions should include 'Tests'") + } +} + +func TestMainMenuOptions_TestsBeforeQuit(t *testing.T) { + opts := MainMenuOptions() + testsIdx, quitIdx := -1, -1 + for i, opt := range opts { + if opt.Label == "Tests" { + testsIdx = i + } + if opt.Label == "Quit" { + quitIdx = i + } + } + if testsIdx < 0 || quitIdx < 0 { + t.Fatal("expected both Tests and Quit in menu") + } + if testsIdx >= quitIdx { + t.Errorf("Tests (index %d) should come before Quit (index %d)", testsIdx, quitIdx) + } +} + +func TestServerMenuOptions_NoRunTests(t *testing.T) { + for _, running := range []bool{true, false} { + opts := ServerMenuOptions(running) + for _, opt := range opts { + if opt.Label == "Run Tests" { + t.Errorf("ServerMenuOptions(running=%v) should not have 'Run Tests', found it", running) + } + } + } +} + +func TestServerMenuOptions_HasTests(t *testing.T) { + for _, running := range []bool{true, false} { + opts := ServerMenuOptions(running) + found := false + for _, opt := range opts { + if opt.Label == "Tests" { + found = true + } + } + if !found { + t.Errorf("ServerMenuOptions(running=%v) should have 'Tests'", running) + } + } +} + +// ── updateTestsScreen ─────────────────────────────────────────────────── + +func TestUpdateTestsScreen_Navigation(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 0, WindowHeight: 40} + + m, _ = Update(m, KeyMsg{Str: "down"}) + if m.Cursor != 1 { + t.Errorf("expected cursor 1 after down, got %d", m.Cursor) + } + + m, _ = Update(m, KeyMsg{Str: "up"}) + if m.Cursor != 0 { + t.Errorf("expected cursor 0 after up, got %d", m.Cursor) + } + + // Can't go below 0 + m, _ = Update(m, KeyMsg{Str: "up"}) + if m.Cursor != 0 { + t.Errorf("expected cursor 0 clamped, got %d", m.Cursor) + } +} + +func TestUpdateTestsScreen_Back(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 2} + m, _ = Update(m, KeyMsg{Str: "0"}) + if m.Screen != ScreenMain { + t.Errorf("expected ScreenMain, got %d", m.Screen) + } + if m.Cursor != 0 { + t.Errorf("expected cursor reset to 0, got %d", m.Cursor) + } +} + +func TestUpdateTestsScreen_SelectGoTests(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 0} + m, intents := Update(m, KeyMsg{Str: "enter"}) + if len(intents) != 1 || intents[0].Kind != IntentRunGoTests { + t.Errorf("expected IntentRunGoTests, got %v", intents) + } + if m.LastTestKind != TestKindGo { + t.Errorf("expected LastTestKind=TestKindGo, got %d", m.LastTestKind) + } +} + +func TestUpdateTestsScreen_SelectE2ETests(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 1} + _, intents := Update(m, KeyMsg{Str: "enter"}) + if len(intents) != 1 || intents[0].Kind != IntentRunE2ETests { + t.Errorf("expected IntentRunE2ETests, got %v", intents) + } +} + +func TestUpdateTestsScreen_SelectE2EHeaded(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 2} + _, intents := Update(m, KeyMsg{Str: "enter"}) + if len(intents) != 1 || intents[0].Kind != IntentRunE2EHeadTests { + t.Errorf("expected IntentRunE2EHeadTests, got %v", intents) + } +} + +func TestUpdateTestsScreen_SelectAllTests(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 3} + _, intents := Update(m, KeyMsg{Str: "enter"}) + if len(intents) != 1 || intents[0].Kind != IntentRunAllTests { + t.Errorf("expected IntentRunAllTests, got %v", intents) + } +} + +// ── MsgTestsDone ──────────────────────────────────────────────────────── + +func TestMsgTestsDone_SetsKindAndStatus(t *testing.T) { + m := Model{Screen: ScreenTests} + m, _ = Update(m, MsgTestsDone{Kind: TestKindE2E, Passed: true, Output: []string{"ok"}}) + if m.Screen != ScreenTestOutput { + t.Errorf("expected ScreenTestOutput, got %d", m.Screen) + } + if m.LastTestKind != TestKindE2E { + t.Errorf("expected LastTestKind=TestKindE2E, got %d", m.LastTestKind) + } + if !strings.Contains(m.StatusMsg, "PASSED") { + t.Errorf("expected PASSED in status, got %q", m.StatusMsg) + } + if !strings.Contains(m.StatusMsg, "E2E") { + t.Errorf("expected E2E in status, got %q", m.StatusMsg) + } +} + +func TestMsgTestsDone_Failed(t *testing.T) { + m := Model{} + m, _ = Update(m, MsgTestsDone{Kind: TestKindGo, Passed: false, Output: []string{"FAIL"}}) + if !strings.Contains(m.StatusMsg, "FAILED") { + t.Errorf("expected FAILED in status, got %q", m.StatusMsg) + } +} + +// ── updateTestOutput ──────────────────────────────────────────────────── + +func TestUpdateTestOutput_BackGoesToTests(t *testing.T) { + m := Model{Screen: ScreenTestOutput, LastTestKind: TestKindGo} + m, _ = Update(m, KeyMsg{Str: "0"}) + if m.Screen != ScreenTests { + t.Errorf("expected ScreenTests, got %d", m.Screen) + } +} + +func TestUpdateTestOutput_RerunUsesLastKind(t *testing.T) { + m := Model{Screen: ScreenTestOutput, LastTestKind: TestKindE2E, WindowHeight: 40} + _, intents := Update(m, KeyMsg{Str: "r"}) + if len(intents) != 1 || intents[0].Kind != IntentRunE2ETests { + t.Errorf("expected IntentRunE2ETests, got %v", intents) + } +} + +func TestUpdateTestOutput_RerunDefaultsToGo(t *testing.T) { + m := Model{Screen: ScreenTestOutput, LastTestKind: TestKindNone, WindowHeight: 40} + _, intents := Update(m, KeyMsg{Str: "r"}) + if len(intents) != 1 || intents[0].Kind != IntentRunGoTests { + t.Errorf("expected IntentRunGoTests as default, got %v", intents) + } +} + +// ── viewTests ─────────────────────────────────────────────────────────── + +func TestViewTests_ShowsOptions(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 0, WindowWidth: 80, WindowHeight: 40} + out := View(m) + if !strings.Contains(out, "Go Tests") { + t.Error("expected 'Go Tests' in view") + } + if !strings.Contains(out, "E2E Tests") { + t.Error("expected 'E2E Tests' in view") + } + if !strings.Contains(out, "All Tests") { + t.Error("expected 'All Tests' in view") + } +} + +func TestViewTests_ShowsCursor(t *testing.T) { + m := Model{Screen: ScreenTests, Cursor: 1, WindowWidth: 80, WindowHeight: 40} + out := View(m) + // Cursor on E2E Tests (index 1) + lines := strings.Split(out, "\n") + foundCursor := false + for _, line := range lines { + if strings.Contains(line, "> ") && strings.Contains(line, "E2E Tests") && !strings.Contains(line, "(headed)") { + foundCursor = true + } + } + if !foundCursor { + t.Error("expected cursor on E2E Tests") + } +} + +func TestViewTests_ShowsLastRun(t *testing.T) { + m := Model{ + Screen: ScreenTests, + Cursor: 0, + WindowWidth: 80, + WindowHeight: 40, + LastTestKind: TestKindGo, + StatusMsg: "Go Tests PASSED", + } + out := View(m) + if !strings.Contains(out, "Last run:") { + t.Error("expected 'Last run:' in view") + } + if !strings.Contains(out, "PASSED") { + t.Error("expected PASSED in last run") + } +} + +func TestViewTestOutput_ShowsKindInTitle(t *testing.T) { + m := Model{ + Screen: ScreenTestOutput, + LastTestKind: TestKindE2E, + StatusMsg: "E2E Tests PASSED", + LogLines: []string{"ok"}, + WindowWidth: 80, + WindowHeight: 40, + } + out := View(m) + if !strings.Contains(out, "Test Results — E2E Tests") { + t.Errorf("expected 'Test Results — E2E Tests' in view, got:\n%s", out) + } +} + +// ── Main menu Tests navigation ────────────────────────────────────────── + +func TestMainMenu_TestsNavigation(t *testing.T) { + m := Model{Screen: ScreenMain, Cursor: 2} // Tests is at index 2 + m, intents := Update(m, KeyMsg{Str: "enter"}) + if m.Screen != ScreenTests { + t.Errorf("expected ScreenTests, got %d", m.Screen) + } + if len(intents) != 0 { + t.Errorf("expected no intents for Tests nav, got %v", intents) + } +} + +// ── Server menu Tests navigation ──────────────────────────────────────── + +func TestServerMenu_TestsNavigation(t *testing.T) { + // "Tests" is the last option in both running/stopped menus + opts := ServerMenuOptions(false) + testsIdx := -1 + for i, o := range opts { + if o.Label == "Tests" { + testsIdx = i + } + } + if testsIdx < 0 { + t.Fatal("Tests not found in server menu") + } + + m := Model{Screen: ScreenServer, Cursor: testsIdx} + m, _ = Update(m, KeyMsg{Str: "enter"}) + if m.Screen != ScreenTests { + t.Errorf("expected ScreenTests, got %d", m.Screen) + } +} + +// ── testKindLabel ─────────────────────────────────────────────────────── + +func TestTestKindLabel(t *testing.T) { + cases := []struct { + kind TestKind + want string + }{ + {TestKindGo, "Go Tests"}, + {TestKindE2E, "E2E Tests"}, + {TestKindE2EHead, "E2E Tests (headed)"}, + {TestKindAll, "All Tests"}, + {TestKindNone, "Tests"}, + } + for _, tc := range cases { + got := testKindLabel(tc.kind) + if got != tc.want { + t.Errorf("testKindLabel(%d) = %q, want %q", tc.kind, got, tc.want) + } + } +} + +// ── testKindIntent ────────────────────────────────────────────────────── + +func TestTestKindIntent(t *testing.T) { + cases := []struct { + kind TestKind + want IntentKind + }{ + {TestKindGo, IntentRunGoTests}, + {TestKindE2E, IntentRunE2ETests}, + {TestKindE2EHead, IntentRunE2EHeadTests}, + {TestKindAll, IntentRunAllTests}, + {TestKindNone, ""}, + } + for _, tc := range cases { + got := testKindIntent(tc.kind) + if got != tc.want { + t.Errorf("testKindIntent(%d) = %q, want %q", tc.kind, got, tc.want) + } + } +}