package config import ( "os" "path/filepath" "strings" "testing" "time" ) func TestDefaultHasSaneValues(t *testing.T) { d := Default() if d.DailySubdir != "Daily" { t.Errorf("DailySubdir = %q, want Daily", d.DailySubdir) } if d.DailyFormat != "2006-01-02" { t.Errorf("DailyFormat = %q, want 2006-01-02", d.DailyFormat) } if d.GlamourStyle != "" { t.Errorf("GlamourStyle default = %q, want empty (theme drives it)", d.GlamourStyle) } if d.Theme != "auto" { t.Errorf("Theme = %q, want auto", d.Theme) } if d.VaultDir != "" { t.Errorf("VaultDir default = %q, want empty (set vault_dir for `glint -v`)", d.VaultDir) } if d.WorkingDir() == "" { t.Error("WorkingDir should not be empty") } } func TestDefaultExportFonts(t *testing.T) { d := Default() if d.FontDisplay == "" || d.FontBody == "" || d.FontMono == "" { t.Fatalf("export font defaults must be non-empty: display=%q body=%q mono=%q", d.FontDisplay, d.FontBody, d.FontMono) } for _, bad := range []string{"Awke", "Untitled Sans", "Name Mono"} { for _, f := range []string{d.FontDisplay, d.FontBody, d.FontMono} { if strings.Contains(f, bad) { t.Errorf("default font stack contains licensed face %q: %q", bad, f) } } } } func TestLoadFromFileOverlaysFonts(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.toml") body := `font_display = "Charter, serif" font_body = "Inter, sans-serif" font_mono = "Fira Code, monospace" ` if err := os.WriteFile(path, []byte(body), 0o644); err != nil { t.Fatal(err) } cfg, err := loadFromFile(path) if err != nil { t.Fatal(err) } if cfg.FontDisplay != "Charter, serif" || cfg.FontBody != "Inter, sans-serif" || cfg.FontMono != "Fira Code, monospace" { t.Errorf("font overlay failed: %+v", cfg) } } func TestLoadFromFileMissingReturnsDefaults(t *testing.T) { cfg, err := loadFromFile(filepath.Join(t.TempDir(), "nope.toml")) if err != nil { t.Fatalf("missing file should not error, got %v", err) } if cfg.DailyFormat != "2006-01-02" { t.Errorf("DailyFormat = %q, want default", cfg.DailyFormat) } } func TestLoadFromFileOverlays(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.toml") body := `vault_dir = "/tmp/vault" daily_subdir = "journal" daily_format = "20060102" ` if err := os.WriteFile(path, []byte(body), 0o644); err != nil { t.Fatal(err) } cfg, err := loadFromFile(path) if err != nil { t.Fatal(err) } if cfg.VaultDir != "/tmp/vault" { t.Errorf("VaultDir = %q", cfg.VaultDir) } if cfg.DailySubdir != "journal" { t.Errorf("DailySubdir = %q", cfg.DailySubdir) } if cfg.DailyFormat != "20060102" { t.Errorf("DailyFormat = %q", cfg.DailyFormat) } // unset key keeps default if cfg.GlamourStyle != "" { t.Errorf("GlamourStyle = %q, want default empty", cfg.GlamourStyle) } } func TestLoadFromFileOverlaysTheme(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "config.toml") if err := os.WriteFile(path, []byte("theme = \"charm\"\n"), 0o644); err != nil { t.Fatal(err) } cfg, err := loadFromFile(path) if err != nil { t.Fatal(err) } if cfg.Theme != "charm" { t.Errorf("Theme = %q, want charm", cfg.Theme) } } func TestLoadFromFileMalformedErrors(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "bad.toml") if err := os.WriteFile(path, []byte("vault_dir = = ="), 0o644); err != nil { t.Fatal(err) } if _, err := loadFromFile(path); err == nil { t.Error("malformed config should return an error") } } func TestDailyPath(t *testing.T) { t.Setenv("GLINT_VAULT", "/v") // pins the working dir c := Config{DailySubdir: "Daily", DailyFormat: "2006-01-02"} got := c.DailyPath(time.Date(2026, 6, 27, 0, 0, 0, 0, time.UTC)) want := filepath.Join("/v", "Daily", "2026-06-27.md") if got != want { t.Errorf("DailyPath = %q, want %q", got, want) } } func TestInboxDirDefaultsEmptyAndOverlays(t *testing.T) { if d := Default(); d.InboxDir != "" { t.Errorf("InboxDir default = %q, want empty (vault root)", d.InboxDir) } dir := t.TempDir() path := filepath.Join(dir, "config.toml") if err := os.WriteFile(path, []byte("inbox_dir = \"Inbox\"\n"), 0o644); err != nil { t.Fatal(err) } cfg, err := loadFromFile(path) if err != nil { t.Fatal(err) } if cfg.InboxDir != "Inbox" { t.Errorf("InboxDir = %q, want Inbox", cfg.InboxDir) } } func TestInboxRootResolves(t *testing.T) { t.Setenv("GLINT_VAULT", "/v") // pins the working dir c := Config{} if got := c.InboxRoot(); got != "/v" { t.Errorf("empty InboxDir → %q, want /v (working dir)", got) } c.InboxDir = "Inbox" if got := c.InboxRoot(); got != filepath.Join("/v", "Inbox") { t.Errorf("relative InboxDir → %q, want /v/Inbox", got) } c.InboxDir = "/abs/inbox" if got := c.InboxRoot(); got != "/abs/inbox" { t.Errorf("absolute InboxDir → %q, want /abs/inbox", got) } } func TestWorkingDirUsesEnvThenCwd(t *testing.T) { t.Setenv("GLINT_VAULT", "/explicit/dir") if got := (Config{}).WorkingDir(); got != "/explicit/dir" { t.Errorf("with GLINT_VAULT set: WorkingDir = %q, want /explicit/dir", got) } t.Setenv("GLINT_VAULT", "") wd, _ := os.Getwd() if got := (Config{}).WorkingDir(); got != wd { t.Errorf("without GLINT_VAULT: WorkingDir = %q, want cwd %q", got, wd) } } func TestVaultPrefersConfigThenWorkingDir(t *testing.T) { t.Setenv("GLINT_VAULT", "/work") if got := (Config{VaultDir: "/the/vault"}).Vault(); got != "/the/vault" { t.Errorf("with vault_dir set: Vault = %q, want /the/vault", got) } if got := (Config{}).Vault(); got != "/work" { t.Errorf("without vault_dir: Vault = %q, want working dir /work", got) } } func TestPathEndsAtGlintConfig(t *testing.T) { p := Path() if filepath.Base(p) != "config.toml" || filepath.Base(filepath.Dir(p)) != "glint" { t.Errorf("Path() = %q, want .../glint/config.toml", p) } } func TestSaveRoundTrips(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "sub", "config.toml") // parent created by Save cfg := Default() cfg.VaultDir = "/my/vault" cfg.InboxDir = "Inbox" cfg.DailyFormat = "20060102" cfg.Theme = "charm" if err := Save(cfg, path); err != nil { t.Fatal(err) } got, err := loadFromFile(path) if err != nil { t.Fatal(err) } if got.VaultDir != "/my/vault" || got.InboxDir != "Inbox" || got.DailyFormat != "20060102" || got.Theme != "charm" { t.Errorf("round-trip mismatch: %+v", got) } } func TestDailyPathRootsAtVaultNotCwd(t *testing.T) { t.Setenv("GLINT_VAULT", "/work") // working dir (cwd-equivalent) c := Config{VaultDir: "/vault", DailySubdir: "Daily", DailyFormat: "2006-01-02"} got := c.DailyPath(time.Date(2026, 6, 29, 0, 0, 0, 0, time.UTC)) want := filepath.Join("/vault", "Daily", "2026-06-29.md") if got != want { t.Errorf("DailyPath = %q, want %q (vault, not working dir)", got, want) } // Absolute daily_subdir is used as-is. c.DailySubdir = "/abs/Journal" got = c.DailyPath(time.Date(2026, 6, 29, 0, 0, 0, 0, time.UTC)) want = filepath.Join("/abs/Journal", "2026-06-29.md") if got != want { t.Errorf("absolute DailyPath = %q, want %q", got, want) } } func TestDailyDir(t *testing.T) { c := Config{VaultDir: "/vault", DailySubdir: "Daily"} if got := c.DailyDir(); got != filepath.Join("/vault", "Daily") { t.Errorf("DailyDir = %q, want /vault/Daily", got) } c.DailySubdir = "/abs/Journal" if got := c.DailyDir(); got != "/abs/Journal" { t.Errorf("absolute DailyDir = %q, want /abs/Journal", got) } }