v0.29.0 21b3150

execute_context_menu_action_test.go RawHistoryFolder

1package usecase
2
3import (
4	"context"
5	"errors"
6	"testing"
7
8	"github.com/bnema/dumber/internal/application/port"
9	"github.com/bnema/dumber/internal/domain/entity"
10	"github.com/stretchr/testify/require"
11)
12
13type fakeClipboard struct {
14	writeTextCalls  int
15	writeImageCalls int
16	text            string
17	image           entity.ImageData
18}
19
20func (f *fakeClipboard) WriteText(_ context.Context, text string) error {
21	f.writeTextCalls++
22	f.text = text
23	return nil
24}
25
26func (f *fakeClipboard) WriteImage(_ context.Context, image entity.ImageData) error {
27	f.writeImageCalls++
28	f.image = image
29	return nil
30}
31
32func (*fakeClipboard) ReadText(context.Context) (string, error) { return "", nil }
33func (*fakeClipboard) Clear(context.Context) error              { return nil }
34func (*fakeClipboard) HasText(context.Context) (bool, error)    { return false, nil }
35
36type fakeImageResolver struct {
37	resolveCalls int
38	uri          string
39	image        entity.ImageData
40	err          error
41}
42
43func (f *fakeImageResolver) ResolveImageData(_ context.Context, uri string) (entity.ImageData, error) {
44	f.resolveCalls++
45	f.uri = uri
46	return f.image, f.err
47}
48
49type fakeResolvedImageSaver struct {
50	saveCalls   int
51	image       entity.ImageData
52	menuContext port.MenuContext
53	err         error
54}
55
56func (f *fakeResolvedImageSaver) SaveResolvedImage(_ context.Context, image entity.ImageData, menuContext port.MenuContext) error {
57	f.saveCalls++
58	f.image = image
59	f.menuContext = menuContext
60	return f.err
61}
62
63type fakeMenuActionDelegator struct {
64	delegateCalls int
65	action        port.MenuAction
66	menuContext   port.MenuContext
67	err           error
68}
69
70func (f *fakeMenuActionDelegator) DelegateMenuAction(_ context.Context, action port.MenuAction, menuContext port.MenuContext) error {
71	f.delegateCalls++
72	f.action = action
73	f.menuContext = menuContext
74	return f.err
75}
76
77func TestExecuteContextMenuActionUseCase_CopyImageFailsWithoutResolvedData(t *testing.T) {
78	clipboard := &fakeClipboard{}
79	resolver := &fakeImageResolver{}
80	saver := &fakeResolvedImageSaver{}
81	delegator := &fakeMenuActionDelegator{}
82	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
83
84	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
85		Action:  port.MenuActionCopyImage,
86		Context: port.MenuContext{ImageURI: "https://example.com/image.png"},
87	})
88
89	require.Error(t, err)
90	require.Contains(t, err.Error(), "copy image:")
91	require.Contains(t, err.Error(), "image data not available")
92	require.Zero(t, clipboard.writeImageCalls)
93	require.Zero(t, saver.saveCalls)
94	require.Zero(t, delegator.delegateCalls)
95}
96
97func TestExecuteContextMenuActionUseCase_CopyImageWritesResolvedImage(t *testing.T) {
98	clipboard := &fakeClipboard{}
99	resolver := &fakeImageResolver{image: entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}}
100	saver := &fakeResolvedImageSaver{}
101	delegator := &fakeMenuActionDelegator{}
102	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
103
104	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
105		Action:  port.MenuActionCopyImage,
106		Context: port.MenuContext{ImageURI: "https://example.com/image.png"},
107	})
108
109	require.NoError(t, err)
110	require.Equal(t, 1, resolver.resolveCalls)
111	require.Equal(t, "https://example.com/image.png", resolver.uri)
112	require.Equal(t, 1, clipboard.writeImageCalls)
113	require.Equal(t, entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}, clipboard.image)
114	require.Zero(t, saver.saveCalls)
115	require.Zero(t, delegator.delegateCalls)
116}
117
118func TestExecuteContextMenuActionUseCase_CopyImageFailsFastWithoutClipboard(t *testing.T) {
119	resolver := &fakeImageResolver{}
120	saver := &fakeResolvedImageSaver{}
121	delegator := &fakeMenuActionDelegator{}
122	uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator)
123
124	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
125		Action:  port.MenuActionCopyImage,
126		Context: port.MenuContext{ImageURI: "https://example.com/image.png"},
127	})
128
129	require.Error(t, err)
130	require.Contains(t, err.Error(), "clipboard not available")
131	require.Zero(t, resolver.resolveCalls)
132	require.Zero(t, saver.saveCalls)
133	require.Zero(t, delegator.delegateCalls)
134}
135
136func TestExecuteContextMenuActionUseCase_SaveImageDelegatesResolvedImage(t *testing.T) {
137	clipboard := &fakeClipboard{}
138	resolver := &fakeImageResolver{image: entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}}
139	saver := &fakeResolvedImageSaver{}
140	delegator := &fakeMenuActionDelegator{}
141	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
142
143	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
144		Action:  port.MenuActionSaveImage,
145		Context: port.MenuContext{ImageURI: "https://example.com/image.png"},
146	})
147
148	require.NoError(t, err)
149	require.Equal(t, 1, resolver.resolveCalls)
150	require.Equal(t, "https://example.com/image.png", resolver.uri)
151	require.Equal(t, 1, saver.saveCalls)
152	require.Equal(t, entity.ImageData{Bytes: []byte{1, 2, 3}, MimeType: "image/png"}, saver.image)
153	require.Equal(t, port.MenuContext{ImageURI: "https://example.com/image.png"}, saver.menuContext)
154	require.Zero(t, clipboard.writeTextCalls)
155	require.Zero(t, clipboard.writeImageCalls)
156	require.Zero(t, delegator.delegateCalls)
157}
158
159func TestExecuteContextMenuActionUseCase_SaveImageFailsFastWithoutSaver(t *testing.T) {
160	clipboard := &fakeClipboard{}
161	resolver := &fakeImageResolver{}
162	delegator := &fakeMenuActionDelegator{}
163	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, nil, delegator)
164
165	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
166		Action:  port.MenuActionSaveImage,
167		Context: port.MenuContext{ImageURI: "https://example.com/image.png"},
168	})
169
170	require.Error(t, err)
171	require.Contains(t, err.Error(), "image saver not available")
172	require.Zero(t, resolver.resolveCalls)
173	require.Zero(t, delegator.delegateCalls)
174}
175
176func TestExecuteContextMenuActionUseCase_DelegatesInspect(t *testing.T) {
177	clipboard := &fakeClipboard{}
178	resolver := &fakeImageResolver{}
179	saver := &fakeResolvedImageSaver{}
180	delegator := &fakeMenuActionDelegator{}
181	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
182
183	menuContext := port.MenuContext{
184		PageURI: "https://example.com",
185		X:       17,
186		Y:       42,
187	}
188	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
189		Action:  port.MenuActionInspectElement,
190		Context: menuContext,
191	})
192
193	require.NoError(t, err)
194	require.Equal(t, 1, delegator.delegateCalls)
195	require.Equal(t, port.MenuActionInspectElement, delegator.action)
196	require.Equal(t, menuContext, delegator.menuContext)
197	require.Zero(t, clipboard.writeTextCalls)
198	require.Zero(t, clipboard.writeImageCalls)
199	require.Zero(t, resolver.resolveCalls)
200	require.Zero(t, saver.saveCalls)
201}
202
203func TestExecuteContextMenuActionUseCase_WrapsDelegateErrors(t *testing.T) {
204	clipboard := &fakeClipboard{}
205	resolver := &fakeImageResolver{}
206	saver := &fakeResolvedImageSaver{}
207	delegator := &fakeMenuActionDelegator{err: errors.New("boom")}
208	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
209
210	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
211		Action:  port.MenuActionCopySelection,
212		Context: port.MenuContext{HasSelection: true},
213	})
214
215	require.Error(t, err)
216	require.Contains(t, err.Error(), "delegate action copy_selection:")
217	require.Contains(t, err.Error(), "boom")
218}
219
220func TestExecuteContextMenuActionUseCase_CopySelectionWritesSelectedText(t *testing.T) {
221	clipboard := &fakeClipboard{}
222	resolver := &fakeImageResolver{}
223	saver := &fakeResolvedImageSaver{}
224	delegator := &fakeMenuActionDelegator{}
225	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
226
227	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
228		Action: port.MenuActionCopySelection,
229		Context: port.MenuContext{
230			HasSelection:  true,
231			SelectionText: "selected text",
232		},
233	})
234
235	require.NoError(t, err)
236	require.Equal(t, 1, clipboard.writeTextCalls)
237	require.Equal(t, "selected text", clipboard.text)
238	require.Zero(t, delegator.delegateCalls)
239}
240
241func TestExecuteContextMenuActionUseCase_CopySelectionFallsBackToDelegatorWhenClipboardMissing(t *testing.T) {
242	resolver := &fakeImageResolver{}
243	saver := &fakeResolvedImageSaver{}
244	delegator := &fakeMenuActionDelegator{}
245	uc := NewExecuteContextMenuActionUseCase(nil, resolver, saver, delegator)
246
247	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
248		Action: port.MenuActionCopySelection,
249		Context: port.MenuContext{
250			HasSelection:  true,
251			SelectionText: "selected text",
252		},
253	})
254
255	require.NoError(t, err)
256	require.Equal(t, 1, delegator.delegateCalls)
257	require.Equal(t, port.MenuActionCopySelection, delegator.action)
258}
259
260func TestExecuteContextMenuActionUseCase_CopyLinkWritesText(t *testing.T) {
261	clipboard := &fakeClipboard{}
262	resolver := &fakeImageResolver{}
263	saver := &fakeResolvedImageSaver{}
264	delegator := &fakeMenuActionDelegator{}
265	uc := NewExecuteContextMenuActionUseCase(clipboard, resolver, saver, delegator)
266
267	err := uc.Execute(context.Background(), ExecuteContextMenuActionInput{
268		Action:  port.MenuActionCopyLink,
269		Context: port.MenuContext{LinkURI: "https://example.com/link"},
270	})
271
272	require.NoError(t, err)
273	require.Equal(t, 1, clipboard.writeTextCalls)
274	require.Equal(t, "https://example.com/link", clipboard.text)
275	require.Zero(t, clipboard.writeImageCalls)
276	require.Zero(t, resolver.resolveCalls)
277	require.Zero(t, saver.saveCalls)
278	require.Zero(t, delegator.delegateCalls)
279}

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