package gololang_test import ( "slices" "strings" "testing" "rickub.com/turbo-editors/turbo-core/profile" "rickub.com/turbo-editors/turbo-core/syntax" "rickub.com/turbo-editors/turbo-golo/internal/gololang" ) // fixedMenuHotKeys are the hot keys turbo-core's own menus take. The toolchain // menu may not claim one of them, or one of the two would be unreachable from // the keyboard and nothing would say so. var fixedMenuHotKeys = []rune{'F', 'E', 'S', 'R', 'C', 'O', 'W', 'N', 'H'} func TestProfileNamesTheEditor(t *testing.T) { p := gololang.Profile() if p.Name != "Turbo Golo" { t.Errorf("Name = %q, want %q", p.Name, "Turbo Golo") } if p.Slug != "turbo-golo" { t.Errorf("Slug = %q, want %q", p.Slug, "turbo-golo") } // The About box and the status bar read this out. It names the language, // not the implementation: Golo is what the files are written in, and // GoloScript is the binary the editor talks to. if p.Language != "Golo" { t.Errorf("Language = %q, want %q", p.Language, "Golo") } } func TestSlugDerivesEveryPath(t *testing.T) { p := gololang.Profile() if got := p.ProjectDir(); got != ".turbo-golo" { t.Errorf("ProjectDir() = %q, want %q", got, ".turbo-golo") } for name, got := range map[string]string{ "DirEnvVar": p.DirEnvVar(), "ThemeDirEnvVar": p.ThemeDirEnvVar(), "SnippetDirEnvVar": p.SnippetDirEnvVar(), } { if !strings.HasPrefix(got, "TURBO_GOLO_") { t.Errorf("%s() = %q, want a TURBO_GOLO_ prefix", name, got) } } } func TestThemeDirFollowsItsEnvironmentVariable(t *testing.T) { p := gololang.Profile() want := t.TempDir() t.Setenv(p.ThemeDirEnvVar(), want) if got := p.ThemeDir(); got != want { t.Errorf("ThemeDir() = %q, want %q", got, want) } } func TestToolsMenuHotKeyClashesWithNoFixedMenu(t *testing.T) { label := gololang.Profile().ToolsMenu hotKey, ok := hotKeyOf(label) if !ok { t.Fatalf("ToolsMenu = %q, which marks no hot key between tildes", label) } if slices.Contains(fixedMenuHotKeys, hotKey) { t.Errorf("ToolsMenu hot key %q is already taken by a fixed menu", hotKey) } if got := strings.ReplaceAll(label, "~", ""); got != "Golo" { t.Errorf("ToolsMenu reads %q once the tildes are removed, want %q", got, "Golo") } } // hotKeyOf returns the upper-case letter a menu label marks between tildes. func hotKeyOf(label string) (rune, bool) { open := strings.Index(label, "~") if open < 0 { return 0, false } rest := label[open+1:] shut := strings.Index(rest, "~") if shut != 1 { return 0, false } return []rune(strings.ToUpper(rest))[0], true } func TestThereAreNoRootMarkers(t *testing.T) { // Golo has no project manifest, so there is nothing to walk up towards. // With no markers app.ProjectRoot hands the server the directory of the // file being edited, which is what main_test.go checks from the other // side. A marker appearing here would be a claim about the language that // the language does not make. if got := gololang.Profile().RootMarkers; len(got) != 0 { t.Errorf("RootMarkers = %v, want none", got) } } func TestServerIsTheInterpreterInLSPMode(t *testing.T) { server := gololang.Profile().Server if server.Command != "golo" { t.Errorf("Server.Command = %q, want %q", server.Command, "golo") } // golo with no argument starts a REPL that reads standard input as Golo, // which the editor would see as a server that never answers. if !slices.Equal(server.Args, []string{"lsp"}) { t.Errorf("Server.Args = %v, want exactly [lsp]", server.Args) } if server.InstallHint == "" { t.Error("Server.InstallHint is empty; a missing server would say nothing useful") } // It is shown on the status bar, so it has to fit on a narrow line. if len(server.InstallHint) > 72 { t.Errorf("InstallHint is %d characters, too long for a status bar", len(server.InstallHint)) } } func TestServerArgsCannotBeAppendedToByACaller(t *testing.T) { first := gololang.ServerArgs() first = append(first, "--nonsense") if second := gololang.ServerArgs(); slices.Contains(second, "--nonsense") { t.Errorf("ServerArgs() = %v after a caller appended to an earlier result", second) } } func TestServerIsLookedForWhereGoloScriptInstallsIt(t *testing.T) { // GoloScript's install.sh copies golo, gogolo and wagolo into // /usr/local/bin. It is on PATH on nearly every machine, and the editor // searches it anyway for the one where it is not. dirs := gololang.Profile().Server.Dirs if !slices.Contains(dirs, "/usr/local/bin") { t.Errorf("Server.Dirs = %v, want it to contain /usr/local/bin", dirs) } if len(dirs) != 1 { t.Errorf("Server.Dirs = %v, want the one directory the installer writes into and no guesses", dirs) } } func TestProfileHandsOutAFreshCopyEveryTime(t *testing.T) { // A caller that appends to Server.Dirs must not be appending to the // package's one copy, or the next caller would inherit its guess. first := gololang.Profile() first.Server.Dirs = append(first.Server.Dirs, "/somewhere/else") if second := gololang.Profile(); slices.Contains(second.Server.Dirs, "/somewhere/else") { t.Errorf("Profile().Server.Dirs = %v after a caller appended to an earlier result", second.Server.Dirs) } } func TestTemplatesAreAllFilledIn(t *testing.T) { templates := gololang.Profile().Templates for name, template := range map[string]string{ "Settings": templates.Settings, "Snippets": templates.Snippets, "Tools": templates.Tools, } { if template == "" { t.Errorf("Templates.%s is empty; the editor would offer to write nothing", name) } } } func TestRegisterTeachesTheLibraryGolo(t *testing.T) { gololang.Register() if !slices.Contains(syntax.Registered(), gololang.Language) { t.Fatalf("Registered() = %v, want it to contain %q", syntax.Registered(), gololang.Language) } for _, path := range []string{"main.golo", "lib/strings_test.golo", "DEEP/nested/x.GOLO"} { if got := syntax.LanguageOf(path, ""); got != gololang.Language { t.Errorf("LanguageOf(%q) = %q, want %q", path, got, gololang.Language) } } } func TestAShebangNamingGoloIsGolo(t *testing.T) { // A script run as a command has no extension and opens with a line the // interpreter reads as a comment. The first line is what identifies it. gololang.Register() for _, firstLine := range []string{"#!/usr/bin/env golo", "#!/usr/local/bin/golo"} { if got := syntax.LanguageOf("run-me", firstLine); got != gololang.Language { t.Errorf("LanguageOf(%q) = %q, want %q", firstLine, got, gololang.Language) } } } func TestRegisterLeavesOtherFilesAlone(t *testing.T) { gololang.Register() cases := map[string]syntax.Language{ "README.md": syntax.LanguageMarkdown, "Dockerfile": syntax.LanguageDockerfile, "main.py": syntax.LanguageNone, "main.mbt": syntax.LanguageNone, // A golo file's neighbour with a different extension is not Golo // however Golo-flavoured its name. "golo.toml": syntax.LanguageTOML, } for path, want := range cases { if got := syntax.LanguageOf(path, ""); got != want { t.Errorf("LanguageOf(%q) = %q, want %q", path, got, want) } } // A shebang naming another interpreter is not Golo either. if got := syntax.LanguageOf("script", "#!/usr/bin/env python3"); got == gololang.Language { t.Errorf("LanguageOf with a python shebang = %q, want anything but Golo", got) } } // A profile is a literal, so this example is the whole of how one is used. func ExampleProfile() { gololang.Register() var p profile.Profile = gololang.Profile() _ = p.ProjectDir() // ".turbo-golo" }