package app import ( "path/filepath" "testing" "time" "rickub.com/turbo-editors/turbo-core/lsp" ) // These tests cover what saving says to the language server. The defect they // were written against: a window that started Untitled gains its path through // Save As, afterSave sent only didSave, and a server ignores didSave for a // document it was never told is open — so the window had no completion until // the editor was restarted. func TestSavingAnUntitledWindowAnnouncesItsDocument(t *testing.T) { project := t.TempDir() t.Chdir(project) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) a.Tick() // the start-up announcement runs, and finds nothing to tell a.NewFile() typeText(a, "package main") path := filepath.Join(project, "main.go") a.save(a.ActiveView(), path) waitForMethod(t, server, "textDocument/didOpen") if got := server.methodCount("textDocument/didSave"); got != 0 { t.Errorf("didSave was sent %d times for a document the server was never told is open", got) } if !a.language.Knows(path) { t.Errorf("after the save, the server still does not know %q", path) } } func TestSaveAsUnderANewNameClosesTheOldDocument(t *testing.T) { project := t.TempDir() t.Chdir(project) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) writeTestFile(t, filepath.Join(project, "old.go"), "package main\n") a.Open("old.go") waitForMethod(t, server, "textDocument/didOpen") newPath := filepath.Join(project, "new.go") a.save(a.ActiveView(), newPath) waitForMethod(t, server, "textDocument/didClose") if a.language.Knows("old.go") { t.Errorf("the old document is still open on the server after Save As gave the buffer a new name") } if !a.language.Knows(newPath) { t.Errorf("the new document was never announced to the server") } } func TestSaveAsToTheSamePathClosesNothing(t *testing.T) { // Save As offered the file's own name and the user accepted it: that is a // write, not a rename, however the dialog spelt the path. project := t.TempDir() t.Chdir(project) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) writeTestFile(t, filepath.Join(project, "main.go"), "package main\n") a.Open("main.go") // relative, as the command line gives it waitForMethod(t, server, "textDocument/didOpen") a.save(a.ActiveView(), filepath.Join(project, "main.go")) // absolute, as the dialog gives it waitForMethod(t, server, "textDocument/didSave") if got := server.methodCount("textDocument/didClose"); got != 0 { t.Errorf("didClose was sent %d times for a save that renamed nothing", got) } } func TestSavingAKnownDocumentReportsTheWrite(t *testing.T) { project := t.TempDir() t.Chdir(project) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) writeTestFile(t, filepath.Join(project, "main.go"), "package main\n") a.Open("main.go") waitForMethod(t, server, "textDocument/didOpen") a.save(a.ActiveView(), a.ActiveView().Buffer().Path()) waitForMethod(t, server, "textDocument/didSave") if got := server.methodCount("textDocument/didOpen"); got != 1 { t.Errorf("didOpen was sent %d times; saving an open document must not re-announce it", got) } } func TestSavingANewFileTellsTheServerTheFileExists(t *testing.T) { // To moon-lsp a document being open and a file existing are two facts, // and it works a package's files out from the directory: a .mbt saved for // the first time got no diagnostics, however loudly the document had been // announced, until the server was told the file is there. Found on macOS // the first time Turbo MoonBit's suite ran there; confirmed at the // protocol level — the same file is diagnosed the moment // workspace/didChangeWatchedFiles names it. project := t.TempDir() t.Chdir(project) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) a.Tick() a.NewFile() typeText(a, "package main") path := filepath.Join(project, "main.go") a.save(a.ActiveView(), path) waitForMethod(t, server, "workspace/didChangeWatchedFiles") events := server.lastFileEvents() if len(events) != 1 || events[0].Type != lsp.FileCreated { t.Fatalf("the server was told %+v, want one created file", events) } if want := lsp.PathToURI(path); events[0].URI != want { t.Errorf("the created file is %q, want %q", events[0].URI, want) } // The document first, then the file: the order the fix was verified in. methods := server.methodsSeen() if open, created := firstIndexOf(methods, "textDocument/didOpen"), firstIndexOf(methods, "workspace/didChangeWatchedFiles"); open < 0 || created < open { t.Errorf("the file was reported before the document was announced: %v", methods) } } func TestSavingAnExistingFileDoesNotClaimItWasCreated(t *testing.T) { // A file that was already on disk is not news to the server, and a save // that claimed otherwise would have moon-lsp rescan the package on every // Ctrl-S. project := t.TempDir() t.Chdir(project) t.Setenv(testProfile().SnippetDirEnvVar(), t.TempDir()) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) path := filepath.Join(project, "main.go") writeTestFile(t, path, "package main\n") a.Open(path) a.Tick() a.save(a.ActiveView(), path) waitForMethod(t, server, "textDocument/didSave") if got := server.methodCount("workspace/didChangeWatchedFiles"); got != 0 { t.Errorf("saving a file that already existed reported it created %d time(s)", got) } } func TestAutosaveOfANewlyNamedFileTellsTheServerTheFileExists(t *testing.T) { // The other way a file gets written. `turbo-moonbit new.mbt` opens a // buffer for a file that is not on disk yet; the first automatic save // creates it, and the server has to hear that from this path too. project := t.TempDir() t.Chdir(project) t.Setenv(testProfile().SnippetDirEnvVar(), t.TempDir()) a, _ := newTestApp(t) client, server := newFakeLanguage(t, a) connectLanguage(a, client) path := filepath.Join(project, "new.go") a.Open(path) // not on disk: an empty buffer with that name a.Tick() a.SetAutosave(true, time.Millisecond) typeText(a, "package main") deadline := time.Now().Add(2 * time.Second) for server.methodCount("workspace/didChangeWatchedFiles") == 0 && time.Now().Before(deadline) { time.Sleep(2 * time.Millisecond) a.Tick() } events := server.lastFileEvents() if len(events) != 1 || events[0].Type != lsp.FileCreated || events[0].URI != lsp.PathToURI(path) { t.Errorf("autosave reported %+v, want %s created", events, path) } } // firstIndexOf returns where a string first appears in a list, or -1. func firstIndexOf(list []string, want string) int { for i, s := range list { if s == want { return i } } return -1 }