This repository has been archived on 2026-10-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
submarine/internal/tui/branches/branches_test.go
T
simonfrankenandClaude Opus 5.5 2b6b781826 Add switch branch (b): stage the tip and restore to it
b opens a centered popup with a search line and the local and remote
branches of the selection. Enter stages the branch tip as the pin of a
submodule, restores it (and everything below) to it, and puts HEAD on the
branch; a remote branch gets a local tracking branch unless one exists
elsewhere. Works on the superproject too. Losses are asked like restore.
The backdrop behind the popup is dimmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 14:18:49 +02:00

130 lines
3.5 KiB
Go

package branches
import (
"errors"
"strings"
"testing"
tea "github.com/charmbracelet/bubbletea"
"submarine/internal/submodule"
"submarine/internal/tui/theme"
)
func list() []submodule.Branch {
return []submodule.Branch{
{Ref: "refs/heads/main", Name: "main", Local: "main", Current: true, SHA: "aaaaaaaaaa"},
{Ref: "refs/heads/dev", Name: "dev", Local: "dev", SHA: "bbbbbbbbbb"},
{Ref: "refs/remotes/origin/main", Name: "origin/main", Local: "main", Remote: true, SHA: "aaaaaaaaaa"},
{Ref: "refs/remotes/origin/feat/search", Name: "origin/feat/search", Local: "feat/search", Remote: true, SHA: "cccccccccc"},
}
}
func typed(m *Model, s string) {
for _, r := range s {
m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{r}})
}
}
func name(m *Model) string {
if b := m.Selected(); b != nil {
return b.Name
}
return ""
}
func TestSelectionMovesAndClamps(t *testing.T) {
m := New(theme.Default(), "src/app")
m.SetBranches(list())
if name(m) != "main" {
t.Fatalf("first = %q", name(m))
}
m.Up()
if name(m) != "main" {
t.Errorf("up at the top = %q", name(m))
}
m.Down()
m.Down()
if name(m) != "origin/main" {
t.Errorf("down twice = %q", name(m))
}
m.PageDown()
if name(m) != "origin/feat/search" {
t.Errorf("page down = %q, want the last", name(m))
}
m.Down()
if name(m) != "origin/feat/search" {
t.Errorf("down at the end = %q", name(m))
}
}
func TestSearchFiltersAndAllWordsMustMatch(t *testing.T) {
m := New(theme.Default(), "src/app")
m.SetBranches(list())
typed(m, "ORIGIN")
if len(m.shown) != 2 {
t.Errorf("origin matches %d, want 2", len(m.shown))
}
typed(m, " sea")
if len(m.shown) != 1 || name(m) != "origin/feat/search" {
t.Errorf("shown = %d, selected %q", len(m.shown), name(m))
}
typed(m, "zzz")
if m.Selected() != nil {
t.Errorf("no match must select nothing, got %q", name(m))
}
if !strings.Contains(m.View(100, 30), "no branch matches") {
t.Error("view does not say that nothing matches")
}
}
func TestSelectionStaysOnTheSameBranchWhenItStillMatches(t *testing.T) {
m := New(theme.Default(), "src/app")
m.SetBranches(list())
m.Down()
m.Down() // origin/main
typed(m, "main")
if name(m) != "origin/main" {
t.Errorf("selected %q, want origin/main kept", name(m))
}
}
func TestViewStates(t *testing.T) {
m := New(theme.Default(), "src/app")
if v := m.View(100, 30); !strings.Contains(v, "reading branches") || !strings.Contains(v, "Switch src/app") {
t.Errorf("loading view:\n%s", v)
}
m.SetErr(errors.New("not initialized; restore it first"))
if v := m.View(100, 30); !strings.Contains(v, "not initialized") {
t.Errorf("error view:\n%s", v)
}
m.SetBranches(nil)
if v := m.View(100, 30); !strings.Contains(v, "no branches") {
t.Errorf("empty view:\n%s", v)
}
m.SetBranches(list())
v := m.View(100, 30)
for _, want := range []string{"main", "origin/", "feat/search", "aaaaaaa", "enter switch"} {
if !strings.Contains(v, want) {
t.Errorf("view lacks %q:\n%s", want, v)
}
}
}
func TestListScrollsInASmallTerminal(t *testing.T) {
var many []submodule.Branch
for i := 0; i < 30; i++ {
n := string(rune('a'+i%26)) + string(rune('a'+i/26))
many = append(many, submodule.Branch{Ref: "refs/heads/" + n, Name: n, Local: n, SHA: "abcdef0123"})
}
m := New(theme.Default(), "x")
m.SetBranches(many)
m.View(80, 14) // fewer rows than branches
for i := 0; i < 20; i++ {
m.Down()
}
if m.cursor < m.offset || m.cursor >= m.offset+m.rows {
t.Errorf("cursor %d outside the window [%d,%d)", m.cursor, m.offset, m.offset+m.rows)
}
}