-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchat_handler_test.go
More file actions
207 lines (167 loc) · 6.5 KB
/
chat_handler_test.go
File metadata and controls
207 lines (167 loc) · 6.5 KB
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package devtui
import (
"fmt"
"strings"
"testing"
"time"
tea "github.com/charmbracelet/bubbletea"
"github.com/tinywasm/devtui/example"
)
// TestChatHandlerRealScenario tests the complete chat interaction flow
// focusing on handler behavior in different states, not DevTUI orchestration
func TestChatHandlerRealScenario(t *testing.T) {
t.Run("Chat handler behavior following DevTUI responsibility separation", func(t *testing.T) {
// Create chat handler with initial state (using the real handler from example)
chatHandler := &example.SimpleChatHandler{}
var contentDisplayed []string
chatHandler.SetLog(func(message ...any) {
if len(message) > 0 {
msg := fmt.Sprint(message[0])
contentDisplayed = append(contentDisplayed, msg)
}
})
// STATE 1: Initial content display (when DevTUI selects the field)
// Verify initial state
if chatHandler.WaitingForUser() {
t.Errorf("Initial state: should not be waiting for user")
}
// DevTUI calls Change("") when field is selected
chatHandler.Change("")
// Verify welcome content was shown
if len(contentDisplayed) == 0 {
t.Errorf("No content displayed in initial state")
}
welcomeFound := false
for _, content := range contentDisplayed {
if strings.Contains(content, "Welcome") {
welcomeFound = true
break
}
}
if !welcomeFound {
t.Errorf("Expected welcome content, got: %v", contentDisplayed)
}
// STATE 2: DevTUI transitions to input mode (this is DevTUI's responsibility)
contentDisplayed = []string{}
// DevTUI is responsible for managing the input activation
// The handler just needs to be ready when WaitingForUser() should return true
chatHandler.WaitingForUserFlag = true // This simulates DevTUI's state management
// Verify handler is now waiting for user
if !chatHandler.WaitingForUser() {
t.Errorf("After DevTUI activation: should be waiting for user")
}
// Label should reflect input mode (handler's responsibility)
if !strings.Contains(chatHandler.Label(), "Type message") {
t.Errorf("Expected input mode label, got: %s", chatHandler.Label())
}
// STATE 3: User sends message -> handler processes it
userMessage := "Hello, how are you?"
contentDisplayed = []string{}
chatHandler.Change(userMessage)
// Note: We cannot safely check handler state immediately after sending
// as the async operation may not have started yet. The handler's business logic
// manages its own state - DevTUI just displays what it reports.
// Race conditions would occur if we check WaitingForUser() or Label() here.
if chatHandler.Value() != "" {
t.Errorf("Handler should clear input after sending, got '%s'", chatHandler.Value())
}
// Verify handler sent appropriate progress messages
userMessageFound := false
for _, content := range contentDisplayed {
if strings.Contains(content, "U: Hello, how are you?") {
userMessageFound = true
break
}
}
if !userMessageFound {
t.Errorf("Expected user message in progress, got: %v", contentDisplayed)
}
// STATE 4: AI response completion (handler's async business logic)
// Wait for async AI response (handler's responsibility)
maxWait := 50
for i := 0; i < maxWait; i++ {
// Use Label() method to check if still processing instead of direct field access
label := chatHandler.Label()
if !strings.Contains(label, "Processing") {
break // Processing completed
}
time.Sleep(100 * time.Millisecond)
}
// Verify handler managed its async operation correctly
if !chatHandler.WaitingForUser() {
t.Errorf("After AI response: handler should be waiting for user again")
}
// Use Label() method to verify processing is complete
finalLabel := chatHandler.Label()
if strings.Contains(finalLabel, "Processing") {
t.Errorf("After AI response: handler should not be processing, label: %s", finalLabel)
}
// Note: We cannot safely check message count after async operations
// as it would create race conditions. The handler manages its own state
// and DevTUI respects that encapsulation.
// STATE 5: DevTUI re-selects field -> handler shows history
// Simulate DevTUI deactivating input mode (field loses focus, regains focus)
chatHandler.WaitingForUserFlag = false
contentDisplayed = []string{}
// DevTUI calls Change("") when field is re-selected
chatHandler.Change("")
// Verify conversation history is shown (handler's business logic)
historyFound := false
for _, content := range contentDisplayed {
if strings.Contains(content, "U: Hello, how are you?") || strings.Contains(content, "A: Response:") {
historyFound = true
break
}
}
if !historyFound {
t.Errorf("Expected conversation history, got: %v", contentDisplayed)
}
// STATE 6: Test empty input while in input mode (edge case handling)
chatHandler.WaitingForUserFlag = true // Back to input mode
contentDisplayed = []string{}
// User presses Enter without typing anything
chatHandler.Change("")
// Handler should guide the user (handler's responsibility for UX)
guidanceFound := false
for _, content := range contentDisplayed {
if strings.Contains(content, "Type message") {
guidanceFound = true
break
}
}
if !guidanceFound {
t.Errorf("Expected user guidance message, got: %v", contentDisplayed)
}
})
t.Run("Test chat UI rendering and edit mode transitions", func(t *testing.T) {
tui := DefaultTUIForTest()
chatHandler := &example.SimpleChatHandler{}
chatTab := tui.NewTabSection("Chat", "AI Chat Assistant")
tui.AddHandler(chatHandler, "", chatTab)
tui.viewport.Width = 80
tui.viewport.Height = 24
chatTabIndex := len(tui.TabSections) - 1
tui.activeTab = chatTabIndex
chatTabSection := chatTab.(*tabSection)
chatField := chatTabSection.FieldHandlers[0]
// Phase 1: Before any interaction
_ = tui.ContentView()
// Phase 2: Enter to activate input mode
tui.handleKeyboard(tea.KeyMsg{Type: tea.KeyEnter})
_ = tui.ContentView()
// Should now be in edit mode (check if tempEditValue is being used)
if chatField.tempEditValue == "" && !chatHandler.WaitingForUser() {
// This is expected - the handler manages its own state
}
// Phase 3: Type message
tui.handleKeyboard(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("hello")})
_ = tui.ContentView()
// Phase 4: Send message
tui.handleKeyboard(tea.KeyMsg{Type: tea.KeyEnter})
_ = tui.ContentView()
// Should no longer be in edit mode (processing message)
if chatHandler.WaitingForUser() && !chatHandler.IsProcessing {
// This is fine - handler completed processing and is ready for next input
}
})
}