@@ -3,6 +3,7 @@ import { promises as fs } from "fs"
33import { exec } from "child_process"
44import { promisify } from "util"
55import * as path from "path"
6+ import * as os from "os"
67
78// Use vi.hoisted to ensure the mock is available at the right time
89const { mockExecAsync } = vi . hoisted ( ( ) => ( {
@@ -25,9 +26,9 @@ vi.mock("vscode", () => ({
2526 appendLine : vi . fn ( ) ,
2627 show : vi . fn ( ) ,
2728 } ) ) ,
28- showErrorMessage : vi . fn ( ) ,
29- showWarningMessage : vi . fn ( ) ,
30- showInformationMessage : vi . fn ( ) ,
29+ showErrorMessage : vi . fn ( ( ) => Promise . resolve ( "Show Output" ) ) ,
30+ showWarningMessage : vi . fn ( ( ) => Promise . resolve ( ) ) ,
31+ showInformationMessage : vi . fn ( ( ) => Promise . resolve ( ) ) ,
3132 } ,
3233 env : {
3334 openExternal : vi . fn ( ) ,
@@ -190,6 +191,82 @@ Working Copy Root Path: /test/workspace`,
190191 expect ( result [ 0 ] . message ) . toBe ( "Test commit message" )
191192 } )
192193
194+ it ( "should search for specific revision when query is in 'r123' format" , async ( ) => {
195+ // Mock checkSvnInstalled to return true
196+ mockExecAsync . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } ) . mockResolvedValueOnce ( {
197+ stdout : `<?xml version="1.0" encoding="UTF-8"?>
198+ <log>
199+ <logentry revision="456">
200+ <author>jane.smith</author>
201+ <date>2023-02-15T14:30:00.000000Z</date>
202+ <msg>Specific revision commit</msg>
203+ </logentry>
204+ </log>` ,
205+ stderr : "" ,
206+ } )
207+
208+ // Mock checkSvnRepo to return true
209+ mockFsAccess . mockResolvedValue ( undefined )
210+
211+ const result = await searchSvnCommits ( "r456" , "/test/workspace" )
212+ expect ( result ) . toHaveLength ( 1 )
213+ expect ( result [ 0 ] . revision ) . toBe ( "456" )
214+ expect ( result [ 0 ] . author ) . toBe ( "jane.smith" )
215+ expect ( result [ 0 ] . message ) . toBe ( "Specific revision commit" )
216+ } )
217+
218+ it ( "should handle case-insensitive 'r' prefix in revision search" , async ( ) => {
219+ // Mock checkSvnInstalled to return true
220+ mockExecAsync . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } ) . mockResolvedValueOnce ( {
221+ stdout : `<?xml version="1.0" encoding="UTF-8"?>
222+ <log>
223+ <logentry revision="789">
224+ <author>test.user</author>
225+ <date>2023-03-15T16:30:00.000000Z</date>
226+ <msg>Case insensitive test</msg>
227+ </logentry>
228+ </log>` ,
229+ stderr : "" ,
230+ } )
231+
232+ // Mock checkSvnRepo to return true
233+ mockFsAccess . mockResolvedValue ( undefined )
234+
235+ const result = await searchSvnCommits ( "R789" , "/test/workspace" )
236+ expect ( result ) . toHaveLength ( 1 )
237+ expect ( result [ 0 ] . revision ) . toBe ( "789" )
238+ } )
239+
240+ it ( "should NOT treat pure numbers as revision searches" , async ( ) => {
241+ // Mock checkSvnInstalled to return true
242+ mockExecAsync . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } ) . mockResolvedValueOnce ( {
243+ stdout : `<?xml version="1.0" encoding="UTF-8"?>
244+ <log>
245+ <logentry revision="123">
246+ <author>john.doe</author>
247+ <date>2023-01-15T10:30:00.000000Z</date>
248+ <msg>Message containing 456 number</msg>
249+ </logentry>
250+ <logentry revision="456">
251+ <author>jane.smith</author>
252+ <date>2023-02-15T14:30:00.000000Z</date>
253+ <msg>Another commit</msg>
254+ </logentry>
255+ </log>` ,
256+ stderr : "" ,
257+ } )
258+
259+ // Mock checkSvnRepo to return true
260+ mockFsAccess . mockResolvedValue ( undefined )
261+
262+ // Search for pure number "456" should search in message content, not as specific revision
263+ const result = await searchSvnCommits ( "456" , "/test/workspace" )
264+ // Should find the commit with "456" in the message, NOT the commit with revision 456
265+ expect ( result ) . toHaveLength ( 1 )
266+ expect ( result [ 0 ] . revision ) . toBe ( "123" )
267+ expect ( result [ 0 ] . message ) . toContain ( "456" )
268+ } )
269+
193270 it ( "should return empty array when SVN is not available" , async ( ) => {
194271 mockExecAsync . mockRejectedValue ( new Error ( "Command not found" ) )
195272
@@ -204,6 +281,8 @@ Working Copy Root Path: /test/workspace`,
204281 mockExecAsync . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } ) . mockResolvedValueOnce ( {
205282 stdout : `------------------------------------------------------------------------
206283r123 | john.doe | 2023-01-15 10:30:00 +0000 (Sun, 15 Jan 2023) | 1 line
284+ Changed paths:
285+ M /test.txt
207286
208287Test commit message
209288------------------------------------------------------------------------` ,
@@ -218,13 +297,269 @@ Test commit message
218297 expect ( result ) . toContain ( "Test commit message" )
219298 } )
220299
300+ it ( "should parse changed files information correctly" , async ( ) => {
301+ // Mock checkSvnInstalled and checkSvnRepo
302+ mockFsAccess . mockResolvedValue ( undefined )
303+ mockExecAsync
304+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
305+ . mockResolvedValueOnce ( {
306+ stdout : `------------------------------------------------------------------------
307+ r456 | jane.smith | 2023-02-15 14:30:00 +0800 (Wed, 15 Feb 2023) | 2 lines
308+ Changed paths:
309+ A /src/new-file.ts
310+ M /src/existing-file.ts
311+ D /src/old-file.ts
312+
313+ Added new feature
314+ Fixed bug in existing functionality
315+ ------------------------------------------------------------------------` ,
316+ stderr : "" ,
317+ } )
318+ . mockResolvedValueOnce ( {
319+ stdout : `Index: src/new-file.ts
320+ ===================================================================
321+ --- src/new-file.ts (nonexistent)
322+ +++ src/new-file.ts (revision 456)
323+ @@ -0,0 +1,3 @@
324+ +export function newFunction() {
325+ + return 'Hello World';
326+ +}` ,
327+ stderr : "" ,
328+ } )
329+
330+ const result = await getSvnCommitInfoForMentions ( "456" , "/test/workspace" )
331+
332+ // Should contain basic commit info
333+ expect ( result ) . toContain ( "r456 by jane.smith" )
334+ expect ( result ) . toContain ( "Added new feature" )
335+ expect ( result ) . toContain ( "Fixed bug in existing functionality" )
336+
337+ // Should contain changed files section
338+ expect ( result ) . toContain ( "Changed files:" )
339+ expect ( result ) . toContain ( "A /src/new-file.ts" )
340+ expect ( result ) . toContain ( "M /src/existing-file.ts" )
341+ expect ( result ) . toContain ( "D /src/old-file.ts" )
342+
343+ // Should contain diff section
344+ expect ( result ) . toContain ( "Diff:" )
345+ expect ( result ) . toContain ( "export function newFunction()" )
346+ } )
347+
348+ it ( "should handle date parsing with Chinese day names gracefully" , async ( ) => {
349+ // Mock checkSvnInstalled and checkSvnRepo
350+ mockFsAccess . mockResolvedValue ( undefined )
351+ mockExecAsync
352+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
353+ . mockResolvedValueOnce ( {
354+ stdout : `------------------------------------------------------------------------
355+ r789 | chinese.user | 2023-03-15 16:30:00 +0800 (星期三, 15 三月 2023) | 1 line
356+ Changed paths:
357+ M /中文文件.txt
358+
359+ 测试中文提交信息
360+ ------------------------------------------------------------------------` ,
361+ stderr : "" ,
362+ } )
363+ . mockResolvedValueOnce ( {
364+ stdout : `Index: 中文文件.txt
365+ ===================================================================
366+ --- 中文文件.txt (revision 788)
367+ +++ 中文文件.txt (revision 789)
368+ @@ -1 +1 @@
369+ -旧内容
370+ +新内容` ,
371+ stderr : "" ,
372+ } )
373+
374+ const result = await getSvnCommitInfoForMentions ( "789" , "/test/workspace" )
375+
376+ // Should contain commit info with extracted date
377+ expect ( result ) . toContain ( "r789 by chinese.user" )
378+ expect ( result ) . toContain ( "2023-03-15 16:30:00 +0800" )
379+ expect ( result ) . toContain ( "测试中文提交信息" )
380+
381+ // Should handle Chinese file names and content
382+ expect ( result ) . toContain ( "M /中文文件.txt" )
383+ expect ( result ) . toContain ( "新内容" )
384+ } )
385+
386+ it ( "should handle commit message extraction from improved format" , async ( ) => {
387+ // Mock checkSvnInstalled and checkSvnRepo
388+ mockFsAccess . mockResolvedValue ( undefined )
389+ mockExecAsync
390+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
391+ . mockResolvedValueOnce ( {
392+ stdout : `------------------------------------------------------------------------
393+ r100 | developer | 2023-01-01 12:00:00 +0000 (Sun, 01 Jan 2023) | 3 lines
394+ Changed paths:
395+ M /src/component.ts
396+
397+ This is a multi-line commit message
398+ with detailed explanation
399+ and some additional notes
400+ ------------------------------------------------------------------------` ,
401+ stderr : "" ,
402+ } )
403+ . mockResolvedValueOnce ( {
404+ stdout : "" ,
405+ stderr : "" ,
406+ } )
407+
408+ const result = await getSvnCommitInfoForMentions ( "100" , "/test/workspace" )
409+
410+ // Should extract multi-line commit message correctly
411+ expect ( result ) . toContain ( "This is a multi-line commit message" )
412+ expect ( result ) . toContain ( "with detailed explanation" )
413+ expect ( result ) . toContain ( "and some additional notes" )
414+ } )
415+
416+ it ( "should handle revision with 'r' prefix input" , async ( ) => {
417+ // Mock checkSvnInstalled and checkSvnRepo
418+ mockFsAccess . mockResolvedValue ( undefined )
419+ mockExecAsync
420+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
421+ . mockResolvedValueOnce ( {
422+ stdout : `------------------------------------------------------------------------
423+ r555 | test.author | 2023-05-15 10:15:00 +0000 (Mon, 15 May 2023) | 1 line
424+ Changed paths:
425+ M /test-file.txt
426+
427+ Test with r prefix input
428+ ------------------------------------------------------------------------` ,
429+ stderr : "" ,
430+ } )
431+ . mockResolvedValueOnce ( {
432+ stdout : "" ,
433+ stderr : "" ,
434+ } )
435+
436+ const result = await getSvnCommitInfoForMentions ( "r555" , "/test/workspace" )
437+ expect ( result ) . toContain ( "r555 by test.author" )
438+ expect ( result ) . toContain ( "Test with r prefix input" )
439+ } )
440+
221441 it ( "should return error message for invalid revision" , async ( ) => {
222442 // Mock checkSvnInstalled to fail
223443 mockExecAsync . mockRejectedValue ( new Error ( "Command not found" ) )
224444
225445 const result = await getSvnCommitInfoForMentions ( "invalid" , "/test/workspace" )
226446 expect ( result ) . toBe ( "Error: SVN not available or not an SVN repository" )
227447 } )
448+
449+ it ( "should handle diff output with UTF-8 encoding" , async ( ) => {
450+ // Mock checkSvnInstalled and checkSvnRepo to return true
451+ mockFsAccess . mockResolvedValue ( undefined )
452+
453+ // Create a buffer with UTF-8 encoded Chinese characters
454+ const utf8Buffer = Buffer . from ( "测试文件内容" , "utf8" )
455+
456+ // Mock the svn log and diff commands
457+ mockExecAsync
458+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
459+ . mockResolvedValueOnce ( {
460+ stdout : `------------------------------------------------------------------------
461+ r123 | john.doe | 2023-01-15 10:30:00 +0000 (Sun, 15 Jan 2023) | 1 line
462+ Changed paths:
463+ M /test.txt
464+
465+ Test commit message
466+ ------------------------------------------------------------------------` ,
467+ stderr : "" ,
468+ } )
469+ . mockResolvedValueOnce ( { stdout : utf8Buffer , stderr : "" } )
470+
471+ const result = await getSvnCommitInfoForMentions ( "123" , "/test/workspace" )
472+ expect ( result ) . toContain ( "r123" )
473+ expect ( result ) . toContain ( "john.doe" )
474+ expect ( result ) . toContain ( "Test commit message" )
475+ expect ( result ) . toContain ( "测试文件内容" )
476+ } )
477+
478+ it ( "should handle diff output with problematic encoding" , async ( ) => {
479+ // Mock checkSvnInstalled and checkSvnRepo to return true
480+ mockFsAccess . mockResolvedValue ( undefined )
481+
482+ // Create a buffer with problematic encoding (contains replacement characters)
483+ const problematicBuffer = Buffer . from ( "Test file with � characters" , "utf8" )
484+
485+ // Mock the svn log and diff commands
486+ mockExecAsync
487+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
488+ . mockResolvedValueOnce ( {
489+ stdout : `------------------------------------------------------------------------
490+ r123 | john.doe | 2023-01-15 10:30:00 +0000 (Sun, 15 Jan 2023) | 1 line
491+ Changed paths:
492+ M /test.txt
493+
494+ Test commit message
495+ ------------------------------------------------------------------------` ,
496+ stderr : "" ,
497+ } )
498+ . mockResolvedValueOnce ( { stdout : problematicBuffer , stderr : "" } )
499+
500+ const result = await getSvnCommitInfoForMentions ( "123" , "/test/workspace" )
501+ expect ( result ) . toContain ( "r123" )
502+ expect ( result ) . toContain ( "john.doe" )
503+ expect ( result ) . toContain ( "Test commit message" )
504+ // Should handle the problematic encoding gracefully
505+ expect ( result ) . toContain ( "Test file with" )
506+ } )
507+
508+ it ( "should handle string output from svn diff command" , async ( ) => {
509+ // Mock checkSvnInstalled and checkSvnRepo to return true
510+ mockFsAccess . mockResolvedValue ( undefined )
511+
512+ // Mock the svn log command with string output instead of Buffer
513+ mockExecAsync
514+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
515+ . mockResolvedValueOnce ( {
516+ stdout : `------------------------------------------------------------------------
517+ r123 | john.doe | 2023-01-15 10:30:00 +0000 (Sun, 15 Jan 2023) | 1 line
518+ Changed paths:
519+ M /test.txt
520+
521+ Test commit message
522+ ------------------------------------------------------------------------` ,
523+ stderr : "" ,
524+ } )
525+ . mockResolvedValueOnce ( {
526+ stdout : "Index: test.txt\n===================================================================\n--- test.txt\t(revision 122)\n+++ test.txt\t(revision 123)\n@@ -1 +1 @@\n-old content\n+new content" ,
527+ stderr : "" ,
528+ } )
529+
530+ const result = await getSvnCommitInfoForMentions ( "123" , "/test/workspace" )
531+ expect ( result ) . toContain ( "r123" )
532+ expect ( result ) . toContain ( "john.doe" )
533+ expect ( result ) . toContain ( "Test commit message" )
534+ expect ( result ) . toContain ( "new content" )
535+ } )
536+
537+ it ( "should handle diff command failure gracefully" , async ( ) => {
538+ // Mock checkSvnInstalled and checkSvnRepo to return true
539+ mockFsAccess . mockResolvedValue ( undefined )
540+
541+ // Mock the svn log command to succeed but diff to fail
542+ mockExecAsync
543+ . mockResolvedValueOnce ( { stdout : "svn, version 1.14.0\n" , stderr : "" } )
544+ . mockResolvedValueOnce ( {
545+ stdout : `------------------------------------------------------------------------
546+ r123 | john.doe | 2023-01-15 10:30:00 +0000 (Sun, 15 Jan 2023) | 1 line
547+ Changed paths:
548+ M /test.txt
549+
550+ Test commit message
551+ ------------------------------------------------------------------------` ,
552+ stderr : "" ,
553+ } )
554+ . mockRejectedValueOnce ( new Error ( "svn: E160013: File not found" ) )
555+
556+ const result = await getSvnCommitInfoForMentions ( "123" , "/test/workspace" )
557+ expect ( result ) . toContain ( "r123" )
558+ expect ( result ) . toContain ( "john.doe" )
559+ expect ( result ) . toContain ( "Test commit message" )
560+ // Should not contain diff section when diff fails
561+ expect ( result ) . not . toContain ( "Diff:" )
562+ } )
228563 } )
229564
230565 describe ( "getWorkspaceSvnInfo" , ( ) => {
0 commit comments