v0.28.0 3cb3770

lifecycle_test.go RawHistoryFolder

1package content
2
3import (
4	"context"
5	"errors"
6	"testing"
7
8	"github.com/stretchr/testify/assert"
9	"github.com/stretchr/testify/mock"
10	"github.com/stretchr/testify/require"
11
12	"github.com/bnema/dumber/internal/application/port"
13	"github.com/bnema/dumber/internal/application/port/mocks"
14	"github.com/bnema/dumber/internal/domain/entity"
15)
16
17// newMinimalCoordinator returns a Coordinator with the maps required by
18// ReleaseWebView initialized so that calls to clearPendingAppearance,
19// titleMu / navOriginMu locks do not panic.
20func newMinimalCoordinator() *Coordinator {
21	return &Coordinator{
22		webViews:   make(map[entity.PaneID]port.WebView),
23		paneTitles: make(map[entity.PaneID]string),
24		navOrigins: make(map[entity.PaneID]string),
25	}
26}
27
28// ---------------------------------------------------------------------------
29// EnsureWebView
30// ---------------------------------------------------------------------------
31
32func TestLifecycle_EnsureWebView_ReusesExistingNonDestroyed(t *testing.T) {
33	t.Parallel()
34
35	wv := mocks.NewMockWebView(t)
36	wv.EXPECT().IsDestroyed().Return(false)
37
38	pool := mocks.NewMockWebViewPool(t)
39	// pool.Acquire must NOT be called
40
41	c := newMinimalCoordinator()
42	c.pool = pool
43	c.webViews[entity.PaneID("pane-1")] = wv
44
45	got, err := c.EnsureWebView(context.Background(), "pane-1")
46
47	require.NoError(t, err)
48	assert.Equal(t, wv, got)
49}
50
51func TestLifecycle_EnsureWebView_AcquiresFromPoolWhenNoneExists(t *testing.T) {
52	t.Parallel()
53
54	newWV := mocks.NewMockWebView(t)
55	newWV.EXPECT().SetCallbacks(mock.Anything).Maybe()
56	newWV.EXPECT().Generation().Return(uint64(0)).Maybe()
57
58	pool := mocks.NewMockWebViewPool(t)
59	pool.EXPECT().Acquire(mock.Anything).Return(newWV, nil)
60
61	c := newMinimalCoordinator()
62	c.pool = pool
63
64	got, err := c.EnsureWebView(context.Background(), "pane-1")
65
66	require.NoError(t, err)
67	assert.Equal(t, newWV, got)
68	assert.Equal(t, newWV, c.webViews[entity.PaneID("pane-1")])
69}
70
71func TestLifecycle_EnsureWebView_AcquiresFromPoolWhenExistingIsDestroyed(t *testing.T) {
72	t.Parallel()
73
74	oldWV := mocks.NewMockWebView(t)
75	oldWV.EXPECT().IsDestroyed().Return(true)
76
77	newWV := mocks.NewMockWebView(t)
78	newWV.EXPECT().SetCallbacks(mock.Anything).Maybe()
79	newWV.EXPECT().Generation().Return(uint64(0)).Maybe()
80
81	pool := mocks.NewMockWebViewPool(t)
82	pool.EXPECT().Acquire(mock.Anything).Return(newWV, nil)
83
84	c := newMinimalCoordinator()
85	c.pool = pool
86	c.webViews[entity.PaneID("pane-1")] = oldWV
87
88	got, err := c.EnsureWebView(context.Background(), "pane-1")
89
90	require.NoError(t, err)
91	assert.Equal(t, newWV, got)
92}
93
94func TestLifecycle_EnsureWebView_ErrorWhenPoolIsNil(t *testing.T) {
95	t.Parallel()
96
97	c := newMinimalCoordinator()
98	// pool remains nil
99
100	_, err := c.EnsureWebView(context.Background(), "pane-1")
101
102	require.Error(t, err)
103	assert.Contains(t, err.Error(), "webview pool not configured")
104}
105
106func TestLifecycle_EnsureWebView_ErrorWhenAcquireFails(t *testing.T) {
107	t.Parallel()
108
109	acquireErr := errors.New("pool exhausted")
110
111	pool := mocks.NewMockWebViewPool(t)
112	pool.EXPECT().Acquire(mock.Anything).Return(nil, acquireErr)
113
114	c := newMinimalCoordinator()
115	c.pool = pool
116
117	_, err := c.EnsureWebView(context.Background(), "pane-1")
118
119	require.Error(t, err)
120	assert.Equal(t, acquireErr, err)
121}
122
123// ---------------------------------------------------------------------------
124// ReleaseWebView
125// ---------------------------------------------------------------------------
126
127func TestLifecycle_ReleaseWebView_ReleasesToPool(t *testing.T) {
128	t.Parallel()
129
130	wv := mocks.NewMockWebView(t)
131	// idleInhibitor is nil so these won't be called
132	wv.EXPECT().IsFullscreen().Return(false).Maybe()
133	wv.EXPECT().IsPlayingAudio().Return(false).Maybe()
134
135	pool := mocks.NewMockWebViewPool(t)
136	pool.EXPECT().Release(wv)
137
138	c := newMinimalCoordinator()
139	c.pool = pool
140	c.webViews[entity.PaneID("pane-1")] = wv
141
142	c.ReleaseWebView(context.Background(), "pane-1")
143
144	_, exists := c.webViews[entity.PaneID("pane-1")]
145	assert.False(t, exists, "webview should be removed from map after release")
146}
147
148func TestLifecycle_ReleaseWebView_NoopForUnknownPane(t *testing.T) {
149	t.Parallel()
150
151	pool := mocks.NewMockWebViewPool(t)
152	// pool.Release must NOT be called
153
154	c := newMinimalCoordinator()
155	c.pool = pool
156
157	// Should not panic
158	c.ReleaseWebView(context.Background(), "unknown-pane")
159}
160
161func TestLifecycle_ReleaseWebView_UninhibitsIdleIfFullscreen(t *testing.T) {
162	t.Parallel()
163
164	wv := mocks.NewMockWebView(t)
165	wv.EXPECT().IsFullscreen().Return(true)
166	wv.EXPECT().IsPlayingAudio().Return(false)
167
168	inhibitor := mocks.NewMockIdleInhibitor(t)
169	inhibitor.EXPECT().Uninhibit(mock.Anything).Return(nil)
170
171	pool := mocks.NewMockWebViewPool(t)
172	pool.EXPECT().Release(wv)
173
174	c := newMinimalCoordinator()
175	c.pool = pool
176	c.idleInhibitor = inhibitor
177	c.webViews[entity.PaneID("pane-1")] = wv
178
179	c.ReleaseWebView(context.Background(), "pane-1")
180}
181
182func TestLifecycle_ReleaseWebView_UninhibitsIdleIfPlayingAudio(t *testing.T) {
183	t.Parallel()
184
185	wv := mocks.NewMockWebView(t)
186	wv.EXPECT().IsFullscreen().Return(false)
187	wv.EXPECT().IsPlayingAudio().Return(true)
188
189	inhibitor := mocks.NewMockIdleInhibitor(t)
190	inhibitor.EXPECT().Uninhibit(mock.Anything).Return(nil)
191
192	pool := mocks.NewMockWebViewPool(t)
193	pool.EXPECT().Release(wv)
194
195	c := newMinimalCoordinator()
196	c.pool = pool
197	c.idleInhibitor = inhibitor
198	c.webViews[entity.PaneID("pane-1")] = wv
199
200	c.ReleaseWebView(context.Background(), "pane-1")
201}
202
203func TestLifecycle_ReleaseWebView_DestroysWebViewWhenPoolIsNil(t *testing.T) {
204	t.Parallel()
205
206	wv := mocks.NewMockWebView(t)
207	// idleInhibitor is nil so these won't be called
208	wv.EXPECT().IsFullscreen().Return(false).Maybe()
209	wv.EXPECT().IsPlayingAudio().Return(false).Maybe()
210	wv.EXPECT().Destroy()
211
212	c := newMinimalCoordinator()
213	// pool remains nil
214	c.webViews[entity.PaneID("pane-1")] = wv
215
216	c.ReleaseWebView(context.Background(), "pane-1")
217}
218
219// ---------------------------------------------------------------------------
220// GetWebView / RegisterPopupWebView
221// ---------------------------------------------------------------------------
222
223func TestLifecycle_GetWebView_ReturnsRegisteredWebView(t *testing.T) {
224	t.Parallel()
225
226	wv := mocks.NewMockWebView(t)
227
228	c := newMinimalCoordinator()
229	c.webViews[entity.PaneID("pane-1")] = wv
230
231	got := c.GetWebView("pane-1")
232
233	assert.Equal(t, wv, got)
234}
235
236func TestLifecycle_RegisterPopupWebView_StoresWebView(t *testing.T) {
237	t.Parallel()
238
239	wv := mocks.NewMockWebView(t)
240
241	c := newMinimalCoordinator()
242
243	c.RegisterPopupWebView("popup-1", wv)
244
245	assert.Equal(t, wv, c.GetWebView("popup-1"))
246}
247
248func TestLifecycle_RegisterPopupWebView_IgnoresNil(t *testing.T) {
249	t.Parallel()
250
251	c := newMinimalCoordinator()
252
253	// Should not panic
254	c.RegisterPopupWebView("popup-1", nil)
255
256	assert.Nil(t, c.GetWebView("popup-1"))
257}

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