summaryrefslogtreecommitdiff
path: root/internal/tui/export
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-13 14:28:37 +0300
committerPaul Buetow <paul@buetow.org>2026-05-13 14:28:37 +0300
commit27b94f917064948fa33141309a3f08deb40ffde2 (patch)
tree0f1c63eba01da1cc89fbbedcfe71cdcb55b06cb0 /internal/tui/export
parent140d6c0fe472f112170022b9831dfe700698f382 (diff)
improve unit test coverage to >=60% in probes, common, export, streamrow, pidpicker, tui/export
Before: probes=30%, tui/common=41%, export=0%, streamrow=25%, pidpicker=59%, tui/export=45% After: probes=89%, tui/common=97%, export=77%, streamrow=100%, pidpicker=73%, tui/export=99% New test files cover RingBuffer push/wrap/reset, Row accessor methods, nil Sequencer safety, SnapshotCSV nil and data paths, helper functions snapValue / snapValueF / trendSummary, all table navigation keys, VisibleTableWindow/ ClampTableCol edge cases, RenderTableHeader/Row, PickerShortHelp, probe modal navigation/search/toggle/view/error paths, truncateText/sanitizeOneLine, export modal View rendering, key navigation, status messages, scanAllThreadsFrom, readThreadInfo guards, formatProcess variants, and clamp helper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/tui/export')
-rw-r--r--internal/tui/export/export_extra_test.go195
1 files changed, 195 insertions, 0 deletions
diff --git a/internal/tui/export/export_extra_test.go b/internal/tui/export/export_extra_test.go
new file mode 100644
index 0000000..edc0632
--- /dev/null
+++ b/internal/tui/export/export_extra_test.go
@@ -0,0 +1,195 @@
+package export
+
+import (
+ "errors"
+ "strings"
+ "testing"
+
+ tea "charm.land/bubbletea/v2"
+)
+
+// TestViewInvisibleReturnsEmpty verifies that View returns an empty string when
+// the modal is not visible.
+func TestViewInvisibleReturnsEmpty(t *testing.T) {
+ m := NewModel()
+ if out := m.View(80, 24); out != "" {
+ t.Fatalf("expected empty view when invisible, got %q", out)
+ }
+}
+
+// TestViewVisibleContainsOptionLabels verifies that View renders option labels
+// while the modal is open.
+func TestViewVisibleContainsOptionLabels(t *testing.T) {
+ m := NewModel().Open()
+ out := m.View(80, 24)
+ for _, label := range optionLabels {
+ if !strings.Contains(out, label) {
+ t.Fatalf("expected option label %q in view, got:\n%s", label, out)
+ }
+ }
+ if !strings.Contains(out, "Enter confirm") {
+ t.Fatalf("expected help text in view, got:\n%s", out)
+ }
+}
+
+// TestViewZeroDimensionsUsesDefaults verifies that zero width/height fall back
+// to defaults without panicking.
+func TestViewZeroDimensionsUsesDefaults(t *testing.T) {
+ m := NewModel().Open()
+ out := m.View(0, 0)
+ if out == "" {
+ t.Fatal("expected non-empty view with zero dimensions")
+ }
+}
+
+// TestViewNarrowWidthClamped verifies that very narrow widths are clamped to a
+// minimum modal width.
+func TestViewNarrowWidthClamped(t *testing.T) {
+ m := NewModel().Open()
+ // Should not panic on very narrow terminals.
+ out := m.View(10, 24)
+ if out == "" {
+ t.Fatal("expected non-empty view on narrow terminal")
+ }
+}
+
+// TestViewExportingHidesHelp verifies that the help text "Enter confirm" is
+// hidden while an export is in progress.
+func TestViewExportingHidesHelp(t *testing.T) {
+ m := NewModel().Open()
+ m.exporting = true
+ out := m.View(80, 24)
+ if strings.Contains(out, "Enter confirm") {
+ t.Fatalf("expected help hidden while exporting, got:\n%s", out)
+ }
+}
+
+// TestViewShowsStatus verifies that status messages appear in the view.
+func TestViewShowsStatus(t *testing.T) {
+ m := NewModel().Open()
+ m.status = "Export done: out.csv"
+ out := m.View(80, 24)
+ if !strings.Contains(out, "Export done: out.csv") {
+ t.Fatalf("expected status in view, got:\n%s", out)
+ }
+}
+
+// TestUpDownKeysChangeSelection verifies that up/down and j/k move the
+// selected option index.
+func TestUpDownKeysChangeSelection(t *testing.T) {
+ m := NewModel().Open()
+
+ // Move down to last option.
+ m, _ = m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: "j"})
+ if m.selected != 1 {
+ t.Fatalf("expected selected=1 after j, got %d", m.selected)
+ }
+
+ // Move up back to first.
+ m, _ = m.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: "k"})
+ if m.selected != 0 {
+ t.Fatalf("expected selected=0 after k, got %d", m.selected)
+ }
+
+ // down key alias.
+ m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyDown})
+ if m.selected != 1 {
+ t.Fatalf("expected selected=1 after down, got %d", m.selected)
+ }
+
+ // up key alias.
+ m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyUp})
+ if m.selected != 0 {
+ t.Fatalf("expected selected=0 after up, got %d", m.selected)
+ }
+}
+
+// TestKeyIgnoredWhenNotVisible verifies that key events are no-ops when the
+// modal is closed.
+func TestKeyIgnoredWhenNotVisible(t *testing.T) {
+ m := NewModel() // not opened
+ next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
+ if cmd != nil {
+ t.Fatal("expected no command when modal is invisible")
+ }
+ if next.selected != 0 {
+ t.Fatalf("expected no selection change when invisible, got %d", next.selected)
+ }
+}
+
+// TestEscWhileExportingCloses verifies that esc can close the modal even while
+// an export is in progress.
+func TestEscWhileExportingCloses(t *testing.T) {
+ m := NewModel().Open()
+ m.exporting = true
+ m, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
+ if m.Visible() {
+ t.Fatal("expected modal closed by esc while exporting")
+ }
+}
+
+// TestCompletedMsgWithEmptyPathUsesDone verifies that an empty path in
+// CompletedMsg defaults to "done".
+func TestCompletedMsgWithEmptyPathUsesDone(t *testing.T) {
+ m := NewModel().Open()
+ m.exporting = true
+ m, _ = m.Update(CompletedMsg{Path: ""})
+ if !strings.Contains(m.status, "done") {
+ t.Fatalf("expected 'done' in status for empty path, got %q", m.status)
+ }
+}
+
+// TestFailedMsgWithNilErrUsesDefault verifies that a nil error in FailedMsg
+// produces a non-empty error message.
+func TestFailedMsgWithNilErrUsesDefault(t *testing.T) {
+ m := NewModel().Open()
+ m.exporting = true
+ m, _ = m.Update(FailedMsg{Err: nil})
+ if m.status == "" {
+ t.Fatal("expected non-empty status after FailedMsg with nil error")
+ }
+}
+
+// TestUnknownMsgIsNoOp verifies that unsupported message types leave the model
+// unchanged.
+func TestUnknownMsgIsNoOp(t *testing.T) {
+ m := NewModel().Open()
+ next, cmd := m.Update("unknown message type")
+ if cmd != nil {
+ t.Fatal("expected no command for unknown message")
+ }
+ if next.selected != m.selected {
+ t.Fatalf("unexpected state change on unknown msg")
+ }
+}
+
+// TestKeyIgnoredWhenExportingAndNotEsc verifies that non-esc keys are ignored
+// during an in-progress export.
+func TestKeyIgnoredWhenExportingAndNotEsc(t *testing.T) {
+ m := NewModel().Open()
+ m.exporting = true
+ origSelected := m.selected
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: "j"})
+ if cmd != nil {
+ t.Fatal("expected no command for j during export")
+ }
+ if next.selected != origSelected {
+ t.Fatalf("expected selection unchanged during export, got %d", next.selected)
+ }
+ if !next.Visible() {
+ t.Fatal("expected modal still visible during export")
+ }
+}
+
+// TestUpdateWithErrors verifies the error recovery path for unknown errors.
+func TestUpdateWithErrors(t *testing.T) {
+ m := NewModel().Open()
+ m.exporting = true
+ m, _ = m.Update(FailedMsg{Err: errors.New("disk full")})
+ if !strings.Contains(m.status, "disk full") {
+ t.Fatalf("expected disk full in status, got %q", m.status)
+ }
+ if m.exporting {
+ t.Fatal("expected exporting=false after failure")
+ }
+}