180 lines
6.3 KiB
Go
180 lines
6.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/openclaw/gitcrawl/internal/store"
|
|
)
|
|
|
|
func TestTUIRemainingActionAndErrorBranches(t *testing.T) {
|
|
thread := store.Thread{
|
|
ID: 1, Number: 10, Kind: "issue", State: "open", Title: "Thread title",
|
|
Body: "Body with https://example.com/docs", HTMLURL: "https://github.com/openclaw/openclaw/issues/10",
|
|
UpdatedAt: "2026-05-08T00:00:00Z",
|
|
}
|
|
cluster := store.ClusterSummary{
|
|
ID: 7, Source: store.ClusterSourceRun, StableSlug: "cluster-7", Status: "active",
|
|
Title: "Cluster title", RepresentativeNumber: 10, RepresentativeKind: "issue",
|
|
RepresentativeTitle: "Thread title", MemberCount: 1, UpdatedAt: "2026-05-08T00:00:00Z",
|
|
}
|
|
detail := store.ClusterDetail{
|
|
Cluster: cluster,
|
|
Members: []store.ClusterMemberDetail{{
|
|
Thread: thread,
|
|
Role: "member",
|
|
State: "active",
|
|
BodySnippet: "Body with https://example.com/docs",
|
|
Summaries: map[string]string{"problem_summary": "summary"},
|
|
}},
|
|
}
|
|
model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{
|
|
Repository: "openclaw/openclaw",
|
|
Sort: "size",
|
|
MinSize: 1,
|
|
Clusters: []store.ClusterSummary{cluster},
|
|
})
|
|
model.detailCache[7] = detail
|
|
model.loadSelectedCluster()
|
|
model.memberIndex = 0
|
|
model.neighborCache[thread.ID] = []tuiNeighbor{{Thread: thread, Score: 0.9}}
|
|
|
|
for _, action := range []string{"sort-oldest", "member-sort-oldest", "toggle-closed", "close-menu"} {
|
|
if !model.runAction(action) {
|
|
t.Fatalf("action %s was not handled", action)
|
|
}
|
|
}
|
|
if model.payload.Sort != "oldest" || model.memberSort != memberSortOldest {
|
|
t.Fatalf("sort actions failed sort=%q member=%q", model.payload.Sort, model.memberSort)
|
|
}
|
|
|
|
t.Setenv("PATH", "")
|
|
errorActions := []string{
|
|
"open-cluster-representative",
|
|
"copy-cluster-url",
|
|
"copy-thread-detail",
|
|
"copy-body-preview",
|
|
"copy-summaries",
|
|
"copy-neighbors",
|
|
"copy-cluster-id",
|
|
"copy-cluster-name",
|
|
"copy-cluster-title",
|
|
"copy-member-list",
|
|
"copy-cluster",
|
|
"copy-visible-clusters",
|
|
"copy-reference-links",
|
|
"open",
|
|
"copy-url",
|
|
"copy-markdown",
|
|
"copy-title",
|
|
"open-first-link",
|
|
"copy-first-link",
|
|
}
|
|
for _, action := range errorActions {
|
|
model.status = ""
|
|
handled := model.runMenuItem(tuiMenuItem{label: action, action: action, value: "https://example.com/docs"})
|
|
if !handled || model.status == "" {
|
|
t.Fatalf("error action %s handled=%v status=%q", action, handled, model.status)
|
|
}
|
|
}
|
|
model.openReferenceLinkMenu("copy")
|
|
model.runAction("back-to-actions")
|
|
if model.menuTitle != "Actions" {
|
|
t.Fatalf("back to actions failed title=%q", model.menuTitle)
|
|
}
|
|
model.runMenuItem(tuiMenuItem{label: "Open picked", action: "open-picked-link", value: "https://example.com/docs"})
|
|
model.runMenuItem(tuiMenuItem{label: "Copy picked", action: "copy-picked-link", value: "https://example.com/docs"})
|
|
|
|
model.closeSelectedClusterLocally()
|
|
if !strings.Contains(model.status, "only available for durable clusters") {
|
|
t.Fatalf("raw cluster local close status=%q", model.status)
|
|
}
|
|
model.reopenSelectedClusterLocally()
|
|
model.excludeSelectedClusterMemberLocally()
|
|
model.includeSelectedClusterMemberLocally()
|
|
model.setSelectedClusterCanonicalLocally()
|
|
if !strings.Contains(model.status, "only available for durable clusters") {
|
|
t.Fatalf("raw member local action status=%q", model.status)
|
|
}
|
|
}
|
|
|
|
func TestTUIRemainingHelperBranches(t *testing.T) {
|
|
model := newClusterBrowserModel(context.Background(), nil, 0, clusterBrowserPayload{
|
|
Repository: "openclaw/openclaw",
|
|
MinSize: 1,
|
|
Limit: 1,
|
|
Clusters: []store.ClusterSummary{
|
|
{ID: 1, Status: "active", RepresentativeNumber: 101, MemberCount: 1, UpdatedAt: "2026-05-08T00:00:00Z"},
|
|
},
|
|
})
|
|
if model.currentClusterID() != 1 {
|
|
t.Fatalf("current cluster id = %d", model.currentClusterID())
|
|
}
|
|
if model.clusterRefreshLimit() != 1 {
|
|
t.Fatalf("cluster refresh limit = %d", model.clusterRefreshLimit())
|
|
}
|
|
model.ensureClusterInWorkingSet(store.ClusterSummary{ID: 2, Status: "closed", ClosedAt: "2026-05-08T00:00:00Z", MemberCount: 2})
|
|
if !model.selectClusterIDForJump(2) || !model.showClosed || model.minSize != 1 {
|
|
t.Fatalf("jump selection showClosed=%v minSize=%d selected=%d", model.showClosed, model.minSize, model.selected)
|
|
}
|
|
model.payload.Clusters = nil
|
|
if model.currentClusterID() != 0 || model.clusterSignature() != "" {
|
|
t.Fatalf("empty cluster helpers id=%d sig=%q", model.currentClusterID(), model.clusterSignature())
|
|
}
|
|
if _, ok := model.clusterFromWorkingSet(999); ok {
|
|
t.Fatal("missing working-set cluster should not resolve")
|
|
}
|
|
model.applyClusterRefresh(nil, 0)
|
|
if model.payload.Clusters == nil {
|
|
t.Fatal("nil refresh should normalize clusters")
|
|
}
|
|
model.autoRefreshFromStore()
|
|
if model.status != "Refresh unavailable for this view" {
|
|
t.Fatalf("auto refresh status=%q", model.status)
|
|
}
|
|
if cmd := model.autoRefreshCmd(); cmd != nil {
|
|
t.Fatalf("auto refresh command without store = %v", cmd)
|
|
}
|
|
model.switchRepository("")
|
|
if model.status != "Repository picker unavailable for this view" {
|
|
t.Fatalf("switch repository no store status=%q", model.status)
|
|
}
|
|
if label := (clusterBrowserModel{}).clusterPositionLabel(); label != "0" {
|
|
t.Fatalf("zero cluster position label = %q", label)
|
|
}
|
|
if label := model.clusterPositionLabel(); label != "0" {
|
|
t.Fatalf("empty model cluster position label = %q", label)
|
|
}
|
|
memberModel := model
|
|
memberModel.memberRows = []memberRow{}
|
|
if label := memberModel.memberPositionLabel(); label != "0" {
|
|
t.Fatalf("zero member position label = %q", label)
|
|
}
|
|
if got := formatRelativeTime(time.Now().Add(-30 * time.Minute).Format(time.RFC3339Nano)); got != "30m ago" {
|
|
t.Fatalf("minute age = %q", got)
|
|
}
|
|
if got := formatRelativeTime(time.Now().Add(-75 * 24 * time.Hour).Format(time.RFC3339Nano)); !strings.Contains(got, "mo ago") {
|
|
t.Fatalf("month age = %q", got)
|
|
}
|
|
if got := formatRelativeTime(""); got != "never" {
|
|
t.Fatalf("empty age = %q", got)
|
|
}
|
|
if got := formatRelativeTime("bad-time"); got != "bad-time" {
|
|
t.Fatalf("bad age = %q", got)
|
|
}
|
|
if got := wrapPlain("", 10); len(got) != 1 || got[0] != "" {
|
|
t.Fatalf("empty wrap = %+v", got)
|
|
}
|
|
if got := clampInt(5, 10, 1); got != 10 {
|
|
t.Fatalf("inverted clamp = %d", got)
|
|
}
|
|
if got := padCells("abcdef", 0); got != "" {
|
|
t.Fatalf("zero pad = %q", got)
|
|
}
|
|
if got := fitBlock("a\nb", 2, 1); got != "a " {
|
|
t.Fatalf("fit block = %q", got)
|
|
}
|
|
}
|