summaryrefslogtreecommitdiff
path: root/internal/tui/eventstream/model_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-25 08:38:56 +0200
committerPaul Buetow <paul@buetow.org>2026-02-25 08:38:56 +0200
commit1a6e71ac31353167ec4c614d45e8e06de411a8f9 (patch)
treeb42c23fd58600fef0c7780140429abb6d997faaa /internal/tui/eventstream/model_test.go
parent87462508e417816c3f1ae832e02bda19c1642ec9 (diff)
Add event stream model with filtering and scroll state
Diffstat (limited to 'internal/tui/eventstream/model_test.go')
-rw-r--r--internal/tui/eventstream/model_test.go124
1 files changed, 124 insertions, 0 deletions
diff --git a/internal/tui/eventstream/model_test.go b/internal/tui/eventstream/model_test.go
new file mode 100644
index 0000000..69369d8
--- /dev/null
+++ b/internal/tui/eventstream/model_test.go
@@ -0,0 +1,124 @@
+package eventstream
+
+import "testing"
+
+func pushEvents(rb *RingBuffer, count int) {
+ for i := 0; i < count; i++ {
+ rb.Push(StreamEvent{
+ Seq: uint64(i),
+ Syscall: map[bool]string{true: "read", false: "write"}[i%2 == 0],
+ Comm: "proc",
+ PID: 100,
+ TID: uint32(100 + i),
+ DurationNs: uint64(1000 + i),
+ GapNs: uint64(10 + i),
+ Bytes: uint64(64 + i),
+ FileName: "/tmp/file",
+ RetVal: int64(i),
+ IsError: i%3 == 0,
+ })
+ }
+}
+
+func TestModelPauseFreezesDisplay(t *testing.T) {
+ rb := NewRingBuffer()
+ m := NewModel(rb)
+ m.height = 20
+ pushEvents(rb, 3)
+ m.Refresh()
+ if len(m.filtered) != 3 {
+ t.Fatalf("filtered=%d, want 3", len(m.filtered))
+ }
+
+ if !m.HandleKey("space") {
+ t.Fatalf("space should be handled")
+ }
+ pushEvents(rb, 2)
+ m.Refresh()
+ if len(m.filtered) != 3 {
+ t.Fatalf("paused refresh should not change filtered len, got %d", len(m.filtered))
+ }
+}
+
+func TestModelScrollClamp(t *testing.T) {
+ rb := NewRingBuffer()
+ m := NewModel(rb)
+ m.height = 10
+ pushEvents(rb, 30)
+ m.Refresh()
+
+ for i := 0; i < 100; i++ {
+ m.HandleKey("j")
+ }
+ if m.scrollOffset > m.maxScrollOffset() {
+ t.Fatalf("scrollOffset=%d exceeds max=%d", m.scrollOffset, m.maxScrollOffset())
+ }
+
+ for i := 0; i < 100; i++ {
+ m.HandleKey("k")
+ }
+ if m.scrollOffset != 0 {
+ t.Fatalf("scrollOffset=%d, want 0", m.scrollOffset)
+ }
+}
+
+func TestModelFilterReducesVisibleRows(t *testing.T) {
+ rb := NewRingBuffer()
+ m := NewModel(rb)
+ m.height = 20
+ pushEvents(rb, 10)
+ m.Refresh()
+
+ m.setFilterForTest(Filter{Syscall: &StringFilter{Pattern: "read"}})
+ m.applyFilter()
+
+ if len(m.filtered) >= len(m.allEvents) {
+ t.Fatalf("expected filtered rows to be less than all rows: filtered=%d all=%d", len(m.filtered), len(m.allEvents))
+ }
+}
+
+func TestModelAutoScrollBehavior(t *testing.T) {
+ rb := NewRingBuffer()
+ m := NewModel(rb)
+ m.height = 10
+ pushEvents(rb, 12)
+ m.Refresh()
+
+ if m.scrollOffset != m.maxScrollOffset() {
+ t.Fatalf("expected auto-scroll at bottom, got offset=%d max=%d", m.scrollOffset, m.maxScrollOffset())
+ }
+
+ m.HandleKey("k")
+ prev := m.scrollOffset
+ pushEvents(rb, 3)
+ m.Refresh()
+ if m.scrollOffset != prev {
+ t.Fatalf("when autoScroll=false, offset should stay %d, got %d", prev, m.scrollOffset)
+ }
+
+ m.HandleKey("G")
+ if m.scrollOffset != m.maxScrollOffset() {
+ t.Fatalf("G should jump to tail")
+ }
+}
+
+func TestModelHandleKeyRouting(t *testing.T) {
+ rb := NewRingBuffer()
+ m := NewModel(rb)
+
+ if m.HandleKey("x") {
+ t.Fatalf("unknown key should not be handled")
+ }
+ if !m.HandleKey("f") {
+ t.Fatalf("f should be handled")
+ }
+ if !m.filterModal.Visible() {
+ t.Fatalf("modal should be visible after f")
+ }
+ if !m.HandleKey("esc") {
+ t.Fatalf("esc should route to modal")
+ }
+ if m.filterModal.Visible() {
+ t.Fatalf("modal should close on esc")
+ }
+}