@@ -616,6 +616,22 @@ describe("ClineProvider - Parallel Mode Support", () => {
616616 dispose : vi . fn ( ) ,
617617 } as unknown as vscode . OutputChannel
618618 } )
619+
620+ const createMockWebviewView = ( postMessage = vi . fn ( ) ) =>
621+ ( {
622+ webview : {
623+ postMessage,
624+ html : "" ,
625+ options : { } ,
626+ onDidReceiveMessage : vi . fn ( ) ,
627+ asWebviewUri : vi . fn ( ) ,
628+ cspSource : "vscode-webview://test-csp-source" ,
629+ } ,
630+ visible : true ,
631+ onDidChangeVisibility : vi . fn ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
632+ onDidDispose : vi . fn ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
633+ } ) as any
634+
619635 describe ( "viewId uniqueness" , ( ) => {
620636 it ( "should assign unique viewId to each instance" , async ( ) => {
621637 const provider1 = new ClineProvider (
@@ -1022,6 +1038,155 @@ describe("ClineProvider - Parallel Mode Support", () => {
10221038 } )
10231039 } )
10241040
1041+ describe ( "handleModeSwitch integration" , ( ) => {
1042+ it ( "should update viewLocalState.mode when handleModeSwitch is called" , async ( ) => {
1043+ const postMessage = vi . fn ( )
1044+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1045+
1046+ await ( provider as any ) . resolveWebviewView ( createMockWebviewView ( postMessage ) )
1047+
1048+ const saveViewStateSpy = vi . spyOn ( provider as any , "saveViewState" )
1049+
1050+ await provider . handleModeSwitch ( "architect" as any )
1051+
1052+ expect ( ( provider as any ) . viewLocalState . mode ) . toBe ( "architect" )
1053+ expect ( saveViewStateSpy ) . toHaveBeenCalledWith ( "mode" , "architect" )
1054+
1055+ await provider . dispose ( )
1056+ } )
1057+
1058+ it ( "should post state and skip mode config lookup when API config locking is enabled" , async ( ) => {
1059+ const postMessage = vi . fn ( )
1060+ mockContext . workspaceState . get = vi . fn ( ) . mockImplementation ( ( key : string , fallback ?: unknown ) => {
1061+ return key === "lockApiConfigAcrossModes" ? true : fallback
1062+ } )
1063+
1064+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1065+ const getModeConfigIdSpy = vi . spyOn ( provider . providerSettingsManager , "getModeConfigId" )
1066+
1067+ await ( provider as any ) . resolveWebviewView ( createMockWebviewView ( postMessage ) )
1068+ postMessage . mockClear ( )
1069+
1070+ await provider . handleModeSwitch ( "architect" as any )
1071+
1072+ expect ( getModeConfigIdSpy ) . not . toHaveBeenCalled ( )
1073+ expect ( postMessage ) . toHaveBeenCalled ( )
1074+
1075+ await provider . dispose ( )
1076+ } )
1077+
1078+ it ( "should activate configured mode profile when switching modes" , async ( ) => {
1079+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1080+ vi . spyOn ( provider . providerSettingsManager , "getModeConfigId" ) . mockResolvedValueOnce ( "profile-id" )
1081+ vi . spyOn ( provider . providerSettingsManager , "listConfig" ) . mockResolvedValueOnce ( [
1082+ { id : "profile-id" , name : "mode-profile" , apiProvider : "openrouter" } ,
1083+ ] as any )
1084+ vi . spyOn ( provider . providerSettingsManager , "getProfile" ) . mockResolvedValueOnce ( {
1085+ apiProvider : "openrouter" ,
1086+ } as any )
1087+ const activateProviderProfileSpy = vi . spyOn ( provider , "activateProviderProfile" )
1088+
1089+ await provider . handleModeSwitch ( "architect" as any )
1090+
1091+ expect ( activateProviderProfileSpy ) . toHaveBeenCalledWith ( { name : "mode-profile" } )
1092+
1093+ await provider . dispose ( )
1094+ } )
1095+
1096+ it ( "should leave current configuration unchanged for empty mode profiles" , async ( ) => {
1097+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1098+ vi . spyOn ( provider . providerSettingsManager , "getModeConfigId" ) . mockResolvedValueOnce ( "empty-profile-id" )
1099+ vi . spyOn ( provider . providerSettingsManager , "listConfig" ) . mockResolvedValueOnce ( [
1100+ { id : "empty-profile-id" , name : "empty-profile" } ,
1101+ ] as any )
1102+ vi . spyOn ( provider . providerSettingsManager , "getProfile" ) . mockResolvedValueOnce ( { } as any )
1103+ const activateProviderProfileSpy = vi . spyOn ( provider , "activateProviderProfile" )
1104+
1105+ await provider . handleModeSwitch ( "architect" as any )
1106+
1107+ expect ( activateProviderProfileSpy ) . not . toHaveBeenCalled ( )
1108+
1109+ await provider . dispose ( )
1110+ } )
1111+
1112+ it ( "should emit ModeChanged event after handleModeSwitch" , async ( ) => {
1113+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1114+ const modeChangedSpy = vi . fn ( )
1115+
1116+ provider . on ( RooCodeEventName . ModeChanged , modeChangedSpy )
1117+
1118+ await provider . handleModeSwitch ( "architect" as any )
1119+
1120+ expect ( modeChangedSpy ) . toHaveBeenCalledWith ( "architect" )
1121+
1122+ await provider . dispose ( )
1123+ } )
1124+ } )
1125+
1126+ describe ( "multi-instance isolation" , ( ) => {
1127+ it ( "should maintain independent state across three instances" , async ( ) => {
1128+ const provider1 = new ClineProvider (
1129+ mockContext ,
1130+ mockOutputChannel ,
1131+ "sidebar" ,
1132+ new ContextProxy ( mockContext ) ,
1133+ )
1134+ const provider2 = new ClineProvider ( mockContext , mockOutputChannel , "editor" , new ContextProxy ( mockContext ) )
1135+ const provider3 = new ClineProvider ( mockContext , mockOutputChannel , "editor" , new ContextProxy ( mockContext ) )
1136+
1137+ await ( provider1 as any ) . saveViewState ( "mode" , "code" )
1138+ await ( provider1 as any ) . saveViewState ( "currentApiConfigName" , "profile-1" )
1139+ await ( provider2 as any ) . saveViewState ( "mode" , "architect" )
1140+ await ( provider2 as any ) . saveViewState ( "currentApiConfigName" , "profile-2" )
1141+ await ( provider3 as any ) . saveViewState ( "mode" , "debugger" )
1142+ await ( provider3 as any ) . saveViewState ( "currentApiConfigName" , "profile-3" )
1143+
1144+ const state1 = await provider1 . getState ( )
1145+ const state2 = await provider2 . getState ( )
1146+ const state3 = await provider3 . getState ( )
1147+
1148+ expect ( state1 . mode ) . toBe ( "code" )
1149+ expect ( state1 . currentApiConfigName ) . toBe ( "profile-1" )
1150+ expect ( state2 . mode ) . toBe ( "architect" )
1151+ expect ( state2 . currentApiConfigName ) . toBe ( "profile-2" )
1152+ expect ( state3 . mode ) . toBe ( "debugger" )
1153+ expect ( state3 . currentApiConfigName ) . toBe ( "profile-3" )
1154+
1155+ await provider1 . dispose ( )
1156+ await provider2 . dispose ( )
1157+ await provider3 . dispose ( )
1158+ } )
1159+
1160+ it ( "should handle mode switch in one instance without affecting others" , async ( ) => {
1161+ const postMessage1 = vi . fn ( )
1162+ const postMessage2 = vi . fn ( )
1163+ const provider1 = new ClineProvider (
1164+ mockContext ,
1165+ mockOutputChannel ,
1166+ "sidebar" ,
1167+ new ContextProxy ( mockContext ) ,
1168+ )
1169+ const provider2 = new ClineProvider ( mockContext , mockOutputChannel , "editor" , new ContextProxy ( mockContext ) )
1170+
1171+ await ( provider1 as any ) . resolveWebviewView ( createMockWebviewView ( postMessage1 ) )
1172+ await ( provider2 as any ) . resolveWebviewView ( createMockWebviewView ( postMessage2 ) )
1173+ await ( provider1 as any ) . saveViewState ( "mode" , "code" )
1174+ await ( provider2 as any ) . saveViewState ( "mode" , "debugger" )
1175+
1176+ await provider1 . handleModeSwitch ( "architect" as any )
1177+
1178+ const state1 = await provider1 . getState ( )
1179+ const state2 = await provider2 . getState ( )
1180+
1181+ expect ( state1 . mode ) . toBe ( "architect" )
1182+ expect ( state2 . mode ) . toBe ( "debugger" )
1183+ expect ( ( provider2 as any ) . viewLocalState . mode ) . toBe ( "debugger" )
1184+
1185+ await provider1 . dispose ( )
1186+ await provider2 . dispose ( )
1187+ } )
1188+ } )
1189+
10251190 describe ( "_clearViewLocalState" , ( ) => {
10261191 it ( "should clear all view-local state values" , async ( ) => {
10271192 const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
0 commit comments