Skip to content

Commit 41882f3

Browse files
committed
Improve hide duplicate logic to check names and paths
- Add name duplicate check - Ensure no path and no name duplicate is kept (fixes #301)
1 parent 8a4fcae commit 41882f3

File tree

2 files changed

+195
-5
lines changed

2 files changed

+195
-5
lines changed

lister/list.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ func (l *RealLister) List(opts ListOptions) (model.SeshSessions, error) {
5555

5656
if opts.HideDuplicates {
5757
directoryHash := make(map[string]int)
58+
nameHash := make(map[string]int)
5859
destIndex := 0
5960
for _, index := range fullOrderedIndex {
60-
directoryPath := fullDirectory[index].Path
61-
if _, exists := directoryHash[directoryPath]; !exists {
61+
session := fullDirectory[index]
62+
nameIsDuplicate := nameHash[session.Name] != 0
63+
pathIsDuplicate := session.Path != "" && directoryHash[session.Path] != 0
64+
if !nameIsDuplicate && !pathIsDuplicate {
6265
fullOrderedIndex[destIndex] = index
63-
directoryHash[directoryPath] = 1
66+
directoryHash[session.Path] = 1
67+
nameHash[session.Name] = 1
6468
destIndex = destIndex + 1
6569
}
6670
}

lister/list_test.go

Lines changed: 188 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,192 @@
11
package lister
22

3-
import "testing"
3+
import (
4+
"testing"
45

5-
func TestList(t *testing.T) {
6+
"github.com/joshmedeski/sesh/v2/home"
7+
"github.com/joshmedeski/sesh/v2/model"
8+
"github.com/joshmedeski/sesh/v2/tmux"
9+
"github.com/joshmedeski/sesh/v2/tmuxinator"
10+
"github.com/joshmedeski/sesh/v2/zoxide"
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestHideDuplicates(t *testing.T) {
15+
tests := []struct {
16+
name string
17+
tmuxSessions []*model.TmuxSession
18+
zoxideResults []*model.ZoxideResult
19+
configSessions []model.SessionConfig
20+
tmuxinatorConfigs []*model.TmuxinatorConfig
21+
homeShortenHome map[string]string
22+
expectedNames []string
23+
}{
24+
{
25+
name: "no duplicates",
26+
tmuxSessions: []*model.TmuxSession{
27+
{Name: "session1", Path: "/path/to/session1"},
28+
},
29+
zoxideResults: []*model.ZoxideResult{
30+
{Path: "/path/to/session2", Score: 1.0},
31+
},
32+
homeShortenHome: map[string]string{
33+
"/path/to/session2": "session2",
34+
},
35+
expectedNames: []string{"session1", "session2"},
36+
},
37+
{
38+
name: "name duplicates only",
39+
tmuxSessions: []*model.TmuxSession{
40+
{Name: "dev", Path: "/path/to/dev1"},
41+
},
42+
zoxideResults: []*model.ZoxideResult{
43+
{Path: "/path/to/dev2", Score: 1.0},
44+
},
45+
homeShortenHome: map[string]string{
46+
"/path/to/dev2": "dev",
47+
},
48+
expectedNames: []string{"dev"},
49+
},
50+
{
51+
name: "path duplicates only",
52+
tmuxSessions: []*model.TmuxSession{
53+
{Name: "dev1", Path: "/path/to/dev"},
54+
},
55+
zoxideResults: []*model.ZoxideResult{
56+
{Path: "/path/to/dev", Score: 1.0},
57+
},
58+
homeShortenHome: map[string]string{
59+
"/path/to/dev": "dev2",
60+
},
61+
expectedNames: []string{"dev1"},
62+
},
63+
{
64+
name: "empty path tmuxinator sessions filtered by name only",
65+
tmuxinatorConfigs: []*model.TmuxinatorConfig{
66+
{Name: "tmux1"},
67+
{Name: "tmux1"},
68+
{Name: "tmux2"},
69+
},
70+
expectedNames: []string{"tmux1", "tmux2"},
71+
},
72+
{
73+
name: "mixed empty path and regular sessions with name conflicts",
74+
tmuxinatorConfigs: []*model.TmuxinatorConfig{
75+
{Name: "dev"},
76+
},
77+
zoxideResults: []*model.ZoxideResult{
78+
{Path: "/path/to/dev", Score: 1.0},
79+
},
80+
homeShortenHome: map[string]string{
81+
"/path/to/dev": "dev",
82+
},
83+
expectedNames: []string{"dev"},
84+
},
85+
{
86+
name: "order preservation",
87+
tmuxSessions: []*model.TmuxSession{
88+
{Name: "a", Path: "/path/to/a"},
89+
},
90+
zoxideResults: []*model.ZoxideResult{
91+
{Path: "/path/to/b", Score: 1.0},
92+
},
93+
configSessions: []model.SessionConfig{
94+
{Name: "a", Path: "/path/to/c"},
95+
},
96+
homeShortenHome: map[string]string{
97+
"/path/to/b": "b",
98+
},
99+
expectedNames: []string{"a", "b"},
100+
},
101+
{
102+
name: "empty input",
103+
tmuxSessions: []*model.TmuxSession{},
104+
zoxideResults: []*model.ZoxideResult{},
105+
configSessions: []model.SessionConfig{},
106+
tmuxinatorConfigs: []*model.TmuxinatorConfig{},
107+
expectedNames: []string{},
108+
},
109+
{
110+
name: "single session",
111+
tmuxSessions: []*model.TmuxSession{
112+
{Name: "single", Path: "/path/to/single"},
113+
},
114+
expectedNames: []string{"single"},
115+
},
116+
{
117+
name: "all duplicates",
118+
tmuxSessions: []*model.TmuxSession{
119+
{Name: "dup", Path: "/path/to/dup"},
120+
},
121+
zoxideResults: []*model.ZoxideResult{
122+
{Path: "/path/to/dup", Score: 1.0},
123+
},
124+
configSessions: []model.SessionConfig{
125+
{Name: "dup", Path: "/path/to/dup"},
126+
},
127+
homeShortenHome: map[string]string{
128+
"/path/to/dup": "dup",
129+
},
130+
expectedNames: []string{"dup"},
131+
},
132+
}
133+
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
// Setup mocks
137+
mockTmux := new(tmux.MockTmux)
138+
mockZoxide := new(zoxide.MockZoxide)
139+
mockHome := new(home.MockHome)
140+
mockTmuxinator := new(tmuxinator.MockTmuxinator)
141+
142+
// Set up mocks for all sources that have data (including empty data)
143+
if tt.tmuxSessions != nil {
144+
mockTmux.On("ListSessions").Return(tt.tmuxSessions, nil)
145+
}
146+
if tt.zoxideResults != nil {
147+
mockZoxide.On("ListResults").Return(tt.zoxideResults, nil)
148+
for path, shortened := range tt.homeShortenHome {
149+
mockHome.On("ShortenHome", path).Return(shortened, nil)
150+
}
151+
}
152+
if tt.configSessions != nil {
153+
for _, session := range tt.configSessions {
154+
mockHome.On("ExpandHome", session.Path).Return(session.Path, nil)
155+
}
156+
}
157+
if tt.tmuxinatorConfigs != nil {
158+
mockTmuxinator.On("List").Return(tt.tmuxinatorConfigs, nil)
159+
}
160+
161+
config := model.Config{
162+
SessionConfigs: tt.configSessions,
163+
}
164+
165+
lister := NewLister(config, mockHome, mockTmux, mockZoxide, mockTmuxinator)
166+
167+
// Call the actual List function with HideDuplicates
168+
result, err := lister.List(ListOptions{
169+
Tmux: tt.tmuxSessions != nil,
170+
Zoxide: tt.zoxideResults != nil,
171+
Config: tt.configSessions != nil,
172+
Tmuxinator: tt.tmuxinatorConfigs != nil,
173+
HideDuplicates: true,
174+
})
175+
176+
assert.NoError(t, err)
177+
assert.Equal(t, len(tt.expectedNames), len(result.OrderedIndex))
178+
179+
for i, expectedName := range tt.expectedNames {
180+
if i < len(result.OrderedIndex) {
181+
session := result.Directory[result.OrderedIndex[i]]
182+
assert.Equal(t, expectedName, session.Name)
183+
}
184+
}
185+
186+
// Verify all mocks were called
187+
mockTmux.AssertExpectations(t)
188+
mockZoxide.AssertExpectations(t)
189+
mockTmuxinator.AssertExpectations(t)
190+
})
191+
}
6192
}

0 commit comments

Comments
 (0)