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

terminals_test.go · 246 lines · 6.5 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1package app
2
3import (
4 "os"
5 "path/filepath"
6 "runtime"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/gdamore/tcell/v2"
12
📦 Turbo Core f3ade8d k33g 10h ago13 "rickub.com/turbo-editors/turbo-core/terminal"
🛟 Updated. 28d5985 k33g 18h ago14)
15
16// newTerminalTestApp returns an editor whose terminals run a plain /bin/sh.
17//
18// The shell is pinned rather than taken from SHELL so the tests do not depend
19// on whoever is running them: a login shell with a themed prompt writes enough
20// to the screen to hide what a test is looking for.
21func newTerminalTestApp(t *testing.T) (*App, tcell.SimulationScreen) {
22 t.Helper()
23 skipWithoutPTY(t)
24 t.Setenv("SHELL", "/bin/sh")
25
26 return newTestApp(t)
27}
28
29// skipWithoutPTY skips a test on a platform with no pseudo-terminals, which is
30// where NewTerminal is expected to refuse rather than work.
31func skipWithoutPTY(t *testing.T) {
32 t.Helper()
33
34 if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
35 t.Skipf("pseudo-terminals are not supported on %s yet", runtime.GOOS)
36 }
37 if _, err := os.Stat("/dev/ptmx"); err != nil {
38 t.Skipf("no /dev/ptmx here: %v", err)
39 }
40}
41
42// runInTerminal types a command into the front window and waits for a string to
43// appear on the screen.
44//
45// It redraws while it waits, which is what makes it an end-to-end check: the
46// text only turns up if the key routing, the pseudo-terminal, the emulator and
47// the drawing all work.
48func runInTerminal(t *testing.T, a *App, screen tcell.SimulationScreen, command, want string) {
49 t.Helper()
50
51 typeText(a, command)
52 press(a, tcell.KeyEnter, 0, tcell.ModNone)
53
54 deadline := time.After(20 * time.Second)
55 for {
56 lines := render(t, a, screen)
57 for _, line := range lines {
58 if strings.Contains(line, want) {
59 return
60 }
61 }
62
63 select {
64 case <-deadline:
65 t.Fatalf("after %q the screen never showed %q; it shows:\n%s",
66 command, want, strings.Join(lines, "\n"))
67 case <-time.After(10 * time.Millisecond):
68 }
69 }
70}
71
72func TestNewTerminalOpensAWindowHoldingAShell(t *testing.T) {
73 a, _ := newTerminalTestApp(t)
74
75 a.NewTerminal()
76 t.Cleanup(a.closeTerminals)
77
78 if a.Desktop().Count() != 1 {
79 t.Fatalf("Count() = %d, want 1", a.Desktop().Count())
80 }
81 if !a.isTerminalWindow(a.Desktop().Active()) {
82 t.Error("the new window does not hold a terminal")
83 }
84 if a.activeTerminal() == nil {
85 t.Error("activeTerminal() returned nil with a terminal in front")
86 }
87 if a.activeView() != nil {
88 t.Error("activeView() returned an editor for a terminal window")
89 }
90}
91
92func TestATerminalRunsWhatIsTypedIntoIt(t *testing.T) {
93 a, screen := newTerminalTestApp(t)
94
95 a.NewTerminal()
96 t.Cleanup(a.closeTerminals)
97
98 // The quotes are what makes this a test of the shell rather than of the
99 // echo: what is typed reads "turbo''-go-works", and only a shell that
100 // really ran it puts "turbo-go-works" on the screen.
101 runInTerminal(t, a, screen, "echo turbo''-go-works", "turbo-go-works")
102}
103
104func TestATerminalStartsBesideTheFileBeingEdited(t *testing.T) {
105 a, screen := newTerminalTestApp(t)
106
107 directory := t.TempDir()
108 // The marker names the directory without naming it in the command, so
109 // finding it on screen can only mean the shell really started there.
110 writeTestFile(t, filepath.Join(directory, "marker-is-here"), "")
111 writeTestFile(t, filepath.Join(directory, "main.go"), "package main\n")
112 a.Open(filepath.Join(directory, "main.go"))
113
114 a.NewTerminal()
115 t.Cleanup(a.closeTerminals)
116
117 runInTerminal(t, a, screen, "ls", "marker-is-here")
118}
119
120func TestATerminalStartsInTheWorkingDirectoryWithNoFileOpen(t *testing.T) {
121 a, _ := newTerminalTestApp(t)
122
123 working, err := os.Getwd()
124 if err != nil {
125 t.Fatalf("reading the working directory: %v", err)
126 }
127 if got := a.terminalDirectory(); got != working {
128 t.Errorf("terminalDirectory() = %q, want the working directory %q", got, working)
129 }
130}
131
132func TestClosingATerminalWindowTakesItAway(t *testing.T) {
133 a, _ := newTerminalTestApp(t)
134
135 a.NewTerminal()
136 a.CloseFile()
137
138 if a.Desktop().Count() != 0 {
139 t.Errorf("Count() = %d, want 0", a.Desktop().Count())
140 }
141 if len(a.terminals) != 0 {
142 t.Errorf("%d terminals are still recorded", len(a.terminals))
143 }
144 if a.Modals() != 0 {
145 t.Error("closing a terminal asked about unsaved changes")
146 }
147}
148
149func TestLeavingTheEditorClosesEveryTerminal(t *testing.T) {
150 a, _ := newTerminalTestApp(t)
151
152 a.NewTerminal()
153 a.NewTerminal()
154
155 a.Quit()
156
157 if !a.quitting {
158 t.Error("Quit() did not ask the editor to stop")
159 }
160 if len(a.terminals) != 0 {
161 t.Errorf("%d terminals were left running", len(a.terminals))
162 }
163}
164
165func TestTheFileActionsLeaveATerminalAlone(t *testing.T) {
166 a, _ := newTerminalTestApp(t)
167
168 a.NewTerminal()
169 t.Cleanup(a.closeTerminals)
170
171 // None of these has anything to act on, and each of them used to assume
172 // every window held an editor.
173 a.SaveFile()
174 a.SaveFileAs()
175 a.ToggleLineNumbers()
176 a.RequestCompletion()
177
178 if a.Modals() != 0 {
179 t.Errorf("%d dialogs opened over a terminal window", a.Modals())
180 }
181 if a.Desktop().Count() != 1 {
182 t.Errorf("Count() = %d, want the terminal to still be there", a.Desktop().Count())
183 }
184}
185
186func TestAFocusedTerminalKeepsTheKeysAShellNeeds(t *testing.T) {
187 a, _ := newTerminalTestApp(t)
188
189 a.NewTerminal()
190 t.Cleanup(a.closeTerminals)
191
192 // Ctrl-W deletes a word in a shell. If the editor kept it, it would close
193 // the window instead, which is the whole reason terminalTakesKey exists.
194 press(a, tcell.KeyCtrlW, 0, tcell.ModCtrl)
195
196 if a.Desktop().Count() != 1 {
197 t.Error("Ctrl-W closed the terminal instead of reaching the shell")
198 }
199}
200
201func TestAFocusedTerminalStillLetsTheEditorHaveItsOwnKeys(t *testing.T) {
202 a, _ := newTerminalTestApp(t)
203
204 a.NewTerminal()
205 t.Cleanup(a.closeTerminals)
206
207 press(a, tcell.KeyF8, 0, tcell.ModNone) // Window ▸ New terminal
208 if a.Desktop().Count() != 2 {
209 t.Fatalf("Count() = %d, want F8 to have opened a second terminal", a.Desktop().Count())
210 }
211
212 press(a, tcell.KeyRune, 'x', tcell.ModAlt) // Exit
213 if !a.quitting {
214 t.Error("Alt-X did not leave the editor with a terminal in front")
215 }
216}
217
218func TestAPlatformWithoutPseudoTerminalsSaysSo(t *testing.T) {
219 a, _ := newTestApp(t)
220
221 a.reportTerminalFailure(terminal.ErrUnsupported)
222
223 if a.Modals() != 1 {
224 t.Fatalf("Modals() = %d, want the refusal to be shown", a.Modals())
225 }
226 if a.Desktop().Count() != 0 {
227 t.Errorf("Count() = %d, want no window for a terminal that never started", a.Desktop().Count())
228 }
229}
230
231func TestATerminalWindowIsNamedAfterTheProgramInIt(t *testing.T) {
232 a, _ := newTerminalTestApp(t)
233
234 a.NewTerminal()
235 t.Cleanup(a.closeTerminals)
236
237 window := a.Desktop().Active()
238 if !strings.Contains(window.Title(), "sh") {
239 t.Errorf("the window is called %q, want the name of the shell", window.Title())
240 }
241
242 a.refreshTerminalTitles()
243 if window.Title() == "" {
244 t.Error("refreshing the titles emptied one")
245 }
246}