diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-13 14:28:37 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-13 14:28:37 +0300 |
| commit | 27b94f917064948fa33141309a3f08deb40ffde2 (patch) | |
| tree | 0f1c63eba01da1cc89fbbedcfe71cdcb55b06cb0 /internal/tui/common | |
| parent | 140d6c0fe472f112170022b9831dfe700698f382 (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.go | 209 |
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)) + } +} |
