4749 lines · 163.3 KB · Go
RawDownload
package cli import (	"context"	"fmt"	"os"	"path/filepath"	"runtime"	"strings"	"testing"	"time" 	"github.com/charmbracelet/bubbles/table"	tea "github.com/charmbracelet/bubbletea"	"github.com/charmbracelet/lipgloss"	"github.com/openclaw/gitcrawl/internal/store"	"github.com/openclaw/gitcrawl/internal/vector") func TestTUILayoutStacksNarrowTerminals(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 80	model.height = 24 	layout := model.layout()	if !layout.stacked {		t.Fatal("expected narrow terminal to use stacked layout")	}	if layout.members.x != 0 || layout.members.y <= layout.clusters.y {		t.Fatalf("expected members pane below clusters, got clusters=%+v members=%+v", layout.clusters, layout.members)	} 	view := model.View()	for _, label := range []string{"[*] Clusters", "[ ] Members", "[ ] Detail"} {		if !strings.Contains(view, label) {			t.Fatalf("expected view to contain %q", label)		}	}} func TestTUIViewShowsRowsInDefaultTerminal(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 80	model.height = 24 	view := model.View()	if !strings.Contains(view, "alpha-bravo") {		t.Fatalf("expected default terminal view to render cluster rows:\n%s", view)	}	if model.clusterViewportHeight() < 1 {		t.Fatalf("cluster table viewport height = %d, want at least 1", model.clusterViewportHeight())	}} func TestTUIHeaderShowsDetailMode(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 160	model.height = 32 	header := model.renderHeader(160)	if !strings.Contains(header, "detail:full") {		t.Fatalf("header missing full detail mode:\n%s", header)	}	model.compactDetail = true	header = model.renderHeader(160)	if !strings.Contains(header, "detail:compact") {		t.Fatalf("header missing compact detail mode:\n%s", header)	}} func TestTUIHeaderDoesNotWrapAtTerminalWidth(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: strings.Repeat("openclaw/", 20),		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	header := model.renderHeader(80)	lines := strings.Split(header, "\n")	if len(lines) != 1 {		t.Fatalf("header rendered %d lines, want 1:\n%s", len(lines), header)	}	if width := lipgloss.Width(lines[0]); width > 80 {		t.Fatalf("header width = %d, want <= 80: %q", width, lines[0])	}} func TestTUIViewKeepsEssentialFooterHintsNarrow(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 80	model.height = 24 	view := model.View()	for _, want := range []string{"right-click menu", "? help", "q quit"} {		if !strings.Contains(view, want) {			t.Fatalf("narrow footer missing %q:\n%s", want, view)		}	}} func TestTUIFooterShowsLocalDatabaseLocation(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		DBSource:   "local",		DBLocation: "gitcrawl.db",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	}) 	footer := model.renderFooter(120)	if !strings.Contains(footer, "local gitcrawl.db") {		t.Fatalf("footer missing local database location:\n%s", footer)	}	bg, _ := footerPalette(model.payload.DBSource)	if bg != lipgloss.Color("#5bc0eb") {		t.Fatalf("local footer background = %q, want blue", bg)	}} func TestTUIFooterShowsRemoteDatabaseLocation(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		DBSource:   "remote",		DBLocation: "openclaw/gitcrawl-store:openclaw__openclaw.sync.db",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	}) 	footer := model.renderFooter(140)	if !strings.Contains(footer, "remote openclaw/gitcrawl-store:openclaw__openclaw.sync.db") {		t.Fatalf("footer missing remote database location:\n%s", footer)	}	bg, _ := footerPalette(model.payload.DBSource)	if bg == lipgloss.Color("#5bc0eb") {		t.Fatalf("remote footer background should not use local blue")	}} func TestTUIFooterShowsRemoteRefreshLoadingState(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository:      "openclaw/openclaw",		DBSource:        "remote",		DBLocation:      "openclaw/gitcrawl-store:openclaw__openclaw.sync.db",		DBRefreshSource: "/tmp/source.db",		DBRuntimePath:   "/tmp/runtime.db",		Sort:            "recent",		Clusters:        sampleTUIClusters(),	}) 	if !model.remoteRefreshing {		t.Fatal("remote model should start in refresh loading state")	}	footer := model.renderFooter(140)	if !strings.Contains(footer, "Refreshing remote data") {		t.Fatalf("footer missing remote refresh loading state:\n%s", footer)	}} func TestTUIFooterDoesNotWrapLongRemoteLocation(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		DBSource:   "remote",		DBLocation: "openclaw/gitcrawl-store:" + strings.Repeat("openclaw__openclaw.sync.db", 6),		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.status = "Cluster 14316" 	footer := model.renderFooter(80)	lines := strings.Split(footer, "\n")	if len(lines) != 2 {		t.Fatalf("footer rendered %d lines, want 2:\n%s", len(lines), footer)	}	if !strings.Contains(lines[1], "? help") || !strings.Contains(lines[1], "q quit") {		t.Fatalf("footer controls were displaced:\n%s", footer)	}	for index, line := range lines {		if width := lipgloss.Width(line); width > 80 {			t.Fatalf("footer line %d width = %d, want <= 80: %q", index, width, line)		}	}} func TestTUIViewFitsTerminalFrame(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "size",		Clusters:   sampleTUIClusters(),	})	model.width = 190	model.height = 32	model.focus = focusMembers	model.showClosed = true	model.memberRows = []memberRow{		{label: "ISSUES (37)"},		{selectable: true, member: store.ClusterMemberDetail{Thread: store.Thread{Number: 44718, State: "closed", Title: strings.Repeat("ReferenceError ", 20), UpdatedAtGitHub: "2026-04-27T00:00:00Z"}}},		{selectable: true, member: store.ClusterMemberDetail{Thread: store.Thread{Number: 45057, State: "closed", Title: strings.Repeat("ReferenceError ", 20), UpdatedAtGitHub: "2026-04-27T00:00:00Z"}}},	}	model.memberIndex = 1 	view := model.View()	if got := lipgloss.Height(view); got != model.height {		t.Fatalf("view height = %d, want %d\n%s", got, model.height, view)	}	for lineIndex, line := range strings.Split(view, "\n") {		if got := lipgloss.Width(line); got > model.width {			t.Fatalf("line %d width = %d, want <= %d: %q", lineIndex, got, model.width, line)		}	}} func TestTUIInAppHelpMentionsMenuMouse(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	}) 	help := strings.Join(model.helpLines(80), "\n")	for _, want := range []string{"left click menu row", "wheel in menu", "a: open action menu", "b in submenu", "#: jump to issue/PR number", "p: switch repository", "n: load neighbors", "open selected thread or representative", "open link picker", "repos, filter, jump, sort"} {		if !strings.Contains(help, want) {			t.Fatalf("help missing %q:\n%s", want, help)		}	}} func TestTUIActionShortcutOpensMenu(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	}) 	updated, _ := model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}})	model = updated.(clusterBrowserModel) 	if !model.menuOpen || model.menuTitle != "Actions" {		t.Fatalf("action shortcut state menu=%v title=%q", model.menuOpen, model.menuTitle)	}	if model.menuFloating {		t.Fatal("keyboard action menu should use the detail pane, not floating placement")	}} func TestTUIMouseSelectsClusterRows(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 140	model.height = 32	layout := model.layout() 	model.handleMouse(tea.MouseMsg{		X:      layout.clusters.x + 2,		Y:      layout.clusters.y + 3,		Action: tea.MouseActionPress,		Button: tea.MouseButtonLeft,	})	if model.selected != 0 {		t.Fatalf("first row click selected %d, want 0", model.selected)	} 	model.handleMouse(tea.MouseMsg{		X:      layout.clusters.x + 2,		Y:      layout.clusters.y + 4,		Action: tea.MouseActionPress,		Button: tea.MouseButtonLeft,	})	if model.selected != 1 {		t.Fatalf("second row click selected %d, want 1", model.selected)	}} func TestTUIMouseDoubleClickOpensClusterRepresentative(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 140	model.height = 32	layout := model.layout()	restoreOpenURL, opened := captureOpenURL(t) 	msg := tea.MouseMsg{		X:      layout.clusters.x + 2,		Y:      layout.clusters.y + 4,		Action: tea.MouseActionPress,		Button: tea.MouseButtonLeft,	}	model.handleMouse(msg)	if len(*opened) != 0 {		t.Fatalf("single click opened URL: %#v", *opened)	}	model.handleMouse(msg)	restoreOpenURL() 	if got := *opened; len(got) != 1 || got[0] != "https://github.com/openclaw/openclaw/issues/11" {		t.Fatalf("opened URLs = %#v", got)	}} func TestTUIMouseDoubleClickOpensMemberThread(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 140	model.height = 32	model.memberRows = []memberRow{		{label: "ISSUES (1)"},		{selectable: true, member: store.ClusterMemberDetail{Thread: store.Thread{			ID:              42,			Number:          42,			Kind:            "issue",			State:           "open",			Title:           "Selected issue",			HTMLURL:         "https://github.com/openclaw/openclaw/issues/42",			UpdatedAtGitHub: "2026-04-27T10:00:00Z",		}}},	}	model.memberIndex = 0	layout := model.layout()	restoreOpenURL, opened := captureOpenURL(t) 	msg := tea.MouseMsg{		X:      layout.members.x + 2,		Y:      layout.members.y + 4,		Action: tea.MouseActionPress,		Button: tea.MouseButtonLeft,	}	model.handleMouse(msg)	if len(*opened) != 0 {		t.Fatalf("single click opened URL: %#v", *opened)	}	model.handleMouse(msg)	restoreOpenURL() 	if got := *opened; len(got) != 1 || got[0] != "https://github.com/openclaw/openclaw/issues/42" {		t.Fatalf("opened URLs = %#v", got)	}} func TestTUIMouseSelectsVisibleClusterWindow(t *testing.T) {	clusters := make([]store.ClusterSummary, 0, 30)	for i := 0; i < 30; i++ {		clusters = append(clusters, store.ClusterSummary{			ID:                   int64(i + 1),			StableSlug:           fmt.Sprintf("cluster-%02d", i+1),			Status:               "active",			RepresentativeKind:   "issue",			RepresentativeTitle:  fmt.Sprintf("Issue %02d", i+1),			RepresentativeNumber: 100 + i,			MemberCount:          3,			UpdatedAt:            fmt.Sprintf("2026-04-27T%02d:00:00Z", i%24),		})	}	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   clusters,	})	model.width = 140	model.height = 24	model.selected = 20	model.syncComponents()	model.keepVisible()	start := model.clusterVisibleStart()	if start == 0 {		t.Fatalf("expected selected row to force a scrolled cluster window")	}	layout := model.layout() 	model.handleMouse(tea.MouseMsg{		X:      layout.clusters.x + 2,		Y:      layout.clusters.y + 3,		Action: tea.MouseActionPress,		Button: tea.MouseButtonLeft,	}) 	if model.selected != start {		t.Fatalf("visible first row click selected %d, want %d", model.selected, start)	}} func TestTUIFastWheelScrollKeepsFrameStable(t *testing.T) {	clusters := make([]store.ClusterSummary, 0, 120)	for i := 0; i < 120; i++ {		clusters = append(clusters, store.ClusterSummary{			ID:                   int64(i + 1),			StableSlug:           fmt.Sprintf("cluster-%03d", i+1),			Status:               "active",			RepresentativeKind:   "issue",			RepresentativeTitle:  fmt.Sprintf("Issue %03d", i+1),			RepresentativeNumber: 1000 + i,			MemberCount:          5,			UpdatedAt:            fmt.Sprintf("2026-04-27T%02d:00:00Z", i%24),		})	}	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		DBSource:   "remote",		DBLocation: "openclaw/gitcrawl-store:openclaw__openclaw.sync.db",		Sort:       "recent",		Clusters:   clusters,	})	model.width = 190	model.height = 34	layout := model.layout()	initialSelected := model.selected 	queued := 0	for i := 0; i < 80; i++ {		cmd := model.handleMouse(tea.MouseMsg{			X:      layout.clusters.x + 2,			Y:      layout.clusters.y + 4,			Action: tea.MouseActionPress,			Button: tea.MouseButtonWheelDown,		})		if cmd != nil {			queued++		}		model.keepVisible()		model.syncComponents()	}	if queued != 1 {		t.Fatalf("wheel burst queued %d frame ticks, want 1", queued)	}	if model.selected != initialSelected {		t.Fatalf("wheel burst moved immediately to %d, want %d", model.selected, initialSelected)	}	if model.wheelDelta != tuiWheelMaxBufferedDelta {		t.Fatalf("wheel burst delta = %d, want capped %d", model.wheelDelta, tuiWheelMaxBufferedDelta)	}	updated, cmd := model.Update(tuiWheelScrollMsg{seq: model.wheelScrollSeq})	model = updated.(clusterBrowserModel)	if cmd == nil {		t.Fatal("cluster wheel frame should defer detail reload until scrolling settles")	}	wantSelected := clampInt(initialSelected+tuiWheelMaxBufferedDelta, 0, len(model.payload.Clusters)-1)	if model.selected != wantSelected {		t.Fatalf("wheel burst selected = %d, want capped movement to %d", model.selected, wantSelected)	} 	view := model.View()	lines := strings.Split(view, "\n")	if len(lines) != model.height {		t.Fatalf("view height = %d, want %d", len(lines), model.height)	}	if !strings.Contains(lines[0], "openclaw/openclaw") {		t.Fatalf("header moved or disappeared: %q", lines[0])	}	if !strings.Contains(lines[len(lines)-1], "q quit") {		t.Fatalf("footer moved or disappeared: %q", lines[len(lines)-1])	}	if count := strings.Count(view, "openclaw/openclaw  "); count != 1 {		t.Fatalf("header count = %d, want 1", count)	}} func TestTUIMouseSelectsVisibleMemberWindow(t *testing.T) {	model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{		Repository: "openclaw/openclaw",		Sort:       "recent",		Clusters:   sampleTUIClusters(),	})	model.width = 140	model.height = 24	model.focus = focusMembers	model.memberRows = make([]memberRow, 0, 30)	for i := 0; i < 30; i++ {		model.memberRows = append(model.memberRows, memberRow{			selectable: true,			member: store.ClusterMemberDetail{				Thread: store.Thread{					ID:              int64(i + 1),					Number:          200 + i,					Kind:            "issue",					State:           "open",					Title:           fmt.Sprintf("Member %02d", i+1),					UpdatedAtGitHub: fmt.Sprintf("2026-04-27T%02d:00:00Z", i%24),				},

Lines 1–500 of 4749.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t