Something went wrong. Try again.
An alternative CLI for git-bug that simplifies working with agents on issues
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182package main
import ( "io" "os" "strings" "testing" "time")
func TestApplySort_AgeDescOldestFirst(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "newer", CreatedAt: now.Add(-1 * time.Hour)}, {FullID: "older", CreatedAt: now.Add(-2 * time.Hour)}, }
ApplySort(issues, &SortSpec{Field: "age", Direction: "desc"})
if issues[0].FullID != "older" { t.Errorf("age:desc first = %q, want %q (oldest first)", issues[0].FullID, "older") }}
func TestApplySort_AgeAscNewestFirst(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "older", CreatedAt: now.Add(-2 * time.Hour)}, {FullID: "newer", CreatedAt: now.Add(-1 * time.Hour)}, }
ApplySort(issues, &SortSpec{Field: "age", Direction: "asc"})
if issues[0].FullID != "newer" { t.Errorf("age:asc first = %q, want %q (newest first)", issues[0].FullID, "newer") }}
func TestBuildIssueTree_NestsChildrenUnderParent(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "parent-full-id", ShortID: "parent", Title: "Parent", CreatedAt: now.Add(-2 * time.Hour)}, {FullID: "child1-full-id", ShortID: "child1", Title: "Child 1", CreatedAt: now.Add(-1 * time.Hour), ParentID: "parent-full-id"}, {FullID: "child2-full-id", ShortID: "child2", Title: "Child 2", CreatedAt: now.Add(-30 * time.Minute), ParentID: "parent-full-id"}, }
roots := buildIssueTree(issues)
if len(roots) != 1 { t.Fatalf("roots = %d, want 1", len(roots)) } if len(roots[0].Children) != 2 { t.Fatalf("children = %d, want 2", len(roots[0].Children)) }}
func TestBuildIssueTree_SortsRootsAndChildrenOldestFirst(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "old-parent", ShortID: "old", Title: "Old parent", CreatedAt: now.Add(-48 * time.Hour)}, {FullID: "new-parent", ShortID: "new", Title: "New parent", CreatedAt: now.Add(-24 * time.Hour)}, {FullID: "old-child", ShortID: "oldc", Title: "Old child", CreatedAt: now.Add(-2 * time.Hour), ParentID: "old-parent"}, {FullID: "new-child", ShortID: "newc", Title: "New child", CreatedAt: now.Add(-1 * time.Hour), ParentID: "old-parent"}, }
roots := buildIssueTree(issues)
if len(roots) != 2 { t.Fatalf("roots = %d, want 2", len(roots)) } if roots[0].Issue.FullID != "old-parent" { t.Errorf("first root = %q, want %q (oldest parent first)", roots[0].Issue.FullID, "old-parent") } if len(roots[0].Children) != 2 { t.Fatalf("children = %d, want 2", len(roots[0].Children)) } if roots[0].Children[0].Issue.FullID != "old-child" { t.Errorf("first child = %q, want %q (oldest child first)", roots[0].Children[0].Issue.FullID, "old-child") } if roots[0].Children[1].Issue.FullID != "new-child" { t.Errorf("second child = %q, want %q (youngest child last)", roots[0].Children[1].Issue.FullID, "new-child") }}
func TestBuildIssueTree_OrphanParentTreatedAsRoot(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "orphan", ShortID: "orphan", Title: "Orphan", CreatedAt: now, ParentID: "missing-parent"}, }
roots := buildIssueTree(issues)
if len(roots) != 1 { t.Fatalf("roots = %d, want 1 (orphan treated as root)", len(roots)) }}
func TestPrintTreeTable_ColumnsAligned(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "parent-full-id", ShortID: "parent", Title: "Parent", CreatedAt: now.Add(-2 * time.Hour)}, {FullID: "child1-full-id", ShortID: "child1", Title: "Child 1", CreatedAt: now.Add(-1 * time.Hour), ParentID: "parent-full-id"}, {FullID: "child2-full-id", ShortID: "child2", Title: "Child 2 with a longer title", CreatedAt: now.Add(-30 * time.Minute), ParentID: "parent-full-id"}, }
roots := buildIssueTree(issues) out := captureTreeOutput(t, roots)
lines := strings.Split(strings.TrimRight(out, "\n"), "\n") if len(lines) < 4 { t.Fatalf("expected header, separator, and 3 rows, got:\n%s", out) }
// Every row's Summary, Labels, and Age columns must start at the // same rune index as the header's, even with multibyte tree markers. header := []rune(stripANSI(lines[0])) summaryAt := strings.Index(string(header), "Summary") labelsAt := strings.Index(string(header), "Labels") ageAt := strings.Index(string(header), "Age") if summaryAt < 0 || labelsAt < 0 || ageAt < 0 { t.Fatalf("header missing columns:\n%s", out) }
// The Summary text of each row must start exactly at summaryAt. wantSummaries := []string{"Parent", "Child 1", "Child 2 with a longer title"} for i, line := range lines[2:] { row := stripANSI(line) if len([]rune(row)) < summaryAt+len(wantSummaries[i]) { t.Errorf("row %d too short:\n%s", i, line) continue } if got := string([]rune(row)[summaryAt : summaryAt+len(wantSummaries[i])]); got != wantSummaries[i] { t.Errorf("row %d summary at column %d = %q, want %q\nfull output:\n%s", i, summaryAt, got, wantSummaries[i], out) } }}
func captureTreeOutput(t *testing.T, roots []*TreeNode) string { t.Helper()
old := os.Stdout r, w, err := os.Pipe() if err != nil { t.Fatalf("os.Pipe failed: %v", err) } os.Stdout = w
printErr := printTreeTable(roots)
_ = w.Close() os.Stdout = old
if printErr != nil { t.Fatalf("printTreeTable failed: %v", printErr) }
out, err := io.ReadAll(r) if err != nil { t.Fatalf("reading captured output failed: %v", err) } return string(out)}
func TestPrintTreeTable_ShowsChildMarkers(t *testing.T) { now := time.Now() issues := []BugIssue{ {FullID: "parent-full-id", ShortID: "parent", Title: "Parent", CreatedAt: now.Add(-2 * time.Hour)}, {FullID: "child1-full-id", ShortID: "child1", Title: "Child 1", CreatedAt: now.Add(-1 * time.Hour), ParentID: "parent-full-id"}, {FullID: "child2-full-id", ShortID: "child2", Title: "Child 2", CreatedAt: now.Add(-30 * time.Minute), ParentID: "parent-full-id"}, }
roots := buildIssueTree(issues) out := captureTreeOutput(t, roots)
if !strings.Contains(out, "├─ ") { t.Errorf("output missing non-last child marker %q\noutput:\n%s", "├─ ", out) } if !strings.Contains(out, "└─ ") { t.Errorf("output missing last child marker %q\noutput:\n%s", "└─ ", out) }}