package main import ( "flag" "os" "path/filepath" "slices" "testing" "rickub.com/turbo-editors/turbo-core/app" "rickub.com/turbo-editors/turbo-core/settings" "rickub.com/turbo-editors/turbo-golo/internal/gololang" ) // withArgs runs the command line parser against a fixed argument list, and // restores the real one afterwards so tests do not affect each other. func withArgs(t *testing.T, args ...string) options { t.Helper() realArgs, realFlags := os.Args, flag.CommandLine t.Cleanup(func() { os.Args, flag.CommandLine = realArgs, realFlags }) os.Args = append([]string{"turbo-golo"}, args...) flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) return parseFlags() } func TestFilesAreWhatIsLeftAfterTheFlags(t *testing.T) { opts := withArgs(t, "-no-lsp", "main.golo", "lib/x.golo") if !opts.noLSP { t.Error("-no-lsp was not read") } if want := []string{"main.golo", "lib/x.golo"}; !slices.Equal(opts.files, want) { t.Errorf("files = %v, want %v", opts.files, want) } } func TestTheThemeFlagDefaultsToEmptyRatherThanToAName(t *testing.T) { // Empty is what lets "was -theme given?" still be answered afterwards, and // that is what lets the project's settings fill it in without overriding an // explicit choice. if opts := withArgs(t); opts.theme != "" { t.Errorf("theme = %q with no flag, want the empty string", opts.theme) } } func TestTheThemeFlagBeatsTheProjectSettings(t *testing.T) { project := settings.Default() project.Theme = "cobalt" if got := themeName(options{theme: "monochrome"}, project); got != "monochrome" { t.Errorf("themeName() = %q, want the flag's %q", got, "monochrome") } } func TestTheProjectSettingsBeatTheBuiltInDefault(t *testing.T) { project := settings.Default() project.Theme = "cobalt" if got := themeName(options{}, project); got != "cobalt" { t.Errorf("themeName() = %q, want the project's %q", got, "cobalt") } } func TestTheBuiltInDefaultIsUsedWhenNobodySaysOtherwise(t *testing.T) { if got := themeName(options{}, settings.Default()); got == "" { t.Error("themeName() = \"\" with nothing set, want the library's default") } } func TestTheProjectRootIsTheDirectoryOfTheFile(t *testing.T) { // app.ProjectRoot walks up looking for the profile's RootMarkers, and Golo // has none: a script is a file, and there is no manifest above it to find. // So the server is started in the file's own directory, however deep. root := t.TempDir() nested := filepath.Join(root, "scripts", "tools") if err := os.MkdirAll(nested, 0o755); err != nil { t.Fatal(err) } file := filepath.Join(nested, "main.golo") if got := app.ProjectRoot(gololang.Profile(), []string{file}); got != nested { t.Errorf("ProjectRoot(%q) = %q, want the file's own directory %q", file, got, nested) } } func TestNothingAboveTheFileChangesTheRoot(t *testing.T) { // A .git directory and a main.golo higher up are the two things somebody // might expect to count as a project. Neither is a marker here, so neither // moves the root: the rule is "no walk", and this pins it. root := t.TempDir() nested := filepath.Join(root, "lib") if err := os.MkdirAll(filepath.Join(root, ".git"), 0o755); err != nil { t.Fatal(err) } if err := os.MkdirAll(nested, 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(root, "main.golo"), []byte("module m\n"), 0o644); err != nil { t.Fatal(err) } file := filepath.Join(nested, "helpers.golo") if got := app.ProjectRoot(gololang.Profile(), []string{file}); got != nested { t.Errorf("ProjectRoot(%q) = %q, want %q; something above the file was taken for a marker", file, got, nested) } } func TestWithNoFileTheRootIsTheWorkingDirectory(t *testing.T) { // `turbo-golo` with no file opens an empty window, and the server has to // be started somewhere. Where the editor was started is the only answer. dir := t.TempDir() t.Chdir(dir) got := app.ProjectRoot(gololang.Profile(), nil) want, err := os.Getwd() if err != nil { t.Fatal(err) } if got != want { t.Errorf("ProjectRoot() with no file = %q, want the working directory %q", got, want) } } func TestLoadingSettingsFromADirectoryWithNoneGivesTheDefaults(t *testing.T) { // A directory somebody merely started the editor in has said nothing, and // the editor must not write to it. The starter file turns autosave on; the // default leaves it off. dir := t.TempDir() t.Chdir(dir) project, loaded := loadProjectSettings(gololang.Profile()) if project == "" { t.Error("loadProjectSettings returned no project directory") } if loaded.Autosave { t.Error("autosave is on with no settings file, want it off") } } func TestABrokenSettingsFileDoesNotStopTheEditorOpening(t *testing.T) { // A broken settings file must not stop the editor opening, because the // editor is how you would fix it. dir := t.TempDir() p := gololang.Profile() if err := os.MkdirAll(filepath.Join(dir, p.ProjectDir()), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(settings.Path(p, dir), []byte("this is not ["), 0o644); err != nil { t.Fatal(err) } t.Chdir(dir) if _, loaded := loadProjectSettings(p); loaded != settings.Default() { t.Errorf("loadProjectSettings() = %+v with a broken file, want the defaults", loaded) } }