v0.33.1 9577fee

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}

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