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}