turbo-editors/turbo-gopublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-go.git
git clone ssh://git@rickub.com/turbo-editors/turbo-go.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

editor_test.go · 185 lines · 6.1 KBGo Blame HistoryRaw
📦 Turbo Go 3d7798b k33g 12h ago1package golang_test
2
3import (
4 "context"
5 "errors"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10 "time"
11
12 "github.com/gdamore/tcell/v2"
13
14 "rickub.com/turbo-editors/turbo-core/app"
15 "rickub.com/turbo-editors/turbo-core/buffer"
16 "rickub.com/turbo-editors/turbo-core/lsp"
17 "rickub.com/turbo-editors/turbo-core/syntax"
18
19 "rickub.com/turbo-editors/turbo-go/internal/golang"
20)
21
22// TestCompletionEndToEndWithRealGopls drives the exact sequence the command
23// does at start-up: open the files first, start the language server second,
24// then ask for a completion.
25//
26// That order is the whole point. The editor's earlier version announced its
27// open documents to a server that did not exist yet and never mentioned them
28// again, so gopls answered every completion about a file it had never heard
29// of — which looks, from the outside, exactly like completion not working.
30//
31// It lives in Turbo Go rather than in turbo-core because gopls is Turbo Go's
32// server: the library has no language server of its own to be driven against.
33//
34// It skips itself when gopls is not installed, and under -short.
35func TestCompletionEndToEndWithRealGopls(t *testing.T) {
36 if testing.Short() {
37 t.Skip("-short: not starting a language server")
38 }
39 if _, err := lsp.FindServer(golang.Profile().Server); errors.Is(err, lsp.ErrServerNotFound) {
40 t.Skipf("%s is not installed; %s", golang.ServerCommand, golang.InstallHint)
41 }
42
43 root := t.TempDir()
44 writeFile(t, filepath.Join(root, "go.mod"), "module example.test\n\ngo 1.24\n")
45
46 // The file on disk stops short of the dot. The text the completion is
47 // about gets *typed* below, so the answer can only come from what the
48 // editor told the server — which is the whole point of this test. A
49 // fixture already containing "strings." would be answered from disk, and
50 // would pass whether or not the editor said anything at all.
51 source := "package main\n\nimport \"strings\"\n\nfunc main() {\n\t\n}\n"
52 path := filepath.Join(root, "main.go")
53 writeFile(t, path, source)
54
55 editor := newTestEditor(t)
56
57 // 1. Open the file, exactly as main does — before there is any server.
58 editor.Open(path)
59
60 // 2. Start the language server, exactly as main does — afterwards.
61 ctx, cancel := context.WithCancel(t.Context())
62 defer cancel()
63 editor.StartLanguageServer(ctx, root)
64 t.Cleanup(func() { editor.Language().Stop(context.Background()) })
65
66 waitUntilReady(t, editor)
67
68 // 3. Let the event loop notice the server is ready, as Run does on every
69 // turn. This is what announces the file that was already open.
70 editor.Tick()
71
72 // 4. Type "strings." into the buffer, so that only the editor knows it is
73 // there, then ask for a completion.
74 view := editor.ActiveView()
75 view.Buffer().SetCursor(buffer.Position{Line: 5, Col: 1})
76 typeText(editor, "strings.")
77
78 if !editor.Completion().Visible() {
79 // Typing the dot asks for a completion by itself; ask again explicitly
80 // so a failure reports the status rather than the popup's absence.
81 editor.RequestCompletion()
82 }
83 if !editor.Completion().Visible() {
84 t.Fatalf("no completion list opened; the status bar says %q", editor.StatusBar().Message())
85 }
86 if !completionOffers(editor, "Contains") {
87 t.Errorf("the list does not offer strings.Contains; it has %d entries", editor.Completion().Count())
88 }
89}
90
91func TestTheEditorColoursGoSourceItOpens(t *testing.T) {
92 // The whole path in one test: Register taught the library about Go, the
93 // profile named the editor, and a .go file opened through the public API
94 // comes out coloured.
95 root := t.TempDir()
96 path := filepath.Join(root, "main.go")
97 writeFile(t, path, "package main\n")
98
99 editor := newTestEditor(t)
100 editor.Open(path)
101
102 if got := editor.ActiveView().Language(); got != golang.Language {
103 t.Fatalf("the view colours the file as %q, want %q", got, golang.Language)
104 }
105 if spans := syntax.Highlight(golang.Language, "package main"); len(spans[0]) == 0 {
106 t.Error("the registered Go scanner colours nothing")
107 }
108}
109
110func TestTheEditorCallsItselfTurboGo(t *testing.T) {
111 editor := newTestEditor(t)
112
113 if got := editor.Profile().Name; got != golang.Name {
114 t.Errorf("Profile().Name = %q, want %q", got, golang.Name)
115 }
116 if got := editor.Profile().ProjectDir(); got != ".turbo-go" {
117 t.Errorf("ProjectDir() = %q, want %q", got, ".turbo-go")
118 }
119}
120
121// newTestEditor returns Turbo Go drawing on a simulated terminal, set up the
122// way the command sets it up.
123func newTestEditor(t *testing.T) *app.App {
124 t.Helper()
125
126 golang.Register()
127 screen := tcell.NewSimulationScreen("UTF-8")
128 if err := screen.Init(); err != nil {
129 t.Fatalf("initialising the simulation screen: %v", err)
130 }
131 t.Cleanup(screen.Fini)
132 screen.SetSize(80, 24)
133
134 // Never read the themes or snippets of whoever is running the tests.
135 p := golang.Profile()
136 t.Setenv(p.ThemeDirEnvVar(), t.TempDir())
137 t.Setenv(p.SnippetDirEnvVar(), t.TempDir())
138
139 editor := app.New(screen, "turbo-classic", p)
140 editor.Render()
141 return editor
142}
143
144// typeText sends a run of printable characters through the whole routing chain.
145func typeText(editor *app.App, text string) {
146 for _, r := range text {
147 editor.Handle(tcell.NewEventKey(tcell.KeyRune, r, tcell.ModNone))
148 }
149}
150
151// completionOffers reports whether the open popup holds an entry starting with
152// a label.
153func completionOffers(editor *app.App, label string) bool {
154 for _, item := range editor.Completion().Matches() {
155 if strings.HasPrefix(item.Label, label) {
156 return true
157 }
158 }
159 return false
160}
161
162// waitUntilReady blocks until the language server has finished starting.
163func waitUntilReady(t *testing.T, editor *app.App) {
164 t.Helper()
165
166 deadline := time.After(lsp.InitializeTimeout)
167 for !editor.Language().Ready() {
168 select {
169 case <-deadline:
170 t.Fatalf("the language server never became ready: %s", editor.Language().Status())
171 case <-time.After(10 * time.Millisecond):
172 }
173 }
174}
175
176// writeFile creates a file, making its directory first.
177func writeFile(t *testing.T, path, content string) {
178 t.Helper()
179 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
180 t.Fatalf("creating %s: %v", filepath.Dir(path), err)
181 }
182 if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
183 t.Fatalf("writing %s: %v", path, err)
184 }
185}