summaryrefslogtreecommitdiff
path: root/internal/tui/common
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/common
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/common')
-rw-r--r--internal/tui/common/table_test.go209
1 files changed, 209 insertions, 0 deletions
diff --git a/internal/tui/common/table_test.go b/internal/tui/common/table_test.go
new file mode 100644
index 0000000..fa104d1
--- /dev/null
+++ b/internal/tui/common/table_test.go
@@ -0,0 +1,209 @@
+package common
+
+import (
+ "strings"
+ "testing"
+
+ "charm.land/lipgloss/v2"
+)
+
+// TestRenderTableHeaderProducesColumns verifies that RenderTableHeader creates a
+// styled row containing each column title padded to its declared width.
+func TestRenderTableHeaderProducesColumns(t *testing.T) {
+ cols := []TableColumn{
+ {Title: "PID", Width: 8},
+ {Title: "COMM", Width: 12},
+ }
+ out := RenderTableHeader(cols)
+ if !strings.Contains(out, "PID") {
+ t.Fatalf("expected header to contain PID, got: %q", out)
+ }
+ if !strings.Contains(out, "COMM") {
+ t.Fatalf("expected header to contain COMM, got: %q", out)
+ }
+}
+
+// TestRenderTableRowSelectedAndUnselected verifies that selected rows include
+// highlighted style and unselected rows use the base style.
+func TestRenderTableRowSelectedAndUnselected(t *testing.T) {
+ cols := []TableColumn{
+ {Title: "SYS", Width: 10},
+ {Title: "CNT", Width: 6},
+ }
+ cells := []string{"openat", "42"}
+ base := lipgloss.NewStyle()
+
+ unselected := RenderTableRow(cols, cells, false, 0, base)
+ if !strings.Contains(unselected, "openat") {
+ t.Fatalf("unselected row missing cell content, got: %q", unselected)
+ }
+
+ // Selected row: all cells present, no base style rendering branch.
+ selected := RenderTableRow(cols, cells, true, 1, base)
+ if !strings.Contains(selected, "openat") {
+ t.Fatalf("selected row missing first cell, got: %q", selected)
+ }
+ if !strings.Contains(selected, "42") {
+ t.Fatalf("selected row missing second cell, got: %q", selected)
+ }
+}
+
+// TestRenderTableRowMissingCells verifies that missing cells default to empty
+// strings rather than panicking.
+func TestRenderTableRowMissingCells(t *testing.T) {
+ cols := []TableColumn{
+ {Title: "A", Width: 4},
+ {Title: "B", Width: 4},
+ }
+ // Provide only one cell for two columns.
+ out := RenderTableRow(cols, []string{"x"}, false, 0, lipgloss.NewStyle())
+ if !strings.Contains(out, "x") {
+ t.Fatalf("expected cell content x in output, got: %q", out)
+ }
+}
+
+// TestHandleTableNavigationKeyMovement verifies all supported key strings
+// produce the expected row/col changes.
+func TestHandleTableNavigationKeyMovement(t *testing.T) {
+ cases := []struct {
+ key string
+ initRow int
+ initCol int
+ wantRow int
+ wantCol int
+ rowCount int
+ colCount int
+ pageStep int
+ wantResult bool
+ }{
+ {"down", 0, 0, 1, 0, 5, 3, 1, true},
+ {"j", 2, 0, 3, 0, 5, 3, 1, true},
+ {"up", 2, 0, 1, 0, 5, 3, 1, true},
+ {"k", 1, 0, 0, 0, 5, 3, 1, true},
+ {"right", 0, 0, 0, 1, 5, 3, 1, true},
+ {"l", 0, 1, 0, 2, 5, 3, 1, true},
+ {"left", 0, 2, 0, 1, 5, 3, 1, true},
+ {"h", 0, 1, 0, 0, 5, 3, 1, true},
+ {"g", 3, 1, 0, 1, 5, 3, 1, true},
+ {"G", 0, 1, 4, 1, 5, 3, 1, true},
+ {"pgup", 4, 0, 2, 0, 5, 3, 2, true},
+ {"pageup", 4, 0, 2, 0, 5, 3, 2, true},
+ {"pgdown", 0, 0, 2, 0, 5, 3, 2, true},
+ {"pgdn", 0, 0, 2, 0, 5, 3, 2, true},
+ {"pagedown", 0, 0, 2, 0, 5, 3, 2, true},
+ {"x", 0, 0, 0, 0, 5, 3, 1, false},
+ }
+
+ for _, tc := range cases {
+ row, col := tc.initRow, tc.initCol
+ got := HandleTableNavigationKey(tc.key, &row, &col, tc.rowCount, tc.colCount, tc.pageStep)
+ if got != tc.wantResult {
+ t.Errorf("key=%q: handled=%v, want %v", tc.key, got, tc.wantResult)
+ }
+ if tc.wantResult && (row != tc.wantRow || col != tc.wantCol) {
+ t.Errorf("key=%q: row=%d col=%d, want row=%d col=%d",
+ tc.key, row, col, tc.wantRow, tc.wantCol)
+ }
+ }
+}
+
+// TestHandleTableNavigationKeyZeroPageStep verifies that a zero page step is
+// treated as 1 to avoid staying in place.
+func TestHandleTableNavigationKeyZeroPageStep(t *testing.T) {
+ row, col := 3, 0
+ HandleTableNavigationKey("pgup", &row, &col, 5, 3, 0)
+ if row != 2 {
+ t.Fatalf("expected row=2 after pgup with pageStep=0 (clamped to 1), got %d", row)
+ }
+}
+
+// TestVisibleTableWindowEdgeCases verifies boundary conditions for the visible
+// window calculation.
+func TestVisibleTableWindowEdgeCases(t *testing.T) {
+ // Empty row set: returns (0,0).
+ s, e := VisibleTableWindow(0, 0, 10)
+ if s != 0 || e != 0 {
+ t.Fatalf("empty rows: want (0,0), got (%d,%d)", s, e)
+ }
+
+ // All rows fit in the visible window.
+ s, e = VisibleTableWindow(2, 5, 10)
+ if s != 0 || e != 5 {
+ t.Fatalf("all fit: want (0,5), got (%d,%d)", s, e)
+ }
+
+ // Selection near start.
+ s, e = VisibleTableWindow(0, 10, 4)
+ if s != 0 || e != 4 {
+ t.Fatalf("start: want (0,4), got (%d,%d)", s, e)
+ }
+
+ // Selection near end.
+ s, e = VisibleTableWindow(9, 10, 4)
+ if s != 6 || e != 10 {
+ t.Fatalf("end: want (6,10), got (%d,%d)", s, e)
+ }
+
+ // Middle selection.
+ s, e = VisibleTableWindow(5, 10, 4)
+ if s != 3 || e != 7 {
+ t.Fatalf("middle: want (3,7), got (%d,%d)", s, e)
+ }
+}
+
+// TestClampTableCol verifies ClampTableCol constrains within [0, colCount-1].
+func TestClampTableCol(t *testing.T) {
+ if got := ClampTableCol(-1, 5); got != 0 {
+ t.Fatalf("ClampTableCol(-1,5) = %d, want 0", got)
+ }
+ if got := ClampTableCol(10, 5); got != 4 {
+ t.Fatalf("ClampTableCol(10,5) = %d, want 4", got)
+ }
+ if got := ClampTableCol(2, 5); got != 2 {
+ t.Fatalf("ClampTableCol(2,5) = %d, want 2", got)
+ }
+ if got := ClampTableCol(0, 0); got != 0 {
+ t.Fatalf("ClampTableCol(0,0) = %d, want 0", got)
+ }
+}
+
+// TestRenderTableCellTruncation verifies that cells wider than their column are
+// truncated with an ellipsis.
+func TestRenderTableCellTruncation(t *testing.T) {
+ // renderTableCell is unexported; exercise it via RenderTableHeader.
+ cols := []TableColumn{{Title: "ABCDEFGHIJ", Width: 6}}
+ out := RenderTableHeader(cols)
+ // The title should be truncated to fit within width=6: "ABC..."
+ if !strings.Contains(out, "ABC...") {
+ t.Fatalf("expected truncated header ABC..., got: %q", out)
+ }
+}
+
+// TestRenderTableCellZeroWidth verifies that a zero-width column renders an
+// empty string without panicking.
+func TestRenderTableCellZeroWidth(t *testing.T) {
+ cols := []TableColumn{{Title: "X", Width: 0}}
+ out := RenderTableHeader(cols)
+ // Expect nothing meaningful — just no panic and no content.
+ _ = out
+}
+
+// TestSanitizeAndTruncateCellEmbeddedNewlines verifies that embedded newlines
+// and tabs in cell values are replaced by spaces.
+func TestSanitizeAndTruncateCellEmbeddedNewlines(t *testing.T) {
+ cols := []TableColumn{{Title: "A\nB\tC", Width: 10}}
+ out := RenderTableHeader(cols)
+ if strings.Contains(out, "\n") || strings.Contains(out, "\t") {
+ t.Fatalf("expected newlines and tabs stripped from cell, got: %q", out)
+ }
+}
+
+// TestPickerShortHelpReturnsThreeBindings verifies that PickerShortHelp from
+// the KeyMap returns three entries (Enter, Refresh, Esc).
+func TestPickerShortHelpReturnsThreeBindings(t *testing.T) {
+ keys := DefaultKeyMap()
+ bindings := keys.PickerShortHelp()
+ if len(bindings) != 3 {
+ t.Fatalf("PickerShortHelp len = %d, want 3", len(bindings))
+ }
+}