@@ -198,7 +198,7 @@ vi.mock("vscode", () => ({
198198 showErrorMessage : vi . fn ( ) ,
199199 activeTextEditor : undefined ,
200200 onDidChangeActiveTextEditor : vi . fn ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
201- createTextEditorDecorationType : vi . fn ( ) . mockReturnValue ( { } ) ,
201+ createTextEditorDecorationType : vi . fn ( ) . mockReturnValue ( { dispose : vi . fn ( ) } ) ,
202202 tabGroups : {
203203 onDidChangeTabs : vi . fn ( ) . mockReturnValue ( { dispose : vi . fn ( ) } ) ,
204204 } ,
@@ -691,13 +691,14 @@ describe("ClineProvider - Parallel Mode Support", () => {
691691 )
692692 const provider2 = new ClineProvider ( mockContext , mockOutputChannel , "editor" , new ContextProxy ( mockContext ) )
693693
694+ await ( provider2 as any ) . saveViewState ( "mode" , "debugger" )
694695 await ( provider1 as any ) . saveViewState ( "mode" , "architect" )
695696
696697 const state1 = await provider1 . getState ( )
697698 const state2 = await provider2 . getState ( )
698699
699700 expect ( state1 . mode ) . toBe ( "architect" )
700- expect ( state2 . mode ) . toBe ( "code " )
701+ expect ( state2 . mode ) . toBe ( "debugger " )
701702
702703 await provider1 . dispose ( )
703704 await provider2 . dispose ( )
@@ -1064,6 +1065,7 @@ describe("ClineProvider - Parallel Mode Support", () => {
10641065 expect ( state . apiConfiguration . apiProvider ) . toBe ( "bedrock" )
10651066 expect ( state . apiConfiguration . awsBedrockEndpoint ) . toBe ( "http://127.0.0.1:4567" )
10661067 expect ( ( provider as any ) . viewLocalState . apiConfiguration . apiProvider ) . toBe ( "bedrock" )
1068+ expect ( ( provider as any ) . viewLocalState . apiConfiguration ) . not . toHaveProperty ( "openRouterModelId" )
10671069
10681070 await provider . dispose ( )
10691071 } )
@@ -1093,6 +1095,72 @@ describe("ClineProvider - Parallel Mode Support", () => {
10931095
10941096 await provider . dispose ( )
10951097 } )
1098+
1099+ it ( "should sanitize raw viewStateId before using it as persisted viewStates key" , async ( ) => {
1100+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1101+
1102+ await ( provider as any ) . setViewStateId ( "tab panel/with.dots and spaces" )
1103+ await provider . setValue ( "mode" as any , "architect" as any )
1104+
1105+ expect ( provider . contextProxy . getValue ( "viewStates" as any ) ) . toMatchObject ( {
1106+ tab_panel_with_dots_and_spaces : { mode : "architect" } ,
1107+ } )
1108+ expect ( provider . contextProxy . getValue ( "viewStates" as any ) ) . not . toHaveProperty (
1109+ "tab panel/with.dots and spaces" ,
1110+ )
1111+
1112+ await provider . dispose ( )
1113+ } )
1114+
1115+ it ( "should persist queued writes under the viewStateId active when the change was made" , async ( ) => {
1116+ let releaseFirstWrite ! : ( ) => void
1117+ const firstWriteStarted = new Promise < void > ( ( resolve ) => {
1118+ mockContext . globalState . update = vi
1119+ . fn ( )
1120+ . mockImplementationOnce ( ( key : string , value : any ) => {
1121+ mockContext . globalState . get = vi
1122+ . fn ( )
1123+ . mockImplementation ( ( lookupKey : string ) => ( lookupKey === key ? value : undefined ) )
1124+ resolve ( )
1125+ return new Promise < void > ( ( writeResolve ) => {
1126+ releaseFirstWrite = writeResolve
1127+ } )
1128+ } )
1129+ . mockImplementation ( ( key : string , value : any ) => {
1130+ mockContext . globalState . get = vi
1131+ . fn ( )
1132+ . mockImplementation ( ( lookupKey : string ) => ( lookupKey === key ? value : undefined ) )
1133+ return Promise . resolve ( )
1134+ } )
1135+ } )
1136+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1137+
1138+ await ( provider as any ) . setViewStateId ( "view-a" )
1139+ const firstSave = ( provider as any ) . saveViewState ( "mode" , "architect" )
1140+ await firstWriteStarted
1141+ await ( provider as any ) . setViewStateId ( "view-b" )
1142+ releaseFirstWrite ( )
1143+ await firstSave
1144+
1145+ expect ( provider . contextProxy . getValue ( "viewStates" as any ) ) . toMatchObject ( {
1146+ "view-a" : { mode : "architect" } ,
1147+ } )
1148+ expect ( provider . contextProxy . getValue ( "viewStates" as any ) ) . not . toHaveProperty ( "view-b" )
1149+
1150+ await provider . dispose ( )
1151+ } )
1152+
1153+ it ( "should clean up persisted viewStates entry when a tab provider is disposed" , async ( ) => {
1154+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "editor" , new ContextProxy ( mockContext ) )
1155+
1156+ await ( provider as any ) . setViewStateId ( "tab-to-dispose" )
1157+ await ( provider as any ) . saveViewState ( "mode" , "architect" )
1158+ expect ( provider . contextProxy . getValue ( "viewStates" as any ) ) . toHaveProperty ( "tab-to-dispose" )
1159+
1160+ await provider . dispose ( )
1161+
1162+ expect ( provider . contextProxy . getValue ( "viewStates" as any ) ) . not . toHaveProperty ( "tab-to-dispose" )
1163+ } )
10961164 } )
10971165
10981166 describe ( "profile mutations" , ( ) => {
0 commit comments