v0.26.0 ae3a77a

autocomplete_test.go RawHistoryFolder

1package usecase_test
2
3import (
4	"testing"
5
6	"github.com/bnema/dumber/internal/application/usecase"
7	"github.com/bnema/dumber/internal/domain/autocomplete"
8	"github.com/bnema/dumber/internal/domain/entity"
9	repomocks "github.com/bnema/dumber/internal/domain/repository/mocks"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/mock"
12	"github.com/stretchr/testify/require"
13)
14
15func TestAutocompleteUseCase_GetSuggestion_EmptyInput(t *testing.T) {
16	ctx := testContext()
17	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
18
19	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: ""})
20
21	require.NotNil(t, output)
22	assert.False(t, output.Found)
23	assert.Nil(t, output.Suggestion)
24}
25
26func TestAutocompleteUseCase_GetSuggestion_NilDependencies(t *testing.T) {
27	ctx := testContext()
28	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
29
30	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
31
32	require.NotNil(t, output)
33	assert.False(t, output.Found)
34}
35
36func TestAutocompleteUseCase_GetSuggestion_FavoritesPriority(t *testing.T) {
37	ctx := testContext()
38
39	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
40	folderRepo := repomocks.NewMockFolderRepository(t)
41	tagRepo := repomocks.NewMockTagRepository(t)
42
43	favorites := []*entity.Favorite{
44		{ID: 1, URL: "https://github.com/user/repo", Title: "My Repo"},
45		{ID: 2, URL: "https://google.com", Title: "Google"},
46	}
47	favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
48
49	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
50	uc := usecase.NewAutocompleteUseCase(nil, favoritesUC, nil)
51
52	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
53
54	require.NotNil(t, output)
55	assert.True(t, output.Found)
56	assert.NotNil(t, output.Suggestion)
57	assert.Equal(t, autocomplete.SourceFavorite, output.Suggestion.Source)
58	assert.Equal(t, "github.com/user/repo", output.Suggestion.FullText)
59	assert.Equal(t, ".com/user/repo", output.Suggestion.Suffix)
60	assert.Equal(t, "My Repo", output.Suggestion.Title)
61}
62
63func TestAutocompleteUseCase_GetSuggestion_HistoryFallback(t *testing.T) {
64	ctx := testContext()
65
66	// Favorites returns empty
67	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
68	folderRepo := repomocks.NewMockFolderRepository(t)
69	tagRepo := repomocks.NewMockTagRepository(t)
70	favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
71
72	// History returns a match
73	historyRepo := repomocks.NewMockHistoryRepository(t)
74	historyMatches := []entity.HistoryMatch{
75		{Entry: &entity.HistoryEntry{URL: "https://news.ycombinator.com", Title: "Hacker News"}},
76	}
77	historyRepo.EXPECT().Search(mock.Anything, "news", 10).Return(historyMatches, nil)
78
79	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
80	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
81	uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
82
83	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "news"})
84
85	require.NotNil(t, output)
86	assert.True(t, output.Found)
87	assert.NotNil(t, output.Suggestion)
88	assert.Equal(t, autocomplete.SourceHistory, output.Suggestion.Source)
89	assert.Equal(t, "news.ycombinator.com", output.Suggestion.FullText)
90	assert.Equal(t, ".ycombinator.com", output.Suggestion.Suffix)
91}
92
93func TestAutocompleteUseCase_GetSuggestion_BangShortcut(t *testing.T) {
94	ctx := testContext()
95
96	shortcuts := map[string]usecase.SearchShortcut{
97		"gh":  {URL: "https://github.com/search?q=%s", Description: "GitHub search"},
98		"ddg": {URL: "https://duckduckgo.com/?q=%s", Description: "DuckDuckGo"},
99	}
100	shortcutsUC := usecase.NewSearchShortcutsUseCase(shortcuts)
101	uc := usecase.NewAutocompleteUseCase(nil, nil, shortcutsUC)
102
103	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "!g"})
104
105	require.NotNil(t, output)
106	assert.True(t, output.Found)
107	assert.NotNil(t, output.Suggestion)
108	assert.Equal(t, autocomplete.SourceBangShortcut, output.Suggestion.Source)
109	assert.Equal(t, "!gh", output.Suggestion.FullText)
110	assert.Equal(t, "h", output.Suggestion.Suffix)
111	assert.Equal(t, "GitHub search", output.Suggestion.Title)
112}
113
114func TestAutocompleteUseCase_GetSuggestion_BangWithoutPrefix(t *testing.T) {
115	ctx := testContext()
116
117	shortcuts := map[string]usecase.SearchShortcut{
118		"gh": {URL: "https://github.com/search?q=%s", Description: "GitHub search"},
119	}
120	shortcutsUC := usecase.NewSearchShortcutsUseCase(shortcuts)
121	uc := usecase.NewAutocompleteUseCase(nil, nil, shortcutsUC)
122
123	// Input without ! should not match bang shortcuts
124	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "gh"})
125
126	require.NotNil(t, output)
127	assert.False(t, output.Found)
128}
129
130func TestAutocompleteUseCase_GetSuggestion_NoMatch(t *testing.T) {
131	ctx := testContext()
132
133	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
134	folderRepo := repomocks.NewMockFolderRepository(t)
135	tagRepo := repomocks.NewMockTagRepository(t)
136	favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
137
138	historyRepo := repomocks.NewMockHistoryRepository(t)
139	historyRepo.EXPECT().Search(mock.Anything, "xyz123", 10).Return([]entity.HistoryMatch{}, nil)
140
141	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
142	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
143	uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
144
145	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "xyz123"})
146
147	require.NotNil(t, output)
148	assert.False(t, output.Found)
149}
150
151func TestAutocompleteUseCase_GetSuggestionForURL_EmptyInput(t *testing.T) {
152	ctx := testContext()
153	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
154
155	tests := []struct {
156		name      string
157		input     string
158		targetURL string
159	}{
160		{name: "empty input", input: "", targetURL: "https://example.com"},
161		{name: "empty URL", input: "exa", targetURL: ""},
162		{name: "both empty", input: "", targetURL: ""},
163	}
164
165	for _, tt := range tests {
166		t.Run(tt.name, func(t *testing.T) {
167			output := uc.GetSuggestionForURL(ctx, tt.input, tt.targetURL)
168			require.NotNil(t, output)
169			assert.False(t, output.Found)
170		})
171	}
172}
173
174func TestAutocompleteUseCase_GetSuggestionForURL_Match(t *testing.T) {
175	ctx := testContext()
176	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
177
178	tests := []struct {
179		name           string
180		input          string
181		targetURL      string
182		wantSuffix     string
183		wantMatchedURL string
184	}{
185		{
186			name:           "prefix match without protocol",
187			input:          "github",
188			targetURL:      "https://github.com/user/repo",
189			wantSuffix:     ".com/user/repo",
190			wantMatchedURL: "github.com/user/repo",
191		},
192		{
193			name:           "prefix match with protocol",
194			input:          "https://github",
195			targetURL:      "https://github.com/user/repo",
196			wantSuffix:     ".com/user/repo",
197			wantMatchedURL: "https://github.com/user/repo",
198		},
199		{
200			name:           "case insensitive match",
201			input:          "GITHUB",
202			targetURL:      "https://github.com",
203			wantSuffix:     ".com",
204			wantMatchedURL: "github.com",
205		},
206	}
207
208	for _, tt := range tests {
209		t.Run(tt.name, func(t *testing.T) {
210			output := uc.GetSuggestionForURL(ctx, tt.input, tt.targetURL)
211			require.NotNil(t, output)
212			assert.True(t, output.Found)
213			require.NotNil(t, output.Suggestion)
214			assert.Equal(t, tt.wantSuffix, output.Suggestion.Suffix)
215			assert.Equal(t, tt.wantMatchedURL, output.Suggestion.FullText)
216		})
217	}
218}
219
220func TestAutocompleteUseCase_GetSuggestionForURL_NoMatch(t *testing.T) {
221	ctx := testContext()
222	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
223
224	output := uc.GetSuggestionForURL(ctx, "xyz", "https://github.com")
225
226	require.NotNil(t, output)
227	assert.False(t, output.Found)
228}
229
230func TestAutocompleteUseCase_GetSuggestion_FavoritesOverHistory(t *testing.T) {
231	ctx := testContext()
232
233	// Both favorites and history have matches - favorites should win
234	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
235	folderRepo := repomocks.NewMockFolderRepository(t)
236	tagRepo := repomocks.NewMockTagRepository(t)
237	favorites := []*entity.Favorite{
238		{ID: 1, URL: "https://github.com/favorite", Title: "Favorite Repo"},
239	}
240	favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
241
242	// History would also match, but should not be called because favorites matched
243	historyRepo := repomocks.NewMockHistoryRepository(t)
244	// Note: no EXPECT on historyRepo - it shouldn't be called
245
246	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
247	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
248	uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
249
250	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
251
252	require.NotNil(t, output)
253	assert.True(t, output.Found)
254	assert.Equal(t, autocomplete.SourceFavorite, output.Suggestion.Source)
255	assert.Equal(t, "Favorite Repo", output.Suggestion.Title)
256}
257
258func TestAutocompleteUseCase_GetSuggestion_HistoryNotPrefixMatch(t *testing.T) {
259	ctx := testContext()
260
261	// Favorites returns empty
262	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
263	folderRepo := repomocks.NewMockFolderRepository(t)
264	tagRepo := repomocks.NewMockTagRepository(t)
265	favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
266
267	// History returns results but none are prefix matches
268	historyRepo := repomocks.NewMockHistoryRepository(t)
269	historyMatches := []entity.HistoryMatch{
270		// URL doesn't start with "git", so no prefix match
271		{Entry: &entity.HistoryEntry{URL: "https://example.com/git", Title: "Example"}},
272	}
273	historyRepo.EXPECT().Search(mock.Anything, "git", 10).Return(historyMatches, nil)
274
275	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
276	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
277	uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
278
279	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "git"})
280
281	require.NotNil(t, output)
282	assert.False(t, output.Found)
283}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close