@@ -2,11 +2,33 @@ import type { Mock } from "vitest"
22
33// Mock dependencies - must come before imports
44vi . mock ( "../../../api/providers/fetchers/modelCache" )
5+ vi . mock ( "../../../utils/fs" )
6+ vi . mock ( "../../../utils/path" )
7+ vi . mock ( "../../../i18n" , ( ) => ( {
8+ t : vi . fn ( ( key : string ) => key ) , // Return the key as-is for testing
9+ } ) )
10+ vi . mock ( "vscode" , ( ) => ( {
11+ window : {
12+ showInformationMessage : vi . fn ( ) ,
13+ showErrorMessage : vi . fn ( ) ,
14+ } ,
15+ workspace : {
16+ workspaceFolders : [ ] ,
17+ } ,
18+ } ) )
519
620import { webviewMessageHandler } from "../webviewMessageHandler"
721import type { ClineProvider } from "../ClineProvider"
822import { getModels } from "../../../api/providers/fetchers/modelCache"
923import type { ModelRecord } from "../../../shared/api"
24+ import { fileExistsAtPath } from "../../../utils/fs"
25+ import { getWorkspacePath } from "../../../utils/path"
26+ import * as vscode from "vscode"
27+
28+ const mockFileExistsAtPath = fileExistsAtPath as Mock < typeof fileExistsAtPath >
29+ const mockGetWorkspacePath = getWorkspacePath as Mock < typeof getWorkspacePath >
30+ const mockShowInformationMessage = vscode . window . showInformationMessage as Mock
31+ const mockShowErrorMessage = vscode . window . showErrorMessage as Mock
1032
1133const mockGetModels = getModels as Mock < typeof getModels >
1234
@@ -275,6 +297,199 @@ describe("webviewMessageHandler - requestRouterModels", () => {
275297 } )
276298 } )
277299
300+ describe ( "webviewMessageHandler - deleteCustomMode" , ( ) => {
301+ const mockCustomModesManager = {
302+ getCustomModes : vi . fn ( ) ,
303+ deleteCustomMode : vi . fn ( ) ,
304+ }
305+
306+ const mockContextProxy = {
307+ setValue : vi . fn ( ) ,
308+ }
309+
310+ const mockProvider = {
311+ ...mockClineProvider ,
312+ customModesManager : mockCustomModesManager ,
313+ contextProxy : mockContextProxy ,
314+ postStateToWebview : vi . fn ( ) ,
315+ } as unknown as ClineProvider
316+
317+ beforeEach ( ( ) => {
318+ vi . clearAllMocks ( )
319+ mockGetWorkspacePath . mockReturnValue ( "/test/workspace" )
320+ } )
321+
322+ it ( "shows enhanced warning when rules folder exists" , async ( ) => {
323+ const testMode = {
324+ slug : "test-mode" ,
325+ name : "Test Mode" ,
326+ source : "project" as const ,
327+ }
328+
329+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ testMode ] )
330+ mockFileExistsAtPath . mockResolvedValue ( true )
331+ mockShowInformationMessage . mockResolvedValue ( "common:answers.yes" )
332+
333+ await webviewMessageHandler ( mockProvider , {
334+ type : "deleteCustomMode" ,
335+ slug : "test-mode" ,
336+ } )
337+
338+ // Verify rules folder check was performed
339+ expect ( mockFileExistsAtPath ) . toHaveBeenCalledWith ( "/test/workspace/.roo/rules-test-mode" )
340+
341+ // Verify enhanced warning message was shown
342+ expect ( mockShowInformationMessage ) . toHaveBeenCalledWith (
343+ "common:confirmation.delete_custom_mode_with_rules" ,
344+ { modal : true } ,
345+ "common:answers.yes" ,
346+ )
347+
348+ // Verify deletion proceeded
349+ expect ( mockCustomModesManager . deleteCustomMode ) . toHaveBeenCalledWith ( "test-mode" )
350+ } )
351+
352+ it ( "shows standard warning when rules folder does not exist" , async ( ) => {
353+ const testMode = {
354+ slug : "test-mode" ,
355+ name : "Test Mode" ,
356+ source : "global" as const ,
357+ }
358+
359+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ testMode ] )
360+ mockFileExistsAtPath . mockResolvedValue ( false )
361+ mockShowInformationMessage . mockResolvedValue ( "common:answers.yes" )
362+
363+ await webviewMessageHandler ( mockProvider , {
364+ type : "deleteCustomMode" ,
365+ slug : "test-mode" ,
366+ } )
367+
368+ // Verify standard warning message was shown
369+ expect ( mockShowInformationMessage ) . toHaveBeenCalledWith (
370+ "common:confirmation.delete_custom_mode" ,
371+ { modal : true } ,
372+ "common:answers.yes" ,
373+ )
374+
375+ // Verify deletion proceeded
376+ expect ( mockCustomModesManager . deleteCustomMode ) . toHaveBeenCalledWith ( "test-mode" )
377+ } )
378+
379+ it ( "shows standard warning when workspace path is not available" , async ( ) => {
380+ const testMode = {
381+ slug : "test-mode" ,
382+ name : "Test Mode" ,
383+ source : "project" as const ,
384+ }
385+
386+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ testMode ] )
387+ mockGetWorkspacePath . mockReturnValue ( "" )
388+ mockShowInformationMessage . mockResolvedValue ( "common:answers.yes" )
389+
390+ await webviewMessageHandler ( mockProvider , {
391+ type : "deleteCustomMode" ,
392+ slug : "test-mode" ,
393+ } )
394+
395+ // Verify file check was not performed
396+ expect ( mockFileExistsAtPath ) . not . toHaveBeenCalled ( )
397+
398+ // Verify standard warning message was shown
399+ expect ( mockShowInformationMessage ) . toHaveBeenCalledWith (
400+ "common:confirmation.delete_custom_mode" ,
401+ { modal : true } ,
402+ "common:answers.yes" ,
403+ )
404+ } )
405+
406+ it ( "shows standard warning when file check fails" , async ( ) => {
407+ const testMode = {
408+ slug : "test-mode" ,
409+ name : "Test Mode" ,
410+ source : "project" as const ,
411+ }
412+
413+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ testMode ] )
414+ mockFileExistsAtPath . mockRejectedValue ( new Error ( "File system error" ) )
415+ mockShowInformationMessage . mockResolvedValue ( "common:answers.yes" )
416+
417+ await webviewMessageHandler ( mockProvider , {
418+ type : "deleteCustomMode" ,
419+ slug : "test-mode" ,
420+ } )
421+
422+ // Verify standard warning message was shown (fallback)
423+ expect ( mockShowInformationMessage ) . toHaveBeenCalledWith (
424+ "common:confirmation.delete_custom_mode" ,
425+ { modal : true } ,
426+ "common:answers.yes" ,
427+ )
428+
429+ // Verify deletion still proceeded
430+ expect ( mockCustomModesManager . deleteCustomMode ) . toHaveBeenCalledWith ( "test-mode" )
431+ } )
432+
433+ it ( "does not delete when user cancels" , async ( ) => {
434+ const testMode = {
435+ slug : "test-mode" ,
436+ name : "Test Mode" ,
437+ source : "project" as const ,
438+ }
439+
440+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ testMode ] )
441+ mockFileExistsAtPath . mockResolvedValue ( true )
442+ mockShowInformationMessage . mockResolvedValue ( undefined ) // User cancelled
443+
444+ await webviewMessageHandler ( mockProvider , {
445+ type : "deleteCustomMode" ,
446+ slug : "test-mode" ,
447+ } )
448+
449+ // Verify deletion was not performed
450+ expect ( mockCustomModesManager . deleteCustomMode ) . not . toHaveBeenCalled ( )
451+ } )
452+
453+ it ( "shows error when mode is not found" , async ( ) => {
454+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ ] )
455+
456+ await webviewMessageHandler ( mockProvider , {
457+ type : "deleteCustomMode" ,
458+ slug : "nonexistent-mode" ,
459+ } )
460+
461+ // Verify error message was shown
462+ expect ( mockShowErrorMessage ) . toHaveBeenCalledWith ( "common:customModes.errors.modeNotFound" )
463+
464+ // Verify deletion was not attempted
465+ expect ( mockCustomModesManager . deleteCustomMode ) . not . toHaveBeenCalled ( )
466+ } )
467+
468+ it ( "correctly identifies GLOBAL mode source" , async ( ) => {
469+ const testMode = {
470+ slug : "global-mode" ,
471+ name : "Global Mode" ,
472+ source : "global" as const ,
473+ }
474+
475+ mockCustomModesManager . getCustomModes . mockResolvedValue ( [ testMode ] )
476+ mockFileExistsAtPath . mockResolvedValue ( true )
477+ mockShowInformationMessage . mockResolvedValue ( "common:answers.yes" )
478+
479+ await webviewMessageHandler ( mockProvider , {
480+ type : "deleteCustomMode" ,
481+ slug : "global-mode" ,
482+ } )
483+
484+ // Verify enhanced warning message shows GLOBAL
485+ expect ( mockShowInformationMessage ) . toHaveBeenCalledWith (
486+ "common:confirmation.delete_custom_mode_with_rules" ,
487+ { modal : true } ,
488+ "common:answers.yes" ,
489+ )
490+ } )
491+ } )
492+
278493 it ( "prefers config values over message values for LiteLLM" , async ( ) => {
279494 const mockModels : ModelRecord = { }
280495 mockGetModels . mockResolvedValue ( mockModels )
0 commit comments