turbo-editors/turbo-corepublic Fork 0
v0.9.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

snippets_test.go · 327 lines · 9.5 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 17h ago1package app
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/gdamore/tcell/v2"
10
11 "codeberg.org/turbo-editors/turbo-core/snippets"
12 "codeberg.org/turbo-editors/turbo-core/ui"
13)
14
15// newSnippetApp returns an editor whose working directory is a project holding
16// the given snippets file, and whose user-level snippets are empty.
17func newSnippetApp(t *testing.T, snippetsTOML string) (*App, tcell.SimulationScreen, string) {
18 t.Helper()
19
20 root := t.TempDir()
21 t.Chdir(root)
22 t.Setenv(testProfile().SnippetDirEnvVar(), t.TempDir()) // never read whoever runs the tests
23
24 if snippetsTOML != "" {
25 path := snippets.ProjectPath(testProfile(), root)
26 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
27 t.Fatalf("creating the snippets directory: %v", err)
28 }
29 if err := os.WriteFile(path, []byte(snippetsTOML), 0o644); err != nil {
30 t.Fatalf("writing the snippets file: %v", err)
31 }
32 }
33
34 a, screen := newTestApp(t)
35 return a, screen, root
36}
37
38// snippetsMenuOf returns the Snippets menu with its items filled in, which is
39// what OnOpen does just before it drops down.
40func snippetsMenuOf(t *testing.T, a *App) *ui.Menu {
41 t.Helper()
42
43 for _, menu := range a.menu.Menus() {
44 if ui.PlainLabel(menu.Label) != "Snippets" {
45 continue
46 }
47 if menu.OnOpen == nil {
48 t.Fatal("the Snippets menu has no OnOpen, so it can never be filled")
49 }
50 menu.OnOpen()
51 return menu
52 }
53 t.Fatal("there is no Snippets menu on the bar")
54 return nil
55}
56
57// labels returns the plain labels of a list of menu items.
58// sameLabels reports whether two lists of menu labels are equal, in order.
59func sameLabels(got, want []string) bool {
60 if len(got) != len(want) {
61 return false
62 }
63 for i := range want {
64 if got[i] != want[i] {
65 return false
66 }
67 }
68 return true
69}
70
71func labels(items []*ui.MenuItem) []string {
72 out := make([]string, 0, len(items))
73 for _, item := range items {
74 if item.Separator {
75 out = append(out, "---")
76 continue
77 }
78 out = append(out, ui.PlainLabel(item.Label))
79 }
80 return out
81}
82
83// findItem returns the item with a plain label, or nil.
84func findItem(items []*ui.MenuItem, label string) *ui.MenuItem {
85 for _, item := range items {
86 if ui.PlainLabel(item.Label) == label {
87 return item
88 }
89 }
90 return nil
91}
92
93const twoGroups = `
94[[snippet]]
95name = "if broken"
96group = "Shell"
97languages = ["bash"]
98body = """
99if [ -n "$broken" ]; then
100\treturn 1
101fi"""
102
103[[snippet]]
104name = "TODO"
105body = "TODO: "
106`
107
108func TestNoTwoMenusShareAHotKey(t *testing.T) {
109 // The bar answers the first match it finds, so a duplicate silently makes
110 // one menu unreachable from the keyboard. Snippets and Search both wanted
111 // S, and Snippets was the one nobody could open.
112 a, _ := newTestApp(t)
113
114 seen := map[rune]string{}
115 for _, menu := range a.menu.Menus() {
116 label, hot, _ := ui.SplitHotKey(menu.Label)
117 if hot == 0 {
118 t.Errorf("the %q menu has no hot key", label)
119 continue
120 }
121 if other, clash := seen[hot]; clash {
122 t.Errorf("%q and %q both answer to Alt-%c", other, label, hot)
123 }
124 seen[hot] = label
125 }
126}
127
128func TestThereIsAlwaysASnippetsMenu(t *testing.T) {
129 // Without one, the item that creates the file is unreachable and the
130 // feature cannot be started.
131 a, _, _ := newSnippetApp(t, "")
132
133 menu := snippetsMenuOf(t, a)
134
135 want := []string{"Create snippets file", "Open snippets file"}
136 if got := labels(menu.Items); !sameLabels(got, want) {
137 t.Errorf("an empty project's Snippets menu holds %v, want %v", got, want)
138 }
139}
140
141func TestTheMenuIsBuiltFromTheFile(t *testing.T) {
142 a, _, root := newSnippetApp(t, twoGroups)
143 writeTestFile(t, filepath.Join(root, "build.sh"), "#!/bin/sh\n")
144 a.Open(filepath.Join(root, "build.sh"))
145
146 menu := snippetsMenuOf(t, a)
147
148 if got := labels(menu.Items); len(got) != 5 || got[0] != "Shell" || got[1] != "General" {
149 t.Fatalf("the menu holds %v, want the two groups then the create and open items", got)
150 }
151
152 group := findItem(menu.Items, "Shell")
153 if group == nil || len(group.Items) != 1 {
154 t.Fatalf("the Shell group holds %v", labels(group.Items))
155 }
156 if got := ui.PlainLabel(group.Items[0].Label); got != "if broken" {
157 t.Errorf("the Shell submenu holds %q", got)
158 }
159}
160
161func TestTheMenuIsFilteredByTheFrontWindowsLanguage(t *testing.T) {
162 a, _, root := newSnippetApp(t, twoGroups)
163 writeTestFile(t, filepath.Join(root, "notes.md"), "# Notes\n")
164 a.Open(filepath.Join(root, "notes.md"))
165
166 menu := snippetsMenuOf(t, a)
167
168 if findItem(menu.Items, "Shell") != nil {
169 t.Errorf("a shell snippet is offered in a Markdown file: %v", labels(menu.Items))
170 }
171 if findItem(menu.Items, "General") == nil {
172 t.Errorf("the snippet that names no language is not offered: %v", labels(menu.Items))
173 }
174}
175
176func TestWithNoFileOpenEveryUnrestrictedSnippetIsOffered(t *testing.T) {
177 a, _, _ := newSnippetApp(t, twoGroups)
178
179 menu := snippetsMenuOf(t, a)
180
181 if findItem(menu.Items, "General") == nil {
182 t.Errorf("nothing is offered with no file open: %v", labels(menu.Items))
183 }
184 if findItem(menu.Items, "Shell") != nil {
185 t.Errorf("a shell-only snippet is offered with no file open: %v", labels(menu.Items))
186 }
187}
188
189func TestTheMenuFollowsTheWindowThatIsInFront(t *testing.T) {
190 // OnOpen is the whole point: the menu cannot be decided at start-up.
191 a, _, root := newSnippetApp(t, twoGroups)
192 writeTestFile(t, filepath.Join(root, "build.sh"), "#!/bin/sh\n")
193 writeTestFile(t, filepath.Join(root, "notes.md"), "# Notes\n")
194 a.Open(filepath.Join(root, "build.sh"))
195 a.Open(filepath.Join(root, "notes.md"))
196
197 if findItem(snippetsMenuOf(t, a).Items, "Shell") != nil {
198 t.Error("the Shell group is offered while a Markdown file is in front")
199 }
200
201 a.NextWindow() // back to build.sh
202
203 if findItem(snippetsMenuOf(t, a).Items, "Shell") == nil {
204 t.Error("the Shell group is not offered after moving to the shell file")
205 }
206}
207
208func TestChoosingASnippetInsertsItAtTheCursor(t *testing.T) {
209 a, _, root := newSnippetApp(t, twoGroups)
210 writeTestFile(t, filepath.Join(root, "build.sh"), "f() {\n\t\n}\n")
211 a.Open(filepath.Join(root, "build.sh"))
212 press(a, tcell.KeyDown, 0, tcell.ModNone) // onto the indented blank line
213 press(a, tcell.KeyEnd, 0, tcell.ModNone)
214
215 group := findItem(snippetsMenuOf(t, a).Items, "Shell")
216 group.Items[0].Action()
217
218 want := "f() {\n\tif [ -n \"$broken\" ]; then\n\t\treturn 1\n\tfi\n}\n"
219 if got := activeBuffer(t, a).Text(); got != want {
220 t.Errorf("the buffer holds\n%q\nwant\n%q", got, want)
221 }
222}
223
224func TestASnippetItemIsDisabledWithNoFileOpen(t *testing.T) {
225 a, _, _ := newSnippetApp(t, twoGroups)
226
227 group := findItem(snippetsMenuOf(t, a).Items, "General")
228 if group == nil || len(group.Items) == 0 {
229 t.Fatal("the General group is missing")
230 }
231 if group.Items[0].Enabled == nil || group.Items[0].Enabled() {
232 t.Error("a snippet can be chosen with no file to insert it into")
233 }
234}
235
236func TestInsertingWithNoFileOpenDoesNothing(t *testing.T) {
237 a, _, _ := newSnippetApp(t, twoGroups)
238
239 a.InsertSnippet("anything")
240
241 if a.Desktop().Count() != 0 {
242 t.Error("inserting a snippet with no window opened one")
243 }
244 if a.Modals() != 0 {
245 t.Error("inserting a snippet with no window opened a dialog")
246 }
247}
248
249func TestAnUnreadableSnippetsFileIsSaidSoInTheMenu(t *testing.T) {
250 // A typo should be visible where the snippets were expected, not silent.
251 a, _, _ := newSnippetApp(t, "[[snippet]\nname = ")
252
253 items := snippetsMenuOf(t, a).Items
254
255 if got := labels(items); len(got) == 0 || !strings.Contains(got[0], "Cannot read") {
256 t.Errorf("the menu holds %v, want a line saying the file cannot be read", got)
257 }
258 if items[0].Enabled == nil || items[0].Enabled() {
259 t.Error("the error line can be chosen")
260 }
261 if findItem(items, "Create snippets file") == nil {
262 t.Error("the create item is missing, so there is no way forward")
263 }
264}
265
266func TestCreateSnippetsWritesAndOpensTheFile(t *testing.T) {
267 a, _, root := newSnippetApp(t, "")
268
269 a.CreateSnippets()
270
271 if !snippets.Exists(testProfile(), root) {
272 t.Fatal("no snippets file was written")
273 }
274 if a.Desktop().Count() != 1 {
275 t.Fatalf("Count() = %d, want the file open", a.Desktop().Count())
276 }
277 if got := a.Desktop().Active().Title(); got != snippets.FileName {
278 t.Errorf("the window shows %q, want %q", got, snippets.FileName)
279 }
280}
281
282func TestTheCreatedFileFillsTheMenu(t *testing.T) {
283 // The starter file has to be usable, not merely parseable. Which groups
284 // are in it belongs to the editor's own template and its own test; what is
285 // checked here is that whatever it holds reaches the menu.
286 a, _, root := newSnippetApp(t, "")
287 a.CreateSnippets()
288 writeTestFile(t, filepath.Join(root, "build.sh"), "#!/bin/sh\n")
289 a.Open(filepath.Join(root, "build.sh"))
290
291 items := snippetsMenuOf(t, a).Items
292
293 group := findItem(items, "General")
294 if group == nil || len(group.Items) == 0 {
295 t.Errorf("the created file gave no snippets at all: %v", labels(items))
296 }
297}
298
299func TestCreateSnippetsTwiceOpensRatherThanOverwrites(t *testing.T) {
300 a, _, root := newSnippetApp(t, "[[snippet]]\nname = \"mine\"\nbody = \"hand written\"\n")
301
302 a.CreateSnippets()
303
304 contents := readTestFile(t, snippets.ProjectPath(testProfile(), root))
305 if !strings.Contains(contents, "hand written") {
306 t.Errorf("the existing file was overwritten:\n%s", contents)
307 }
308 if a.Desktop().Count() != 1 {
309 t.Errorf("Count() = %d, want the existing file opened", a.Desktop().Count())
310 }
311}
312
313func TestTheSnippetsFileIsColouredAsTOML(t *testing.T) {
314 a, screen, _ := newSnippetApp(t, "")
315
316 a.CreateSnippets()
317 lines := render(t, a, screen)
318
319 row, col := findRune(t, lines, "# turbo-test snippets", '#')
320 cells, width, _ := screen.GetContents()
321 got, _, _ := cells[row*width+col].Style.Decompose()
322
323 want, _, _ := a.Theme().Style("syntax.comment").Decompose()
324 if got != want {
325 t.Errorf("the comment is drawn in %v, want the comment colour %v", got, want)
326 }
327}