Skip to content

Commit 17c1b25

Browse files
committed
test: add tests for GetDescendants, git methods, and log command
New tests: - tree.GetDescendants and GetDescendantsEmpty - git.GetTip, GetMergeBase, DeleteBranch, GetGitDir, NeedsRebase - log command tree building (3 tests) Total: 38 tests (was 27)
1 parent 0dabc67 commit 17c1b25

3 files changed

Lines changed: 262 additions & 0 deletions

File tree

cmd/log_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// cmd/log_test.go
2+
package cmd_test
3+
4+
import (
5+
"testing"
6+
7+
"github.com/boneskull/gh-stack/internal/config"
8+
"github.com/boneskull/gh-stack/internal/tree"
9+
)
10+
11+
func TestLogBuildTree(t *testing.T) {
12+
dir := setupTestRepo(t)
13+
14+
cfg, _ := config.Load(dir)
15+
cfg.SetTrunk("main")
16+
cfg.SetParent("feature-a", "main")
17+
cfg.SetParent("feature-b", "feature-a")
18+
cfg.SetPR("feature-a", 123)
19+
20+
root, err := tree.Build(cfg)
21+
if err != nil {
22+
t.Fatalf("Build failed: %v", err)
23+
}
24+
25+
// Verify tree structure
26+
if root.Name != "main" {
27+
t.Errorf("expected root 'main', got %q", root.Name)
28+
}
29+
if len(root.Children) != 1 {
30+
t.Fatalf("expected 1 child of main, got %d", len(root.Children))
31+
}
32+
33+
featureA := root.Children[0]
34+
if featureA.Name != "feature-a" {
35+
t.Errorf("expected 'feature-a', got %q", featureA.Name)
36+
}
37+
if featureA.PR != 123 {
38+
t.Errorf("expected PR 123, got %d", featureA.PR)
39+
}
40+
if len(featureA.Children) != 1 {
41+
t.Fatalf("expected 1 child of feature-a, got %d", len(featureA.Children))
42+
}
43+
44+
featureB := featureA.Children[0]
45+
if featureB.Name != "feature-b" {
46+
t.Errorf("expected 'feature-b', got %q", featureB.Name)
47+
}
48+
}
49+
50+
func TestLogEmptyStack(t *testing.T) {
51+
dir := setupTestRepo(t)
52+
53+
cfg, _ := config.Load(dir)
54+
cfg.SetTrunk("main")
55+
56+
// No branches tracked, just trunk
57+
root, err := tree.Build(cfg)
58+
if err != nil {
59+
t.Fatalf("Build failed: %v", err)
60+
}
61+
62+
if root.Name != "main" {
63+
t.Errorf("expected root 'main', got %q", root.Name)
64+
}
65+
if len(root.Children) != 0 {
66+
t.Errorf("expected 0 children for trunk-only stack, got %d", len(root.Children))
67+
}
68+
}
69+
70+
func TestLogMultipleBranches(t *testing.T) {
71+
dir := setupTestRepo(t)
72+
73+
cfg, _ := config.Load(dir)
74+
cfg.SetTrunk("main")
75+
// Two branches off main
76+
cfg.SetParent("feature-a", "main")
77+
cfg.SetParent("feature-b", "main")
78+
// One branch off feature-a
79+
cfg.SetParent("feature-a-sub", "feature-a")
80+
81+
root, err := tree.Build(cfg)
82+
if err != nil {
83+
t.Fatalf("Build failed: %v", err)
84+
}
85+
86+
if len(root.Children) != 2 {
87+
t.Errorf("expected 2 children of main, got %d", len(root.Children))
88+
}
89+
90+
// Find feature-a and check its children
91+
var featureA *tree.Node
92+
for _, child := range root.Children {
93+
if child.Name == "feature-a" {
94+
featureA = child
95+
break
96+
}
97+
}
98+
99+
if featureA == nil {
100+
t.Fatal("feature-a not found")
101+
}
102+
103+
if len(featureA.Children) != 1 {
104+
t.Errorf("expected 1 child of feature-a, got %d", len(featureA.Children))
105+
}
106+
if featureA.Children[0].Name != "feature-a-sub" {
107+
t.Errorf("expected 'feature-a-sub', got %q", featureA.Children[0].Name)
108+
}
109+
}

internal/git/git_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,112 @@ func TestHasStagedChanges(t *testing.T) {
147147
t.Error("expected staged changes after git add")
148148
}
149149
}
150+
151+
func TestGetTip(t *testing.T) {
152+
dir := setupTestRepo(t)
153+
g := git.New(dir)
154+
155+
current, _ := g.CurrentBranch()
156+
tip, err := g.GetTip(current)
157+
if err != nil {
158+
t.Fatalf("GetTip failed: %v", err)
159+
}
160+
161+
// Should be a 40-character hex SHA
162+
if len(tip) != 40 {
163+
t.Errorf("expected 40-char SHA, got %q (len %d)", tip, len(tip))
164+
}
165+
}
166+
167+
func TestGetMergeBase(t *testing.T) {
168+
dir := setupTestRepo(t)
169+
g := git.New(dir)
170+
171+
current, _ := g.CurrentBranch()
172+
173+
// Create a branch, make a commit on each
174+
g.CreateBranch("feature")
175+
originalTip, _ := g.GetTip(current)
176+
177+
// Commit on main
178+
os.WriteFile(filepath.Join(dir, "main.txt"), []byte("main"), 0644)
179+
exec.Command("git", "-C", dir, "add", ".").Run()
180+
exec.Command("git", "-C", dir, "commit", "-m", "main commit").Run()
181+
182+
// Commit on feature
183+
g.Checkout("feature")
184+
os.WriteFile(filepath.Join(dir, "feature.txt"), []byte("feature"), 0644)
185+
exec.Command("git", "-C", dir, "add", ".").Run()
186+
exec.Command("git", "-C", dir, "commit", "-m", "feature commit").Run()
187+
188+
// Merge base should be the original tip
189+
base, err := g.GetMergeBase(current, "feature")
190+
if err != nil {
191+
t.Fatalf("GetMergeBase failed: %v", err)
192+
}
193+
if base != originalTip {
194+
t.Errorf("expected merge base %q, got %q", originalTip, base)
195+
}
196+
}
197+
198+
func TestDeleteBranch(t *testing.T) {
199+
dir := setupTestRepo(t)
200+
g := git.New(dir)
201+
202+
g.CreateBranch("to-delete")
203+
if !g.BranchExists("to-delete") {
204+
t.Fatal("branch should exist before deletion")
205+
}
206+
207+
err := g.DeleteBranch("to-delete")
208+
if err != nil {
209+
t.Fatalf("DeleteBranch failed: %v", err)
210+
}
211+
212+
if g.BranchExists("to-delete") {
213+
t.Error("branch should not exist after deletion")
214+
}
215+
}
216+
217+
func TestGetGitDir(t *testing.T) {
218+
dir := setupTestRepo(t)
219+
g := git.New(dir)
220+
221+
gitDir := g.GetGitDir()
222+
expected := filepath.Join(dir, ".git")
223+
if gitDir != expected {
224+
t.Errorf("expected %q, got %q", expected, gitDir)
225+
}
226+
}
227+
228+
func TestNeedsRebase(t *testing.T) {
229+
dir := setupTestRepo(t)
230+
g := git.New(dir)
231+
232+
current, _ := g.CurrentBranch()
233+
234+
// Create feature branch
235+
g.CreateBranch("feature")
236+
237+
// Initially, feature doesn't need rebase (same commit)
238+
needs, err := g.NeedsRebase("feature", current)
239+
if err != nil {
240+
t.Fatalf("NeedsRebase failed: %v", err)
241+
}
242+
if needs {
243+
t.Error("feature should not need rebase initially")
244+
}
245+
246+
// Add commit to main - now feature needs rebase
247+
os.WriteFile(filepath.Join(dir, "new.txt"), []byte("new"), 0644)
248+
exec.Command("git", "-C", dir, "add", ".").Run()
249+
exec.Command("git", "-C", dir, "commit", "-m", "new commit").Run()
250+
251+
needs, err = g.NeedsRebase("feature", current)
252+
if err != nil {
253+
t.Fatalf("NeedsRebase failed: %v", err)
254+
}
255+
if !needs {
256+
t.Error("feature should need rebase after main moved forward")
257+
}
258+
}

internal/tree/tree_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,47 @@ func TestGetAncestors(t *testing.T) {
9090
t.Errorf("unexpected ancestors: %v", ancestors)
9191
}
9292
}
93+
94+
func TestGetDescendants(t *testing.T) {
95+
cfg, _ := setupTestRepo(t)
96+
cfg.SetTrunk("main")
97+
cfg.SetParent("feature-a", "main")
98+
cfg.SetParent("feature-b", "feature-a")
99+
cfg.SetParent("feature-c", "feature-a")
100+
cfg.SetParent("feature-d", "feature-b")
101+
102+
root, _ := tree.Build(cfg)
103+
node := tree.FindNode(root, "feature-a")
104+
105+
descendants := tree.GetDescendants(node)
106+
107+
// Should get feature-b, feature-c, feature-d in depth-first order
108+
if len(descendants) != 3 {
109+
t.Fatalf("expected 3 descendants, got %d", len(descendants))
110+
}
111+
112+
// Verify all expected descendants are present
113+
names := make(map[string]bool)
114+
for _, d := range descendants {
115+
names[d.Name] = true
116+
}
117+
for _, expected := range []string{"feature-b", "feature-c", "feature-d"} {
118+
if !names[expected] {
119+
t.Errorf("missing descendant: %s", expected)
120+
}
121+
}
122+
}
123+
124+
func TestGetDescendantsEmpty(t *testing.T) {
125+
cfg, _ := setupTestRepo(t)
126+
cfg.SetTrunk("main")
127+
cfg.SetParent("feature-a", "main")
128+
129+
root, _ := tree.Build(cfg)
130+
node := tree.FindNode(root, "feature-a")
131+
132+
descendants := tree.GetDescendants(node)
133+
if len(descendants) != 0 {
134+
t.Errorf("expected 0 descendants for leaf node, got %d", len(descendants))
135+
}
136+
}

0 commit comments

Comments
 (0)