package main import ( "os" "path/filepath" "testing" "rickub.com/turbo-editors/turbo-core/app" "rickub.com/turbo-editors/turbo-core/settings" "rickub.com/turbo-editors/turbo-core/theme" "rickub.com/turbo-editors/turbo-python/internal/pythonlang" ) // Each of the three markers finds the root on its own, and the walk stops at // the nearest directory holding any of them. pylsp is started there, and it is // what decides the code the server loads. func TestTheProjectRootIsFoundByAnyOfTheThreeMarkers(t *testing.T) { markers := map[string]string{ "pyproject.toml": "[project]\nname = \"x\"\n", "setup.py": "from setuptools import setup\n\nsetup()\n", "setup.cfg": "[metadata]\nname = x\n", } for marker, contents := range markers { t.Run(marker, func(t *testing.T) { root := t.TempDir() writeFile(t, filepath.Join(root, marker), contents) file := filepath.Join(root, "src", "package", "deep.py") writeFile(t, file, "def f() -> None:\n pass\n") if got := app.ProjectRoot(pythonlang.Profile(), []string{file}); got != root { t.Errorf("ProjectRoot() = %q, want the project root %q", got, root) } }) } } // A directory with none of the three is its own root: the walk must not climb // past a project into whatever is above it. func TestAFileInNoProjectIsItsOwnRoot(t *testing.T) { root := t.TempDir() file := filepath.Join(root, "script.py") writeFile(t, file, "print(1)\n") if got := app.ProjectRoot(pythonlang.Profile(), []string{file}); got != root { t.Errorf("ProjectRoot() = %q, want the file's own directory %q", got, root) } } // writeFile creates a file, making its directory first. func writeFile(t *testing.T, path, contents string) { t.Helper() if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { t.Fatalf("creating %s: %v", filepath.Dir(path), err) } if err := os.WriteFile(path, []byte(contents), 0o644); err != nil { t.Fatalf("writing %s: %v", path, err) } } func TestThemeNamePrefersTheFlagOverTheProject(t *testing.T) { got := themeName(options{theme: "borland-light"}, settings.Settings{Theme: "turbo-dark"}) if got != "borland-light" { t.Errorf("themeName() = %q; an explicit -theme must win over the project's", got) } } func TestThemeNameUsesTheProjectWhenNoFlagWasGiven(t *testing.T) { got := themeName(options{}, settings.Settings{Theme: "turbo-dark"}) if got != "turbo-dark" { t.Errorf("themeName() = %q, want the project's theme", got) } } func TestThemeNameFallsBackToTheDefault(t *testing.T) { got := themeName(options{}, settings.Settings{}) if got != theme.DefaultName { t.Errorf("themeName() = %q, want %q", got, theme.DefaultName) } } func TestLoadProjectSettingsReadsTheWorkingDirectory(t *testing.T) { project := t.TempDir() t.Chdir(project) if err := os.MkdirAll(settings.Dir(pythonlang.Profile(), project), 0o755); err != nil { t.Fatalf("creating the settings directory: %v", err) } contents := "[editor]\ntheme = \"turbo-dark\"\nautosave = true\n" if err := os.WriteFile(settings.Path(pythonlang.Profile(), project), []byte(contents), 0o644); err != nil { t.Fatalf("writing the settings file: %v", err) } _, loaded := loadProjectSettings(pythonlang.Profile()) if loaded.Theme != "turbo-dark" { t.Errorf("Theme = %q, want turbo-dark", loaded.Theme) } if !loaded.Autosave { t.Error("Autosave = false, want the file's true") } } func TestLoadProjectSettingsDoesNotWalkUpToAParent(t *testing.T) { // "The project is where you started the editor" is the rule; a settings // file one directory up belongs to a different project. parent := t.TempDir() if err := os.MkdirAll(settings.Dir(pythonlang.Profile(), parent), 0o755); err != nil { t.Fatalf("creating the settings directory: %v", err) } if err := os.WriteFile(settings.Path(pythonlang.Profile(), parent), []byte("[editor]\ntheme = \"turbo-dark\"\n"), 0o644); err != nil { t.Fatalf("writing the settings file: %v", err) } child := filepath.Join(parent, "src") if err := os.Mkdir(child, 0o755); err != nil { t.Fatalf("creating the child directory: %v", err) } t.Chdir(child) _, loaded := loadProjectSettings(pythonlang.Profile()) if loaded.Theme != "" { t.Errorf("Theme = %q; settings were read from a parent directory", loaded.Theme) } } func TestLoadProjectSettingsCarriesOnWithoutAFile(t *testing.T) { t.Chdir(t.TempDir()) _, loaded := loadProjectSettings(pythonlang.Profile()) if loaded != settings.Default() { t.Errorf("loadProjectSettings(pythonlang.Profile()) = %+v, want the defaults", loaded) } } func TestABrokenSettingsFileDoesNotStopTheEditor(t *testing.T) { // The editor is how you would fix the file, so it has to open. project := t.TempDir() t.Chdir(project) if err := os.MkdirAll(settings.Dir(pythonlang.Profile(), project), 0o755); err != nil { t.Fatalf("creating the settings directory: %v", err) } if err := os.WriteFile(settings.Path(pythonlang.Profile(), project), []byte("[editor\nnot toml"), 0o644); err != nil { t.Fatalf("writing the settings file: %v", err) } _, loaded := loadProjectSettings(pythonlang.Profile()) if loaded != settings.Default() { t.Errorf("loadProjectSettings(pythonlang.Profile()) = %+v, want the defaults", loaded) } }