package dashboard
import (
"fmt"
"regexp"
"strconv"
"strings"
"testing"
coreflamegraph "ior/internal/flamegraph"
"ior/internal/statsengine"
common "ior/internal/tui/common"
"ior/internal/tui/eventstream"
"ior/internal/tui/messages"
tea "charm.land/bubbletea/v2"
)
var ansiEscapePattern = regexp.MustCompile(`\x1b\[[0-9;]*m`)
type fakeSnapshotSource struct {
snapshots int
snap *statsengine.Snapshot
}
func (f *fakeSnapshotSource) Snapshot() *statsengine.Snapshot {
f.snapshots++
return f.snap
}
type fakeResettableSnapshotSource struct {
resetCount int
snapCount int
snap *statsengine.Snapshot
}
func (f *fakeResettableSnapshotSource) Reset() {
f.resetCount++
}
func (f *fakeResettableSnapshotSource) Snapshot() *statsengine.Snapshot {
f.snapCount++
return f.snap
}
func stripANSIEscape(value string) string {
return ansiEscapePattern.ReplaceAllString(value, "")
}
func firstLineContaining(value, needle string) string {
for _, line := range strings.Split(value, "\n") {
if strings.Contains(line, needle) {
return line
}
}
return ""
}
func TestKeySwitchingChangesActiveTab(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'2'}[0], Text: string([]rune{'2'})})
model := next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected overview tab on key 2, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyTab})
model = next.(Model)
if model.activeTab != TabSyscalls {
t.Fatalf("expected next tab to be syscalls, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyTab, Mod: tea.ModShift})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected previous tab to be overview, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})})
model = next.(Model)
if model.activeTab != TabStream {
t.Fatalf("expected stream tab on key 7, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'1'}[0], Text: string([]rune{'1'})})
model = next.(Model)
if model.activeTab != TabFlame {
t.Fatalf("expected flame tab on key 1, got %v", model.activeTab)
}
}
func TestArrowAndViKeysDoNotCycleTabs(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabOverview
next, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
model := next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected right arrow not to change tabs, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'l'}[0], Text: string([]rune{'l'})})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected l not to change tabs, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected left arrow not to change tabs, got %v", model.activeTab)
}
next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'h'}[0], Text: string([]rune{'h'})})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected h not to change tabs, got %v", model.activeTab)
}
}
func TestSyscallsTabScrollsWithJK(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabSyscalls
snap := statsengine.NewSnapshot(nil, nil, nil, []statsengine.SyscallSnapshot{{Name: "read", Count: 1}, {Name: "write", Count: 1}}, nil, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
model := next.(Model)
if model.syscallsOffset != 1 {
t.Fatalf("expected offset 1 after j, got %d", model.syscallsOffset)
}
next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})})
model = next.(Model)
if model.syscallsOffset != 0 {
t.Fatalf("expected offset 0 after k, got %d", model.syscallsOffset)
}
}
func TestProcessesTabScrollsWithJK(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabProcesses
snap := statsengine.NewSnapshot(nil, nil, nil, nil, nil, []statsengine.ProcessSnapshot{{PID: 1}, {PID: 2}}, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
model := next.(Model)
if model.processesOffset != 1 {
t.Fatalf("expected processes offset 1 after j, got %d", model.processesOffset)
}
next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})})
model = next.(Model)
if model.processesOffset != 0 {
t.Fatalf("expected processes offset 0 after k, got %d", model.processesOffset)
}
}
func TestSyscallsTabSupportsHorizontalColumnNavigation(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabSyscalls
snap := statsengine.NewSnapshot(nil, nil, nil, []statsengine.SyscallSnapshot{{Name: "read", Count: 1}}, nil, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
next, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
model := next.(Model)
if model.syscallsCol != 1 {
t.Fatalf("expected syscalls selected column 1 after right, got %d", model.syscallsCol)
}
next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
model = next.(Model)
if model.syscallsCol != 0 {
t.Fatalf("expected syscalls selected column 0 after left, got %d", model.syscallsCol)
}
}
func TestFilesTabSupportsHorizontalColumnNavigation(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabFiles
snap := statsengine.NewSnapshot(nil, nil, nil, nil, []statsengine.FileSnapshot{{Path: "/a"}}, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
next, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
model := next.(Model)
if model.filesCol != 1 {
t.Fatalf("expected files selected column 1 after right, got %d", model.filesCol)
}
next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
model = next.(Model)
if model.filesCol != 0 {
t.Fatalf("expected files selected column 0 after left, got %d", model.filesCol)
}
}
func TestProcessesTabSupportsHorizontalColumnNavigation(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabProcesses
snap := statsengine.NewSnapshot(nil, nil, nil, nil, nil, []statsengine.ProcessSnapshot{{PID: 1, Comm: "alpha"}}, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
next, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
model := next.(Model)
if model.processesCol != 1 {
t.Fatalf("expected processes selected column 1 after right, got %d", model.processesCol)
}
next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
model = next.(Model)
if model.processesCol != 0 {
t.Fatalf("expected processes selected column 0 after left, got %d", model.processesCol)
}
}
func TestProcessesTabEnterEmitsGlobalFilterRequest(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabProcesses
snap := statsengine.NewSnapshot(nil, nil, nil, nil, nil, []statsengine.ProcessSnapshot{
{PID: 111, Comm: "alpha", Syscalls: 9},
{PID: 222, Comm: "beta", Syscalls: 4},
}, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
m.processesOffset = 1
next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyE
|