Files
stack-sync/tui_test.go
T
LyAhn bf67f1a005 feat(sync): remove the hub dependency
- add sync.go to fetch and prune the selected upstream, github, origin, or sole remote
- fast-forward the current branch with merge --ff-only and inactive branches with update-ref
- protect a dirty current branch and branches checked out in linked worktrees
- warn without changing divergent branches
- delete gone-upstream branches only after confirming they are merged into the remote default branch
- allow dirty repositories by default and retain the all-clean policy behind --strict
- return per-branch results in CLI, JSON, and TUI output
- add bare-remote integration tests, the hub MIT notice, and version 0.3.0
2026-09-06 19:41:53 +01:00

86 lines
2.7 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", 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)
}
}