main 5041353

pane_view_jsdialog_test.go RawHistoryFolder

1package component
2
3import (
4	"testing"
5
6	"github.com/bnema/puregotk/v4/gtk"
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/application/port"
12	"github.com/bnema/dumber/internal/ui/layout"
13	"github.com/bnema/dumber/internal/ui/layout/mocks"
14)
15
16// fakeJSDialogView records calls made by PaneView.
17type fakeJSDialogView struct {
18	respond       func(bool, string)
19	focus         bool
20	shown         int
21	focusRequests int
22	destroyed     bool
23	hiddenCleared bool
24	dismissedWith []bool
25}
26
27func (f *fakeJSDialogView) Widget() *gtk.Widget { return nil }
28func (f *fakeJSDialogView) Show(_ port.JSDialogRequest, respond func(bool, string), focus bool, _ float64) {
29	f.shown++
30	f.respond, f.focus = respond, focus
31}
32func (f *fakeJSDialogView) Visible() bool { return f.respond != nil }
33func (f *fakeJSDialogView) Hide()         {}
34func (f *fakeJSDialogView) RequestFocus() { f.focusRequests++ }
35func (f *fakeJSDialogView) SetOnHidden(fn func(bool)) {
36	if fn == nil {
37		f.hiddenCleared = true
38	}
39}
40func (f *fakeJSDialogView) Dismiss(ok bool) {
41	f.dismissedWith = append(f.dismissedWith, ok)
42	if f.respond != nil {
43		r := f.respond
44		f.respond = nil
45		r(ok, "")
46	}
47}
48func (f *fakeJSDialogView) Destroy() { f.destroyed = true }
49
50func newJSDialogPaneView(t *testing.T, fake *fakeJSDialogView) *PaneView {
51	t.Helper()
52	overlay := mocks.NewMockOverlayWidget(t)
53	factory := mocks.NewMockWidgetFactory(t)
54	wrapped := mocks.NewMockWidget(t)
55	factory.EXPECT().WrapWidget(mock.Anything).Return(wrapped).Maybe()
56	overlay.EXPECT().AddOverlay(wrapped).Maybe()
57	overlay.EXPECT().SetClipOverlay(wrapped, true).Maybe()
58	overlay.EXPECT().SetMeasureOverlay(wrapped, false).Maybe()
59	overlay.EXPECT().RemoveOverlay(wrapped).Maybe()
60	pv := &PaneView{
61		overlay: overlay,
62		factory: factory,
63		newJSDialog: func(layout.OverlayWidget) jsDialogView {
64			return fake
65		},
66	}
67	return pv
68}
69
70func TestPaneViewCleanupAnswersOpenJSDialog(t *testing.T) {
71	fake := &fakeJSDialogView{}
72	pv := newJSDialogPaneView(t, fake)
73
74	var answers []bool
75	require.True(t, pv.ShowJSDialog(port.JSDialogRequest{}, func(ok bool, _ string) { answers = append(answers, ok) }, 1))
76
77	pv.Cleanup()
78
79	assert.Equal(t, []bool{false}, answers, "page must be answered, not left blocked")
80	assert.True(t, fake.hiddenCleared, "hidden callback detached before dismissing")
81	assert.True(t, fake.destroyed)
82	assert.Nil(t, pv.jsDialog)
83
84	// Cleanup again is a no-op.
85	pv.Cleanup()
86	assert.Equal(t, []bool{false}, answers)
87}
88
89func TestPaneViewShowJSDialogFocusFollowsActive(t *testing.T) {
90	fake := &fakeJSDialogView{}
91	pv := newJSDialogPaneView(t, fake)
92
93	require.True(t, pv.ShowJSDialog(port.JSDialogRequest{}, func(bool, string) {}, 1))
94	assert.False(t, fake.focus, "inactive pane's dialog must not steal focus")
95
96	pv.isActive = true
97	require.True(t, pv.ShowJSDialog(port.JSDialogRequest{}, func(bool, string) {}, 1))
98	assert.True(t, fake.focus)
99}
100
101func TestPaneViewSetActiveFocusesPendingJSDialog(t *testing.T) {
102	fake := &fakeJSDialogView{}
103	pv := newJSDialogPaneView(t, fake)
104	border := mocks.NewMockBoxWidget(t)
105	border.EXPECT().AddCssClass(activePaneClass).Once()
106	pv.borderBox = border
107	require.True(t, pv.ShowJSDialog(port.JSDialogRequest{}, func(bool, string) {}, 1))
108
109	pv.SetActive(true)
110
111	assert.Equal(t, 1, fake.focusRequests)
112}
113
114func TestPaneViewGrabFocusDefersToVisibleJSDialog(t *testing.T) {
115	fake := &fakeJSDialogView{}
116	pv := newJSDialogPaneView(t, fake)
117	webview := mocks.NewMockWidget(t) // GrabFocus on it would fail the test (no expectation)
118	pv.webViewWidget = webview
119	border := mocks.NewMockBoxWidget(t)
120	border.EXPECT().AddCssClass(activePaneClass).Once()
121	pv.borderBox = border
122	require.True(t, pv.ShowJSDialog(port.JSDialogRequest{}, func(bool, string) {}, 1))
123
124	pv.SetActive(true)
125	require.Equal(t, 1, fake.focusRequests)
126	assert.True(t, pv.GrabFocus())
127
128	assert.Equal(t, 2, fake.focusRequests, "dialog focus requested again, WebView not focused")
129}
130
131func TestPaneViewGrabFocusFocusesWebViewWithoutDialog(t *testing.T) {
132	fake := &fakeJSDialogView{}
133	pv := newJSDialogPaneView(t, fake)
134	webview := mocks.NewMockWidget(t)
135	webview.EXPECT().GrabFocus().Return(true).Once()
136	pv.webViewWidget = webview
137
138	assert.True(t, pv.GrabFocus())
139}
140
141func TestPaneViewShowJSDialogAfterCleanupFails(t *testing.T) {
142	fake := &fakeJSDialogView{}
143	pv := newJSDialogPaneView(t, fake)
144	pv.Cleanup()
145
146	assert.False(t, pv.ShowJSDialog(port.JSDialogRequest{}, func(bool, string) {}, 1))
147	assert.Zero(t, fake.shown)
148}

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