@@ -1780,4 +1780,121 @@ describe("ClineProvider - Parallel Mode Support", () => {
17801780 expect ( mockPostMessage2 ) . toHaveBeenCalled ( )
17811781 } )
17821782 } )
1783+
1784+ describe ( "deleteProviderProfile" , ( ) => {
1785+ it ( "should use merged state (view-local) when determining profileToActivate in parallel mode" , async ( ) => {
1786+ const mockPostMessage1 = vi . fn ( )
1787+ const mockPostMessage2 = vi . fn ( )
1788+
1789+ const createMockWebviewView = ( postMessage : any ) => ( {
1790+ webview : {
1791+ postMessage,
1792+ html : "" ,
1793+ options : { } ,
1794+ onDidReceiveMessage : vi . fn ( ) ,
1795+ asWebviewUri : vi . fn ( ) ,
1796+ cspSource : "vscode-webview://test-csp-source" ,
1797+ } ,
1798+ visible : true ,
1799+ onDidChangeVisibility : vi . fn ( ) . mockImplementation ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
1800+ onDidDispose : vi . fn ( ) . mockImplementation ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
1801+ } )
1802+
1803+ const provider1 = new ClineProvider (
1804+ mockContext ,
1805+ mockOutputChannel ,
1806+ "sidebar" ,
1807+ new ContextProxy ( mockContext ) ,
1808+ )
1809+ const provider2 = new ClineProvider ( mockContext , mockOutputChannel , "editor" , new ContextProxy ( mockContext ) )
1810+
1811+ await ( provider1 as any ) . resolveWebviewView ( createMockWebviewView ( mockPostMessage1 ) )
1812+ await ( provider2 as any ) . resolveWebviewView ( createMockWebviewView ( mockPostMessage2 ) )
1813+
1814+ // Set different profiles for each provider via saveViewState
1815+ await ( provider1 as any ) . saveViewState ( "currentApiConfigName" , "profile-a" )
1816+ await ( provider2 as any ) . saveViewState ( "currentApiConfigName" , "profile-b" )
1817+
1818+ // Verify view-local state is isolated
1819+ const state1 = await provider1 . getState ( )
1820+ let state2 = await provider2 . getState ( )
1821+ expect ( state1 . currentApiConfigName ) . toBe ( "profile-a" )
1822+ expect ( state2 . currentApiConfigName ) . toBe ( "profile-b" )
1823+
1824+ // Set up global state listApiConfigMeta with 3 profiles (including profile-to-delete)
1825+ const profileToDelete = { id : "del-id" , name : "profile-to-delete" , apiProvider : "anthropic" as const }
1826+ await provider1 . contextProxy . setValues ( {
1827+ listApiConfigMeta : [
1828+ { id : "a-id" , name : "profile-a" , apiProvider : "anthropic" } ,
1829+ { id : "b-id" , name : "profile-b" , apiProvider : "anthropic" } ,
1830+ profileToDelete ,
1831+ ] ,
1832+ } )
1833+
1834+ // provider2's viewLocalState has currentApiConfigName = "profile-b"
1835+ // When provider2 deletes "profile-to-delete", it should NOT activate "profile-a" (provider1's profile)
1836+ // It should keep "profile-b" because that's what THIS TAB is using
1837+
1838+ // Spy on _updateViewLocalStateFromMutation to capture what profile gets activated
1839+ const updateViewLocalSpy = vi . spyOn ( provider2 as any , "_updateViewLocalStateFromMutation" )
1840+
1841+ await provider2 . deleteProviderProfile ( profileToDelete )
1842+
1843+ // The key assertion: profileToActivate should be "profile-b" (provider2's view-local),
1844+ // NOT "profile-a" (provider1's view-local from shared global state).
1845+ // _updateViewLocalStateFromMutation IS called with the merged state's currentApiConfigName.
1846+ expect ( updateViewLocalSpy ) . toHaveBeenCalledWith ( { currentApiConfigName : "profile-b" } )
1847+
1848+ // Verify provider2's state is still "profile-b" after deletion
1849+ state2 = await provider2 . getState ( )
1850+ expect ( state2 . currentApiConfigName ) . toBe ( "profile-b" )
1851+
1852+ await provider1 . dispose ( )
1853+ await provider2 . dispose ( )
1854+ } )
1855+
1856+ it ( "should activate the correct fallback profile when deleting the current profile in parallel mode" , async ( ) => {
1857+ const mockPostMessage = vi . fn ( )
1858+
1859+ const createMockWebviewView = ( postMessage : any ) => ( {
1860+ webview : {
1861+ postMessage,
1862+ html : "" ,
1863+ options : { } ,
1864+ onDidReceiveMessage : vi . fn ( ) ,
1865+ asWebviewUri : vi . fn ( ) ,
1866+ cspSource : "vscode-webview://test-csp-source" ,
1867+ } ,
1868+ visible : true ,
1869+ onDidChangeVisibility : vi . fn ( ) . mockImplementation ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
1870+ onDidDispose : vi . fn ( ) . mockImplementation ( ( ) => ( { dispose : vi . fn ( ) } ) ) ,
1871+ } )
1872+
1873+ const provider = new ClineProvider ( mockContext , mockOutputChannel , "sidebar" , new ContextProxy ( mockContext ) )
1874+
1875+ await ( provider as any ) . resolveWebviewView ( createMockWebviewView ( mockPostMessage ) )
1876+
1877+ // Set up view-local state to use "profile-a"
1878+ await ( provider as any ) . saveViewState ( "currentApiConfigName" , "profile-a" )
1879+
1880+ // Set up global state with multiple profiles
1881+ await provider . contextProxy . setValues ( {
1882+ listApiConfigMeta : [
1883+ { id : "a-id" , name : "profile-a" , apiProvider : "anthropic" } ,
1884+ { id : "b-id" , name : "profile-b" , apiProvider : "openrouter" } ,
1885+ { id : "c-id" , name : "profile-c" , apiProvider : "anthropic" } ,
1886+ ] ,
1887+ } )
1888+
1889+ // Delete the currently active profile (profile-a)
1890+ const profileToDelete = { id : "a-id" , name : "profile-a" , apiProvider : "anthropic" as const }
1891+ await provider . deleteProviderProfile ( profileToDelete )
1892+
1893+ // After deletion, should activate to the first remaining profile (profile-b)
1894+ const state = await provider . getState ( )
1895+ expect ( state . currentApiConfigName ) . toBe ( "profile-b" )
1896+
1897+ await provider . dispose ( )
1898+ } )
1899+ } )
17831900} )
0 commit comments