Files
stack-sync/tui_test.go
T
LyAhn b80713b334 feat(sync): replace hub with an internal branch engine
Fetch and prune the preferred remote, then reconcile every local branch
independently. Fast-forward branches that are behind, preserve divergent work,
and delete a branch only when its configured upstream is gone and its commits
are already contained by the remote default branch.

Keep dirty repositories eligible while protecting the active branch and refs
checked out in linked worktrees. Preserve the previous all-clean policy behind
--strict, expose structured per-branch results, add real bare-remote integration
tests, and retain the upstream MIT notice for the adapted hub sync behavior.
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)
}
}