main 5041353

floating_pane_test.go RawHistoryFolder

1package component_test
2
3import (
4	"context"
5	"errors"
6	"sync"
7	"testing"
8
9	"github.com/bnema/dumber/internal/ui/component"
10	layoutmocks "github.com/bnema/dumber/internal/ui/layout/mocks"
11	"github.com/stretchr/testify/assert"
12	"github.com/stretchr/testify/require"
13)
14
15func TestFloatingPane_HideShowPreservesState(t *testing.T) {
16	overlay := layoutmocks.NewMockOverlayWidget(t)
17	overlay.EXPECT().GetAllocatedWidth().Return(1200).Maybe()
18	overlay.EXPECT().GetAllocatedHeight().Return(800).Maybe()
19
20	navigated := make([]string, 0)
21	fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
22		WidthPct:       0.82,
23		HeightPct:      0.72,
24		FallbackWidth:  1200,
25		FallbackHeight: 800,
26		OnNavigate: func(_ context.Context, url string) error {
27			navigated = append(navigated, url)
28			return nil
29		},
30	})
31
32	require.NoError(t, fp.ShowToggle(context.Background()))
33	require.Equal(t, "about:blank", fp.CurrentURL())
34
35	require.NoError(t, fp.Navigate(context.Background(), "https://google.com"))
36	fp.Hide(context.Background())
37	require.NoError(t, fp.ShowToggle(context.Background()))
38
39	assert.Equal(t, "https://google.com", fp.CurrentURL())
40	assert.False(t, fp.IsOmniboxVisible())
41	assert.Equal(t, []string{"about:blank", "https://google.com"}, navigated)
42}
43
44func TestFloatingPane_ShowURLLoadsTarget(t *testing.T) {
45	overlay := layoutmocks.NewMockOverlayWidget(t)
46	overlay.EXPECT().GetAllocatedWidth().Return(1000).Maybe()
47	overlay.EXPECT().GetAllocatedHeight().Return(700).Maybe()
48
49	fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
50		WidthPct:       0.82,
51		HeightPct:      0.72,
52		FallbackWidth:  1000,
53		FallbackHeight: 700,
54	})
55
56	require.NoError(t, fp.ShowURL(context.Background(), "https://github.com"))
57
58	assert.True(t, fp.IsVisible())
59	assert.False(t, fp.IsOmniboxVisible())
60	assert.Equal(t, "https://github.com", fp.CurrentURL())
61}
62
63func TestFloatingPane_ResizeUsesWorkspacePercentages(t *testing.T) {
64	overlay := layoutmocks.NewMockOverlayWidget(t)
65	overlay.EXPECT().GetAllocatedWidth().Return(1000).Maybe()
66	overlay.EXPECT().GetAllocatedHeight().Return(700).Maybe()
67
68	fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
69		WidthPct:       0.82,
70		HeightPct:      0.72,
71		FallbackWidth:  1000,
72		FallbackHeight: 700,
73	})
74
75	fp.Resize()
76	width, height := fp.Dimensions()
77
78	assert.Equal(t, 820, width)
79	assert.Equal(t, 504, height)
80}
81
82func TestFloatingPane_FirstToggleOpensBlankAndOmnibox(t *testing.T) {
83	overlay := layoutmocks.NewMockOverlayWidget(t)
84	overlay.EXPECT().GetAllocatedWidth().Return(1200).Maybe()
85	overlay.EXPECT().GetAllocatedHeight().Return(800).Maybe()
86
87	fp := component.NewFloatingPane(overlay, component.FloatingPaneOptions{
88		WidthPct:       0.82,
89		HeightPct:      0.72,
90		FallbackWidth:  1200,
91		FallbackHeight: 800,
92	})
93
94	require.NoError(t, fp.ShowToggle(context.Background()))
95
96	assert.True(t, fp.IsVisible())
97	assert.True(t, fp.IsOmniboxVisible())
98	assert.Equal(t, "about:blank", fp.CurrentURL())
99}
100
101func TestFloatingPane_SetOmniboxVisible(t *testing.T) {
102	fp := component.NewFloatingPane(nil, component.FloatingPaneOptions{
103		WidthPct:       0.82,
104		HeightPct:      0.72,
105		FallbackWidth:  1200,
106		FallbackHeight: 800,
107	})
108
109	fp.SetOmniboxVisible(true)
110	assert.True(t, fp.IsOmniboxVisible())
111
112	fp.SetOmniboxVisible(false)
113	assert.False(t, fp.IsOmniboxVisible())
114}
115
116func TestFloatingPane_ConcurrentResizeAndParentSwitch(t *testing.T) {
117	overlayA := layoutmocks.NewMockOverlayWidget(t)
118	overlayA.EXPECT().GetAllocatedWidth().Return(1200).Maybe()
119	overlayA.EXPECT().GetAllocatedHeight().Return(800).Maybe()
120
121	overlayB := layoutmocks.NewMockOverlayWidget(t)
122	overlayB.EXPECT().GetAllocatedWidth().Return(1000).Maybe()
123	overlayB.EXPECT().GetAllocatedHeight().Return(700).Maybe()
124
125	fp := component.NewFloatingPane(overlayA, component.FloatingPaneOptions{
126		WidthPct:       0.82,
127		HeightPct:      0.72,
128		FallbackWidth:  1200,
129		FallbackHeight: 800,
130	})
131
132	var wg sync.WaitGroup
133	for i := range 200 {
134		wg.Add(2)
135		go func() {
136			defer wg.Done()
137			fp.Resize()
138		}()
139		go func(iter int) {
140			defer wg.Done()
141			if iter%2 == 0 {
142				fp.SetParentOverlay(overlayA)
143				return
144			}
145			fp.SetParentOverlay(overlayB)
146		}(i)
147	}
148	wg.Wait()
149
150	width, height := fp.Dimensions()
151	assert.Positive(t, width)
152	assert.Positive(t, height)
153}
154
155func TestFloatingPane_ShowURL_RollsBackVisibilityOnNavigateError(t *testing.T) {
156	expectedErr := errors.New("navigate failed")
157	fp := component.NewFloatingPane(nil, component.FloatingPaneOptions{
158		WidthPct:       0.82,
159		HeightPct:      0.72,
160		FallbackWidth:  1200,
161		FallbackHeight: 800,
162		OnNavigate: func(context.Context, string) error {
163			return expectedErr
164		},
165	})
166
167	err := fp.ShowURL(context.Background(), "https://example.com")
168	require.ErrorIs(t, err, expectedErr)
169	assert.False(t, fp.IsVisible())
170	assert.False(t, fp.IsOmniboxVisible())
171	assert.Empty(t, fp.CurrentURL())
172}
173
174func TestFloatingPane_ShowToggleNavigateError_DoesNotClobberConcurrentStateChange(t *testing.T) {
175	navigationStarted := make(chan struct{})
176	releaseNavigation := make(chan struct{})
177	expectedErr := errors.New("navigate failed")
178
179	fp := component.NewFloatingPane(nil, component.FloatingPaneOptions{
180		WidthPct:       0.82,
181		HeightPct:      0.72,
182		FallbackWidth:  1200,
183		FallbackHeight: 800,
184		OnNavigate: func(context.Context, string) error {
185			close(navigationStarted)
186			<-releaseNavigation
187			return expectedErr
188		},
189	})
190
191	errCh := make(chan error, 1)
192	go func() {
193		errCh <- fp.ShowToggle(context.Background())
194	}()
195
196	<-navigationStarted
197	fp.Show()
198	close(releaseNavigation)
199
200	err := <-errCh
201	require.ErrorIs(t, err, expectedErr)
202	assert.True(t, fp.IsVisible())
203	assert.False(t, fp.IsOmniboxVisible())
204}

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