file_dialog_test.go RawHistoryFolder

1package cef
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"path/filepath"
8	"testing"
9
10	purecef "github.com/bnema/purego-cef/cef"
11	"github.com/rs/zerolog"
12	"github.com/stretchr/testify/require"
13)
14
15type stubFileDialogCallback struct {
16	list     purecef.StringList
17	paths    []string
18	canceled bool
19}
20
21func (s *stubFileDialogCallback) Cont(filePaths purecef.StringList) {
22	s.list = filePaths
23	s.paths = decodeCEFStringList(filePaths)
24}
25
26func (s *stubFileDialogCallback) Cancel() {
27	s.canceled = true
28}
29
30func TestGetDialogHandlerEnabled(t *testing.T) {
31	h := &handlerSet{}
32	require.Same(t, h, h.GetDialogHandler())
33}
34
35func TestOnFileDialogRejectsNilWebViewOrCallback(t *testing.T) {
36	require.Zero(t, (*handlerSet)(nil).OnFileDialog(nil, purecef.FileDialogModeFileDialogOpen, "", "", 0, 0, 0, &stubFileDialogCallback{}))
37	require.Zero(t, (&handlerSet{}).OnFileDialog(nil, purecef.FileDialogModeFileDialogOpen, "", "", 0, 0, 0, &stubFileDialogCallback{}))
38	require.Zero(t, (&handlerSet{wv: &WebView{ctx: context.Background()}}).OnFileDialog(nil, purecef.FileDialogModeFileDialogOpen, "", "", 0, 0, 0, nil))
39}
40
41func TestNormalizeGTKResponseID(t *testing.T) {
42	require.Equal(t, -3, normalizeGTKResponseID(4294967293))
43	require.Equal(t, -6, normalizeGTKResponseID(4294967290))
44	require.Equal(t, 0, normalizeGTKResponseID(0))
45}
46
47func TestOnFileDialogDelegatesDecodedRequestToPresenter(t *testing.T) {
48	prevDecode := decodeCEFStringList
49	decodeCEFStringList = func(list purecef.StringList) []string {
50		switch list {
51		case purecef.StringList(11):
52			return []string{"image/*", ".txt"}
53		case purecef.StringList(12):
54			return []string{".png;.jpg", ".txt"}
55		case purecef.StringList(13):
56			return []string{"Image Files", "Text Files"}
57		default:
58			return nil
59		}
60	}
61	defer func() { decodeCEFStringList = prevDecode }()
62
63	var gotReq cefFileDialogRequest
64	var gotCalls int
65	h := &handlerSet{
66		wv: &WebView{ctx: context.Background()},
67		fileDialogPresenter: func(_ *WebView, req cefFileDialogRequest, _ purecef.FileDialogCallback) {
68			gotReq = req
69			gotCalls++
70		},
71	}
72
73	handled := h.OnFileDialog(nil, purecef.FileDialogModeFileDialogOpenMultiple, "Pick files", "/tmp/start.txt", purecef.StringList(11), purecef.StringList(12), purecef.StringList(13), &stubFileDialogCallback{})
74
75	require.Equal(t, int32(1), handled)
76	require.Equal(t, 1, gotCalls)
77	require.Equal(t, purecef.FileDialogModeFileDialogOpenMultiple, gotReq.Mode)
78	require.Equal(t, "Pick files", gotReq.Title)
79	require.Equal(t, "/tmp/start.txt", gotReq.DefaultFilePath)
80	require.Equal(t, []string{"image/*", ".txt"}, gotReq.AcceptFilters)
81	require.Equal(t, []string{".png;.jpg", ".txt"}, gotReq.AcceptExtensions)
82	require.Equal(t, []string{"Image Files", "Text Files"}, gotReq.AcceptDescriptions)
83}
84
85func TestFolderUploadPathsExpandsDirectoryFiles(t *testing.T) {
86	dir := t.TempDir()
87	first := filepath.Join(dir, "first.txt")
88	second := filepath.Join(dir, "nested", "second.txt")
89	require.NoError(t, os.MkdirAll(filepath.Dir(second), 0o755))
90	require.NoError(t, os.WriteFile(first, []byte("first"), 0o644))
91	require.NoError(t, os.WriteFile(second, []byte("second"), 0o644))
92
93	got := folderUploadPaths(&WebView{ctx: context.Background()}, purecef.FileDialogModeFileDialogOpenFolder, []string{dir})
94
95	require.ElementsMatch(t, []string{first, second}, got)
96}
97
98func TestFolderUploadPathsKeepsEmptyDirectoryPath(t *testing.T) {
99	dir := t.TempDir()
100
101	got := folderUploadPaths(&WebView{ctx: context.Background()}, purecef.FileDialogModeFileDialogOpenFolder, []string{dir})
102
103	require.Equal(t, []string{dir}, got)
104}
105
106func TestParseDefaultDialogPathSaveModeWithExistingDirectory(t *testing.T) {
107	dir := t.TempDir()
108	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogSave, dir)
109	require.Equal(t, dir, info.setFolder)
110	require.Empty(t, info.setName)
111	require.Empty(t, info.setFile)
112}
113
114func TestParseDefaultDialogPathSaveModeWithFilePath(t *testing.T) {
115	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogSave, "/tmp/foo/bar.txt")
116	require.Equal(t, "/tmp/foo", info.setFolder)
117	require.Equal(t, "bar.txt", info.setName)
118	require.Empty(t, info.setFile)
119}
120
121func TestParseDefaultDialogPathSaveModeWithRelativeFilePath(t *testing.T) {
122	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogSave, "bar.txt")
123	require.Empty(t, info.setFolder)
124	require.Equal(t, "bar.txt", info.setName)
125	require.Empty(t, info.setFile)
126}
127
128func TestParseDefaultDialogPathOpenModeWithDirectory(t *testing.T) {
129	dir := t.TempDir()
130	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogOpen, dir)
131	require.Equal(t, dir, info.setFolder)
132	require.Empty(t, info.setFile)
133}
134
135func TestParseDefaultDialogPathOpenModeWithFilePath(t *testing.T) {
136	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogOpen, "/tmp/foo/bar.txt")
137	require.Equal(t, "/tmp/foo", info.setFolder)
138	require.Equal(t, "/tmp/foo/bar.txt", info.setFile)
139}
140
141func TestParseDefaultDialogPathOpenFolderMode(t *testing.T) {
142	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogOpenFolder, "/tmp/foo/bar.txt")
143	require.Equal(t, "/tmp/foo", info.setFolder)
144	require.Empty(t, info.setFile)
145}
146
147func TestParseDefaultDialogPathEmptyPath(t *testing.T) {
148	info := parseDefaultDialogPath(purecef.FileDialogModeFileDialogSave, "")
149	require.Empty(t, info.setFolder)
150	require.Empty(t, info.setName)
151	require.Empty(t, info.setFile)
152
153	info = parseDefaultDialogPath(purecef.FileDialogModeFileDialogOpen, "  ")
154	require.Empty(t, info.setFolder)
155	require.Empty(t, info.setName)
156	require.Empty(t, info.setFile)
157}
158
159func TestFolderUploadPathsTruncationCancels(t *testing.T) {
160	prev := maxExpandedFolderUploadFiles
161	maxExpandedFolderUploadFiles = 2
162	defer func() { maxExpandedFolderUploadFiles = prev }()
163
164	dir := t.TempDir()
165	for i := range 5 {
166		p := filepath.Join(dir, fmt.Sprintf("file_%d.txt", i))
167		require.NoError(t, os.WriteFile(p, []byte("data"), 0o644))
168	}
169
170	got := folderUploadPaths(&WebView{ctx: context.Background()}, purecef.FileDialogModeFileDialogOpenFolder, []string{dir})
171	require.Nil(t, got)
172}
173
174func TestDispatchFileDialogResultCancelsWhenEngineMissing(t *testing.T) {
175	callback := &stubFileDialogCallback{}
176	dispatchFileDialogResult(&WebView{ctx: context.Background()}, callback, []string{"/tmp/test.txt"})
177	require.True(t, callback.canceled)
178}
179
180func TestDispatchFileDialogResultCancelsWhenCefPostTaskFails(t *testing.T) {
181	prevNewTask := cefNewTask
182	cefNewTask = func(task purecef.Task) purecef.Task { return task }
183	defer func() { cefNewTask = prevNewTask }()
184
185	prevPost := cefPostTask
186	cefPostTask = func(_ purecef.ThreadID, _ purecef.Task) int32 {
187		return 0
188	}
189	defer func() { cefPostTask = prevPost }()
190
191	callback := &stubFileDialogCallback{}
192	wv := &WebView{
193		ctx:    context.Background(),
194		engine: &Engine{},
195	}
196	dispatchFileDialogResult(wv, callback, []string{"/tmp/test.txt"})
197	require.True(t, callback.canceled)
198}
199
200func TestOnFileDialogPresenterCanContinueCallback(t *testing.T) {
201	prevDecode := decodeCEFStringList
202	decodeCEFStringList = func(purecef.StringList) []string { return nil }
203	defer func() { decodeCEFStringList = prevDecode }()
204
205	prevContinue := continueCEFFileDialog
206	continueCEFFileDialog = func(_ *zerolog.Logger, callback purecef.FileDialogCallback, filePaths ...string) {
207		recorder, ok := callback.(*stubFileDialogCallback)
208		require.True(t, ok)
209		recorder.paths = append([]string(nil), filePaths...)
210	}
211	defer func() { continueCEFFileDialog = prevContinue }()
212
213	callback := &stubFileDialogCallback{}
214	h := &handlerSet{
215		wv: &WebView{ctx: context.Background()},
216		fileDialogPresenter: func(_ *WebView, _ cefFileDialogRequest, cb purecef.FileDialogCallback) {
217			continueCEFFileDialog(nil, cb, "/tmp/report.txt")
218		},
219	}
220
221	handled := h.OnFileDialog(nil, purecef.FileDialogModeFileDialogSave, "", "", 0, 0, 0, callback)
222
223	require.Equal(t, int32(1), handled)
224	require.False(t, callback.canceled)
225	require.Equal(t, []string{"/tmp/report.txt"}, callback.paths)
226}

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