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 TestStreamViewportUsesSharedChromeCalculator(t *testing.T) {
wantWidth, wantHeight := common.EffectiveViewport(120, 40)
wantHeight -= streamChromeRows
width, height := streamViewport(120, 40)
if width != wantWidth || height != wantHeight {
t.Fatalf("streamViewport() = %dx%d, want %dx%d", width, height, wantWidth, wantHeight)
}
}
func TestFlameViewportClampsHeightWithExpandedHelp(t *testing.T) {
wantWidth, _ := common.EffectiveViewport(80, 2)
width, height := flameViewport(80, 2, true)
if width != wantWidth || height != 1 {
t.Fatalf("flameViewport() = %dx%d, want %dx%d", width, height, wantWidth, 1)
}
}
func TestSnapshotOrZeroReturnsZeroSnapshotWhenLatestMissing(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
snap := m.snapshotOrZero()
if snap.SyscallsCount() != 0 || snap.FilesCount() != 0 || snap.ProcessesCount() != 0 {
t.Fatalf("snapshotOrZero() should return an empty snapshot when latest is nil, got counts %d/%d/%d", snap.SyscallsCount(), snap.FilesCount(), snap.ProcessesCount())
}
}
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
|