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) 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 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 true
98 }
99 }
100 return 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 // Verify completions menu is open
146 assert.True(t, testEditor.isCompletionsOpen)
147 assert.Equal(t, "/", testEditor.textarea.Value())
148
149 // Verify the query is empty (since we just opened it)
150 assert.Equal(t, "", testEditor.currentQuery)
151}
152
153func TestEditorAutocompletionFilteringOpens(t *testing.T) {
154 testEditor := newEditor(&app.App{}, mockDirLister([]string{"file1.txt", "file2.txt"}))
155 require.NotNil(t, testEditor)
156
157 // First, open the completions menu by simulating a '/' key press
158 testEditor.Focus()
159 keyPressMsg := tea.KeyPressMsg{
160 Text: "/",
161 }
162
163 m, cmds := testEditor.Update(keyPressMsg)
164 testEditor = m.(*editorCmp)
165
166 // Execute the command and check if it returns a BatchMsg
167 msg := cmds()
168 if batchMsg, ok := msg.(tea.BatchMsg); ok {
169 // Use our enhanced helper to check for OpenCompletionsMsg with specific completions
170 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 // Verify completions menu is open
177 assert.True(t, testEditor.isCompletionsOpen)
178 assert.Equal(t, "/", testEditor.textarea.Value())
179
180 // Now simulate typing a query to filter the completions
181 // Set the text to "/tes" and then simulate typing "t" to make "/test"
182 testEditor.textarea.SetValue("/tes")
183
184 // Simulate typing a key that would trigger filtering
185 keyPressMsg = tea.KeyPressMsg{
186 Text: "t",
187 }
188
189 m, cmds = testEditor.Update(keyPressMsg)
190 testEditor = m.(*editorCmp)
191 cmds()
192
193 // Verify the editor still has completions open
194 assert.True(t, testEditor.isCompletionsOpen)
195
196 // The currentQuery should be updated based on what we typed
197 // In this case, it would be "test" (the word after the initial '/')
198 // Note: The actual filtering is handled by the completions component,
199 // so we're just verifying the editor's state is correct
200 assert.Equal(t, "test", testEditor.currentQuery)
201}
202
203// TestHelperFunctions demonstrates how to use the batch message helpers
204func TestHelperFunctions(t *testing.T) {
205 testEditor := newEditor(&app.App{}, mockDirLister([]string{"file1.txt", "file2.txt"}))
206 require.NotNil(t, testEditor)
207
208 // Simulate pressing the '/' key
209 testEditor.Focus()
210 keyPressMsg := tea.KeyPressMsg{
211 Text: "/",
212 }
213
214 m, cmds := testEditor.Update(keyPressMsg)
215 testEditor = m.(*editorCmp)
216
217 // Execute the command and check if it returns a BatchMsg
218 msg := cmds()
219 if batchMsg, ok := msg.(tea.BatchMsg); ok {
220 // Test our helper functions
221 found := assertBatchContainsMessage(t, batchMsg, completions.OpenCompletionsMsg{})
222 assert.True(t, found, "Expected to find OpenCompletionsMsg in batched messages")
223
224 // Test exact message helper
225 foundExact := assertBatchContainsExactMessage(t, batchMsg, completions.OpenCompletionsMsg{})
226 assert.True(t, foundExact, "Expected to find exact OpenCompletionsMsg in batched messages")
227
228 // Test specific completions helper
229 foundSpecific := assertBatchContainsOpenCompletionsMsg(t, batchMsg, nil) // Just check type
230 assert.True(t, foundSpecific, "Expected to find OpenCompletionsMsg in batched messages")
231 } else {
232 t.Fatal("Expected BatchMsg from cmds()")
233 }
234}