package main import ( "strings" "testing" tea "github.com/charmbracelet/bubbletea" ) func testTUIRepos() []repo { return []repo{ {Path: "/workspace/clean", RelativePath: "clean", Branch: "main", Eligible: true}, {Path: "/workspace/dirty", RelativePath: "dirty", Branch: "develop", Dirty: []string{" M file.txt"}, Eligible: true}, {Path: "/workspace/blocked", RelativePath: "blocked", BlockReason: "detached HEAD"}, } } func TestTUIOnlySelectsEligibleRepositories(t *testing.T) { m := newTUIModel("/workspace", testTUIRepos(), options{}) if got := len(m.selectedRepos()); got != 2 { t.Fatalf("selected repositories = %d, want 2", got) } m.cursor = 2 updated, _ := m.Update(tea.KeyMsg{Type: tea.KeySpace}) m = updated.(tuiModel) if got := len(m.selectedRepos()); got != 2 { t.Fatalf("blocked repository became selectable; selected = %d", got) } } func TestTUIRequiresExplicitConfirmation(t *testing.T) { m := newTUIModel("/workspace", testTUIRepos(), options{}) updated, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter}) m = updated.(tuiModel) if m.mode != modeConfirm || cmd != nil { t.Fatalf("sync should enter confirmation without starting command: mode=%v cmd=%v", m.mode, cmd) } updated, cmd = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'n'}}) m = updated.(tuiModel) if m.mode != modeReady || cmd != nil || !strings.Contains(m.status, "cancelled") { t.Fatalf("cancel should return to ready state: %+v", m) } } func TestTUIViewExplainsBlockedRepository(t *testing.T) { m := newTUIModel("/workspace", testTUIRepos(), options{}) m.width, m.height, m.cursor = 100, 30, 2 view := m.View() for _, want := range []string{"STACK SYNC", "detached HEAD", "1 blocked"} { if !strings.Contains(view, want) { t.Fatalf("view missing %q:\n%s", want, view) } } } func TestTUIViewExplainsDirtyBranchProtection(t *testing.T) { m := newTUIModel("/workspace", testTUIRepos(), options{}) m.width, m.height, m.cursor = 100, 30, 1 view := m.View() for _, want := range []string{"ready (dirty)", "CHECKED-OUT BRANCH PROTECTED", "file.txt"} { if !strings.Contains(view, want) { t.Fatalf("view missing %q:\n%s", want, view) } } } func TestTUIViewShowsLastSyncOutputForFocusedRepository(t *testing.T) { m := newTUIModel("/workspace", testTUIRepos(), options{}) m.width, m.height = 100, 30 m.lastResult = []syncResult{{Path: "clean", Success: true, Message: "Updated branch main", Duration: "120ms"}} view := m.View() for _, want := range []string{"LAST SYNC", "120ms", "Updated branch main"} { if !strings.Contains(view, want) { t.Fatalf("view missing %q:\n%s", want, view) } } } func TestTruncateMiddle(t *testing.T) { if got := truncateMiddle("abcdefghij", 7); got != "abc…hij" { t.Fatalf("truncateMiddle() = %q", got) } }