summaryrefslogtreecommitdiff
path: root/internal/editor
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 03:10:55 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 03:10:55 +0200
commit1fc1611fa99993cab5dc8bf0844183285296e3b2 (patch)
treec5c9b8b5abac5b5d4c0d56ed90b0580184cc4383 /internal/editor
parent12090f25a3677291863dbb80277bdad3eaec0324 (diff)
Release v0.24.0v0.24.0
Bring unit test coverage from ~75% to 85.1% project-wide. All internal packages now exceed 80% coverage. Refactored cmd entrypoints to extract testable run() functions with injectable seams. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/editor')
-rw-r--r--internal/editor/editor_test.go140
1 files changed, 140 insertions, 0 deletions
diff --git a/internal/editor/editor_test.go b/internal/editor/editor_test.go
index 06cc165..260fb85 100644
--- a/internal/editor/editor_test.go
+++ b/internal/editor/editor_test.go
@@ -1,11 +1,32 @@
package editor
import (
+ "errors"
"os"
"path/filepath"
"testing"
)
+// TestRunEditor_Default exercises the default RunEditor function with a harmless command.
+func TestRunEditor_Default(t *testing.T) {
+ t.Setenv("HEXAI_EDITOR", "true") // /usr/bin/true — exits 0 immediately
+ tmp := filepath.Join(t.TempDir(), "test.md")
+ if err := os.WriteFile(tmp, []byte("hello"), 0o600); err != nil {
+ t.Fatal(err)
+ }
+ if err := RunEditor("true", tmp); err != nil {
+ t.Fatalf("RunEditor with 'true': %v", err)
+ }
+}
+
+// TestRunEditor_Default_BadCommand verifies RunEditor returns an error for a nonexistent command.
+func TestRunEditor_Default_BadCommand(t *testing.T) {
+ err := RunEditor("nonexistent-editor-cmd-12345", "/dev/null")
+ if err == nil {
+ t.Fatal("expected error for nonexistent editor command")
+ }
+}
+
func TestResolve_EnvPriority(t *testing.T) {
t.Setenv("HEXAI_EDITOR", "ed1")
t.Setenv("EDITOR", "ed2")
@@ -20,6 +41,26 @@ func TestResolve_EnvPriority(t *testing.T) {
}
}
+// TestResolve_NoEditor verifies the error when neither HEXAI_EDITOR nor EDITOR is set.
+func TestResolve_NoEditor(t *testing.T) {
+ t.Setenv("HEXAI_EDITOR", "")
+ t.Setenv("EDITOR", "")
+ _, err := Resolve()
+ if err == nil {
+ t.Fatal("expected error when no editor is configured")
+ }
+}
+
+// TestResolve_WhitespaceOnly verifies that whitespace-only values are treated as empty.
+func TestResolve_WhitespaceOnly(t *testing.T) {
+ t.Setenv("HEXAI_EDITOR", " ")
+ t.Setenv("EDITOR", " \t ")
+ _, err := Resolve()
+ if err == nil {
+ t.Fatal("expected error for whitespace-only editor values")
+ }
+}
+
func TestOpenTempAndEdit_UsesRunEditor(t *testing.T) {
old := RunEditor
t.Cleanup(func() { RunEditor = old })
@@ -42,3 +83,102 @@ func TestOpenTempAndEdit_UsesRunEditor(t *testing.T) {
t.Fatalf("expected .md suffix: %s", capturedPath)
}
}
+
+// TestOpenTempAndEdit_NoEditor verifies error propagation when no editor is configured.
+func TestOpenTempAndEdit_NoEditor(t *testing.T) {
+ t.Setenv("HEXAI_EDITOR", "")
+ t.Setenv("EDITOR", "")
+ _, err := OpenTempAndEdit(nil)
+ if err == nil {
+ t.Fatal("expected error when no editor is set")
+ }
+}
+
+// TestOpenTempAndEdit_NilInitial verifies that nil initial content works (empty file).
+func TestOpenTempAndEdit_NilInitial(t *testing.T) {
+ old := RunEditor
+ t.Cleanup(func() { RunEditor = old })
+ t.Setenv("HEXAI_EDITOR", "dummy")
+ RunEditor = func(editor, path string) error {
+ // simulate user writing content into a file that started empty
+ return os.WriteFile(path, []byte("result"), 0o600)
+ }
+ out, err := OpenTempAndEdit(nil)
+ if err != nil {
+ t.Fatalf("OpenTempAndEdit with nil initial: %v", err)
+ }
+ if out != "result" {
+ t.Fatalf("unexpected content: %q", out)
+ }
+}
+
+// TestOpenTempAndEdit_EmptyInitial verifies that empty (zero-length) initial content
+// skips the write branch but still works end-to-end.
+func TestOpenTempAndEdit_EmptyInitial(t *testing.T) {
+ old := RunEditor
+ t.Cleanup(func() { RunEditor = old })
+ t.Setenv("HEXAI_EDITOR", "dummy")
+ RunEditor = func(editor, path string) error {
+ return os.WriteFile(path, []byte(" trimmed "), 0o600)
+ }
+ out, err := OpenTempAndEdit([]byte{})
+ if err != nil {
+ t.Fatalf("OpenTempAndEdit with empty initial: %v", err)
+ }
+ if out != "trimmed" {
+ t.Fatalf("expected trimmed content, got %q", out)
+ }
+}
+
+// TestOpenTempAndEdit_EditorError verifies that an editor failure propagates the error.
+func TestOpenTempAndEdit_EditorError(t *testing.T) {
+ old := RunEditor
+ t.Cleanup(func() { RunEditor = old })
+ t.Setenv("HEXAI_EDITOR", "dummy")
+ editorErr := errors.New("editor crashed")
+ RunEditor = func(editor, path string) error {
+ return editorErr
+ }
+ _, err := OpenTempAndEdit([]byte("some content"))
+ if err == nil {
+ t.Fatal("expected error when editor fails")
+ }
+ if !errors.Is(err, editorErr) {
+ t.Fatalf("expected editor error, got: %v", err)
+ }
+}
+
+// TestOpenTempAndEdit_EditorDeletesFile verifies error when the editor removes the temp file.
+func TestOpenTempAndEdit_EditorDeletesFile(t *testing.T) {
+ old := RunEditor
+ t.Cleanup(func() { RunEditor = old })
+ t.Setenv("HEXAI_EDITOR", "dummy")
+ RunEditor = func(editor, path string) error {
+ // simulate the editor deleting the file
+ return os.Remove(path)
+ }
+ _, err := OpenTempAndEdit([]byte("content"))
+ if err == nil {
+ t.Fatal("expected error when temp file is deleted by editor")
+ }
+}
+
+// TestOpenTempAndEdit_TempFileCleanup verifies the temp file is removed after success.
+func TestOpenTempAndEdit_TempFileCleanup(t *testing.T) {
+ old := RunEditor
+ t.Cleanup(func() { RunEditor = old })
+ t.Setenv("HEXAI_EDITOR", "dummy")
+ var capturedPath string
+ RunEditor = func(editor, path string) error {
+ capturedPath = path
+ return os.WriteFile(path, []byte("done"), 0o600)
+ }
+ _, err := OpenTempAndEdit(nil)
+ if err != nil {
+ t.Fatalf("OpenTempAndEdit: %v", err)
+ }
+ // The deferred os.Remove should have cleaned up the temp file
+ if _, err := os.Stat(capturedPath); !os.IsNotExist(err) {
+ t.Fatalf("temp file was not cleaned up: %s", capturedPath)
+ }
+}