turbo-editors/turbo-corepublic Fork 0
d662cebdb65b319885da903daf7eff9ab1bfbb78
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.

🛟 Updated. 28d5985 · on d662cebdb65b319885da903daf7eff9ab1bfbb78 · k33g · 21h ago
snippets_test.go · 327 lines · 9.5 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
package app

import (
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/gdamore/tcell/v2"

	"codeberg.org/turbo-editors/turbo-core/snippets"
	"codeberg.org/turbo-editors/turbo-core/ui"
)

// newSnippetApp returns an editor whose working directory is a project holding
// the given snippets file, and whose user-level snippets are empty.
func newSnippetApp(t *testing.T, snippetsTOML string) (*App, tcell.SimulationScreen, string) {
	t.Helper()

	root := t.TempDir()
	t.Chdir(root)
	t.Setenv(testProfile().SnippetDirEnvVar(), t.TempDir()) // never read whoever runs the tests

	if snippetsTOML != "" {
		path := snippets.ProjectPath(testProfile(), root)
		if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
			t.Fatalf("creating the snippets directory: %v", err)
		}
		if err := os.WriteFile(path, []byte(snippetsTOML), 0o644); err != nil {
			t.Fatalf("writing the snippets file: %v", err)
		}
	}

	a, screen := newTestApp(t)
	return a, screen, root
}

// snippetsMenuOf returns the Snippets menu with its items filled in, which is
// what OnOpen does just before it drops down.
func snippetsMenuOf(t *testing.T, a *App) *ui.Menu {
	t.Helper()

	for _, menu := range a.menu.Menus() {
		if ui.PlainLabel(menu.Label) != "Snippets" {
			continue
		}
		if menu.OnOpen == nil {
			t.Fatal("the Snippets menu has no OnOpen, so it can never be filled")
		}
		menu.OnOpen()
		return menu
	}
	t.Fatal("there is no Snippets menu on the bar")
	return nil
}

// labels returns the plain labels of a list of menu items.
// sameLabels reports whether two lists of menu labels are equal, in order.
func sameLabels(got, want []string) bool {
	if len(got) != len(want) {
		return false
	}
	for i := range want {
		if got[i] != want[i] {
			return false
		}
	}
	return true
}

func labels(items []*ui.MenuItem) []string {
	out := make([]string, 0, len(items))
	for _, item := range items {
		if item.Separator {
			out = append(out, "---")
			continue
		}
		out = append(out, ui.PlainLabel(item.Label))
	}
	return out
}

// findItem returns the item with a plain label, or nil.
func findItem(items []*ui.MenuItem, label string) *ui.MenuItem {
	for _, item := range items {
		if ui.PlainLabel(item.Label) == label {
			return item
		}
	}
	return nil
}

const twoGroups = `
[[snippet]]
name = "if broken"
group = "Shell"
languages = ["bash"]
body = """
if [ -n "$broken" ]; then
\treturn 1
fi"""

[[snippet]]
name = "TODO"
body = "TODO: "
`

func TestNoTwoMenusShareAHotKey(t *testing.T) {
	// The bar answers the first match it finds, so a duplicate silently makes
	// one menu unreachable from the keyboard. Snippets and Search both wanted
	// S, and Snippets was the one nobody could open.
	a, _ := newTestApp(t)

	seen := map[rune]string{}
	for _, menu := range a.menu.Menus() {
		label, hot, _ := ui.SplitHotKey(menu.Label)
		if hot == 0 {
			t.Errorf("the %q menu has no hot key", label)
			continue
		}
		if other, clash := seen[hot]; clash {
			t.Errorf("%q and %q both answer to Alt-%c", other, label, hot)
		}
		seen[hot] = label
	}
}

func TestThereIsAlwaysASnippetsMenu(t *testing.T) {
	// Without one, the item that creates the file is unreachable and the
	// feature cannot be started.
	a, _, _ := newSnippetApp(t, "")

	menu := snippetsMenuOf(t, a)

	want := []string{"Create snippets file", "Open snippets file"}
	if got := labels(menu.Items); !sameLabels(got, want) {
		t.Errorf("an empty project's Snippets menu holds %v, want %v", got, want)
	}
}

func TestTheMenuIsBuiltFromTheFile(t *testing.T) {
	a, _, root := newSnippetApp(t, twoGroups)
	writeTestFile(t, filepath.Join(root, "build.sh"), "#!/bin/sh\n")
	a.Open(filepath.Join(root, "build.sh"))

	menu := snippetsMenuOf(t, a)

	if got := labels(menu.Items); len(got) != 5 || got[0] != "Shell" || got[1] != "General" {
		t.Fatalf("the menu holds %v, want the two groups then the create and open items", got)
	}

	group := findItem(menu.Items, "Shell")
	if group == nil || len(group.Items) != 1 {
		t.Fatalf("the Shell group holds %v", labels(group.Items))
	}
	if got := ui.PlainLabel(group.Items[0].Label); got != "if broken" {
		t.Errorf("the Shell submenu holds %q", got)
	}
}

func TestTheMenuIsFilteredByTheFrontWindowsLanguage(t *testing.T) {
	a, _, root := newSnippetApp(t, twoGroups)
	writeTestFile(t, filepath.Join(root, "notes.md"), "# Notes\n")
	a.Open(filepath.Join(root, "notes.md"))

	menu := snippetsMenuOf(t, a)

	if findItem(menu.Items, "Shell") != nil {
		t.Errorf("a shell snippet is offered in a Markdown file: %v", labels(menu.Items))
	}
	if findItem(menu.Items, "General") == nil {
		t.Errorf("the snippet that names no language is not offered: %v", labels(menu.Items))
	}
}

func TestWithNoFileOpenEveryUnrestrictedSnippetIsOffered(t *testing.T) {
	a, _, _ := newSnippetApp(t, twoGroups)

	menu := snippetsMenuOf(t, a)

	if findItem(menu.Items, "General") == nil {
		t.Errorf("nothing is offered with no file open: %v", labels(menu.Items))
	}
	if findItem(menu.Items, "Shell") != nil {
		t.Errorf("a shell-only snippet is offered with no file open: %v", labels(menu.Items))
	}
}

func TestTheMenuFollowsTheWindowThatIsInFront(t *testing.T) {
	// OnOpen is the whole point: the menu cannot be decided at start-up.
	a, _, root := newSnippetApp(t, twoGroups)
	writeTestFile(t, filepath.Join(root, "build.sh"), "#!/bin/sh\n")
	writeTestFile(t, filepath.Join(root, "notes.md"), "# Notes\n")
	a.Open(filepath.Join(root, "build.sh"))
	a.Open(filepath.Join(root, "notes.md"))

	if findItem(snippetsMenuOf(t, a).Items, "Shell") != nil {
		t.Error("the Shell group is offered while a Markdown file is in front")
	}

	a.NextWindow() // back to build.sh

	if findItem(snippetsMenuOf(t, a).Items, "Shell") == nil {
		t.Error("the Shell group is not offered after moving to the shell file")
	}
}

func TestChoosingASnippetInsertsItAtTheCursor(t *testing.T) {
	a, _, root := newSnippetApp(t, twoGroups)
	writeTestFile(t, filepath.Join(root, "build.sh"), "f() {\n\t\n}\n")
	a.Open(filepath.Join(root, "build.sh"))
	press(a, tcell.KeyDown, 0, tcell.ModNone) // onto the indented blank line
	press(a, tcell.KeyEnd, 0, tcell.ModNone)

	group := findItem(snippetsMenuOf(t, a).Items, "Shell")
	group.Items[0].Action()

	want := "f() {\n\tif [ -n \"$broken\" ]; then\n\t\treturn 1\n\tfi\n}\n"
	if got := activeBuffer(t, a).Text(); got != want {
		t.Errorf("the buffer holds\n%q\nwant\n%q", got, want)
	}
}

func TestASnippetItemIsDisabledWithNoFileOpen(t *testing.T) {
	a, _, _ := newSnippetApp(t, twoGroups)

	group := findItem(snippetsMenuOf(t, a).Items, "General")
	if group == nil || len(group.Items) == 0 {
		t.Fatal("the General group is missing")
	}
	if group.Items[0].Enabled == nil || group.Items[0].Enabled() {
		t.Error("a snippet can be chosen with no file to insert it into")
	}
}

func TestInsertingWithNoFileOpenDoesNothing(t *testing.T) {
	a, _, _ := newSnippetApp(t, twoGroups)

	a.InsertSnippet("anything")

	if a.Desktop().Count() != 0 {
		t.Error("inserting a snippet with no window opened one")
	}
	if a.Modals() != 0 {
		t.Error("inserting a snippet with no window opened a dialog")
	}
}

func TestAnUnreadableSnippetsFileIsSaidSoInTheMenu(t *testing.T) {
	// A typo should be visible where the snippets were expected, not silent.
	a, _, _ := newSnippetApp(t, "[[snippet]\nname = ")

	items := snippetsMenuOf(t, a).Items

	if got := labels(items); len(got) == 0 || !strings.Contains(got[0], "Cannot read") {
		t.Errorf("the menu holds %v, want a line saying the file cannot be read", got)
	}
	if items[0].Enabled == nil || items[0].Enabled() {
		t.Error("the error line can be chosen")
	}
	if findItem(items, "Create snippets file") == nil {
		t.Error("the create item is missing, so there is no way forward")
	}
}

func TestCreateSnippetsWritesAndOpensTheFile(t *testing.T) {
	a, _, root := newSnippetApp(t, "")

	a.CreateSnippets()

	if !snippets.Exists(testProfile(), root) {
		t.Fatal("no snippets file was written")
	}
	if a.Desktop().Count() != 1 {
		t.Fatalf("Count() = %d, want the file open", a.Desktop().Count())
	}
	if got := a.Desktop().Active().Title(); got != snippets.FileName {
		t.Errorf("the window shows %q, want %q", got, snippets.FileName)
	}
}

func TestTheCreatedFileFillsTheMenu(t *testing.T) {
	// The starter file has to be usable, not merely parseable. Which groups
	// are in it belongs to the editor's own template and its own test; what is
	// checked here is that whatever it holds reaches the menu.
	a, _, root := newSnippetApp(t, "")
	a.CreateSnippets()
	writeTestFile(t, filepath.Join(root, "build.sh"), "#!/bin/sh\n")
	a.Open(filepath.Join(root, "build.sh"))

	items := snippetsMenuOf(t, a).Items

	group := findItem(items, "General")
	if group == nil || len(group.Items) == 0 {
		t.Errorf("the created file gave no snippets at all: %v", labels(items))
	}
}

func TestCreateSnippetsTwiceOpensRatherThanOverwrites(t *testing.T) {
	a, _, root := newSnippetApp(t, "[[snippet]]\nname = \"mine\"\nbody = \"hand written\"\n")

	a.CreateSnippets()

	contents := readTestFile(t, snippets.ProjectPath(testProfile(), root))
	if !strings.Contains(contents, "hand written") {
		t.Errorf("the existing file was overwritten:\n%s", contents)
	}
	if a.Desktop().Count() != 1 {
		t.Errorf("Count() = %d, want the existing file opened", a.Desktop().Count())
	}
}

func TestTheSnippetsFileIsColouredAsTOML(t *testing.T) {
	a, screen, _ := newSnippetApp(t, "")

	a.CreateSnippets()
	lines := render(t, a, screen)

	row, col := findRune(t, lines, "# turbo-test snippets", '#')
	cells, width, _ := screen.GetContents()
	got, _, _ := cells[row*width+col].Style.Decompose()

	want, _, _ := a.Theme().Style("syntax.comment").Decompose()
	if got != want {
		t.Errorf("the comment is drawn in %v, want the comment colour %v", got, want)
	}
}