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

autosave_test.go · 320 lines · 8.8 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 14h ago1package app
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8 "time"
9
📦 Turbo Core f3ade8d k33g 7h ago10 "rickub.com/turbo-editors/turbo-core/settings"
🛟 Updated. 28d5985 k33g 14h ago11)
12
13// fakeClock is a clock the test moves by hand, so an autosave deadline is
14// reached without anything waiting for it.
15type fakeClock struct{ at time.Time }
16
17func (c *fakeClock) now() time.Time { return c.at }
18func (c *fakeClock) pass(d time.Duration) { c.at = c.at.Add(d) }
19
20// newAutosaveApp returns an editor with autosave on, a clock the test drives,
21// and a file open in the front window.
22func newAutosaveApp(t *testing.T, delay time.Duration) (*App, *fakeClock, string) {
23 t.Helper()
24
25 a, _ := newTestApp(t)
26 clock := &fakeClock{at: time.Date(2026, 8, 31, 12, 0, 0, 0, time.UTC)}
27 a.now = clock.now
28
29 path := filepath.Join(t.TempDir(), "main.go")
30 writeTestFile(t, path, "package main\n")
31 a.Open(path)
32 a.SetAutosave(true, delay)
33
34 return a, clock, path
35}
36
37// onDisk returns what a file holds right now.
38func onDisk(t *testing.T, path string) string {
39 t.Helper()
40
41 data, err := os.ReadFile(path)
42 if err != nil {
43 t.Fatalf("reading %s: %v", path, err)
44 }
45 return string(data)
46}
47
48func TestAutosaveIsOffUnlessAProjectAsksForIt(t *testing.T) {
49 a, _ := newTestApp(t)
50
51 if a.AutosaveEnabled() {
52 t.Error("autosave is on in a project that never asked for it")
53 }
54}
55
56func TestAutosaveWritesTheFileOnceTypingStops(t *testing.T) {
57 a, clock, path := newAutosaveApp(t, 2*time.Second)
58
59 typeText(a, "// hello")
60 clock.pass(2 * time.Second)
61 a.saveDueDocuments()
62
63 if got := onDisk(t, path); !strings.Contains(got, "// hello") {
64 t.Errorf("the file on disk is %q; autosave did not write it", got)
65 }
66 if activeBuffer(t, a).Modified() {
67 t.Error("the buffer is still marked modified after being saved")
68 }
69}
70
71func TestAutosaveWaitsWhileTypingContinues(t *testing.T) {
72 a, clock, path := newAutosaveApp(t, 2*time.Second)
73
74 // Each keystroke pushes the deadline out again, so a steady typist is
75 // never interrupted by a write.
76 for range 5 {
77 typeText(a, "x")
78 clock.pass(1900 * time.Millisecond)
79 a.saveDueDocuments()
80 }
81
82 if got := onDisk(t, path); got != "package main\n" {
83 t.Errorf("the file was written mid-typing: %q", got)
84 }
85}
86
87func TestAutosaveDoesNothingBeforeTheDelayHasPassed(t *testing.T) {
88 a, clock, path := newAutosaveApp(t, 2*time.Second)
89
90 typeText(a, "// hello")
91 clock.pass(1999 * time.Millisecond)
92 a.saveDueDocuments()
93
94 if got := onDisk(t, path); got != "package main\n" {
95 t.Errorf("the file was written a millisecond early: %q", got)
96 }
97}
98
99func TestAutosaveDoesNothingWhenItIsOff(t *testing.T) {
100 a, clock, path := newAutosaveApp(t, 2*time.Second)
101 a.SetAutosave(false, 0)
102
103 typeText(a, "// hello")
104 clock.pass(time.Hour)
105 a.saveDueDocuments()
106
107 if got := onDisk(t, path); got != "package main\n" {
108 t.Errorf("a file was written with autosave off: %q", got)
109 }
110}
111
112func TestTurningAutosaveOffForgetsThePendingSave(t *testing.T) {
113 a, clock, path := newAutosaveApp(t, 2*time.Second)
114
115 typeText(a, "// hello")
116 a.SetAutosave(false, 0) // the deadline was already set when this ran
117 clock.pass(time.Hour)
118 a.saveDueDocuments()
119
120 if got := onDisk(t, path); got != "package main\n" {
121 t.Errorf("a save pending when autosave was turned off still happened: %q", got)
122 }
123}
124
125func TestAutosaveLeavesAFileThatHasNoNameAlone(t *testing.T) {
126 a, clock, _ := newAutosaveApp(t, 2*time.Second)
127 a.NewFile() // untitled: naming it is a question, and autosave asks nothing
128
129 typeText(a, "package main")
130 clock.pass(2 * time.Second)
131 a.saveDueDocuments()
132
133 if a.Modals() != 0 {
134 t.Errorf("autosave opened %d dialogs; it must never ask anything", a.Modals())
135 }
136 if !activeBuffer(t, a).Modified() {
137 t.Error("an unnamed buffer was somehow saved")
138 }
139}
140
141func TestAutosaveSavesEveryOpenFile(t *testing.T) {
142 a, clock, first := newAutosaveApp(t, 2*time.Second)
143 second := filepath.Join(t.TempDir(), "other.go")
144 writeTestFile(t, second, "package other\n")
145 a.Open(second)
146
147 typeText(a, "// two")
148 a.NextWindow()
149 typeText(a, "// one")
150 clock.pass(2 * time.Second)
151 a.saveDueDocuments()
152
153 if got := onDisk(t, first); !strings.Contains(got, "// one") {
154 t.Errorf("the first file was not saved: %q", got)
155 }
156 if got := onDisk(t, second); !strings.Contains(got, "// two") {
157 t.Errorf("the second file was not saved: %q", got)
158 }
159}
160
161func TestAFailedAutosaveIsReportedWithoutADialog(t *testing.T) {
162 a, clock, path := newAutosaveApp(t, 2*time.Second)
163 typeText(a, "// hello")
164
165 // A directory cannot be written to as a file, which is the simplest way to
166 // make the write fail without depending on who is running the test.
167 if err := os.Remove(path); err != nil {
168 t.Fatalf("removing the file: %v", err)
169 }
170 if err := os.Mkdir(path, 0o755); err != nil {
171 t.Fatalf("putting a directory in its place: %v", err)
172 }
173
174 clock.pass(2 * time.Second)
175 a.saveDueDocuments()
176
177 if a.Modals() != 0 {
178 t.Errorf("a failed autosave opened %d dialogs", a.Modals())
179 }
180}
181
182func TestAFailedAutosaveIsNotRetriedUntilTheNextEdit(t *testing.T) {
183 a, clock, path := newAutosaveApp(t, 2*time.Second)
184 typeText(a, "// hello")
185 if err := os.Remove(path); err != nil {
186 t.Fatalf("removing the file: %v", err)
187 }
188 if err := os.Mkdir(path, 0o755); err != nil {
189 t.Fatalf("putting a directory in its place: %v", err)
190 }
191
192 clock.pass(2 * time.Second)
193 a.saveDueDocuments()
194
195 // An attempt that failed must leave nothing pending. A deadline still set
196 // would come due again on the very next turn of the event loop, and go on
197 // doing so for as long as the editor is open.
198 if !a.autosave.due.IsZero() {
199 t.Error("a failed autosave left its deadline set, so it will retry forever")
200 }
201
202 // And a further turn of the loop really does nothing, rather than merely
203 // not being due yet.
204 clock.pass(time.Hour)
205 a.saveDueDocuments()
206 if a.Modals() != 0 {
207 t.Error("a later turn of the event loop retried the failed save")
208 }
209}
210
211func TestClosingAWindowWithAutosaveOnAsksNothing(t *testing.T) {
212 a, _, path := newAutosaveApp(t, 2*time.Second)
213
214 typeText(a, "// hello")
215 a.CloseFile()
216
217 if a.Modals() != 0 {
218 t.Errorf("closing asked about work autosave was going to write (%d dialogs)", a.Modals())
219 }
220 if a.Desktop().Count() != 0 {
221 t.Error("the window did not close")
222 }
223 if got := onDisk(t, path); !strings.Contains(got, "// hello") {
224 t.Errorf("closing discarded the changes instead of saving them: %q", got)
225 }
226}
227
228func TestClosingAnUnnamedWindowStillAsksWithAutosaveOn(t *testing.T) {
229 a, _, _ := newAutosaveApp(t, 2*time.Second)
230 a.NewFile()
231
232 typeText(a, "package main")
233 a.CloseFile()
234
235 if a.Modals() != 1 {
236 t.Errorf("Modals() = %d; work autosave cannot write is still a real question", a.Modals())
237 }
238}
239
240func TestLeavingWithAutosaveOnSavesInsteadOfAsking(t *testing.T) {
241 a, _, path := newAutosaveApp(t, 2*time.Second)
242
243 typeText(a, "// hello")
244 a.Quit()
245
246 if a.Modals() != 0 {
247 t.Errorf("Quit() asked about work autosave was going to write (%d dialogs)", a.Modals())
248 }
249 if !a.Quitting() {
250 t.Error("Quit() did not leave")
251 }
252 if got := onDisk(t, path); !strings.Contains(got, "// hello") {
253 t.Errorf("leaving discarded the changes: %q", got)
254 }
255}
256
257func TestLeavingStillAsksAboutAnUnnamedFile(t *testing.T) {
258 a, _, _ := newAutosaveApp(t, 2*time.Second)
259 a.NewFile()
260 typeText(a, "package main")
261
262 a.Quit()
263
264 if a.Quitting() {
265 t.Error("Quit() left without asking about a file it could not save")
266 }
267 if a.Modals() != 1 {
268 t.Errorf("Modals() = %d, want the question", a.Modals())
269 }
270}
271
272func TestSetAutosaveKeepsTheDelayWhenGivenNone(t *testing.T) {
273 a, _ := newTestApp(t)
274 a.SetAutosave(true, 5*time.Second)
275
276 a.SetAutosave(true, 0) // "on", with nothing said about how long
277
278 if a.autosave.delay != 5*time.Second {
279 t.Errorf("delay = %v, want the 5s already set rather than zero", a.autosave.delay)
280 }
281}
282
283func TestANewEditorHasTheDefaultAutosaveDelay(t *testing.T) {
284 a, _ := newTestApp(t)
285
286 // Zero would mean "save on every keystroke" the moment a project turned
287 // autosave on without naming a delay.
288 if a.autosave.delay <= 0 {
289 t.Errorf("the default delay is %v", a.autosave.delay)
290 }
291}
292
293func TestUseSettingsAppliesAutosaveAndRemembersTheFile(t *testing.T) {
294 a, _ := newTestApp(t)
295
296 a.UseSettings(settings.Settings{Autosave: true, AutosaveDelay: 5 * time.Second}, "/p/.turbo-go/settings.toml")
297
298 if !a.AutosaveEnabled() {
299 t.Error("UseSettings() did not turn autosave on")
300 }
301 if a.autosave.delay != 5*time.Second {
302 t.Errorf("delay = %v, want 5s", a.autosave.delay)
303 }
304 if a.SettingsPath() != "/p/.turbo-go/settings.toml" {
305 t.Errorf("SettingsPath() = %q", a.SettingsPath())
306 }
307}
308
309func TestUseSettingsDoesNotApplyTheTheme(t *testing.T) {
310 // main resolves the theme, because only it knows whether -theme was given
311 // and an explicit flag outranks the project's choice.
312 a, _ := newTestApp(t)
313 before := a.ThemeName()
314
315 a.UseSettings(settings.Settings{Theme: "turbo-dark"}, "/p/.turbo-go/settings.toml")
316
317 if a.ThemeName() != before {
318 t.Errorf("ThemeName() = %q; UseSettings must leave the theme to the caller", a.ThemeName())
319 }
320}