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}