diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-01 18:10:19 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-01 18:10:19 +0200 |
| commit | 5247a201c8ba77329c7b1ee40da7b8e43a56b567 (patch) | |
| tree | d5cd331e7bd5040ef3267e7f888a8c4463cd2832 | |
| parent | 5fcb97aeb4ec1e291e221794df5d40b27ea60197 (diff) | |
Release v0.1.0: code quality audit — fix dialog bug, refactor, add testsv0.1.0
- Fix per-keystroke dialog storm when text exceeds maxTextLength
- Extract logEntry() for testable persistence with filepath.Join
- Extract newInputWidget() and deduplicate shared-text loading
- Rename appId to appID per Go naming conventions
- Delete unused debugSharedPath function
- Add error distinction in readSharedFromCache (log real errors)
- Fix ANDORID_NDK_HOME typo to ANDRIOD_NDK_HOME in Magefile
- Run gofmt, go mod tidy; promote defaultDirectory to const
- Add unit tests for logEntry and readSharedFromCache stub
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | FyneApp.toml | 4 | ||||
| -rw-r--r-- | Magefile.go | 334 | ||||
| -rw-r--r-- | android_shared_android.go | 36 | ||||
| -rw-r--r-- | android_shared_stub.go | 1 | ||||
| -rw-r--r-- | android_shared_stub_test.go | 15 | ||||
| -rw-r--r-- | go.mod | 6 | ||||
| -rw-r--r-- | main.go | 235 | ||||
| -rw-r--r-- | main_test.go | 62 |
8 files changed, 390 insertions, 303 deletions
diff --git a/FyneApp.toml b/FyneApp.toml index b409766..9a1ae86 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -4,5 +4,5 @@ Website = "https://codeberg.org/snonux/quicklogger" Icon = "icon.png" Name = "quicklogger" ID = "org.buetow.quicklogger" - Version = "0.0.4" - Build = 52 + Version = "0.1.0" + Build = 55 diff --git a/Magefile.go b/Magefile.go index 49a5fb4..ef25814 100644 --- a/Magefile.go +++ b/Magefile.go @@ -4,198 +4,198 @@ package main import ( - "io" - "fmt" - "os" - "path/filepath" - "runtime" - "strconv" - "time" - - "github.com/magefile/mage/sh" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "strconv" + "time" + + "github.com/magefile/mage/sh" ) // Build compiles the quicklogger app into ./bin. func Build() error { - outDir := "bin" - if err := os.MkdirAll(outDir, 0o755); err != nil { - return err - } - - binName := "quicklogger" - if runtime.GOOS == "windows" { - binName += ".exe" - } - - out := filepath.Join(outDir, binName) - fmt.Printf("Building %s\n", out) - return sh.RunV("go", "build", "-o", out, ".") + outDir := "bin" + if err := os.MkdirAll(outDir, 0o755); err != nil { + return err + } + + binName := "quicklogger" + if runtime.GOOS == "windows" { + binName += ".exe" + } + + out := filepath.Join(outDir, binName) + fmt.Printf("Building %s\n", out) + return sh.RunV("go", "build", "-o", out, ".") } // Run launches the app with `go run .`. func Run() error { - fmt.Println("Running quicklogger (verbose build)") - // Show Go version and key env for diagnostics - _ = sh.RunV("go", "version") - _ = sh.RunV("go", "env", "GOVERSION", "GOOS", "GOARCH", "GOMOD", "GOMODCACHE", "GOCACHE") - // Compile+run with verbose and trace flags to show build steps - return sh.RunV("go", "run", "-v", "-x", ".") + fmt.Println("Running quicklogger (verbose build)") + // Show Go version and key env for diagnostics + _ = sh.RunV("go", "version") + _ = sh.RunV("go", "env", "GOVERSION", "GOOS", "GOARCH", "GOMOD", "GOMODCACHE", "GOCACHE") + // Compile+run with verbose and trace flags to show build steps + return sh.RunV("go", "run", "-v", "-x", ".") } // Clean removes build artifacts (bin/ and local APK). func Clean() error { - fmt.Println("Cleaning build artifacts") - // Remove bin directory - if err := os.RemoveAll("bin"); err != nil { - return err - } - // Remove generated APK if present - _ = os.Remove("quicklogger.apk") - return nil + fmt.Println("Cleaning build artifacts") + // Remove bin directory + if err := os.RemoveAll("bin"); err != nil { + return err + } + // Remove generated APK if present + _ = os.Remove("quicklogger.apk") + return nil } // Android packages an Android APK via Fyne and copies it to ~/Documents/APKs if present. func Android() error { - env := map[string]string{} - - // Respect existing ANDROID_NDK_HOME. If not set, try legacy var or a common default path. - ndk := os.Getenv("ANDROID_NDK_HOME") - if ndk == "" { - // Some setups export a misspelled var; honor it if present. - ndk = os.Getenv("ANDORID_NDK_HOME") - } - if ndk == "" { - if home, err := os.UserHomeDir(); err == nil { - guess := filepath.Join(home, "android-ndk", "android-ndk-r26b") - env["ANDROID_NDK_HOME"] = guess - fmt.Printf("ANDROID_NDK_HOME not set, guessing %s\n", guess) - } - } - - fmt.Println("Packaging Android APK via Fyne") - if err := sh.RunWithV(env, "fyne", "package", "-os", "android"); err != nil { - return err - } - - // If ~/Documents/APKs exists, copy the APK there. - home, err := os.UserHomeDir() - if err != nil { - return nil // packaging succeeded; copying is best-effort - } - destDir := filepath.Join(home, "Documents", "APKs") - if st, err := os.Stat(destDir); err == nil && st.IsDir() { - src := "quicklogger.apk" - if _, err := os.Stat(src); err == nil { - dst := filepath.Join(destDir, filepath.Base(src)) - if err := copyFile(src, dst); err != nil { - return err - } - fmt.Printf("Copied %s to %s\n", src, dst) - } - } - return nil + env := map[string]string{} + + // Respect existing ANDROID_NDK_HOME. If not set, try legacy var or a common default path. + ndk := os.Getenv("ANDROID_NDK_HOME") + if ndk == "" { + // Some setups export a misspelled var; honor it if present. + ndk = os.Getenv("ANDRIOD_NDK_HOME") + } + if ndk == "" { + if home, err := os.UserHomeDir(); err == nil { + guess := filepath.Join(home, "android-ndk", "android-ndk-r26b") + env["ANDROID_NDK_HOME"] = guess + fmt.Printf("ANDROID_NDK_HOME not set, guessing %s\n", guess) + } + } + + fmt.Println("Packaging Android APK via Fyne") + if err := sh.RunWithV(env, "fyne", "package", "-os", "android"); err != nil { + return err + } + + // If ~/Documents/APKs exists, copy the APK there. + home, err := os.UserHomeDir() + if err != nil { + return nil // packaging succeeded; copying is best-effort + } + destDir := filepath.Join(home, "Documents", "APKs") + if st, err := os.Stat(destDir); err == nil && st.IsDir() { + src := "quicklogger.apk" + if _, err := os.Stat(src); err == nil { + dst := filepath.Join(destDir, filepath.Base(src)) + if err := copyFile(src, dst); err != nil { + return err + } + fmt.Printf("Copied %s to %s\n", src, dst) + } + } + return nil } // AndroidCross builds an Android APK using fyne-cross with Docker/Podman. // Mirrors steps from README using a Podman socket if available. func AndroidCross() error { - env := map[string]string{} - - // If DOCKER_HOST is not set, try a sensible Podman socket default. - if os.Getenv("DOCKER_HOST") == "" { - uid := os.Geteuid() - sock := filepath.Join("/run/user", strconv.Itoa(uid), "podman", "podman.sock") - if _, err := os.Stat(sock); err == nil { - env["DOCKER_HOST"] = "unix://" + sock - fmt.Printf("Using Podman socket: %s\n", env["DOCKER_HOST"]) - } else { - fmt.Println("DOCKER_HOST is not set and no Podman socket found; relying on default Docker daemon.") - } - } - - // Ensure fyne-cross is available; attempt install if not. - if _, err := sh.Output("which", "fyne-cross"); err != nil { - fmt.Println("Installing fyne-cross (requires network access)...") - if err := sh.RunV("go", "install", "github.com/fyne-io/fyne-cross@latest"); err != nil { - return fmt.Errorf("fyne-cross not found and installation failed: %w", err) - } - } - - // Pull/update builder image then build. - if err := sh.RunWithV(env, "fyne-cross", "android", "--pull"); err != nil { - return err - } - if err := sh.RunWithV(env, "fyne-cross", "android"); err != nil { - return err - } - - // Copy newest APK to ~/Documents/APKs if available. - apk, err := newestAPK("fyne-cross/dist/android") - if err != nil { - // Not fatal; print a note and finish - fmt.Printf("Note: could not locate built APK: %v\n", err) - return nil - } - home, herr := os.UserHomeDir() - if herr != nil { - return nil - } - destDir := filepath.Join(home, "Documents", "APKs") - if st, err := os.Stat(destDir); err == nil && st.IsDir() { - dst := filepath.Join(destDir, filepath.Base(apk)) - if err := copyFile(apk, dst); err != nil { - return err - } - fmt.Printf("Copied %s to %s\n", apk, dst) - } - return nil + env := map[string]string{} + + // If DOCKER_HOST is not set, try a sensible Podman socket default. + if os.Getenv("DOCKER_HOST") == "" { + uid := os.Geteuid() + sock := filepath.Join("/run/user", strconv.Itoa(uid), "podman", "podman.sock") + if _, err := os.Stat(sock); err == nil { + env["DOCKER_HOST"] = "unix://" + sock + fmt.Printf("Using Podman socket: %s\n", env["DOCKER_HOST"]) + } else { + fmt.Println("DOCKER_HOST is not set and no Podman socket found; relying on default Docker daemon.") + } + } + + // Ensure fyne-cross is available; attempt install if not. + if _, err := sh.Output("which", "fyne-cross"); err != nil { + fmt.Println("Installing fyne-cross (requires network access)...") + if err := sh.RunV("go", "install", "github.com/fyne-io/fyne-cross@latest"); err != nil { + return fmt.Errorf("fyne-cross not found and installation failed: %w", err) + } + } + + // Pull/update builder image then build. + if err := sh.RunWithV(env, "fyne-cross", "android", "--pull"); err != nil { + return err + } + if err := sh.RunWithV(env, "fyne-cross", "android"); err != nil { + return err + } + + // Copy newest APK to ~/Documents/APKs if available. + apk, err := newestAPK("fyne-cross/dist/android") + if err != nil { + // Not fatal; print a note and finish + fmt.Printf("Note: could not locate built APK: %v\n", err) + return nil + } + home, herr := os.UserHomeDir() + if herr != nil { + return nil + } + destDir := filepath.Join(home, "Documents", "APKs") + if st, err := os.Stat(destDir); err == nil && st.IsDir() { + dst := filepath.Join(destDir, filepath.Base(apk)) + if err := copyFile(apk, dst); err != nil { + return err + } + fmt.Printf("Copied %s to %s\n", apk, dst) + } + return nil } func newestAPK(dir string) (string, error) { - entries, err := os.ReadDir(dir) - if err != nil { - return "", err - } - var newest string - var newestMod time.Time - for _, e := range entries { - if e.IsDir() { - continue - } - name := e.Name() - if filepath.Ext(name) != ".apk" { - continue - } - info, err := e.Info() - if err != nil { - continue - } - if info.ModTime().After(newestMod) { - newestMod = info.ModTime() - newest = filepath.Join(dir, name) - } - } - if newest == "" { - return "", fmt.Errorf("no .apk found in %s", dir) - } - return newest, nil + entries, err := os.ReadDir(dir) + if err != nil { + return "", err + } + var newest string + var newestMod time.Time + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if filepath.Ext(name) != ".apk" { + continue + } + info, err := e.Info() + if err != nil { + continue + } + if info.ModTime().After(newestMod) { + newestMod = info.ModTime() + newest = filepath.Join(dir, name) + } + } + if newest == "" { + return "", fmt.Errorf("no .apk found in %s", dir) + } + return newest, nil } func copyFile(src, dst string) error { - in, err := os.Open(src) - if err != nil { - return err - } - defer in.Close() - - out, err := os.Create(dst) - if err != nil { - return err - } - defer func() { _ = out.Close() }() - - if _, err = io.Copy(out, in); err != nil { - return err - } - return out.Sync() + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer func() { _ = out.Close() }() + + if _, err = io.Copy(out, in); err != nil { + return err + } + return out.Sync() } diff --git a/android_shared_android.go b/android_shared_android.go index b736fab..e46fab2 100644 --- a/android_shared_android.go +++ b/android_shared_android.go @@ -3,32 +3,24 @@ package main import ( - "os" - "path/filepath" + "os" + "path/filepath" ) // readSharedFromCache tries to read the shared text written by the Android activity. // The activity writes into getCacheDir()/quicklogger-shared.txt; on Go, os.TempDir() // maps to the same location in Android (app cache directory). func readSharedFromCache() (string, error) { - dir, derr := os.UserCacheDir() - if derr != nil || dir == "" { - dir = os.TempDir() - } - path := filepath.Join(dir, "quicklogger-shared.txt") - b, err := os.ReadFile(path) - if err != nil { - return "", err - } - // best-effort cleanup; ignore errors - _ = os.Remove(path) - return string(b), nil -} - -func debugSharedPath() string { - dir, _ := os.UserCacheDir() - if dir == "" { - dir = os.TempDir() - } - return filepath.Join(dir, "quicklogger-shared.txt") + dir, derr := os.UserCacheDir() + if derr != nil || dir == "" { + dir = os.TempDir() + } + path := filepath.Join(dir, "quicklogger-shared.txt") + b, err := os.ReadFile(path) + if err != nil { + return "", err + } + // best-effort cleanup; ignore errors + _ = os.Remove(path) + return string(b), nil } diff --git a/android_shared_stub.go b/android_shared_stub.go index 4e6f27e..9ebe1a3 100644 --- a/android_shared_stub.go +++ b/android_shared_stub.go @@ -3,4 +3,3 @@ package main func readSharedFromCache() (string, error) { return "", nil } - diff --git a/android_shared_stub_test.go b/android_shared_stub_test.go new file mode 100644 index 0000000..cc62f5d --- /dev/null +++ b/android_shared_stub_test.go @@ -0,0 +1,15 @@ +//go:build !android + +package main + +import "testing" + +func TestReadSharedFromCacheStub(t *testing.T) { + txt, err := readSharedFromCache() + if err != nil { + t.Fatalf("stub returned error: %v", err) + } + if txt != "" { + t.Errorf("stub returned non-empty text: %q", txt) + } +} @@ -2,7 +2,10 @@ module codeberg.org/snonux/quicklogger go 1.21.6 -require fyne.io/fyne/v2 v2.4.3 +require ( + fyne.io/fyne/v2 v2.4.3 + github.com/magefile/mage v1.15.0 +) require ( fyne.io/systray v1.10.1-0.20231115130155-104f5ef7839e // indirect @@ -19,7 +22,6 @@ require ( github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect - github.com/magefile/mage v1.15.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect @@ -1,144 +1,161 @@ package main import ( - "fmt" - "os" - "time" - - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/app" - "fyne.io/fyne/v2/container" - "fyne.io/fyne/v2/dialog" - "fyne.io/fyne/v2/widget" + "errors" + "fmt" + "log" + "os" + "path/filepath" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/widget" ) const ( - appId = "org.buetow.quicklogger" + appID = "org.buetow.quicklogger" placeholderText = "Enter text here..." maxTextLength = 5000 // Limit text length to prevent performance issues ) -var ( - defaultDirectory = "." -) +const defaultDirectory = "." var windowSize = fyne.NewSize(400, 100) -func createPreferenceWindow(a fyne.App) fyne.Window { - window := a.NewWindow("Preferences") - directoryPreference := widget.NewEntry() - directoryPreference.SetText(a.Preferences().StringWithFallback("Directory", defaultDirectory)) - - window.SetContent(container.NewVBox( - container.NewVBox( - widget.NewLabel("Directory:"), - directoryPreference, - ), - container.NewHBox( - widget.NewButton("Save", func() { - a.Preferences().SetString("Directory", directoryPreference.Text) - window.Hide() - }), - ))) - window.Resize(windowSize) - - return window +// logEntry writes text to a timestamped markdown file in dir. +// Separates persistence logic from the UI so it can be tested independently. +func logEntry(dir, text string) error { + filename := filepath.Join(dir, "ql-"+time.Now().Format("060102-150405")+".md") + return os.WriteFile(filename, []byte(text), 0o644) } -func createMainWindow(a fyne.App) fyne.Window { - // Create main window - window := a.NewWindow("Quick logger") - +// newInputWidget creates the multi-line text entry with platform-appropriate +// wrapping and row count settings. +func newInputWidget() *widget.Entry { input := widget.NewMultiLineEntry() - // Optimization 1: Disable word wrapping on Android to improve performance - // Word wrapping causes expensive recalculations on every text change + input.SetPlaceHolder(placeholderText) + + // On mobile, disable word wrapping and reduce visible rows to limit + // expensive recalculations and rendering area. if fyne.CurrentDevice().IsMobile() { input.Wrapping = fyne.TextWrapOff + input.SetMinRowsVisible(10) } else { input.Wrapping = fyne.TextWrapWord + input.SetMinRowsVisible(30) } - input.SetPlaceHolder(placeholderText) - // Optimization 2: Reduce visible rows on mobile to limit rendering area - if fyne.CurrentDevice().IsMobile() { - input.SetMinRowsVisible(10) - } else { - input.SetMinRowsVisible(30) - } - - // Optimization 3: Add text length indicator - charCount := widget.NewLabel("0 chars") - // Optimization 4: Throttle text changes with validation - input.OnChanged = func(text string) { - // Update character count - charCount.SetText(fmt.Sprintf("%d chars", len(text))) + return input +} + +func createPreferenceWindow(a fyne.App) fyne.Window { + window := a.NewWindow("Preferences") + directoryPreference := widget.NewEntry() + directoryPreference.SetText(a.Preferences().StringWithFallback("Directory", defaultDirectory)) + + window.SetContent(container.NewVBox( + container.NewVBox( + widget.NewLabel("Directory:"), + directoryPreference, + ), + container.NewHBox( + widget.NewButton("Save", func() { + a.Preferences().SetString("Directory", directoryPreference.Text) + window.Hide() + }), + ))) + window.Resize(windowSize) + + return window +} + +func createMainWindow(a fyne.App) fyne.Window { + window := a.NewWindow("Quick logger") + input := newInputWidget() + charCount := widget.NewLabel("0 chars") - // Warn if text is getting too long - if len(text) > maxTextLength { + // Track whether the length warning has been shown so we don't fire a + // modal dialog on every keystroke above the limit. + warnShown := false + input.OnChanged = func(text string) { + charCount.SetText(fmt.Sprintf("%d chars", len(text))) + if len(text) > maxTextLength && !warnShown { + warnShown = true dialog.ShowInformation("Text Limit", fmt.Sprintf("Text is getting long (%d chars). Consider logging to avoid performance issues.", len(text)), window) + } else if len(text) <= maxTextLength { + warnShown = false + } + } + + // resetInput clears the text entry and character count. + resetInput := func() { + input.SetText("") + charCount.SetText("0 chars") + } + + logTextButton := widget.NewButton("Log text", func() { + dir := a.Preferences().StringWithFallback("Directory", defaultDirectory) + if err := logEntry(dir, input.Text); err != nil { + dialog.ShowError(err, window) + return + } + resetInput() + }) + + clearButton := widget.NewButton("Clear", func() { + resetInput() + window.Canvas().Focus(input) + }) + + // loadSharedText reads Android-shared text from cache and populates the input. + // Used both at startup and when the app returns to the foreground. + // A missing cache file is expected (no share pending); real errors are logged. + loadSharedText := func() { + txt, err := readSharedFromCache() + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + log.Printf("readSharedFromCache: %v", err) + } + return + } + if txt != "" { + input.SetText(txt) + charCount.SetText(fmt.Sprintf("%d chars", len(txt))) + window.Canvas().Focus(input) } } - logTextButton := widget.NewButton("Log text", func() { - filename := fmt.Sprintf("%s/ql-%s.md", - a.Preferences().StringWithFallback("Directory", defaultDirectory), - time.Now().Format("060102-150405"), - ) - if err := os.WriteFile(filename, []byte(input.Text), 0644); err != nil { - dialog.ShowError(err, window) - return - } - input.SetText("") - input.SetPlaceHolder(placeholderText) - // Reset character count - charCount.SetText("0 chars") - }) - - // Optimization 5: Add clear button for quick text clearing - clearButton := widget.NewButton("Clear", func() { - input.SetText("") - charCount.SetText("0 chars") - window.Canvas().Focus(input) - }) - - // If running on Android and shared text file exists, load it into the input - if fyne.CurrentDevice().IsMobile() { - if txt, err := readSharedFromCache(); err == nil && txt != "" { - input.SetText(txt) - charCount.SetText(fmt.Sprintf("%d chars", len(txt))) - } - } - - window.SetContent(container.NewVBox( - input, - container.NewHBox( - logTextButton, - clearButton, - widget.NewButton("Preferences", func() { - createPreferenceWindow(a).Show() - }), - charCount, // Show character count - ), - )) - window.Resize(windowSize) - window.Canvas().Focus(input) - - // On Android, also check for new shared text whenever app returns to foreground. - if lc := a.Lifecycle(); lc != nil { - lc.SetOnEnteredForeground(func() { - if txt, err := readSharedFromCache(); err == nil && txt != "" { - input.SetText(txt) - charCount.SetText(fmt.Sprintf("%d chars", len(txt))) - window.Canvas().Focus(input) - } - }) - } + if fyne.CurrentDevice().IsMobile() { + loadSharedText() + } + + window.SetContent(container.NewVBox( + input, + container.NewHBox( + logTextButton, + clearButton, + widget.NewButton("Preferences", func() { + createPreferenceWindow(a).Show() + }), + charCount, + ), + )) + window.Resize(windowSize) + window.Canvas().Focus(input) + + // On Android, also check for new shared text whenever app returns to foreground. + if lc := a.Lifecycle(); lc != nil { + lc.SetOnEnteredForeground(loadSharedText) + } return window } func main() { - createMainWindow(app.NewWithID(appId)).ShowAndRun() + createMainWindow(app.NewWithID(appID)).ShowAndRun() } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..085dc24 --- /dev/null +++ b/main_test.go @@ -0,0 +1,62 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLogEntryCreatesFile(t *testing.T) { + dir := t.TempDir() + text := "hello world" + + if err := logEntry(dir, text); err != nil { + t.Fatalf("logEntry returned error: %v", err) + } + + entries, err := os.ReadDir(dir) + if err != nil { + t.Fatalf("reading dir: %v", err) + } + if len(entries) != 1 { + t.Fatalf("expected 1 file, got %d", len(entries)) + } + + name := entries[0].Name() + if !strings.HasPrefix(name, "ql-") || !strings.HasSuffix(name, ".md") { + t.Errorf("unexpected filename pattern: %s", name) + } + + content, err := os.ReadFile(filepath.Join(dir, name)) + if err != nil { + t.Fatalf("reading file: %v", err) + } + if string(content) != text { + t.Errorf("expected %q, got %q", text, string(content)) + } +} + +func TestLogEntryInvalidDir(t *testing.T) { + err := logEntry("/nonexistent/path/that/should/not/exist", "test") + if err == nil { + t.Fatal("expected error for invalid directory, got nil") + } +} + +func TestLogEntryEmptyText(t *testing.T) { + dir := t.TempDir() + if err := logEntry(dir, ""); err != nil { + t.Fatalf("logEntry with empty text returned error: %v", err) + } + + entries, _ := os.ReadDir(dir) + if len(entries) != 1 { + t.Fatalf("expected 1 file, got %d", len(entries)) + } + + content, _ := os.ReadFile(filepath.Join(dir, entries[0].Name())) + if len(content) != 0 { + t.Errorf("expected empty file, got %d bytes", len(content)) + } +} |
