turbo-editors/turbo-moonbitpublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-moonbit.git
git clone ssh://git@rickub.com/turbo-editors/turbo-moonbit.git

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

profile_test.go · 247 lines · 7.4 KBGo Blame HistoryRaw
📦 Turbo MoonBit cc1f595 k33g 11h ago1package moonbitlang_test
2
3import (
4 "os"
5 "path/filepath"
6 "slices"
7 "strings"
8 "testing"
9
10 "rickub.com/turbo-editors/turbo-core/profile"
11 "rickub.com/turbo-editors/turbo-core/syntax"
12
13 "rickub.com/turbo-editors/turbo-moonbit/internal/moonbitlang"
14)
15
16// fixedMenuHotKeys are the hot keys turbo-core's own menus take. The toolchain
17// menu may not claim one of them, or one of the two would be unreachable from
18// the keyboard and nothing would say so.
19var fixedMenuHotKeys = []rune{'F', 'E', 'S', 'R', 'C', 'O', 'W', 'N', 'H'}
20
21func TestProfileNamesTheEditor(t *testing.T) {
22 p := moonbitlang.Profile()
23
24 if p.Name != "Turbo MoonBit" {
25 t.Errorf("Name = %q, want %q", p.Name, "Turbo MoonBit")
26 }
27 if p.Slug != "turbo-moonbit" {
28 t.Errorf("Slug = %q, want %q", p.Slug, "turbo-moonbit")
29 }
30 // The About box and the status bar read this out, so it is the language's
31 // own spelling rather than the registry's lower-case name.
32 if p.Language != "MoonBit" {
33 t.Errorf("Language = %q, want %q", p.Language, "MoonBit")
34 }
35}
36
37func TestSlugDerivesEveryPath(t *testing.T) {
38 p := moonbitlang.Profile()
39
40 if got := p.ProjectDir(); got != ".turbo-moonbit" {
41 t.Errorf("ProjectDir() = %q, want %q", got, ".turbo-moonbit")
42 }
43 for name, got := range map[string]string{
44 "DirEnvVar": p.DirEnvVar(),
45 "ThemeDirEnvVar": p.ThemeDirEnvVar(),
46 "SnippetDirEnvVar": p.SnippetDirEnvVar(),
47 } {
48 if !strings.HasPrefix(got, "TURBO_MOONBIT_") {
49 t.Errorf("%s() = %q, want a TURBO_MOONBIT_ prefix", name, got)
50 }
51 }
52}
53
54func TestThemeDirFollowsItsEnvironmentVariable(t *testing.T) {
55 p := moonbitlang.Profile()
56 want := t.TempDir()
57 t.Setenv(p.ThemeDirEnvVar(), want)
58
59 if got := p.ThemeDir(); got != want {
60 t.Errorf("ThemeDir() = %q, want %q", got, want)
61 }
62}
63
64func TestToolsMenuHotKeyClashesWithNoFixedMenu(t *testing.T) {
65 label := moonbitlang.Profile().ToolsMenu
66
67 hotKey, ok := hotKeyOf(label)
68 if !ok {
69 t.Fatalf("ToolsMenu = %q, which marks no hot key between tildes", label)
70 }
71 if slices.Contains(fixedMenuHotKeys, hotKey) {
72 t.Errorf("ToolsMenu hot key %q is already taken by a fixed menu", hotKey)
73 }
74 if got := strings.ReplaceAll(label, "~", ""); got != "MoonBit" {
75 t.Errorf("ToolsMenu reads %q once the tildes are removed, want %q", got, "MoonBit")
76 }
77}
78
79// hotKeyOf returns the upper-case letter a menu label marks between tildes.
80func hotKeyOf(label string) (rune, bool) {
81 open := strings.Index(label, "~")
82 if open < 0 {
83 return 0, false
84 }
85 rest := label[open+1:]
86 shut := strings.Index(rest, "~")
87 if shut != 1 {
88 return 0, false
89 }
90 return []rune(strings.ToUpper(rest))[0], true
91}
92
93func TestRootMarkersAreMoonModules(t *testing.T) {
94 want := []string{"moon.mod", "moon.mod.json"}
95
96 if got := moonbitlang.Profile().RootMarkers; !slices.Equal(got, want) {
97 t.Errorf("RootMarkers = %v, want %v", got, want)
98 }
99}
100
101func TestServerIsMoonLSPOverStdio(t *testing.T) {
102 server := moonbitlang.Profile().Server
103
104 if server.Command != "moon-lsp" {
105 t.Errorf("Server.Command = %q, want %q", server.Command, "moon-lsp")
106 }
107 // moon-lsp with no argument prints its usage and exits, which the editor
108 // would see as a server that died at once.
109 if !slices.Contains(server.Args, "--stdio") {
110 t.Errorf("Server.Args = %v, want it to contain --stdio", server.Args)
111 }
112 if server.InstallHint == "" {
113 t.Error("Server.InstallHint is empty; a missing server would say nothing useful")
114 }
115 // It is shown on the status bar, so it has to fit on a narrow line.
116 if len(server.InstallHint) > 72 {
117 t.Errorf("InstallHint is %d characters, too long for a status bar", len(server.InstallHint))
118 }
119}
120
121func TestServerArgsCannotBeAppendedToByACaller(t *testing.T) {
122 first := moonbitlang.ServerArgs()
123 first = append(first, "--nonsense")
124
125 if second := moonbitlang.ServerArgs(); slices.Contains(second, "--nonsense") {
126 t.Errorf("ServerArgs() = %v after a caller appended to an earlier result", second)
127 }
128}
129
130func TestServerIsLookedForInMoonHome(t *testing.T) {
131 home := t.TempDir()
132 t.Setenv("MOON_HOME", home)
133
134 dirs := moonbitlang.Profile().Server.Dirs
135 want := filepath.Join(home, "bin")
136 if !slices.Contains(dirs, want) {
137 t.Errorf("Server.Dirs = %v, want it to contain %q", dirs, want)
138 }
139}
140
141func TestServerDirsFallBackToTheDefaultInstallDirectory(t *testing.T) {
142 t.Setenv("MOON_HOME", "")
143
144 home, err := os.UserHomeDir()
145 if err != nil {
146 t.Skip("no home directory on this machine")
147 }
148 want := filepath.Join(home, ".moon", "bin")
149
150 if dirs := moonbitlang.ServerDirs(); !slices.Contains(dirs, want) {
151 t.Errorf("ServerDirs() = %v, want it to contain %q", dirs, want)
152 }
153}
154
155func TestServerDirsDoNotRepeatOneDirectory(t *testing.T) {
156 home, err := os.UserHomeDir()
157 if err != nil {
158 t.Skip("no home directory on this machine")
159 }
160 t.Setenv("MOON_HOME", filepath.Join(home, ".moon"))
161
162 dirs := moonbitlang.ServerDirs()
163 if len(dirs) != 1 {
164 t.Errorf("ServerDirs() = %v with MOON_HOME at the default, want one entry", dirs)
165 }
166}
167
168func TestProfileIsReadAfreshEveryTime(t *testing.T) {
169 // Server.Dirs comes out of the environment, so a package-level variable
170 // would freeze whatever MOON_HOME said when the binary was linked.
171 first := t.TempDir()
172 t.Setenv("MOON_HOME", first)
173 before := moonbitlang.Profile().Server.Dirs
174
175 second := t.TempDir()
176 t.Setenv("MOON_HOME", second)
177 after := moonbitlang.Profile().Server.Dirs
178
179 if slices.Equal(before, after) {
180 t.Errorf("Profile().Server.Dirs = %v both times; it did not follow MOON_HOME", after)
181 }
182}
183
184func TestTemplatesAreAllFilledIn(t *testing.T) {
185 templates := moonbitlang.Profile().Templates
186
187 for name, template := range map[string]string{
188 "Settings": templates.Settings,
189 "Snippets": templates.Snippets,
190 "Tools": templates.Tools,
191 } {
192 if template == "" {
193 t.Errorf("Templates.%s is empty; the editor would offer to write nothing", name)
194 }
195 }
196}
197
198func TestRegisterTeachesTheLibraryMoonBit(t *testing.T) {
199 moonbitlang.Register()
200
201 if !slices.Contains(syntax.Registered(), moonbitlang.Language) {
202 t.Fatalf("Registered() = %v, want it to contain %q", syntax.Registered(), moonbitlang.Language)
203 }
204
205 for _, path := range []string{"main.mbt", "pkg.mbti", "script.mbtx", "DEEP/nested/x.MBT"} {
206 if got := syntax.LanguageOf(path, ""); got != moonbitlang.Language {
207 t.Errorf("LanguageOf(%q) = %q, want %q", path, got, moonbitlang.Language)
208 }
209 }
210}
211
212func TestRegisterLeavesOtherFilesAlone(t *testing.T) {
213 moonbitlang.Register()
214
215 cases := map[string]syntax.Language{
216 // A .mbt.md is a Markdown document with MoonBit in its fences. Its
217 // extension is .md, and Markdown is what should colour it.
218 "README.mbt.md": syntax.LanguageMarkdown,
219 "moon.mod.json": syntax.LanguageNone,
220 "Dockerfile": syntax.LanguageDockerfile,
221 "main.py": syntax.LanguageNone,
222 }
223 for path, want := range cases {
224 if got := syntax.LanguageOf(path, ""); got != want {
225 t.Errorf("LanguageOf(%q) = %q, want %q", path, got, want)
226 }
227 }
228}
229
230func TestNoShebangIsClaimed(t *testing.T) {
231 moonbitlang.Register()
232
233 // MoonBit has no interpreter line: a file opening with #! would lex as an
234 // attribute named ! and fail. Claiming one would take a shell script away
235 // from the scanner that can actually colour it.
236 if got := syntax.LanguageOf("script", "#!/usr/bin/env moon"); got == moonbitlang.Language {
237 t.Errorf("LanguageOf with a moon shebang = %q, want anything but MoonBit", got)
238 }
239}
240
241// A profile is a literal, so this example is the whole of how one is used.
242func ExampleProfile() {
243 moonbitlang.Register()
244
245 var p profile.Profile = moonbitlang.Profile()
246 _ = p.ProjectDir() // ".turbo-moonbit"
247}