9bcaab4dc0
Introduce a Bubble Tea control deck with eligible repositories preselected, keyboard navigation, responsive paging, and detailed explanations for blocked worktrees. Require confirmation before synchronization, allow refreshing the workspace, and retain each repository's latest hub output without changing the existing CLI and JSON workflows.
74 lines
2.3 KiB
Go
74 lines
2.3 KiB
Go
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", BlockReason: "dirty worktree (1 changes)", Dirty: []string{" M file.txt"}},
|
|
}
|
|
}
|
|
|
|
func TestTUIOnlySelectsEligibleRepositories(t *testing.T) {
|
|
m := newTUIModel("/workspace", testTUIRepos(), options{})
|
|
if got := len(m.selectedRepos()); got != 1 {
|
|
t.Fatalf("selected repositories = %d, want 1", got)
|
|
}
|
|
|
|
m.cursor = 1
|
|
updated, _ := m.Update(tea.KeyMsg{Type: tea.KeySpace})
|
|
m = updated.(tuiModel)
|
|
if got := len(m.selectedRepos()); got != 1 {
|
|
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, 1
|
|
view := m.View()
|
|
for _, want := range []string{"STACK SYNC", "dirty worktree", "file.txt", "1 blocked"} {
|
|
if !strings.Contains(view, want) {
|
|
t.Fatalf("view missing %q:\n%s", want, view)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTUIViewShowsLastHubOutputForFocusedRepository(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)
|
|
}
|
|
}
|