v0.32.0 0617278

omnibox_initial_behavior_test.go RawHistoryFolder

1package component
2
3import (
4	"context"
5	"errors"
6	"testing"
7	"time"
8
9	appusecase "github.com/bnema/dumber/internal/application/usecase"
10	"github.com/bnema/dumber/internal/domain/entity"
11	repomocks "github.com/bnema/dumber/internal/domain/repository/mocks"
12	"github.com/stretchr/testify/assert"
13	"github.com/stretchr/testify/mock"
14)
15
16func TestOmniboxInitialBehaviorBadgeState(t *testing.T) {
17	tests := []struct {
18		name        string
19		behavior    entity.OmniboxInitialBehavior
20		wantVisible bool
21		wantLabel   string
22		wantTooltip string
23		wantNext    entity.OmniboxInitialBehavior
24	}{
25		{
26			name:        "recent",
27			behavior:    entity.OmniboxInitialBehaviorRecent,
28			wantVisible: true,
29			wantLabel:   "Recent",
30			wantTooltip: "Toggle default history order (Ctrl+R)",
31			wantNext:    entity.OmniboxInitialBehaviorMostVisited,
32		},
33		{
34			name:        "most visited",
35			behavior:    entity.OmniboxInitialBehaviorMostVisited,
36			wantVisible: true,
37			wantLabel:   "Most used",
38			wantTooltip: "Toggle default history order (Ctrl+R)",
39			wantNext:    entity.OmniboxInitialBehaviorRecent,
40		},
41		{
42			name:     "none",
43			behavior: entity.OmniboxInitialBehaviorNone,
44		},
45		{
46			name:     "invalid",
47			behavior: entity.OmniboxInitialBehavior("unsupported"),
48		},
49	}
50
51	for _, tt := range tests {
52		t.Run(tt.name, func(t *testing.T) {
53			got := initialBehaviorBadgeState(tt.behavior)
54			assert.Equal(t, tt.wantVisible, got.visible)
55			assert.Equal(t, tt.wantLabel, got.label)
56			assert.Equal(t, tt.wantTooltip, got.tooltip)
57			assert.Equal(t, tt.wantNext, got.nextBehavior)
58		})
59	}
60}
61
62func TestShouldRefreshInitialHistoryAfterToggle(t *testing.T) {
63	tests := []struct {
64		name        string
65		viewMode    ViewMode
66		query       string
67		behavior    entity.OmniboxInitialBehavior
68		wantRefresh bool
69	}{
70		{
71			name:        "empty history mode",
72			viewMode:    ViewModeHistory,
73			query:       "",
74			behavior:    entity.OmniboxInitialBehaviorRecent,
75			wantRefresh: true,
76		},
77		{
78			name:        "typed query",
79			viewMode:    ViewModeHistory,
80			query:       "go",
81			behavior:    entity.OmniboxInitialBehaviorRecent,
82			wantRefresh: false,
83		},
84		{
85			name:        "favorites mode",
86			viewMode:    ViewModeFavorites,
87			query:       "",
88			behavior:    entity.OmniboxInitialBehaviorRecent,
89			wantRefresh: false,
90		},
91		{
92			name:        "unsupported behavior",
93			viewMode:    ViewModeHistory,
94			query:       "",
95			behavior:    entity.OmniboxInitialBehaviorNone,
96			wantRefresh: false,
97		},
98	}
99
100	for _, tt := range tests {
101		t.Run(tt.name, func(t *testing.T) {
102			assert.Equal(t, tt.wantRefresh, shouldRefreshInitialHistoryAfterToggle(tt.viewMode, tt.query, tt.behavior))
103		})
104	}
105}
106
107func TestOmniboxToggleInitialBehaviorPreference_SaveFailureKeepsBehaviorAndShowsToast(t *testing.T) {
108	o := &Omnibox{initialBehavior: entity.OmniboxInitialBehaviorRecent, ctx: context.Background()}
109	o.saveInitialBehaviorFn = func(context.Context, entity.OmniboxInitialBehavior) error {
110		return errors.New("boom")
111	}
112
113	var gotMessage string
114	var gotLevel ToastLevel
115	o.onToast = func(_ context.Context, message string, level ToastLevel) {
116		gotMessage = message
117		gotLevel = level
118	}
119
120	refreshCalls := 0
121	origLoadInitialHistoryFn := loadInitialHistoryFn
122	loadInitialHistoryFn = func(*Omnibox, uint64) {
123		refreshCalls++
124	}
125	t.Cleanup(func() {
126		loadInitialHistoryFn = origLoadInitialHistoryFn
127	})
128
129	assert.False(t, o.toggleInitialBehaviorPreference())
130	assert.Equal(t, entity.OmniboxInitialBehaviorRecent, o.initialBehavior)
131	assert.Equal(t, "Failed to save default history order", gotMessage)
132	assert.Equal(t, ToastError, gotLevel)
133	assert.Equal(t, 0, refreshCalls)
134}
135
136func TestOmniboxToggleInitialBehaviorPreference_RefreshesEmptyHistory(t *testing.T) {
137	o := &Omnibox{initialBehavior: entity.OmniboxInitialBehaviorRecent, viewMode: ViewModeHistory, ctx: context.Background()}
138	o.saveInitialBehaviorFn = func(context.Context, entity.OmniboxInitialBehavior) error {
139		return nil
140	}
141
142	refreshCalls := 0
143	origLoadInitialHistoryFn := loadInitialHistoryFn
144	loadInitialHistoryFn = func(*Omnibox, uint64) {
145		refreshCalls++
146	}
147	t.Cleanup(func() {
148		loadInitialHistoryFn = origLoadInitialHistoryFn
149	})
150
151	assert.True(t, o.toggleInitialBehaviorPreference())
152	assert.Equal(t, entity.OmniboxInitialBehaviorMostVisited, o.initialBehavior)
153	assert.Equal(t, 1, refreshCalls)
154}
155
156// omniboxSuggestionUpdatesInline makes suggestion refreshes run synchronously
157// on the calling goroutine instead of through the GTK main loop.
158func omniboxSuggestionUpdatesInline(o *Omnibox) {
159	o.scheduleSuggestionUpdate = func(fn func()) { fn() }
160}
161
162func TestOmniboxLoadInitialHistory_UsesCapturedInitialBehavior(t *testing.T) {
163	repo := repomocks.NewMockHistoryRepository(t)
164	done := make(chan struct{})
165	repo.EXPECT().GetRecent(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
166		func(context.Context, int, int) ([]*entity.HistoryEntry, error) {
167			close(done)
168			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
169		},
170	)
171
172	o := &Omnibox{
173		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
174		initialBehavior: entity.OmniboxInitialBehaviorRecent,
175		ctx:             context.Background(),
176	}
177	omniboxSuggestionUpdatesInline(o)
178
179	o.loadInitialHistory(1)
180	o.initialBehavior = entity.OmniboxInitialBehaviorMostVisited
181
182	select {
183	case <-done:
184	case <-time.After(2 * time.Second):
185		t.Fatal("timed out waiting for recent-history load")
186	}
187}
188
189func TestOmniboxLoadInitialHistory_MostVisitedUsesThirtyDayWindow(t *testing.T) {
190	repo := repomocks.NewMockHistoryRepository(t)
191	done := make(chan struct{})
192	repo.EXPECT().GetMostVisited(mock.Anything, 30).RunAndReturn(
193		func(context.Context, int) ([]*entity.HistoryEntry, error) {
194			close(done)
195			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
196		},
197	)
198
199	o := &Omnibox{
200		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
201		initialBehavior: entity.OmniboxInitialBehaviorMostVisited,
202		mostVisitedDays: 30,
203		ctx:             context.Background(),
204	}
205	omniboxSuggestionUpdatesInline(o)
206
207	o.loadInitialHistory(1)
208
209	select {
210	case <-done:
211	case <-time.After(2 * time.Second):
212		t.Fatal("timed out waiting for most-visited load")
213	}
214}
215
216func TestOmniboxLoadInitialHistory_MostVisitedUsesConfiguredWindow(t *testing.T) {
217	repo := repomocks.NewMockHistoryRepository(t)
218	done := make(chan struct{})
219	repo.EXPECT().GetMostVisited(mock.Anything, 7).RunAndReturn(
220		func(context.Context, int) ([]*entity.HistoryEntry, error) {
221			close(done)
222			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
223		},
224	)
225
226	o := &Omnibox{
227		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
228		initialBehavior: entity.OmniboxInitialBehaviorMostVisited,
229		mostVisitedDays: 7,
230		ctx:             context.Background(),
231	}
232	omniboxSuggestionUpdatesInline(o)
233
234	o.loadInitialHistory(1)
235
236	select {
237	case <-done:
238	case <-time.After(2 * time.Second):
239		t.Fatal("timed out waiting for configured most-visited load")
240	}
241}
242
243func TestOmniboxLoadInitialHistory_MostVisitedZeroWindowUsesAllHistory(t *testing.T) {
244	repo := repomocks.NewMockHistoryRepository(t)
245	done := make(chan struct{})
246	repo.EXPECT().GetAllMostVisited(mock.Anything).RunAndReturn(
247		func(context.Context) ([]*entity.HistoryEntry, error) {
248			close(done)
249			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
250		},
251	)
252
253	o := &Omnibox{
254		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
255		initialBehavior: entity.OmniboxInitialBehaviorMostVisited,
256		mostVisitedDays: 0,
257		ctx:             context.Background(),
258	}
259	omniboxSuggestionUpdatesInline(o)
260
261	o.loadInitialHistory(1)
262
263	select {
264	case <-done:
265	case <-time.After(2 * time.Second):
266		t.Fatal("timed out waiting for all-history most-visited load")
267	}
268}

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