@@ -3,6 +3,7 @@ import ChatTextArea from "../ChatTextArea"
3
3
import { useExtensionState } from "../../../context/ExtensionStateContext"
4
4
import { vscode } from "../../../utils/vscode"
5
5
import { defaultModeSlug } from "../../../../../src/shared/modes"
6
+ import * as pathMentions from "../../../utils/path-mentions"
6
7
7
8
// Mock modules
8
9
jest . mock ( "../../../utils/vscode" , ( ) => ( {
@@ -12,9 +13,20 @@ jest.mock("../../../utils/vscode", () => ({
12
13
} ) )
13
14
jest . mock ( "../../../components/common/CodeBlock" )
14
15
jest . mock ( "../../../components/common/MarkdownBlock" )
16
+ jest . mock ( "../../../utils/path-mentions" , ( ) => ( {
17
+ convertToMentionPath : jest . fn ( ( path , cwd ) => {
18
+ // Simple mock implementation that mimics the real function's behavior
19
+ if ( cwd && path . toLowerCase ( ) . startsWith ( cwd . toLowerCase ( ) ) ) {
20
+ const relativePath = path . substring ( cwd . length )
21
+ return "@" + ( relativePath . startsWith ( "/" ) ? relativePath : "/" + relativePath )
22
+ }
23
+ return path
24
+ } ) ,
25
+ } ) )
15
26
16
27
// Get the mocked postMessage function
17
28
const mockPostMessage = vscode . postMessage as jest . Mock
29
+ const mockConvertToMentionPath = pathMentions . convertToMentionPath as jest . Mock
18
30
19
31
// Mock ExtensionStateContext
20
32
jest . mock ( "../../../context/ExtensionStateContext" )
@@ -160,4 +172,230 @@ describe("ChatTextArea", () => {
160
172
expect ( setInputValue ) . toHaveBeenCalledWith ( "Enhanced test prompt" )
161
173
} )
162
174
} )
175
+
176
+ describe ( "multi-file drag and drop" , ( ) => {
177
+ const mockCwd = "/Users/test/project"
178
+
179
+ beforeEach ( ( ) => {
180
+ jest . clearAllMocks ( )
181
+ ; ( useExtensionState as jest . Mock ) . mockReturnValue ( {
182
+ filePaths : [ ] ,
183
+ openedTabs : [ ] ,
184
+ cwd : mockCwd ,
185
+ } )
186
+ mockConvertToMentionPath . mockClear ( )
187
+ } )
188
+
189
+ it ( "should process multiple file paths separated by newlines" , ( ) => {
190
+ const setInputValue = jest . fn ( )
191
+
192
+ const { container } = render (
193
+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "Initial text" /> ,
194
+ )
195
+
196
+ // Create a mock dataTransfer object with text data containing multiple file paths
197
+ const dataTransfer = {
198
+ getData : jest . fn ( ) . mockReturnValue ( "/Users/test/project/file1.js\n/Users/test/project/file2.js" ) ,
199
+ files : [ ] ,
200
+ }
201
+
202
+ // Simulate drop event
203
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
204
+ dataTransfer,
205
+ preventDefault : jest . fn ( ) ,
206
+ } )
207
+
208
+ // Verify convertToMentionPath was called for each file path
209
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledTimes ( 2 )
210
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( "/Users/test/project/file1.js" , mockCwd )
211
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( "/Users/test/project/file2.js" , mockCwd )
212
+
213
+ // Verify setInputValue was called with the correct value
214
+ // The mock implementation of convertToMentionPath will convert the paths to @/file1.js and @/file2.js
215
+ expect ( setInputValue ) . toHaveBeenCalledWith ( "@/file1.js @/file2.js Initial text" )
216
+ } )
217
+
218
+ it ( "should filter out empty lines in the dragged text" , ( ) => {
219
+ const setInputValue = jest . fn ( )
220
+
221
+ const { container } = render (
222
+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "Initial text" /> ,
223
+ )
224
+
225
+ // Create a mock dataTransfer object with text data containing empty lines
226
+ const dataTransfer = {
227
+ getData : jest . fn ( ) . mockReturnValue ( "/Users/test/project/file1.js\n\n/Users/test/project/file2.js\n\n" ) ,
228
+ files : [ ] ,
229
+ }
230
+
231
+ // Simulate drop event
232
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
233
+ dataTransfer,
234
+ preventDefault : jest . fn ( ) ,
235
+ } )
236
+
237
+ // Verify convertToMentionPath was called only for non-empty lines
238
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledTimes ( 2 )
239
+
240
+ // Verify setInputValue was called with the correct value
241
+ expect ( setInputValue ) . toHaveBeenCalledWith ( "@/file1.js @/file2.js Initial text" )
242
+ } )
243
+
244
+ it ( "should correctly update cursor position after adding multiple mentions" , ( ) => {
245
+ const setInputValue = jest . fn ( )
246
+ const initialCursorPosition = 5
247
+
248
+ const { container } = render (
249
+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "Hello world" /> ,
250
+ )
251
+
252
+ // Set the cursor position manually
253
+ const textArea = container . querySelector ( "textarea" )
254
+ if ( textArea ) {
255
+ textArea . selectionStart = initialCursorPosition
256
+ textArea . selectionEnd = initialCursorPosition
257
+ }
258
+
259
+ // Create a mock dataTransfer object with text data
260
+ const dataTransfer = {
261
+ getData : jest . fn ( ) . mockReturnValue ( "/Users/test/project/file1.js\n/Users/test/project/file2.js" ) ,
262
+ files : [ ] ,
263
+ }
264
+
265
+ // Simulate drop event
266
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
267
+ dataTransfer,
268
+ preventDefault : jest . fn ( ) ,
269
+ } )
270
+
271
+ // The cursor position should be updated based on the implementation in the component
272
+ expect ( setInputValue ) . toHaveBeenCalledWith ( "@/file1.js @/file2.js Hello world" )
273
+ } )
274
+
275
+ it ( "should handle very long file paths correctly" , ( ) => {
276
+ const setInputValue = jest . fn ( )
277
+
278
+ const { container } = render ( < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> )
279
+
280
+ // Create a very long file path
281
+ const longPath =
282
+ "/Users/test/project/very/long/path/with/many/nested/directories/and/a/very/long/filename/with/extension.typescript"
283
+
284
+ // Create a mock dataTransfer object with the long path
285
+ const dataTransfer = {
286
+ getData : jest . fn ( ) . mockReturnValue ( longPath ) ,
287
+ files : [ ] ,
288
+ }
289
+
290
+ // Simulate drop event
291
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
292
+ dataTransfer,
293
+ preventDefault : jest . fn ( ) ,
294
+ } )
295
+
296
+ // Verify convertToMentionPath was called with the long path
297
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( longPath , mockCwd )
298
+
299
+ // The mock implementation will convert it to @/very/long/path/...
300
+ expect ( setInputValue ) . toHaveBeenCalledWith (
301
+ "@/very/long/path/with/many/nested/directories/and/a/very/long/filename/with/extension.typescript " ,
302
+ )
303
+ } )
304
+
305
+ it ( "should handle paths with special characters correctly" , ( ) => {
306
+ const setInputValue = jest . fn ( )
307
+
308
+ const { container } = render ( < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> )
309
+
310
+ // Create paths with special characters
311
+ const specialPath1 = "/Users/test/project/file with spaces.js"
312
+ const specialPath2 = "/Users/test/project/file-with-dashes.js"
313
+ const specialPath3 = "/Users/test/project/file_with_underscores.js"
314
+ const specialPath4 = "/Users/test/project/file.with.dots.js"
315
+
316
+ // Create a mock dataTransfer object with the special paths
317
+ const dataTransfer = {
318
+ getData : jest
319
+ . fn ( )
320
+ . mockReturnValue ( `${ specialPath1 } \n${ specialPath2 } \n${ specialPath3 } \n${ specialPath4 } ` ) ,
321
+ files : [ ] ,
322
+ }
323
+
324
+ // Simulate drop event
325
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
326
+ dataTransfer,
327
+ preventDefault : jest . fn ( ) ,
328
+ } )
329
+
330
+ // Verify convertToMentionPath was called for each path
331
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledTimes ( 4 )
332
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( specialPath1 , mockCwd )
333
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( specialPath2 , mockCwd )
334
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( specialPath3 , mockCwd )
335
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( specialPath4 , mockCwd )
336
+
337
+ // Verify setInputValue was called with the correct value
338
+ expect ( setInputValue ) . toHaveBeenCalledWith (
339
+ "@/file with spaces.js @/file-with-dashes.js @/file_with_underscores.js @/file.with.dots.js " ,
340
+ )
341
+ } )
342
+
343
+ it ( "should handle paths outside the current working directory" , ( ) => {
344
+ const setInputValue = jest . fn ( )
345
+
346
+ const { container } = render ( < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "" /> )
347
+
348
+ // Create paths outside the current working directory
349
+ const outsidePath = "/Users/other/project/file.js"
350
+
351
+ // Mock the convertToMentionPath function to return the original path for paths outside cwd
352
+ mockConvertToMentionPath . mockImplementationOnce ( ( path , cwd ) => {
353
+ return path // Return original path for this test
354
+ } )
355
+
356
+ // Create a mock dataTransfer object with the outside path
357
+ const dataTransfer = {
358
+ getData : jest . fn ( ) . mockReturnValue ( outsidePath ) ,
359
+ files : [ ] ,
360
+ }
361
+
362
+ // Simulate drop event
363
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
364
+ dataTransfer,
365
+ preventDefault : jest . fn ( ) ,
366
+ } )
367
+
368
+ // Verify convertToMentionPath was called with the outside path
369
+ expect ( mockConvertToMentionPath ) . toHaveBeenCalledWith ( outsidePath , mockCwd )
370
+
371
+ // Verify setInputValue was called with the original path
372
+ expect ( setInputValue ) . toHaveBeenCalledWith ( "/Users/other/project/file.js " )
373
+ } )
374
+
375
+ it ( "should do nothing when dropped text is empty" , ( ) => {
376
+ const setInputValue = jest . fn ( )
377
+
378
+ const { container } = render (
379
+ < ChatTextArea { ...defaultProps } setInputValue = { setInputValue } inputValue = "Initial text" /> ,
380
+ )
381
+
382
+ // Create a mock dataTransfer object with empty text
383
+ const dataTransfer = {
384
+ getData : jest . fn ( ) . mockReturnValue ( "" ) ,
385
+ files : [ ] ,
386
+ }
387
+
388
+ // Simulate drop event
389
+ fireEvent . drop ( container . querySelector ( ".chat-text-area" ) ! , {
390
+ dataTransfer,
391
+ preventDefault : jest . fn ( ) ,
392
+ } )
393
+
394
+ // Verify convertToMentionPath was not called
395
+ expect ( mockConvertToMentionPath ) . not . toHaveBeenCalled ( )
396
+
397
+ // Verify setInputValue was not called
398
+ expect ( setInputValue ) . not . toHaveBeenCalled ( )
399
+ } )
400
+ } )
163
401
} )
0 commit comments