turbo-editors/turbo-jspublic Fork 0
v1.0.3
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

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

main_test.go · 245 lines · 8.2 KBGo Blame HistoryRaw
📦 Turbo JS 91999d1 k33g yesterday1package main
2
3import (
📦 Turbo JS 27bdf0b k33g 3h ago4 "errors"
📦 Turbo JS 91999d1 k33g yesterday5 "flag"
6 "os"
7 "path/filepath"
8 "slices"
9 "strings"
10 "testing"
11
12 "rickub.com/turbo-editors/turbo-core/app"
📦 Turbo JS 27bdf0b k33g 3h ago13 "rickub.com/turbo-editors/turbo-core/configrepo"
📦 Turbo JS 91999d1 k33g yesterday14 "rickub.com/turbo-editors/turbo-core/settings"
15 "rickub.com/turbo-editors/turbo-core/theme"
16
17 "rickub.com/turbo-editors/turbo-js/internal/jslang"
18)
19
20// withArgs runs the command line parser against a fixed argument list, and
21// restores the real one afterwards so tests do not affect each other.
22func withArgs(t *testing.T, args ...string) options {
23 t.Helper()
24
25 realArgs, realFlags := os.Args, flag.CommandLine
26 t.Cleanup(func() { os.Args, flag.CommandLine = realArgs, realFlags })
27
28 os.Args = append([]string{"turbo-js"}, args...)
29 flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
30 return parseFlags()
31}
32
33func TestMainRegistersTheLanguageExplicitly(t *testing.T) {
34 // "This editor knows JavaScript" is a line somebody can read in main.go,
35 // not an init function somewhere. Every other test here calls Register
36 // itself, so this is the only one that notices if run() stopped doing so.
37 source, err := os.ReadFile("main.go")
38 if err != nil {
39 t.Fatalf("reading main.go: %v", err)
40 }
41 if !strings.Contains(string(source), "\n\tjslang.Register()\n") {
42 t.Error("main.go no longer calls jslang.Register() as a statement of its own")
43 }
44}
45
46func TestFilesAreWhatIsLeftAfterTheFlags(t *testing.T) {
47 opts := withArgs(t, "-no-lsp", "main.js", "lib/x.mjs")
48
49 if !opts.noLSP {
50 t.Error("-no-lsp was not read")
51 }
52 if want := []string{"main.js", "lib/x.mjs"}; !slices.Equal(opts.files, want) {
53 t.Errorf("files = %v, want %v", opts.files, want)
54 }
55}
56
57func TestTheThemeFlagDefaultsToEmptyRatherThanToAName(t *testing.T) {
58 // Empty is what lets "was -theme given?" still be answered afterwards, and
59 // that is what lets the project's settings fill it in without overriding an
60 // explicit choice.
61 if opts := withArgs(t); opts.theme != "" {
62 t.Errorf("theme = %q with no flag, want the empty string", opts.theme)
63 }
64}
65
66func TestTheProjectRootIsTheDirectoryHoldingPackageJSON(t *testing.T) {
67 // typescript-language-server is given the package's boundary, which is
68 // where it resolves imports from. The walk itself is turbo-core's; what is
69 // checked here is that Turbo JS's profile asks it to look for a
70 // package.json.
71 root := t.TempDir()
72 if err := os.WriteFile(filepath.Join(root, "package.json"), []byte("{\"name\": \"x\"}\n"), 0o644); err != nil {
73 t.Fatalf("writing package.json: %v", err)
74 }
75 nested := filepath.Join(root, "src", "deep")
76 if err := os.MkdirAll(nested, 0o755); err != nil {
77 t.Fatalf("creating the tree: %v", err)
78 }
79 file := filepath.Join(nested, "deep.js")
80 if err := os.WriteFile(file, []byte("export const f = () => 1;\n"), 0o644); err != nil {
81 t.Fatalf("writing the file: %v", err)
82 }
83
84 if got := app.ProjectRoot(jslang.Profile(), []string{file}); got != root {
85 t.Errorf("ProjectRoot() = %q, want the package root %q", got, root)
86 }
87}
88
89func TestTheNearestPackageJSONWinsInAMonorepo(t *testing.T) {
90 // A workspace holds a package.json at its root and one per package. The
91 // package being edited is the nearest one going up, and that is the
92 // project the server should see.
93 workspace := t.TempDir()
94 pkg := filepath.Join(workspace, "packages", "api")
95 if err := os.MkdirAll(filepath.Join(pkg, "src"), 0o755); err != nil {
96 t.Fatalf("creating the tree: %v", err)
97 }
98 for _, dir := range []string{workspace, pkg} {
99 if err := os.WriteFile(filepath.Join(dir, "package.json"), []byte("{}\n"), 0o644); err != nil {
100 t.Fatalf("writing package.json: %v", err)
101 }
102 }
103 file := filepath.Join(pkg, "src", "index.js")
104 if err := os.WriteFile(file, []byte("\n"), 0o644); err != nil {
105 t.Fatalf("writing the file: %v", err)
106 }
107
108 if got := app.ProjectRoot(jslang.Profile(), []string{file}); got != pkg {
109 t.Errorf("ProjectRoot() = %q, want the nearest package %q", got, pkg)
110 }
111}
112
113func TestThemeNamePrefersTheFlagOverTheProject(t *testing.T) {
114 got := themeName(options{theme: "borland-light"}, settings.Settings{Theme: "turbo-dark"})
115
116 if got != "borland-light" {
117 t.Errorf("themeName() = %q; an explicit -theme must win over the project's", got)
118 }
119}
120
121func TestThemeNameUsesTheProjectWhenNoFlagWasGiven(t *testing.T) {
122 got := themeName(options{}, settings.Settings{Theme: "turbo-dark"})
123
124 if got != "turbo-dark" {
125 t.Errorf("themeName() = %q, want the project's theme", got)
126 }
127}
128
129func TestThemeNameFallsBackToTheDefault(t *testing.T) {
130 got := themeName(options{}, settings.Settings{})
131
132 if got != theme.DefaultName {
133 t.Errorf("themeName() = %q, want %q", got, theme.DefaultName)
134 }
135}
136
137func TestLoadProjectSettingsReadsTheWorkingDirectory(t *testing.T) {
138 project := t.TempDir()
139 t.Chdir(project)
140 if err := os.MkdirAll(settings.Dir(jslang.Profile(), project), 0o755); err != nil {
141 t.Fatalf("creating the settings directory: %v", err)
142 }
143 contents := "[editor]\ntheme = \"turbo-dark\"\nautosave = true\n"
144 if err := os.WriteFile(settings.Path(jslang.Profile(), project), []byte(contents), 0o644); err != nil {
145 t.Fatalf("writing the settings file: %v", err)
146 }
147
148 _, loaded := loadProjectSettings(jslang.Profile())
149
150 if loaded.Theme != "turbo-dark" {
151 t.Errorf("Theme = %q, want turbo-dark", loaded.Theme)
152 }
153 if !loaded.Autosave {
154 t.Error("Autosave = false, want the file's true")
155 }
156}
157
158func TestLoadProjectSettingsDoesNotWalkUpToAParent(t *testing.T) {
159 // "The project is where you started the editor" is the rule; a settings
160 // file one directory up belongs to a different project.
161 parent := t.TempDir()
162 if err := os.MkdirAll(settings.Dir(jslang.Profile(), parent), 0o755); err != nil {
163 t.Fatalf("creating the settings directory: %v", err)
164 }
165 if err := os.WriteFile(settings.Path(jslang.Profile(), parent), []byte("[editor]\ntheme = \"turbo-dark\"\n"), 0o644); err != nil {
166 t.Fatalf("writing the settings file: %v", err)
167 }
168 child := filepath.Join(parent, "src")
169 if err := os.Mkdir(child, 0o755); err != nil {
170 t.Fatalf("creating the child directory: %v", err)
171 }
172 t.Chdir(child)
173
174 _, loaded := loadProjectSettings(jslang.Profile())
175
176 if loaded.Theme != "" {
177 t.Errorf("Theme = %q; settings were read from a parent directory", loaded.Theme)
178 }
179}
180
181func TestLoadProjectSettingsCarriesOnWithoutAFile(t *testing.T) {
182 t.Chdir(t.TempDir())
183
184 _, loaded := loadProjectSettings(jslang.Profile())
185
186 if loaded != settings.Default() {
187 t.Errorf("loadProjectSettings() = %+v, want the defaults", loaded)
188 }
189}
190
191func TestABrokenSettingsFileDoesNotStopTheEditor(t *testing.T) {
192 // The editor is how you would fix the file, so it has to open.
193 project := t.TempDir()
194 t.Chdir(project)
195 if err := os.MkdirAll(settings.Dir(jslang.Profile(), project), 0o755); err != nil {
196 t.Fatalf("creating the settings directory: %v", err)
197 }
198 if err := os.WriteFile(settings.Path(jslang.Profile(), project), []byte("[editor\nnot toml"), 0o644); err != nil {
199 t.Fatalf("writing the settings file: %v", err)
200 }
201
202 _, loaded := loadProjectSettings(jslang.Profile())
203
204 if loaded != settings.Default() {
205 t.Errorf("loadProjectSettings() = %+v, want the defaults", loaded)
206 }
207}
📦 Turbo JS 27bdf0b k33g 3h ago208
209func TestDescribeLoadNamesTheSourceAndEveryFile(t *testing.T) {
210 got := describeLoad(configrepo.Result{
211 Remote: "https://git.rickub.com/turbo-editors/configs.git",
212 Ref: "main",
213 Dir: "/work/.turbo-js",
214 Files: []string{"settings.toml", "tools.toml"},
215 })
216
217 want := "Copied /work/.turbo-js from https://git.rickub.com/turbo-editors/configs.git (main):\n settings.toml\n tools.toml\n"
218 if got != want {
219 t.Errorf("describeLoad() =\n%q\nwant\n%q", got, want)
220 }
221}
222
223func TestDescribeLoadOmitsTheRefWhenTheDefaultBranchWasUsed(t *testing.T) {
224 got := describeLoad(configrepo.Result{Remote: "r", Dir: "d"})
225
226 if strings.Contains(got, "(") {
227 t.Errorf("describeLoad() = %q, want no parenthesised ref", got)
228 }
229}
230
231func TestLoadConfigLeavesAProjectThatHasAConfigurationAlone(t *testing.T) {
232 // Checked before anything is fetched, so this needs neither git nor a
233 // network: the project's own .turbo-js is a decision, not a default.
234 project := t.TempDir()
235 if err := os.MkdirAll(filepath.Join(project, ".turbo-js"), 0o755); err != nil {
236 t.Fatal(err)
237 }
238 t.Chdir(project)
239
240 err := loadConfig(jslang.Profile(), "https://rickub.com/turbo-editors/configs/tree/main/golang-init")
241
242 if !errors.Is(err, configrepo.ErrExists) {
243 t.Errorf("loadConfig() error = %v, want ErrExists", err)
244 }
245}