@@ -3,6 +3,7 @@ import ChatTextArea from "../ChatTextArea"
33import { useExtensionState } from "../../../context/ExtensionStateContext"
44import { vscode } from "../../../utils/vscode"
55import { defaultModeSlug } from "../../../../../src/shared/modes"
6+ import * as pathMentions from "../../../utils/path-mentions"
67
78// Mock modules
89jest . mock ( "../../../utils/vscode" , ( ) => ( {
@@ -12,9 +13,20 @@ jest.mock("../../../utils/vscode", () => ({
1213} ) )
1314jest . mock ( "../../../components/common/CodeBlock" )
1415jest . 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+ } ) )
1526
1627// Get the mocked postMessage function
1728const mockPostMessage = vscode . postMessage as jest . Mock
29+ const mockConvertToMentionPath = pathMentions . convertToMentionPath as jest . Mock
1830
1931// Mock ExtensionStateContext
2032jest . mock ( "../../../context/ExtensionStateContext" )
@@ -160,4 +172,230 @@ describe("ChatTextArea", () => {
160172 expect ( setInputValue ) . toHaveBeenCalledWith ( "Enhanced test prompt" )
161173 } )
162174 } )
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+ } )
163401} )
0 commit comments