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

parameters_test.go · 284 lines · 9.2 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 19h ago1package app
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "github.com/gdamore/tcell/v2"
10
📦 Turbo Core f3ade8d k33g 12h ago11 "rickub.com/turbo-editors/turbo-core/tools"
12 "rickub.com/turbo-editors/turbo-core/ui"
🛟 Updated. 28d5985 k33g 19h ago13)
14
15// parametersBox returns the parameters dialog on screen, failing the test when
16// there is none.
17func parametersBox(t *testing.T, a *App) *ui.Dialog {
18 t.Helper()
19
20 dialog := a.TopModal()
21 if dialog == nil {
22 t.Fatal("no dialog opened; the command must not run before its values are given")
23 }
24 return dialog
25}
26
27// typeInto sends a run of characters to the dialog in front.
28func typeInto(a *App, text string) {
29 for _, r := range text {
30 a.handle(tcell.NewEventKey(tcell.KeyRune, r, tcell.ModNone))
31 }
32}
33
34// answer types a value into the first field and presses Enter on OK.
35func answer(t *testing.T, a *App, value string) {
36 t.Helper()
37
38 parametersBox(t, a)
39 typeInto(a, value)
40 a.handle(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone))
41}
42
43func TestAToolWithAPlaceholderAsksBeforeItRuns(t *testing.T) {
44 // The whole point: nothing is started until the value is given, so a
45 // half-formed command never reaches the shell.
46 a, _ := newToolsApp(t, "")
47
48 a.RunTool(tools.Tool{Name: "~I~nit", Command: "touch {{file name}}", Output: tools.OutputPopup})
49
50 dialog := parametersBox(t, a)
51 if got := dialog.Title(); got != "Init" {
52 t.Errorf("the box is titled %q, want the tool's name without its hot-key markers", got)
53 }
54 if a.running != nil {
55 t.Error("the command started before the box was answered")
56 }
57}
58
59func TestAToolWithNoPlaceholderRunsStraightAway(t *testing.T) {
60 a, _ := newToolsApp(t, "")
61
62 runAndDrain(t, a, tools.Tool{Name: "Echo", Command: "echo plain", Output: tools.OutputPopup})
63
64 if a.running == nil {
65 t.Fatal("an ordinary tool did not run")
66 }
67 if lines := a.running.run.Lines(); len(lines) == 0 || lines[0] != "plain" {
68 t.Errorf("the command printed %v, want plain", lines)
69 }
70}
71
72func TestAnsweringTheBoxRunsTheFilledCommand(t *testing.T) {
73 a, _ := newToolsApp(t, "")
74
75 a.RunTool(tools.Tool{Name: "Init", Command: "echo {{message}}", Output: tools.OutputPopup})
76 answer(t, a, "hello there")
77
78 waitForLoopTurn(t, a, "the filled command to finish", func() bool {
79 return a.running != nil && a.runningFinished()
80 })
81
82 lines := a.running.run.Lines()
83 if len(lines) == 0 || lines[0] != "hello there" {
84 t.Fatalf("the command printed %v, want %q — the value did not reach it", lines, "hello there")
85 }
86 // Shell-quoted, so the space stayed inside one argument rather than
87 // becoming two.
88 if got := a.running.run.Command(); !strings.Contains(got, "'hello there'") {
89 t.Errorf("the command was %q, want the value quoted", got)
90 }
91}
92
93func TestCancellingTheBoxRunsNothing(t *testing.T) {
94 a, _ := newToolsApp(t, "")
95
96 a.RunTool(tools.Tool{Name: "Init", Command: "touch {{name}}", Output: tools.OutputPopup})
97
98 parametersBox(t, a)
99 typeInto(a, "cancelled.txt")
100 a.handle(tcell.NewEventKey(tcell.KeyEscape, 0, tcell.ModNone))
101
102 if a.Modals() != 0 {
103 t.Fatal("Escape left the box open")
104 }
105 if a.running != nil {
106 t.Error("the command ran although the box was cancelled")
107 }
108 if _, err := os.Stat("cancelled.txt"); err == nil {
109 t.Error("the cancelled command created its file")
110 }
111}
112
113func TestTheValuesAreRememberedForTheNextRun(t *testing.T) {
114 // Running the same parameterised command twice should not mean typing the
115 // same thing twice.
116 a, _ := newToolsApp(t, "")
117 tool := tools.Tool{Name: "Init", Command: "echo {{message}}", Output: tools.OutputPopup}
118
119 a.RunTool(tool)
120 answer(t, a, "remembered")
121 waitForLoopTurn(t, a, "the first run to finish", func() bool {
122 return a.running != nil && a.runningFinished()
123 })
124 a.finishRun()
125
126 a.RunTool(tool)
127
128 parametersBox(t, a)
129 if got := a.toolValues[tool.Name]["message"]; got != "remembered" {
130 t.Fatalf("the remembered value is %q, want %q", got, "remembered")
131 }
132 // Enter alone, with nothing typed, must re-run what was typed before.
133 a.handle(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone))
134 waitForLoopTurn(t, a, "the second run to finish", func() bool {
135 return a.running != nil && a.runningFinished()
136 })
137 if lines := a.running.run.Lines(); len(lines) == 0 || lines[0] != "remembered" {
138 t.Errorf("the second run printed %v, want the remembered value", lines)
139 }
140}
141
142func TestValuesAreRememberedPerToolAndNotShared(t *testing.T) {
143 a, _ := newToolsApp(t, "")
144
145 a.RunTool(tools.Tool{Name: "First", Command: "echo {{message}}", Output: tools.OutputPopup})
146 answer(t, a, "one")
147 waitForLoopTurn(t, a, "the first tool to finish", func() bool { return a.running != nil && a.runningFinished() })
148 a.finishRun()
149
150 a.RunTool(tools.Tool{Name: "Second", Command: "echo {{message}}", Output: tools.OutputPopup})
151
152 parametersBox(t, a)
153 if got := a.toolValues["Second"]["message"]; got != "" {
154 t.Errorf("the second tool starts with %q; a value belongs to the tool it was typed for", got)
155 }
156}
157
158func TestNothingIsWrittenToTheProjectDirectory(t *testing.T) {
159 // The values live for the session only. A value somebody typed this
160 // afternoon is not a decision the project made.
161 a, root := newToolsApp(t, "")
162
163 a.RunTool(tools.Tool{Name: "Init", Command: "echo {{message}}", Output: tools.OutputPopup})
164 answer(t, a, "not persisted")
165 waitForLoopTurn(t, a, "the command to finish", func() bool { return a.running != nil && a.runningFinished() })
166
167 if entries, err := os.ReadDir(filepath.Join(root, testProfile().ProjectDir())); err == nil {
168 for _, entry := range entries {
169 if entry.Name() != tools.FileName {
170 t.Errorf("running a parameterised tool left %q behind", entry.Name())
171 }
172 }
173 }
174}
175
176func TestSeveralPlaceholdersGetAFieldEach(t *testing.T) {
177 a, _ := newToolsApp(t, "")
178
179 a.RunTool(tools.Tool{Name: "Copy", Command: "echo {{from}} {{to}}", Output: tools.OutputPopup})
180
181 parametersBox(t, a)
182 // Tab moves between the fields; type into each in turn.
183 typeInto(a, "here")
184 a.handle(tcell.NewEventKey(tcell.KeyTab, 0, tcell.ModNone))
185 typeInto(a, "there")
186 a.handle(tcell.NewEventKey(tcell.KeyEnter, 0, tcell.ModNone))
187
188 waitForLoopTurn(t, a, "the command to finish", func() bool { return a.running != nil && a.runningFinished() })
189
190 if lines := a.running.run.Lines(); len(lines) == 0 || lines[0] != "here there" {
191 t.Errorf("the command printed %v, want both values", lines)
192 }
193}
194
195func TestARawPlaceholderReachesTheShellUnquoted(t *testing.T) {
196 // The escape hatch, end to end: one field standing for several arguments.
197 a, _ := newToolsApp(t, "")
198
199 a.RunTool(tools.Tool{Name: "Echo", Command: "echo {{words...}}", Output: tools.OutputPopup})
200 answer(t, a, "a b c")
201
202 waitForLoopTurn(t, a, "the command to finish", func() bool { return a.running != nil && a.runningFinished() })
203
204 if got := a.running.run.Command(); got != "echo a b c" {
205 t.Errorf("the command was %q, want the value verbatim", got)
206 }
207}
208
209func TestATerminalToolIsAlsoAskedFirst(t *testing.T) {
210 // The other output destination takes the same path, and the window's title
211 // is the filled command rather than the one with braces in it.
212 a, _ := newToolsApp(t, "")
213
214 a.RunTool(tools.Tool{Name: "Run", Command: "echo {{message}}", Output: tools.OutputTerminal})
215 parametersBox(t, a)
216 answer(t, a, "in a terminal")
217
218 window := a.Desktop().Active()
219 if window == nil {
220 t.Fatal("no terminal window opened")
221 }
222 if got := window.Title(); !strings.Contains(got, "'in a terminal'") {
223 t.Errorf("the window is titled %q, want the filled command", got)
224 }
225}
226
227func TestATallDemandIsRefusedRatherThanShownOffScreen(t *testing.T) {
228 // A dialog whose OK button is below the bottom of the screen cannot be
229 // answered, which is worse than being told it will not fit.
230 a, _ := newToolsApp(t, "")
231 command := "echo"
232 for i := range MaxParameterFields(24) + 1 {
233 command += " {{value " + string(rune('a'+i)) + "}}"
234 }
235
236 a.RunTool(tools.Tool{Name: "Too many", Command: command, Output: tools.OutputPopup})
237
238 dialog := a.TopModal()
239 if dialog == nil {
240 t.Fatal("nothing was shown at all")
241 }
242 if got := dialog.Title(); got != a.toolsMenuName() {
243 t.Errorf("the box is titled %q, want a message from the toolchain menu", got)
244 }
245 if a.running != nil {
246 t.Error("the command ran although its values were never given")
247 }
248}
249
250func TestMaxParameterFieldsGrowsWithTheScreen(t *testing.T) {
251 if got := MaxParameterFields(24); got != 9 {
252 t.Errorf("MaxParameterFields(24) = %d, want 9", got)
253 }
254 if MaxParameterFields(50) <= MaxParameterFields(24) {
255 t.Error("a taller screen must fit more fields")
256 }
257 // A screen too small for even one still offers one, because refusing every
258 // parameterised tool on a short terminal is worse than a clipped box.
259 if got := MaxParameterFields(4); got != 1 {
260 t.Errorf("MaxParameterFields(4) = %d, want 1", got)
261 }
262}
263
264func TestTheBoxStartsFromWhatWasRemembered(t *testing.T) {
265 box := NewParametersDialog("Init", []string{"module"}, map[string]string{"module": "example.com/mine"}, ui.Rect{W: 80, H: 24})
266
267 if got := box.Values()["module"]; got != "example.com/mine" {
268 t.Errorf("the field holds %q, want the remembered value", got)
269 }
270}
271
272func TestTheBoxAsksForEveryLabelItWasGiven(t *testing.T) {
273 box := NewParametersDialog("Copy", []string{"from", "to"}, nil, ui.Rect{W: 80, H: 24})
274
275 values := box.Values()
276 if len(values) != 2 {
277 t.Fatalf("Values() = %v, want one entry per label", values)
278 }
279 for _, label := range []string{"from", "to"} {
280 if _, asked := values[label]; !asked {
281 t.Errorf("Values() has no entry for %q", label)
282 }
283 }
284}