Skip to content

Commit b41f870

Browse files
committed
add menu chane detection test
1 parent e7fa7c0 commit b41f870

File tree

1 file changed

+200
-0
lines changed

1 file changed

+200
-0
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package main
2+
3+
import (
4+
"slices"
5+
"sync"
6+
"testing"
7+
"time"
8+
)
9+
10+
// TestMenuChangeDetection tests that the menu change detection logic works correctly
11+
// and prevents unnecessary menu rebuilds when PR data hasn't changed.
12+
func TestMenuChangeDetection(t *testing.T) {
13+
// Create app with test data
14+
app := &App{
15+
mu: sync.RWMutex{},
16+
stateManager: NewPRStateManager(time.Now()),
17+
hiddenOrgs: make(map[string]bool),
18+
seenOrgs: make(map[string]bool),
19+
blockedPRTimes: make(map[string]time.Time),
20+
browserRateLimiter: NewBrowserRateLimiter(30*time.Second, 5, defaultMaxBrowserOpensDay),
21+
systrayInterface: &MockSystray{},
22+
incoming: []PR{
23+
{Repository: "org1/repo1", Number: 1, Title: "Fix bug", URL: "https://github.com/org1/repo1/pull/1", NeedsReview: true, UpdatedAt: time.Now()},
24+
{Repository: "org2/repo2", Number: 2, Title: "Add feature", URL: "https://github.com/org2/repo2/pull/2", NeedsReview: false, UpdatedAt: time.Now()},
25+
},
26+
outgoing: []PR{
27+
{Repository: "org3/repo3", Number: 3, Title: "Update docs", URL: "https://github.com/org3/repo3/pull/3", IsBlocked: true, UpdatedAt: time.Now()},
28+
},
29+
}
30+
31+
t.Run("same_titles_should_be_equal", func(t *testing.T) {
32+
// Generate titles twice with same data
33+
titles1 := app.generateMenuTitles()
34+
titles2 := app.generateMenuTitles()
35+
36+
// They should be equal
37+
if !slices.Equal(titles1, titles2) {
38+
t.Errorf("Same PR data generated different titles:\nFirst: %v\nSecond: %v", titles1, titles2)
39+
}
40+
})
41+
42+
t.Run("different_pr_count_changes_titles", func(t *testing.T) {
43+
// Generate initial titles
44+
initialTitles := app.generateMenuTitles()
45+
46+
// Add a new PR
47+
app.incoming = append(app.incoming, PR{
48+
Repository: "org4/repo4",
49+
Number: 4,
50+
Title: "New PR",
51+
URL: "https://github.com/org4/repo4/pull/4",
52+
NeedsReview: true,
53+
UpdatedAt: time.Now(),
54+
})
55+
56+
// Generate new titles
57+
newTitles := app.generateMenuTitles()
58+
59+
// They should be different
60+
if slices.Equal(initialTitles, newTitles) {
61+
t.Error("Adding a PR didn't change the menu titles")
62+
}
63+
64+
// The new titles should have more items
65+
if len(newTitles) <= len(initialTitles) {
66+
t.Errorf("New titles should have more items: got %d, initial had %d", len(newTitles), len(initialTitles))
67+
}
68+
})
69+
70+
t.Run("pr_repository_change_updates_menu", func(t *testing.T) {
71+
// Generate initial titles
72+
initialTitles := app.generateMenuTitles()
73+
74+
// Change a PR repository (this would be unusual but tests the title generation)
75+
app.incoming[0].Repository = "different-org/different-repo"
76+
77+
// Generate new titles
78+
newTitles := app.generateMenuTitles()
79+
80+
// They should be different because menu shows "org/repo #number"
81+
if slices.Equal(initialTitles, newTitles) {
82+
t.Error("Changing a PR repository didn't change the menu titles")
83+
}
84+
})
85+
86+
t.Run("blocked_status_change_updates_menu", func(t *testing.T) {
87+
// Generate initial titles
88+
initialTitles := app.generateMenuTitles()
89+
90+
// Change blocked status
91+
app.incoming[1].NeedsReview = true // Make it blocked
92+
93+
// Generate new titles
94+
newTitles := app.generateMenuTitles()
95+
96+
// They should be different because the title prefix changes for blocked PRs
97+
if slices.Equal(initialTitles, newTitles) {
98+
t.Error("Changing PR blocked status didn't change the menu titles")
99+
}
100+
})
101+
}
102+
103+
// TestFirstRunMenuRebuildBug tests the specific bug where the first scheduled update
104+
// after initial load would unnecessarily rebuild the menu.
105+
func TestFirstRunMenuRebuildBug(t *testing.T) {
106+
// Create app simulating initial state
107+
app := &App{
108+
mu: sync.RWMutex{},
109+
stateManager: NewPRStateManager(time.Now()),
110+
hiddenOrgs: make(map[string]bool),
111+
seenOrgs: make(map[string]bool),
112+
blockedPRTimes: make(map[string]time.Time),
113+
browserRateLimiter: NewBrowserRateLimiter(30*time.Second, 5, defaultMaxBrowserOpensDay),
114+
menuInitialized: false,
115+
systrayInterface: &MockSystray{},
116+
lastMenuTitles: nil, // This is nil on first run - the bug condition
117+
incoming: []PR{
118+
{Repository: "test/repo", Number: 1, Title: "Test PR", URL: "https://github.com/test/repo/pull/1"},
119+
},
120+
}
121+
122+
// Simulate what happens during initial load
123+
// OLD BEHAVIOR: lastMenuTitles would remain nil
124+
// NEW BEHAVIOR: lastMenuTitles should be set after initial menu build
125+
126+
// Generate initial titles (simulating what rebuildMenu would show)
127+
initialTitles := app.generateMenuTitles()
128+
129+
// This is the fix - store titles after initial build
130+
app.mu.Lock()
131+
app.lastMenuTitles = initialTitles
132+
app.menuInitialized = true
133+
app.mu.Unlock()
134+
135+
// Now simulate first scheduled update with same data
136+
// Generate current titles (should be same as initial)
137+
currentTitles := app.generateMenuTitles()
138+
139+
// Get stored titles
140+
app.mu.RLock()
141+
storedTitles := app.lastMenuTitles
142+
app.mu.RUnlock()
143+
144+
// Test 1: Stored titles should not be nil/empty
145+
if len(storedTitles) == 0 {
146+
t.Fatal("BUG: lastMenuTitles not set after initial menu build")
147+
}
148+
149+
// Test 2: Current and stored titles should be equal (no changes)
150+
if !slices.Equal(currentTitles, storedTitles) {
151+
t.Errorf("BUG: Titles marked as different when they're the same:\nCurrent: %v\nStored: %v",
152+
currentTitles, storedTitles)
153+
}
154+
155+
// Test 3: Verify the comparison result that updateMenu would make
156+
// In the bug, this would be comparing non-empty current titles with nil/empty stored titles
157+
// and would return false (different), triggering unnecessary rebuild
158+
shouldSkipRebuild := slices.Equal(currentTitles, storedTitles)
159+
if !shouldSkipRebuild {
160+
t.Error("BUG: Would rebuild menu even though PR data hasn't changed")
161+
}
162+
}
163+
164+
// TestHiddenOrgChangesMenu tests that hiding/showing orgs updates menu titles
165+
func TestHiddenOrgChangesMenu(t *testing.T) {
166+
app := &App{
167+
mu: sync.RWMutex{},
168+
stateManager: NewPRStateManager(time.Now()),
169+
hiddenOrgs: make(map[string]bool),
170+
seenOrgs: make(map[string]bool),
171+
blockedPRTimes: make(map[string]time.Time),
172+
browserRateLimiter: NewBrowserRateLimiter(30*time.Second, 5, defaultMaxBrowserOpensDay),
173+
systrayInterface: &MockSystray{},
174+
incoming: []PR{
175+
{Repository: "org1/repo1", Number: 1, Title: "PR 1", URL: "https://github.com/org1/repo1/pull/1"},
176+
{Repository: "org2/repo2", Number: 2, Title: "PR 2", URL: "https://github.com/org2/repo2/pull/2"},
177+
},
178+
}
179+
180+
// Generate initial titles
181+
initialTitles := app.generateMenuTitles()
182+
initialCount := len(initialTitles)
183+
184+
// Hide org1
185+
app.hiddenOrgs["org1"] = true
186+
187+
// Generate new titles - should have fewer items
188+
newTitles := app.generateMenuTitles()
189+
190+
// Titles should be different
191+
if slices.Equal(initialTitles, newTitles) {
192+
t.Error("Hiding an org didn't change menu titles")
193+
}
194+
195+
// Should have fewer items (org1/repo1 should be hidden)
196+
if len(newTitles) >= initialCount {
197+
t.Errorf("Menu should have fewer items after hiding org: got %d, started with %d",
198+
len(newTitles), initialCount)
199+
}
200+
}

0 commit comments

Comments
 (0)