v0.29.0 21b3150

pane_view_test.go RawHistoryFolder

1package component_test
2
3import (
4	"context"
5	"testing"
6
7	"github.com/stretchr/testify/assert"
8	"github.com/stretchr/testify/mock"
9	"github.com/stretchr/testify/require"
10
11	"github.com/bnema/dumber/internal/domain/entity"
12	"github.com/bnema/dumber/internal/ui/component"
13	"github.com/bnema/dumber/internal/ui/layout"
14	"github.com/bnema/dumber/internal/ui/layout/mocks"
15)
16
17func TestNewPaneView_CreatesOverlay(t *testing.T) {
18	// Arrange
19	mockFactory := mocks.NewMockWidgetFactory(t)
20	mockOverlay := mocks.NewMockOverlayWidget(t)
21	mockBorderBox := mocks.NewMockBoxWidget(t)
22	mockWebView := mocks.NewMockWidget(t)
23
24	mockLoadingContainer := mocks.NewMockBoxWidget(t)
25	mockLoadingContent := mocks.NewMockBoxWidget(t)
26	mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
27	mockLoadingLogo := mocks.NewMockImageWidget(t)
28
29	paneID := entity.PaneID("pane-1")
30
31	mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
32	mockOverlay.EXPECT().SetHexpand(true).Once()
33	mockOverlay.EXPECT().SetVexpand(true).Once()
34	mockOverlay.EXPECT().SetVisible(true).Once()
35	mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
36	mockOverlay.EXPECT().SetChild(mockWebView).Once()
37
38	setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
39
40	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
41	mockBorderBox.EXPECT().SetCanFocus(false).Once()
42	mockBorderBox.EXPECT().SetCanTarget(false).Once()
43	mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
44	mockBorderBox.EXPECT().SetHexpand(true).Once()
45	mockBorderBox.EXPECT().SetVexpand(true).Once()
46	mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
47	mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
48	mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
49
50	// Act
51	pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView)
52
53	// Assert
54	require.NotNil(t, pv)
55	assert.Equal(t, paneID, pv.PaneID())
56	assert.Equal(t, mockWebView, pv.WebViewWidget())
57}
58
59func TestNewPaneView_NilWebView(t *testing.T) {
60	// Arrange
61	mockFactory := mocks.NewMockWidgetFactory(t)
62	mockOverlay := mocks.NewMockOverlayWidget(t)
63	mockBorderBox := mocks.NewMockBoxWidget(t)
64
65	mockLoadingContainer := mocks.NewMockBoxWidget(t)
66	mockLoadingContent := mocks.NewMockBoxWidget(t)
67	mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
68	mockLoadingLogo := mocks.NewMockImageWidget(t)
69
70	paneID := entity.PaneID("pane-1")
71
72	mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
73	mockOverlay.EXPECT().SetHexpand(true).Once()
74	mockOverlay.EXPECT().SetVexpand(true).Once()
75	mockOverlay.EXPECT().SetVisible(true).Once()
76	mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
77	// SetChild should NOT be called when webview is nil
78
79	setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
80
81	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
82	mockBorderBox.EXPECT().SetCanFocus(false).Once()
83	mockBorderBox.EXPECT().SetCanTarget(false).Once()
84	mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
85	mockBorderBox.EXPECT().SetHexpand(true).Once()
86	mockBorderBox.EXPECT().SetVexpand(true).Once()
87	mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
88	mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
89	mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
90
91	// Act
92	pv := component.NewPaneView(context.Background(), mockFactory, paneID, nil)
93
94	// Assert
95	require.NotNil(t, pv)
96	assert.Nil(t, pv.WebViewWidget())
97}
98
99func TestSetActive_True_AddsCSSClass(t *testing.T) {
100	// Arrange
101	mockFactory := mocks.NewMockWidgetFactory(t)
102	mockOverlay := mocks.NewMockOverlayWidget(t)
103	mockBorderBox := mocks.NewMockBoxWidget(t)
104	mockWebView := mocks.NewMockWidget(t)
105
106	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
107
108	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
109
110	// Expect CSS class to be added
111	mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
112
113	// Act
114	pv.SetActive(true)
115
116	// Assert
117	assert.True(t, pv.IsActive())
118}
119
120func TestSetActive_False_RemovesCSSClass(t *testing.T) {
121	// Arrange
122	mockFactory := mocks.NewMockWidgetFactory(t)
123	mockOverlay := mocks.NewMockOverlayWidget(t)
124	mockBorderBox := mocks.NewMockBoxWidget(t)
125	mockWebView := mocks.NewMockWidget(t)
126
127	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
128
129	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
130
131	// First activate
132	mockBorderBox.EXPECT().AddCssClass("pane-active").Once()
133	pv.SetActive(true)
134
135	// Then deactivate
136	mockBorderBox.EXPECT().RemoveCssClass("pane-active").Once()
137
138	// Act
139	pv.SetActive(false)
140
141	// Assert
142	assert.False(t, pv.IsActive())
143}
144
145func TestSetActive_NoChangeWhenSameState(t *testing.T) {
146	// Arrange
147	mockFactory := mocks.NewMockWidgetFactory(t)
148	mockOverlay := mocks.NewMockOverlayWidget(t)
149	mockBorderBox := mocks.NewMockBoxWidget(t)
150	mockWebView := mocks.NewMockWidget(t)
151
152	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
153
154	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
155
156	// Act - setting false when already false should not call RemoveCssClass
157	pv.SetActive(false)
158
159	// Assert - no mock expectations for CSS changes
160	assert.False(t, pv.IsActive())
161}
162
163func TestPaneID_ReturnsPaneID(t *testing.T) {
164	// Arrange
165	mockFactory := mocks.NewMockWidgetFactory(t)
166	mockOverlay := mocks.NewMockOverlayWidget(t)
167	mockBorderBox := mocks.NewMockBoxWidget(t)
168	mockWebView := mocks.NewMockWidget(t)
169
170	paneID := entity.PaneID("test-pane-123")
171	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
172
173	pv := component.NewPaneView(context.Background(), mockFactory, paneID, mockWebView)
174
175	// Act
176	result := pv.PaneID()
177
178	// Assert
179	assert.Equal(t, paneID, result)
180}
181
182func TestWebViewWidget_ReturnsWebView(t *testing.T) {
183	// Arrange
184	mockFactory := mocks.NewMockWidgetFactory(t)
185	mockOverlay := mocks.NewMockOverlayWidget(t)
186	mockBorderBox := mocks.NewMockBoxWidget(t)
187	mockWebView := mocks.NewMockWidget(t)
188
189	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
190
191	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
192
193	// Act
194	result := pv.WebViewWidget()
195
196	// Assert
197	assert.Equal(t, mockWebView, result)
198}
199
200func TestSetWebViewWidget_ReplacesWidget(t *testing.T) {
201	// Arrange
202	mockFactory := mocks.NewMockWidgetFactory(t)
203	mockOverlay := mocks.NewMockOverlayWidget(t)
204	mockBorderBox := mocks.NewMockBoxWidget(t)
205	mockOldWebView := mocks.NewMockWidget(t)
206	mockNewWebView := mocks.NewMockWidget(t)
207
208	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockOldWebView)
209
210	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockOldWebView)
211
212	// Expect removal of old widget and addition of new
213	mockOverlay.EXPECT().SetChild(nil).Once()
214	mockOverlay.EXPECT().GetAllocatedWidth().Return(0).Twice()
215	mockOverlay.EXPECT().GetAllocatedHeight().Return(0).Twice()
216	mockNewWebView.EXPECT().GetParent().Return(nil).Once()
217	mockNewWebView.EXPECT().IsVisible().Return(false).Once()
218	mockNewWebView.EXPECT().GetAllocatedWidth().Return(0).Twice()
219	mockNewWebView.EXPECT().GetAllocatedHeight().Return(0).Twice()
220	mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
221
222	// Act
223	pv.SetWebViewWidget(mockNewWebView)
224
225	// Assert
226	assert.Equal(t, mockNewWebView, pv.WebViewWidget())
227}
228
229func TestSetWebViewWidget_FromNil(t *testing.T) {
230	// Arrange
231	mockFactory := mocks.NewMockWidgetFactory(t)
232	mockOverlay := mocks.NewMockOverlayWidget(t)
233	mockBorderBox := mocks.NewMockBoxWidget(t)
234	mockNewWebView := mocks.NewMockWidget(t)
235
236	setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox)
237
238	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil)
239
240	// Expect only setting new child (no removal since old was nil)
241	mockOverlay.EXPECT().GetAllocatedWidth().Return(0).Twice()
242	mockOverlay.EXPECT().GetAllocatedHeight().Return(0).Twice()
243	mockNewWebView.EXPECT().GetParent().Return(nil).Once()
244	mockNewWebView.EXPECT().IsVisible().Return(false).Once()
245	mockNewWebView.EXPECT().GetAllocatedWidth().Return(0).Twice()
246	mockNewWebView.EXPECT().GetAllocatedHeight().Return(0).Twice()
247	mockOverlay.EXPECT().SetChild(mockNewWebView).Once()
248
249	// Act
250	pv.SetWebViewWidget(mockNewWebView)
251
252	// Assert
253	assert.Equal(t, mockNewWebView, pv.WebViewWidget())
254}
255
256func TestGrabFocus_DelegatesToWebView(t *testing.T) {
257	// Arrange
258	mockFactory := mocks.NewMockWidgetFactory(t)
259	mockOverlay := mocks.NewMockOverlayWidget(t)
260	mockBorderBox := mocks.NewMockBoxWidget(t)
261	mockWebView := mocks.NewMockWidget(t)
262
263	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
264
265	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
266
267	mockWebView.EXPECT().GrabFocus().Return(true).Once()
268
269	// Act
270	result := pv.GrabFocus()
271
272	// Assert
273	assert.True(t, result)
274}
275
276func TestGrabFocus_NilWebView_ReturnsFalse(t *testing.T) {
277	// Arrange
278	mockFactory := mocks.NewMockWidgetFactory(t)
279	mockOverlay := mocks.NewMockOverlayWidget(t)
280	mockBorderBox := mocks.NewMockBoxWidget(t)
281
282	setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox)
283
284	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil)
285
286	// Act
287	result := pv.GrabFocus()
288
289	// Assert
290	assert.False(t, result)
291}
292
293func TestHasFocus_DelegatesToWebView(t *testing.T) {
294	// Arrange
295	mockFactory := mocks.NewMockWidgetFactory(t)
296	mockOverlay := mocks.NewMockOverlayWidget(t)
297	mockBorderBox := mocks.NewMockBoxWidget(t)
298	mockWebView := mocks.NewMockWidget(t)
299
300	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
301
302	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
303
304	mockWebView.EXPECT().HasFocus().Return(true).Once()
305
306	// Act
307	result := pv.HasFocus()
308
309	// Assert
310	assert.True(t, result)
311}
312
313func TestHasFocus_NilWebView_ReturnsFalse(t *testing.T) {
314	// Arrange
315	mockFactory := mocks.NewMockWidgetFactory(t)
316	mockOverlay := mocks.NewMockOverlayWidget(t)
317	mockBorderBox := mocks.NewMockBoxWidget(t)
318
319	setupPaneViewMocksNoWebView(t, mockFactory, mockOverlay, mockBorderBox)
320
321	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), nil)
322
323	// Act
324	result := pv.HasFocus()
325
326	// Assert
327	assert.False(t, result)
328}
329
330func TestWidget_ReturnsOverlay(t *testing.T) {
331	// Arrange
332	mockFactory := mocks.NewMockWidgetFactory(t)
333	mockOverlay := mocks.NewMockOverlayWidget(t)
334	mockBorderBox := mocks.NewMockBoxWidget(t)
335	mockWebView := mocks.NewMockWidget(t)
336
337	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
338
339	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
340
341	// Act
342	result := pv.Widget()
343
344	// Assert
345	assert.Equal(t, mockOverlay, result)
346}
347
348func TestOverlay_ReturnsOverlayWidget(t *testing.T) {
349	// Arrange
350	mockFactory := mocks.NewMockWidgetFactory(t)
351	mockOverlay := mocks.NewMockOverlayWidget(t)
352	mockBorderBox := mocks.NewMockBoxWidget(t)
353	mockWebView := mocks.NewMockWidget(t)
354
355	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
356
357	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
358
359	// Act
360	result := pv.Overlay()
361
362	// Assert
363	assert.Equal(t, mockOverlay, result)
364}
365
366func TestShow_DelegatesToOverlay(t *testing.T) {
367	// Arrange
368	mockFactory := mocks.NewMockWidgetFactory(t)
369	mockOverlay := mocks.NewMockOverlayWidget(t)
370	mockBorderBox := mocks.NewMockBoxWidget(t)
371	mockWebView := mocks.NewMockWidget(t)
372
373	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
374
375	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
376
377	mockOverlay.EXPECT().Show().Once()
378
379	// Act
380	pv.Show()
381
382	// Assert - verified by mock expectations
383}
384
385func TestHide_DelegatesToOverlay(t *testing.T) {
386	// Arrange
387	mockFactory := mocks.NewMockWidgetFactory(t)
388	mockOverlay := mocks.NewMockOverlayWidget(t)
389	mockBorderBox := mocks.NewMockBoxWidget(t)
390	mockWebView := mocks.NewMockWidget(t)
391
392	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
393
394	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
395
396	mockOverlay.EXPECT().Hide().Once()
397
398	// Act
399	pv.Hide()
400
401	// Assert - verified by mock expectations
402}
403
404func TestSetVisible_DelegatesToOverlay(t *testing.T) {
405	// Arrange
406	mockFactory := mocks.NewMockWidgetFactory(t)
407	mockOverlay := mocks.NewMockOverlayWidget(t)
408	mockBorderBox := mocks.NewMockBoxWidget(t)
409	mockWebView := mocks.NewMockWidget(t)
410
411	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
412
413	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
414
415	mockOverlay.EXPECT().SetVisible(true).Once()
416
417	// Act
418	pv.SetVisible(true)
419
420	// Assert - verified by mock expectations
421}
422
423func TestIsVisible_DelegatesToOverlay(t *testing.T) {
424	// Arrange
425	mockFactory := mocks.NewMockWidgetFactory(t)
426	mockOverlay := mocks.NewMockOverlayWidget(t)
427	mockBorderBox := mocks.NewMockBoxWidget(t)
428	mockWebView := mocks.NewMockWidget(t)
429
430	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
431
432	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
433
434	mockOverlay.EXPECT().IsVisible().Return(true).Once()
435
436	// Act
437	result := pv.IsVisible()
438
439	// Assert
440	assert.True(t, result)
441}
442
443func TestAddCssClass_DelegatesToOverlay(t *testing.T) {
444	// Arrange
445	mockFactory := mocks.NewMockWidgetFactory(t)
446	mockOverlay := mocks.NewMockOverlayWidget(t)
447	mockBorderBox := mocks.NewMockBoxWidget(t)
448	mockWebView := mocks.NewMockWidget(t)
449
450	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
451
452	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
453
454	mockOverlay.EXPECT().AddCssClass("custom-class").Once()
455
456	// Act
457	pv.AddCssClass("custom-class")
458
459	// Assert - verified by mock expectations
460}
461
462func TestRemoveCssClass_DelegatesToOverlay(t *testing.T) {
463	// Arrange
464	mockFactory := mocks.NewMockWidgetFactory(t)
465	mockOverlay := mocks.NewMockOverlayWidget(t)
466	mockBorderBox := mocks.NewMockBoxWidget(t)
467	mockWebView := mocks.NewMockWidget(t)
468
469	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
470
471	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
472
473	mockOverlay.EXPECT().RemoveCssClass("custom-class").Once()
474
475	// Act
476	pv.RemoveCssClass("custom-class")
477
478	// Assert - verified by mock expectations
479}
480
481func TestSetOnFocusIn_SetsCallback(t *testing.T) {
482	// Arrange
483	mockFactory := mocks.NewMockWidgetFactory(t)
484	mockOverlay := mocks.NewMockOverlayWidget(t)
485	mockBorderBox := mocks.NewMockBoxWidget(t)
486	mockWebView := mocks.NewMockWidget(t)
487
488	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
489
490	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
491
492	callback := func(paneID entity.PaneID) {}
493
494	// Act
495	pv.SetOnFocusIn(callback)
496
497	// Assert - callback is set (we can't directly verify without triggering focus)
498	assert.NotNil(t, pv)
499}
500
501func TestSetOnFocusOut_SetsCallback(t *testing.T) {
502	// Arrange
503	mockFactory := mocks.NewMockWidgetFactory(t)
504	mockOverlay := mocks.NewMockOverlayWidget(t)
505	mockBorderBox := mocks.NewMockBoxWidget(t)
506	mockWebView := mocks.NewMockWidget(t)
507
508	setupPaneViewMocks(t, mockFactory, mockOverlay, mockBorderBox, mockWebView)
509
510	pv := component.NewPaneView(context.Background(), mockFactory, entity.PaneID("pane-1"), mockWebView)
511
512	callback := func(paneID entity.PaneID) {}
513
514	// Act
515	pv.SetOnFocusOut(callback)
516
517	// Assert - callback is set (we can't directly verify without triggering focus)
518	assert.NotNil(t, pv)
519}
520
521// Helper function to setup common mock expectations for PaneView creation
522func setupLoadingSkeletonMocks(
523	t *testing.T,
524	mockFactory *mocks.MockWidgetFactory,
525	mockOverlay *mocks.MockOverlayWidget,
526	mockLoadingContainer *mocks.MockBoxWidget,
527	mockLoadingContent *mocks.MockBoxWidget,
528	mockLoadingSpinner *mocks.MockSpinnerWidget,
529	mockLoadingLogo *mocks.MockImageWidget,
530) {
531	mockLoadingVersion := mocks.NewMockLabelWidget(t)
532
533	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockLoadingContainer).Once()
534	mockLoadingContainer.EXPECT().SetHexpand(true).Maybe()
535	mockLoadingContainer.EXPECT().SetVexpand(true).Maybe()
536	mockLoadingContainer.EXPECT().SetHalign(mock.Anything).Maybe()
537	mockLoadingContainer.EXPECT().SetValign(mock.Anything).Maybe()
538	mockLoadingContainer.EXPECT().SetCanFocus(false).Maybe()
539	mockLoadingContainer.EXPECT().SetCanTarget(false).Maybe()
540	mockLoadingContainer.EXPECT().AddCssClass("loading-skeleton").Maybe()
541	mockLoadingContainer.EXPECT().SetVisible(true).Maybe()
542
543	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 6).Return(mockLoadingContent).Once()
544	mockLoadingContent.EXPECT().SetHalign(mock.Anything).Maybe()
545	mockLoadingContent.EXPECT().SetValign(mock.Anything).Maybe()
546	mockLoadingContent.EXPECT().SetCanFocus(false).Maybe()
547	mockLoadingContent.EXPECT().SetCanTarget(false).Maybe()
548	mockLoadingContent.EXPECT().AddCssClass("loading-skeleton-content").Maybe()
549
550	mockFactory.EXPECT().NewImage().Return(mockLoadingLogo).Once()
551	mockLoadingLogo.EXPECT().SetHalign(mock.Anything).Maybe()
552	mockLoadingLogo.EXPECT().SetValign(mock.Anything).Maybe()
553	mockLoadingLogo.EXPECT().SetCanFocus(false).Maybe()
554	mockLoadingLogo.EXPECT().SetCanTarget(false).Maybe()
555	mockLoadingLogo.EXPECT().SetSizeRequest(256, 256).Maybe()
556	mockLoadingLogo.EXPECT().SetPixelSize(256).Maybe()
557	mockLoadingLogo.EXPECT().AddCssClass("loading-skeleton-logo").Maybe()
558	mockLoadingLogo.EXPECT().SetFromPaintable(mock.Anything).Maybe()
559
560	mockFactory.EXPECT().NewSpinner().Return(mockLoadingSpinner).Once()
561	mockLoadingSpinner.EXPECT().SetHalign(mock.Anything).Maybe()
562	mockLoadingSpinner.EXPECT().SetValign(mock.Anything).Maybe()
563	mockLoadingSpinner.EXPECT().SetCanFocus(false).Maybe()
564	mockLoadingSpinner.EXPECT().SetCanTarget(false).Maybe()
565	mockLoadingSpinner.EXPECT().SetSizeRequest(32, 32).Maybe()
566	mockLoadingSpinner.EXPECT().AddCssClass("loading-skeleton-spinner").Maybe()
567	mockLoadingSpinner.EXPECT().Start().Maybe()
568
569	mockFactory.EXPECT().NewLabel(mock.Anything).Return(mockLoadingVersion).Once()
570	mockLoadingVersion.EXPECT().SetHalign(mock.Anything).Maybe()
571	mockLoadingVersion.EXPECT().SetValign(mock.Anything).Maybe()
572	mockLoadingVersion.EXPECT().SetCanFocus(false).Maybe()
573	mockLoadingVersion.EXPECT().SetCanTarget(false).Maybe()
574	mockLoadingVersion.EXPECT().SetMaxWidthChars(mock.Anything).Maybe()
575	mockLoadingVersion.EXPECT().SetEllipsize(mock.Anything).Maybe()
576	mockLoadingVersion.EXPECT().AddCssClass("loading-skeleton-version").Maybe()
577
578	mockLoadingContent.EXPECT().Append(mockLoadingLogo).Maybe()
579	mockLoadingContent.EXPECT().Append(mockLoadingSpinner).Maybe()
580	mockLoadingContent.EXPECT().Append(mockLoadingVersion).Maybe()
581	mockLoadingContainer.EXPECT().Append(mockLoadingContent).Maybe()
582
583	mockOverlay.EXPECT().AddOverlay(mockLoadingContainer).Once()
584	mockOverlay.EXPECT().SetClipOverlay(mockLoadingContainer, false).Once()
585	mockOverlay.EXPECT().SetMeasureOverlay(mockLoadingContainer, false).Once()
586}
587
588// Helper function to setup common mock expectations for PaneView creation
589func setupPaneViewMocks(
590	t *testing.T,
591	mockFactory *mocks.MockWidgetFactory,
592	mockOverlay *mocks.MockOverlayWidget,
593	mockBorderBox *mocks.MockBoxWidget,
594	mockWebView *mocks.MockWidget,
595) {
596	mockLoadingContainer := mocks.NewMockBoxWidget(t)
597	mockLoadingContent := mocks.NewMockBoxWidget(t)
598	mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
599	mockLoadingLogo := mocks.NewMockImageWidget(t)
600
601	mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
602	mockOverlay.EXPECT().SetHexpand(true).Once()
603	mockOverlay.EXPECT().SetVexpand(true).Once()
604	mockOverlay.EXPECT().SetVisible(true).Once()
605	mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
606	mockOverlay.EXPECT().SetChild(mockWebView).Once()
607
608	setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
609
610	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
611	mockBorderBox.EXPECT().SetCanFocus(false).Once()
612	mockBorderBox.EXPECT().SetCanTarget(false).Once()
613	mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
614	mockBorderBox.EXPECT().SetHexpand(true).Once()
615	mockBorderBox.EXPECT().SetVexpand(true).Once()
616	mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
617	mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
618	mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
619}
620
621func setupPaneViewMocksNoWebView(
622	t *testing.T,
623	mockFactory *mocks.MockWidgetFactory,
624	mockOverlay *mocks.MockOverlayWidget,
625	mockBorderBox *mocks.MockBoxWidget,
626) {
627	mockLoadingContainer := mocks.NewMockBoxWidget(t)
628	mockLoadingContent := mocks.NewMockBoxWidget(t)
629	mockLoadingSpinner := mocks.NewMockSpinnerWidget(t)
630	mockLoadingLogo := mocks.NewMockImageWidget(t)
631
632	mockFactory.EXPECT().NewOverlay().Return(mockOverlay).Once()
633	mockOverlay.EXPECT().SetHexpand(true).Once()
634	mockOverlay.EXPECT().SetVexpand(true).Once()
635	mockOverlay.EXPECT().SetVisible(true).Once()
636	mockOverlay.EXPECT().AddCssClass("pane-overlay").Once() // Theme background
637
638	setupLoadingSkeletonMocks(t, mockFactory, mockOverlay, mockLoadingContainer, mockLoadingContent, mockLoadingSpinner, mockLoadingLogo)
639
640	mockFactory.EXPECT().NewBox(layout.OrientationVertical, 0).Return(mockBorderBox).Once()
641	mockBorderBox.EXPECT().SetCanFocus(false).Once()
642	mockBorderBox.EXPECT().SetCanTarget(false).Once()
643	mockBorderBox.EXPECT().AddCssClass("pane-border").Once()
644	mockBorderBox.EXPECT().SetHexpand(true).Once()
645	mockBorderBox.EXPECT().SetVexpand(true).Once()
646	mockOverlay.EXPECT().AddOverlay(mockBorderBox).Once()
647	mockOverlay.EXPECT().SetClipOverlay(mockBorderBox, false).Once()
648	mockOverlay.EXPECT().SetMeasureOverlay(mockBorderBox, false).Once()
649}

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