1package editor
2
3import (
4 "testing"
5
6 tea "github.com/charmbracelet/bubbletea/v2"
7 "github.com/charmbracelet/crush/internal/app"
8 "github.com/charmbracelet/crush/internal/fsext"
9 "github.com/charmbracelet/crush/internal/tui/components/completions"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12)
13
14// executeBatchCommands executes all commands in a BatchMsg and returns the resulting messages
15func executeBatchCommands(batchMsg tea.BatchMsg) []tea.Msg {
16 var messages []tea.Msg
17 for _, cmd := range batchMsg {
18 if cmd != nil {
19 msg := cmd()
20 messages = append(messages, msg)
21 }
22 }
23 return messages
24}
25
26// assertBatchContainsMessage checks if a BatchMsg contains a message of the specified type
27func assertBatchContainsMessage(t *testing.T, batchMsg tea.BatchMsg, expectedType interface{}) bool {
28 t.Helper()
29 messages := executeBatchCommands(batchMsg)
30
31 for _, msg := range messages {
32 switch expectedType.(type) {
33 case completions.OpenCompletionsMsg:
34 if _, ok := msg.(completions.OpenCompletionsMsg); ok {
35 return true
36 }
37 }
38 }
39 return false
40}
41
42// assertBatchContainsExactMessage checks if a BatchMsg contains a message with exact field values
43func assertBatchContainsExactMessage(t *testing.T, batchMsg tea.BatchMsg, expected interface{}) bool {
44 t.Helper()
45 messages := executeBatchCommands(batchMsg)
46
47 for _, msg := range messages {
48 switch expected := expected.(type) {
49 case completions.OpenCompletionsMsg:
50 if actual, ok := msg.(completions.OpenCompletionsMsg); ok {
51 // If no specific completions are expected, just match the type
52 if len(expected.Completions) == 0 {
53 return true
54 }
55 // Compare completions if specified
56 if len(actual.Completions) == len(expected.Completions) {
57 // For simplicity, just check the count for now
58 // A more complete implementation would compare each completion
59 return true
60 }
61 }
62 default:
63 // Fallback to type checking only
64 if _, ok := msg.(completions.OpenCompletionsMsg); ok {
65 return true
66 }
67 }
68 }
69 return false
70}
71
72// assertBatchContainsOpenCompletionsMsg checks if a BatchMsg contains an OpenCompletionsMsg
73// with the expected completions. If expectedCompletions is nil, only the message type is checked.
74func assertBatchContainsOpenCompletionsMsg(t *testing.T, batchMsg tea.BatchMsg, expectedCompletions []string) (*completions.OpenCompletionsMsg, bool) {
75 t.Helper()
76 messages := executeBatchCommands(batchMsg)
77
78 for _, msg := range messages {
79 if actual, ok := msg.(completions.OpenCompletionsMsg); ok {
80 if expectedCompletions == nil {
81 return &actual, true
82 }
83
84 // Convert actual completions to string titles for comparison
85 actualTitles := make([]string, len(actual.Completions))
86 for i, comp := range actual.Completions {
87 actualTitles[i] = comp.Title
88 }
89
90 // Check if we have the same number of completions
91 if len(actualTitles) != len(expectedCompletions) {
92 continue
93 }
94
95 // For now, just check that we have the same count
96 // A more sophisticated implementation would check the actual values
97 return &actual, true
98 }
99 }
100 return nil, false
101}
102
103func mockDirLister(paths []string) fsext.DirectoryListerResolver {
104 return func() fsext.DirectoryLister {
105 return func(initialPath string, ignorePatterns []string, limit int) ([]string, bool, error) {
106 return paths, false, nil
107 }
108 }
109}
110
111func TestEditorTypingForwardSlashOpensCompletions(t *testing.T) {
112 testEditor := newEditor(&app.App{}, mockDirLister([]string{}))
113 require.NotNil(t, testEditor)
114
115 // Simulate pressing the '/' key
116 keyPressMsg := tea.KeyPressMsg{
117 Text: "/",
118 }
119
120 m, cmds := testEditor.Update(keyPressMsg)
121 testEditor = m.(*editorCmp)
122 cmds()
123
124 assert.True(t, testEditor.isCompletionsOpen)
125 assert.Equal(t, "/", testEditor.textarea.Value())
126}
127
128func TestEditorAutocompletionWithEmptyInput(t *testing.T) {
129 testEditor := newEditor(&app.App{}, mockDirLister([]string{}))
130 require.NotNil(t, testEditor)
131
132 // First, give the editor focus
133 testEditor.Focus()
134
135 // Simulate pressing the '/' key when the editor is empty
136 // This should trigger the completions to open
137 keyPressMsg := tea.KeyPressMsg{
138 Text: "/",
139 }
140
141 m, cmds := testEditor.Update(keyPressMsg)
142 testEditor = m.(*editorCmp)
143 cmds()
144
145 // completions menu is open
146 assert.True(t, testEditor.isCompletionsOpen)
147 assert.Equal(t, "/", testEditor.textarea.Value())
148
149 // the query is empty (since we just opened it)
150 assert.Equal(t, "", testEditor.currentQuery)
151}
152
153func TestEditorAutoCompletion_WIP(t *testing.T) {
154 testEditor := newEditor(&app.App{}, mockDirLister([]string{"file1.txt", "file2.txt"}))
155 require.NotNil(t, testEditor)
156
157 // open the completions menu by simulating a '/' key press
158 testEditor.Focus()
159 keyPressMsg := tea.KeyPressMsg{
160 Text: "/",
161 }
162
163 m, msg := simulateUpdate(testEditor, keyPressMsg)
164 testEditor = m.(*editorCmp)
165
166 var openCompletionsMsg *completions.OpenCompletionsMsg
167 if batchMsg, ok := msg.(tea.BatchMsg); ok {
168 // Use our enhanced helper to check for OpenCompletionsMsg with specific completions
169 var found bool
170 openCompletionsMsg, found = assertBatchContainsOpenCompletionsMsg(t, batchMsg, []string{"file1.txt", "file2.txt"})
171 assert.True(t, found, "Expected to find OpenCompletionsMsg with specific completions in batched messages")
172 } else {
173 t.Fatal("Expected BatchMsg from cmds()")
174 }
175
176 assert.NotNil(t, openCompletionsMsg)
177}
178
179type noopEvent struct{}
180
181type updater interface {
182 Update(msg tea.Msg) (tea.Model, tea.Cmd)
183}
184
185func simulateUpdate(up updater, msg tea.Msg) (updater, tea.Msg) {
186 up, cmd := up.Update(msg)
187 if cmd != nil {
188 return up, cmd()
189 }
190 return up, noopEvent{}
191}
192
193/*
194func TestEditorAutocompletion_StartFilteringOpens(t *testing.T) {
195 testEditor := newEditor(&app.App{}, mockDirLister([]string{"file1.txt", "file2.txt"}))
196 require.NotNil(t, testEditor)
197
198 // open the completions menu by simulating a '/' key press
199 testEditor.Focus()
200 keyPressMsg := tea.KeyPressMsg{
201 Text: "/",
202 }
203
204 m, cmds := testEditor.Update(keyPressMsg)
205 testEditor = m.(*editorCmp)
206
207 msg := cmds()
208 var openCompletionsMsg *completions.OpenCompletionsMsg
209 if batchMsg, ok := msg.(tea.BatchMsg); ok {
210 // Use our enhanced helper to check for OpenCompletionsMsg with specific completions
211 var found bool
212 openCompletionsMsg, found = assertBatchContainsOpenCompletionsMsg(t, batchMsg, []string{"file1.txt", "file2.txt"})
213 assert.True(t, found, "Expected to find OpenCompletionsMsg with specific completions in batched messages")
214 } else {
215 t.Fatal("Expected BatchMsg from cmds()")
216 }
217
218 assert.NotNil(t, openCompletionsMsg)
219 m, cmds = testEditor.Update(openCompletionsMsg)
220
221 msg = cmds()
222 testEditor = m.(*editorCmp)
223
224 if batchMsg, ok := msg.(tea.BatchMsg); ok {
225 assertBatchContainsExactMessage(t, batchMsg, completions.CompletionsOpenedMsg{})
226 } else {
227 t.Fatal("Expected BatchMsg from cmds()")
228 }
229
230 // Verify completions menu is open
231 assert.True(t, testEditor.isCompletionsOpen)
232 assert.Equal(t, "/", testEditor.textarea.Value())
233
234 // Now simulate typing a query to filter the completions
235 // Set the text to "/tes" and then simulate typing "t" to make "/test"
236 testEditor.textarea.SetValue("/tes")
237
238 // Simulate typing a key that would trigger filtering
239 keyPressMsg = tea.KeyPressMsg{
240 Text: "t",
241 }
242
243 m, cmds = testEditor.Update(keyPressMsg)
244 msg = cmds()
245 testEditor = m.(*editorCmp)
246
247 // Verify the editor still has completions open
248 assert.True(t, testEditor.isCompletionsOpen)
249
250 // The currentQuery should be updated based on what we typed
251 // In this case, it would be "test" (the word after the initial '/')
252 // Note: The actual filtering is handled by the completions component,
253 // so we're just verifying the editor's state is correct
254 assert.Equal(t, "test", testEditor.currentQuery)
255
256 keyPressMsg = tea.KeyPressMsg{
257 Code: tea.KeyEnter,
258 }
259
260 m, cmds = testEditor.Update(keyPressMsg)
261 msg = cmds()
262 testEditor = m.(*editorCmp)
263
264 if batchMsg, ok := msg.(tea.BatchMsg); ok {
265 assertBatchContainsExactMessage(t, batchMsg, completions.CompletionsOpenedMsg{})
266 } else {
267 t.Fatal("Expected BatchMsg from cmds()")
268 }
269
270 m, cmds = testEditor.Update(msg)
271 msg = cmds()
272 testEditor = m.(*editorCmp)
273 // Verify the editor still has completions open
274 assert.True(t, testEditor.isCompletionsOpen)
275}
276*/
277
278func TestEditorAutocompletion_SelectionOfNormalPathAddsToTextAreaClosesCompletion(t *testing.T) {
279 testEditor := newEditor(&app.App{}, mockDirLister([]string{"example_test.go", "file1.txt", "file2.txt"}))
280 require.NotNil(t, testEditor)
281
282 // open the completions menu by simulating a '/' key press
283 testEditor.Focus()
284 keyPressMsg := tea.KeyPressMsg{
285 Text: "/",
286 }
287
288 m, cmds := testEditor.Update(keyPressMsg)
289 testEditor = m.(*editorCmp)
290
291 msg := cmds()
292 assert.NotNil(t, msg)
293 m, cmds = testEditor.Update(msg)
294
295 // Now simulate typing a query to filter the completions
296 // Set the text to "/tes" and then simulate typing "t" to make "/test"
297 testEditor.textarea.SetValue("/tes")
298
299 // Simulate typing a key that would trigger filtering
300 keyPressMsg = tea.KeyPressMsg{
301 Text: "t",
302 }
303
304 m, cmds = testEditor.Update(keyPressMsg)
305 testEditor = m.(*editorCmp)
306
307 // The currentQuery should be updated based on what we typed
308 // In this case, it would be "test" (the word after the initial '/')
309 // Note: The actual filtering is handled by the completions component,
310 // so we're just verifying the editor's state is correct
311 assert.Equal(t, "test", testEditor.currentQuery)
312}
313
314// TestHelperFunctions demonstrates how to use the batch message helpers
315func TestHelperFunctions(t *testing.T) {
316 testEditor := newEditor(&app.App{}, mockDirLister([]string{"file1.txt", "file2.txt"}))
317 require.NotNil(t, testEditor)
318
319 // Simulate pressing the '/' key
320 testEditor.Focus()
321 keyPressMsg := tea.KeyPressMsg{
322 Text: "/",
323 }
324
325 m, cmds := testEditor.Update(keyPressMsg)
326 testEditor = m.(*editorCmp)
327
328 // Execute the command and check if it returns a BatchMsg
329 msg := cmds()
330 if batchMsg, ok := msg.(tea.BatchMsg); ok {
331 // Test our helper functions
332 found := assertBatchContainsMessage(t, batchMsg, completions.OpenCompletionsMsg{})
333 assert.True(t, found, "Expected to find OpenCompletionsMsg in batched messages")
334
335 // Test exact message helper
336 foundExact := assertBatchContainsExactMessage(t, batchMsg, completions.OpenCompletionsMsg{})
337 assert.True(t, foundExact, "Expected to find exact OpenCompletionsMsg in batched messages")
338
339 // Test specific completions helper
340 msg, foundSpecific := assertBatchContainsOpenCompletionsMsg(t, batchMsg, nil) // Just check type
341 assert.NotNil(t, msg)
342 assert.True(t, foundSpecific, "Expected to find OpenCompletionsMsg in batched messages")
343 } else {
344 t.Fatal("Expected BatchMsg from cmds()")
345 }
346}