v0.30.0 eb3068f

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
156func TestOmniboxLoadInitialHistory_UsesCapturedInitialBehavior(t *testing.T) {
157	repo := repomocks.NewMockHistoryRepository(t)
158	done := make(chan struct{})
159	repo.EXPECT().GetRecent(mock.Anything, mock.Anything, mock.Anything).RunAndReturn(
160		func(context.Context, int, int) ([]*entity.HistoryEntry, error) {
161			close(done)
162			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
163		},
164	)
165
166	o := &Omnibox{
167		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
168		initialBehavior: entity.OmniboxInitialBehaviorRecent,
169		ctx:             context.Background(),
170	}
171
172	o.loadInitialHistory(1)
173	o.initialBehavior = entity.OmniboxInitialBehaviorMostVisited
174
175	select {
176	case <-done:
177	case <-time.After(2 * time.Second):
178		t.Fatal("timed out waiting for recent-history load")
179	}
180}
181
182func TestOmniboxLoadInitialHistory_MostVisitedUsesThirtyDayWindow(t *testing.T) {
183	repo := repomocks.NewMockHistoryRepository(t)
184	done := make(chan struct{})
185	repo.EXPECT().GetMostVisited(mock.Anything, 30).RunAndReturn(
186		func(context.Context, int) ([]*entity.HistoryEntry, error) {
187			close(done)
188			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
189		},
190	)
191
192	o := &Omnibox{
193		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
194		initialBehavior: entity.OmniboxInitialBehaviorMostVisited,
195		mostVisitedDays: 30,
196		ctx:             context.Background(),
197	}
198
199	o.loadInitialHistory(1)
200
201	select {
202	case <-done:
203	case <-time.After(2 * time.Second):
204		t.Fatal("timed out waiting for most-visited load")
205	}
206}
207
208func TestOmniboxLoadInitialHistory_MostVisitedUsesConfiguredWindow(t *testing.T) {
209	repo := repomocks.NewMockHistoryRepository(t)
210	done := make(chan struct{})
211	repo.EXPECT().GetMostVisited(mock.Anything, 7).RunAndReturn(
212		func(context.Context, int) ([]*entity.HistoryEntry, error) {
213			close(done)
214			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
215		},
216	)
217
218	o := &Omnibox{
219		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
220		initialBehavior: entity.OmniboxInitialBehaviorMostVisited,
221		mostVisitedDays: 7,
222		ctx:             context.Background(),
223	}
224
225	o.loadInitialHistory(1)
226
227	select {
228	case <-done:
229	case <-time.After(2 * time.Second):
230		t.Fatal("timed out waiting for configured most-visited load")
231	}
232}
233
234func TestOmniboxLoadInitialHistory_MostVisitedZeroWindowUsesAllHistory(t *testing.T) {
235	repo := repomocks.NewMockHistoryRepository(t)
236	done := make(chan struct{})
237	repo.EXPECT().GetAllMostVisited(mock.Anything).RunAndReturn(
238		func(context.Context) ([]*entity.HistoryEntry, error) {
239			close(done)
240			return []*entity.HistoryEntry{{URL: "https://example.com"}}, nil
241		},
242	)
243
244	o := &Omnibox{
245		historyUC:       appusecase.NewSearchHistoryUseCase(repo),
246		initialBehavior: entity.OmniboxInitialBehaviorMostVisited,
247		mostVisitedDays: 0,
248		ctx:             context.Background(),
249	}
250
251	o.loadInitialHistory(1)
252
253	select {
254	case <-done:
255	case <-time.After(2 * time.Second):
256		t.Fatal("timed out waiting for all-history most-visited load")
257	}
258}

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