browser_window_sidebar_laziness_test.go RawHistoryFolder
1package ui
2
3import (
4 "context"
5 "sync/atomic"
6 "testing"
7
8 "github.com/bnema/dumber/internal/application/dto"
9 "github.com/bnema/dumber/internal/application/usecase"
10 "github.com/bnema/dumber/internal/ui/component"
11 "github.com/bnema/dumber/internal/ui/window"
12 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"
14)
15
16func TestWindowCreation_DoesNotConstructHiddenSidebars(t *testing.T) {
17 ctx := context.Background()
18 app := &App{
19 deps: &Dependencies{
20 HistoryUC: usecase.NewSearchHistoryUseCase(nil),
21 FavoritesUC: usecase.NewManageFavoritesUseCase(nil, nil),
22 },
23 browserWindows: map[string]*browserWindow{},
24 }
25 app.browserWindowFactory = func(ctx context.Context, url string) (*browserWindow, error) {
26 return &browserWindow{id: "w-1", initialURL: url, mainWindow: &window.MainWindow{}}, nil
27 }
28
29 created, err := app.createBrowserWindow(ctx, "https://example.com")
30 require.NoError(t, err)
31 require.NotNil(t, created)
32 assert.Nil(t, created.historySidebar, "window creation must not construct the hidden history panel")
33 assert.Nil(t, created.favoritesSidebar, "window creation must not construct the hidden favorites panel")
34 assert.False(t, created.sidebarVisible)
35 assert.Equal(t, nativeSidebarNone, created.activeSidebarKind)
36}
37
38func TestEnsureSidebars_ConstructOnceAndReuse(t *testing.T) {
39 ctx := context.Background()
40 var historyConstructions atomic.Int64
41 var favoritesConstructions atomic.Int64
42 releaseHistorySeam := stubHistorySidebarConstructor(&historyConstructions)
43 defer releaseHistorySeam()
44 releaseFavoritesSeam := stubFavoritesSidebarConstructor(&favoritesConstructions)
45 defer releaseFavoritesSeam()
46
47 app := &App{
48 deps: &Dependencies{
49 HistoryUC: usecase.NewSearchHistoryUseCase(nil),
50 FavoritesUC: usecase.NewManageFavoritesUseCase(nil, nil),
51 },
52 browserWindows: map[string]*browserWindow{},
53 }
54 bw := &browserWindow{id: "w-1", mainWindow: &window.MainWindow{}}
55 app.browserWindows[bw.id] = bw
56
57 require.True(t, bw.ensureHistorySidebar(ctx, app))
58 require.True(t, bw.ensureFavoritesSidebar(ctx, app))
59 assert.EqualValues(t, 1, historyConstructions.Load())
60 assert.EqualValues(t, 1, favoritesConstructions.Load())
61
62 // Reopening resolves the same instance without reconstructing.
63 firstHistory := bw.historySidebar
64 firstFavorites := bw.favoritesSidebar
65 require.True(t, bw.ensureHistorySidebar(ctx, app))
66 require.True(t, bw.ensureFavoritesSidebar(ctx, app))
67 assert.Same(t, firstHistory, bw.historySidebar)
68 assert.Same(t, firstFavorites, bw.favoritesSidebar)
69 assert.EqualValues(t, 1, historyConstructions.Load())
70 assert.EqualValues(t, 1, favoritesConstructions.Load())
71}
72
73func TestEnsureSidebars_UnavailableWithoutDeps(t *testing.T) {
74 ctx := context.Background()
75 bw := &browserWindow{id: "w-1", mainWindow: &window.MainWindow{}}
76 assert.False(t, bw.ensureHistorySidebar(ctx, &App{}))
77 assert.False(t, bw.ensureFavoritesSidebar(ctx, &App{}))
78 assert.False(t, (&browserWindow{}).ensureHistorySidebar(ctx, &App{deps: &Dependencies{}}))
79 assert.False(t, (&browserWindow{}).ensureFavoritesSidebar(ctx, &App{deps: &Dependencies{}}))
80 assert.False(t, (*browserWindow)(nil).ensureHistorySidebar(ctx, &App{}))
81 assert.False(t, (*browserWindow)(nil).ensureFavoritesSidebar(ctx, &App{}))
82}
83
84func TestToggleActions_EnsureOnDemandAndHideWithoutConstruction(t *testing.T) {
85 ctx := context.Background()
86 var historyConstructions atomic.Int64
87 releaseHistorySeam := stubHistorySidebarConstructor(&historyConstructions)
88 defer releaseHistorySeam()
89
90 // Close-before-open does nothing and constructs nothing.
91 quiet := &browserWindow{id: "quiet", mainWindow: &window.MainWindow{}}
92 quiet.hideHistorySidebar()
93 quiet.hideFavoritesSidebar()
94 quiet.toggleHistorySidebar()
95 quiet.toggleFavoritesSidebar()
96 assert.False(t, quiet.sidebarVisible)
97 assert.Zero(t, historyConstructions.Load())
98
99 // Keyboard shortcut constructs on first open.
100 app := &App{
101 deps: &Dependencies{HistoryUC: usecase.NewSearchHistoryUseCase(nil)},
102 browserWindows: map[string]*browserWindow{quiet.id: quiet},
103 lastFocusedWindowID: quiet.id,
104 }
105 require.NoError(t, app.toggleHistorySidebarAction(ctx))
106 assert.EqualValues(t, 1, historyConstructions.Load())
107 assert.True(t, quiet.sidebarVisible)
108 assert.Equal(t, nativeSidebarHistory, quiet.activeSidebarKind)
109
110 // Closing the visible panel hides without further construction.
111 require.NoError(t, app.toggleHistorySidebarAction(ctx))
112 assert.False(t, quiet.sidebarVisible)
113 assert.Equal(t, nativeSidebarNone, quiet.activeSidebarKind)
114 assert.EqualValues(t, 1, historyConstructions.Load())
115
116 // Reopening reuses the retained instance.
117 require.NoError(t, app.toggleHistorySidebarAction(ctx))
118 assert.True(t, quiet.sidebarVisible)
119 assert.EqualValues(t, 1, historyConstructions.Load())
120}
121
122func TestToggleActions_ErrorWhenUnavailable(t *testing.T) {
123 ctx := context.Background()
124 app := &App{browserWindows: map[string]*browserWindow{"w": {id: "w"}}, lastFocusedWindowID: "w"}
125 require.ErrorContains(t, app.toggleHistorySidebarAction(ctx), "history sidebar unavailable")
126 require.ErrorContains(t, app.toggleFavoritesSidebarAction(ctx), "favorites sidebar unavailable")
127
128 empty := &App{}
129 require.ErrorContains(t, empty.toggleHistorySidebarAction(ctx), "no focused browser window")
130 require.ErrorContains(t, empty.toggleFavoritesSidebarAction(ctx), "no focused browser window")
131}
132
133func TestSidebarConfigReload_DoesNotConstructPanels(t *testing.T) {
134 ctx := context.Background()
135 bw := &browserWindow{id: "w-1", mainWindow: &window.MainWindow{}}
136 app := &App{
137 deps: &Dependencies{
138 HistoryUC: usecase.NewSearchHistoryUseCase(nil),
139 FavoritesUC: usecase.NewManageFavoritesUseCase(nil, nil),
140 },
141 browserWindows: map[string]*browserWindow{bw.id: bw},
142 lastFocusedWindowID: bw.id,
143 }
144 app.runtimeConfig = newRuntimeConfigState(nil)
145
146 app.applyRuntimeConfigChange(ctx, app.runtimeConfigSnapshot())
147 assert.Nil(t, bw.historySidebar)
148 assert.Nil(t, bw.favoritesSidebar)
149
150 app.reloadVisibleFavoritesSidebars("config-reload")
151 app.refreshVisibleHistorySidebars(dto.HistoryChange{})
152 assert.Nil(t, bw.historySidebar)
153 assert.Nil(t, bw.favoritesSidebar)
154}
155
156func TestEnsureSidebars_MultipleWindowsIndependent(t *testing.T) {
157 ctx := context.Background()
158 var historyConstructions atomic.Int64
159 releaseHistorySeam := stubHistorySidebarConstructor(&historyConstructions)
160 defer releaseHistorySeam()
161
162 app := &App{
163 deps: &Dependencies{HistoryUC: usecase.NewSearchHistoryUseCase(nil)},
164 browserWindows: map[string]*browserWindow{},
165 }
166 first := &browserWindow{id: "w-1", mainWindow: &window.MainWindow{}}
167 second := &browserWindow{id: "w-2", mainWindow: &window.MainWindow{}}
168 app.browserWindows[first.id] = first
169 app.browserWindows[second.id] = second
170
171 require.True(t, first.ensureHistorySidebar(ctx, app))
172 assert.NotNil(t, first.historySidebar)
173 assert.Nil(t, second.historySidebar)
174 assert.EqualValues(t, 1, historyConstructions.Load())
175}
176
177func stubHistorySidebarConstructor(calls *atomic.Int64) func() {
178 previous := newHistorySidebarComponent
179 newHistorySidebarComponent = func(ctx context.Context, cfg component.HistorySidebarConfig) *component.HistorySidebar {
180 calls.Add(1)
181 return &component.HistorySidebar{}
182 }
183 return func() { newHistorySidebarComponent = previous }
184}
185
186func stubFavoritesSidebarConstructor(calls *atomic.Int64) func() {
187 previous := newFavoritesSidebarComponent
188 newFavoritesSidebarComponent = func(ctx context.Context, cfg component.FavoritesSidebarConfig) *component.FavoritesSidebar {
189 calls.Add(1)
190 return &component.FavoritesSidebar{}
191 }
192 return func() { newFavoritesSidebarComponent = previous }
193}