1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
package pidpicker
import (
"strings"
"testing"
"ior/internal/tui/messages"
tea "charm.land/bubbletea/v2"
)
func TestApplyFilterByPIDCommAndCmdline(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
m.processes = []ProcessInfo{
{Pid: 100, Comm: "bash", Cmdline: "bash -l"},
{Pid: 200, Comm: "sshd", Cmdline: "/usr/sbin/sshd -D"},
}
m.input.SetValue("200")
m.applyFilter()
if len(m.filtered) != 1 || m.filtered[0].Pid != 200 {
t.Fatalf("expected pid filter to keep only 200, got %+v", m.filtered)
}
m.input.SetValue("BASH")
m.applyFilter()
if len(m.filtered) != 1 || m.filtered[0].Pid != 100 {
t.Fatalf("expected comm filter to keep only 100, got %+v", m.filtered)
}
m.input.SetValue("/usr/sbin")
m.applyFilter()
if len(m.filtered) != 1 || m.filtered[0].Pid != 200 {
t.Fatalf("expected cmdline filter to keep only 200, got %+v", m.filtered)
}
}
func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
m.processes = []ProcessInfo{{Pid: 7, Comm: "vim"}, {Pid: 9, Comm: "top"}}
m.applyFilter()
modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelAny
msgAny := cmdAny()
pidAny, ok := msgAny.(messages.PidSelectedMsg)
if !ok {
t.Fatalf("expected PidSelectedMsg for all-pids selection, got %T", msgAny)
}
if pidAny.Pid != 0 {
t.Fatalf("expected all-pids to emit pid 0, got %d", pidAny.Pid)
}
m.selectedIndex = 2
modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelOne
msgOne := cmdOne()
pidOne, ok := msgOne.(messages.PidSelectedMsg)
if !ok {
t.Fatalf("expected PidSelectedMsg for concrete selection, got %T", msgOne)
}
if pidOne.Pid != 9 {
t.Fatalf("expected selected pid 9, got %d", pidOne.Pid)
}
}
func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) {
m := NewTIDWithKeys(42, DefaultKeyMap())
m.processes = []ProcessInfo{
{Pid: 7001, ParentPID: 42, Comm: "main"},
{Pid: 7002, ParentPID: 42, Comm: "worker"},
}
m.applyFilter()
modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelAny
msgAny := cmdAny()
tidAny, ok := msgAny.(messages.TidSelectedMsg)
if !ok {
t.Fatalf("expected TidSelectedMsg for all-tids selection, got %T", msgAny)
}
if tidAny.Tid != 0 {
t.Fatalf("expected all-tids to emit tid 0, got %d", tidAny.Tid)
}
if tidAny.Pid != 0 {
t.Fatalf("expected all-tids to emit pid 0, got %d", tidAny.Pid)
}
m.selectedIndex = 2
modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelOne
msgOne := cmdOne()
tidOne, ok := msgOne.(messages.TidSelectedMsg)
if !ok {
t.Fatalf("expected TidSelectedMsg for concrete selection, got %T", msgOne)
}
if tidOne.Tid != 7002 {
t.Fatalf("expected selected tid 7002, got %d", tidOne.Tid)
}
if tidOne.Pid != 42 {
t.Fatalf("expected selected pid 42, got %d", tidOne.Pid)
}
}
func TestEscQuitsAndRefreshTriggersScan(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
_, escCmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
if escCmd == nil {
t.Fatalf("expected esc to return quit cmd")
}
if msg := escCmd(); msg != (tea.QuitMsg{}) {
t.Fatalf("expected quit msg from esc, got %T", msg)
}
_, refreshCmd := m.Update(tea.KeyPressMsg{Code: rune('r'), Text: "r", Mod: tea.ModCtrl})
if refreshCmd == nil {
t.Fatalf("expected refresh cmd")
}
if _, ok := refreshCmd().(processesLoadedMsg); !ok {
t.Fatalf("expected refresh to emit processesLoadedMsg")
}
}
func TestRuneRDoesNotTriggerRefreshWhileFilterFocused(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'r'}[0], Text: string([]rune{'r'})})
if cmd == nil {
t.Fatalf("expected textinput update cmd")
}
updated := next.(Model)
if got := updated.input.Value(); got != "r" {
t.Fatalf("expected filter input to contain typed r, got %q", got)
}
}
func TestRenderRowsKeepsSelectionVisible(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
m.height = 8 // visible rows == 2
m.processes = []ProcessInfo{
{Pid: 1, Comm: "p1"},
{Pid: 2, Comm: "p2"},
{Pid: 3, Comm: "p3"},
{Pid: 4, Comm: "p4"},
}
m.applyFilter()
m.selectedIndex = 4
rows := m.renderRows()
if !strings.Contains(rows, "> 4 p4") {
t.Fatalf("expected selected row to remain visible, got:\n%s", rows)
}
}
func TestWindowSizeDoesNotCapInputWidthOnWideTerminals(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
next, _ := m.Update(tea.WindowSizeMsg{Width: 160, Height: 40})
updated := next.(Model)
if got, want := updated.input.Width(), 144; got != want {
t.Fatalf("expected input width %d for 160-col terminal, got %d", want, got)
}
}
|