v0.27.2 5960dd8

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", output.Suggestion.FullText)
59	assert.Equal(t, ".com", 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	historyEntries := []*entity.HistoryEntry{
75		{URL: "https://news.ycombinator.com", Title: "Hacker News"},
76	}
77	historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return(historyEntries, 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().GetRecent(mock.Anything, mock.Anything, 0).Return([]*entity.HistoryEntry{}, 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_ResolveCompletion_VisibleURLsOrder(t *testing.T) {
152	ctx := testContext()
153	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
154
155	urls := []string{
156		"https://bnema.dev",
157		"https://bnema.io",
158	}
159
160	suggestion := uc.ResolveCompletion(ctx, "bnem", usecase.CompletionOptions{
161		VisibleURLs: urls,
162		AllowBangs:  false,
163	})
164
165	require.NotNil(t, suggestion)
166	assert.Equal(t, "bnema.dev", suggestion.FullText)
167	assert.Equal(t, "a.dev", suggestion.Suffix)
168}
169
170func TestAutocompleteUseCase_ResolveCompletion_PrefersShortHostForGoo(t *testing.T) {
171	ctx := testContext()
172	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
173
174	urls := []string{
175		"https://www.google.com/url?q=https://dashboard.stripe.com",
176		"https://google.com",
177	}
178
179	suggestion := uc.ResolveCompletion(ctx, "goo", usecase.CompletionOptions{
180		VisibleURLs: urls,
181		AllowBangs:  false,
182	})
183
184	require.NotNil(t, suggestion)
185	assert.Equal(t, "google.com", suggestion.FullText)
186	assert.Equal(t, "gle.com", suggestion.Suffix)
187}
188
189func TestAutocompleteUseCase_GetSuggestion_FavoritesCached(t *testing.T) {
190	ctx := testContext()
191
192	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
193	folderRepo := repomocks.NewMockFolderRepository(t)
194	tagRepo := repomocks.NewMockTagRepository(t)
195
196	favorites := []*entity.Favorite{
197		{ID: 1, URL: "https://github.com/user/repo", Title: "Repo"},
198	}
199	favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil).Once()
200
201	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
202	uc := usecase.NewAutocompleteUseCase(nil, favoritesUC, nil)
203
204	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
205	require.NotNil(t, output)
206	assert.True(t, output.Found)
207
208	output = uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
209	require.NotNil(t, output)
210	assert.True(t, output.Found)
211}
212
213func TestAutocompleteUseCase_GetSuggestion_HistoryCached(t *testing.T) {
214	ctx := testContext()
215
216	historyRepo := repomocks.NewMockHistoryRepository(t)
217	historyEntries := []*entity.HistoryEntry{
218		{URL: "https://news.ycombinator.com", Title: "Hacker News"},
219	}
220	historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return(historyEntries, nil).Once()
221
222	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
223	uc := usecase.NewAutocompleteUseCase(historyUC, nil, nil)
224
225	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "news"})
226	require.NotNil(t, output)
227	assert.True(t, output.Found)
228
229	output = uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "news"})
230	require.NotNil(t, output)
231	assert.True(t, output.Found)
232}
233
234func TestAutocompleteUseCase_GetSuggestionForURL_EmptyInput(t *testing.T) {
235	ctx := testContext()
236	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
237
238	tests := []struct {
239		name      string
240		input     string
241		targetURL string
242	}{
243		{name: "empty input", input: "", targetURL: "https://example.com"},
244		{name: "empty URL", input: "exa", targetURL: ""},
245		{name: "both empty", input: "", targetURL: ""},
246	}
247
248	for _, tt := range tests {
249		t.Run(tt.name, func(t *testing.T) {
250			output := uc.GetSuggestionForURL(ctx, tt.input, tt.targetURL)
251			require.NotNil(t, output)
252			assert.False(t, output.Found)
253		})
254	}
255}
256
257func TestAutocompleteUseCase_GetSuggestionForURL_Match(t *testing.T) {
258	ctx := testContext()
259	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
260
261	tests := []struct {
262		name           string
263		input          string
264		targetURL      string
265		wantSuffix     string
266		wantMatchedURL string
267	}{
268		{
269			name:           "prefix match without protocol",
270			input:          "github",
271			targetURL:      "https://github.com/user/repo",
272			wantSuffix:     ".com/user/repo",
273			wantMatchedURL: "github.com/user/repo",
274		},
275		{
276			name:           "prefix match with protocol",
277			input:          "https://github",
278			targetURL:      "https://github.com/user/repo",
279			wantSuffix:     ".com/user/repo",
280			wantMatchedURL: "https://github.com/user/repo",
281		},
282		{
283			name:           "case insensitive match",
284			input:          "GITHUB",
285			targetURL:      "https://github.com",
286			wantSuffix:     ".com",
287			wantMatchedURL: "github.com",
288		},
289	}
290
291	for _, tt := range tests {
292		t.Run(tt.name, func(t *testing.T) {
293			output := uc.GetSuggestionForURL(ctx, tt.input, tt.targetURL)
294			require.NotNil(t, output)
295			assert.True(t, output.Found)
296			require.NotNil(t, output.Suggestion)
297			assert.Equal(t, tt.wantSuffix, output.Suggestion.Suffix)
298			assert.Equal(t, tt.wantMatchedURL, output.Suggestion.FullText)
299		})
300	}
301}
302
303func TestAutocompleteUseCase_GetSuggestionForURL_NoMatch(t *testing.T) {
304	ctx := testContext()
305	uc := usecase.NewAutocompleteUseCase(nil, nil, nil)
306
307	output := uc.GetSuggestionForURL(ctx, "xyz", "https://github.com")
308
309	require.NotNil(t, output)
310	assert.False(t, output.Found)
311}
312
313func TestAutocompleteUseCase_GetSuggestion_FavoritesOverHistory(t *testing.T) {
314	ctx := testContext()
315
316	// Both favorites and history have matches - favorites should win
317	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
318	folderRepo := repomocks.NewMockFolderRepository(t)
319	tagRepo := repomocks.NewMockTagRepository(t)
320	favorites := []*entity.Favorite{
321		{ID: 1, URL: "https://github.com/favorite", Title: "Favorite Repo"},
322	}
323	favoriteRepo.EXPECT().GetAll(mock.Anything).Return(favorites, nil)
324
325	// History would also match, but should not be called because favorites matched
326	historyRepo := repomocks.NewMockHistoryRepository(t)
327	// Note: no EXPECT on historyRepo - it shouldn't be called
328
329	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
330	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
331	uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
332
333	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "github"})
334
335	require.NotNil(t, output)
336	assert.True(t, output.Found)
337	assert.Equal(t, autocomplete.SourceFavorite, output.Suggestion.Source)
338	assert.Equal(t, "Favorite Repo", output.Suggestion.Title)
339}
340
341func TestAutocompleteUseCase_GetSuggestion_HistoryNotPrefixMatch(t *testing.T) {
342	ctx := testContext()
343
344	// Favorites returns empty
345	favoriteRepo := repomocks.NewMockFavoriteRepository(t)
346	folderRepo := repomocks.NewMockFolderRepository(t)
347	tagRepo := repomocks.NewMockTagRepository(t)
348	favoriteRepo.EXPECT().GetAll(mock.Anything).Return([]*entity.Favorite{}, nil)
349
350	// History returns results but none are prefix matches
351	historyRepo := repomocks.NewMockHistoryRepository(t)
352	historyEntries := []*entity.HistoryEntry{
353		// URL doesn't start with "git", so no prefix match
354		{URL: "https://example.com/git", Title: "Example"},
355	}
356	historyRepo.EXPECT().GetRecent(mock.Anything, mock.Anything, 0).Return(historyEntries, nil)
357
358	favoritesUC := usecase.NewManageFavoritesUseCase(favoriteRepo, folderRepo, tagRepo)
359	historyUC := usecase.NewSearchHistoryUseCase(historyRepo)
360	uc := usecase.NewAutocompleteUseCase(historyUC, favoritesUC, nil)
361
362	output := uc.GetSuggestion(ctx, usecase.GetSuggestionInput{Input: "git"})
363
364	require.NotNil(t, output)
365	assert.False(t, output.Found)
366}

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