@@ -41,6 +41,22 @@ vi.mock('@/component-library', () => ({
4141 { children }
4242 </ button >
4343 ) ,
44+ IconButton : ( {
45+ children,
46+ disabled,
47+ isLoading,
48+ onClick,
49+ tooltip : _tooltip ,
50+ ...props
51+ } : React . ButtonHTMLAttributes < HTMLButtonElement > & {
52+ children : React . ReactNode ;
53+ isLoading ?: boolean ;
54+ tooltip ?: React . ReactNode ;
55+ } ) => (
56+ < button type = "button" disabled = { disabled || isLoading } onClick = { onClick } { ...props } >
57+ { children }
58+ </ button >
59+ ) ,
4460 Input : ( {
4561 value,
4662 onChange,
@@ -84,13 +100,18 @@ vi.mock('./common', () => ({
84100 children,
85101 title,
86102 description,
103+ extra,
87104 } : {
88105 children : React . ReactNode ;
89106 title : string ;
90107 description ?: string ;
108+ extra ?: React . ReactNode ;
91109 } ) => (
92110 < section >
93- < h2 > { title } </ h2 >
111+ < div >
112+ < h2 > { title } </ h2 >
113+ { extra }
114+ </ div >
94115 { description ? < p > { description } </ p > : null }
95116 { children }
96117 </ section >
@@ -141,6 +162,7 @@ describe('AcpAgentsConfig', () => {
141162
142163 beforeEach ( ( ) => {
143164 ( globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT ?: boolean } ) . IS_REACT_ACT_ENVIRONMENT = true ;
165+ localStorage . clear ( ) ;
144166 loadJsonConfigMock . mockResolvedValue ( JSON . stringify ( {
145167 acpClients : {
146168 opencode : {
@@ -233,6 +255,116 @@ describe('AcpAgentsConfig', () => {
233255 } ) ;
234256 } ) ;
235257
258+ it ( 'hides a saved remote server without deleting its SSH connection' , async ( ) => {
259+ listSavedConnectionsMock . mockResolvedValue ( [ {
260+ id : 'huawei-server' ,
261+ name : 'Huawei Server' ,
262+ host : '119.8.182.138' ,
263+ port : 22 ,
264+ username : 'ssh-root' ,
265+ authType : { type : 'Password' } ,
266+ } ] ) ;
267+
268+ await act ( async ( ) => {
269+ root . render ( < AcpAgentsConfig /> ) ;
270+ } ) ;
271+ await act ( async ( ) => {
272+ await Promise . resolve ( ) ;
273+ await Promise . resolve ( ) ;
274+ } ) ;
275+
276+ const hideButton = container . querySelector < HTMLButtonElement > (
277+ 'button[aria-label="remote.hideConnection"]'
278+ ) ;
279+ expect ( hideButton ) . not . toBeNull ( ) ;
280+
281+ await act ( async ( ) => {
282+ hideButton ?. click ( ) ;
283+ await Promise . resolve ( ) ;
284+ } ) ;
285+
286+ expect ( listSavedConnectionsMock ) . toHaveBeenCalledTimes ( 1 ) ;
287+ expect ( container . textContent ) . not . toContain ( 'Huawei Server' ) ;
288+ expect ( JSON . parse ( localStorage . getItem ( 'bitfun:settings:acp-agents:hidden-remote-connections:v1' ) || '[]' ) )
289+ . toEqual ( [ 'huawei-server' ] ) ;
290+ expect ( container . textContent ) . toContain ( 'remote.showHiddenConnections' ) ;
291+ } ) ;
292+
293+ it ( 'restores a hidden remote server from the hidden list' , async ( ) => {
294+ localStorage . setItem (
295+ 'bitfun:settings:acp-agents:hidden-remote-connections:v1' ,
296+ JSON . stringify ( [ 'huawei-server' ] )
297+ ) ;
298+ listSavedConnectionsMock . mockResolvedValue ( [ {
299+ id : 'huawei-server' ,
300+ name : 'Huawei Server' ,
301+ host : '119.8.182.138' ,
302+ port : 22 ,
303+ username : 'ssh-root' ,
304+ authType : { type : 'Password' } ,
305+ } ] ) ;
306+
307+ await act ( async ( ) => {
308+ root . render ( < AcpAgentsConfig /> ) ;
309+ } ) ;
310+ await act ( async ( ) => {
311+ await Promise . resolve ( ) ;
312+ await Promise . resolve ( ) ;
313+ } ) ;
314+
315+ const showHiddenButton = Array . from ( container . querySelectorAll ( 'button' ) )
316+ . find ( button => button . textContent ?. includes ( 'remote.showHiddenConnections' ) ) ;
317+ expect ( showHiddenButton ) . not . toBeUndefined ( ) ;
318+
319+ await act ( async ( ) => {
320+ showHiddenButton ?. click ( ) ;
321+ await Promise . resolve ( ) ;
322+ } ) ;
323+
324+ const restoreButton = container . querySelector < HTMLButtonElement > (
325+ 'button[aria-label="remote.restoreConnection"]'
326+ ) ;
327+ expect ( restoreButton ) . not . toBeNull ( ) ;
328+
329+ await act ( async ( ) => {
330+ restoreButton ?. click ( ) ;
331+ await Promise . resolve ( ) ;
332+ await Promise . resolve ( ) ;
333+ } ) ;
334+
335+ expect ( localStorage . getItem ( 'bitfun:settings:acp-agents:hidden-remote-connections:v1' ) )
336+ . toBe ( '[]' ) ;
337+ expect ( container . textContent ) . toContain ( 'Huawei Server' ) ;
338+ } ) ;
339+
340+ it ( 'does not probe hidden remote servers until they are restored' , async ( ) => {
341+ localStorage . setItem (
342+ 'bitfun:settings:acp-agents:hidden-remote-connections:v1' ,
343+ JSON . stringify ( [ 'huawei-server' ] )
344+ ) ;
345+ listSavedConnectionsMock . mockResolvedValue ( [ {
346+ id : 'huawei-server' ,
347+ name : 'Huawei Server' ,
348+ host : '119.8.182.138' ,
349+ port : 22 ,
350+ username : 'ssh-root' ,
351+ authType : { type : 'Password' } ,
352+ } ] ) ;
353+
354+ await act ( async ( ) => {
355+ root . render ( < AcpAgentsConfig /> ) ;
356+ } ) ;
357+ await act ( async ( ) => {
358+ await Promise . resolve ( ) ;
359+ await Promise . resolve ( ) ;
360+ } ) ;
361+
362+ expect ( probeClientRequirementsMock ) . not . toHaveBeenCalledWith ( {
363+ remoteConnectionId : 'huawei-server' ,
364+ force : undefined ,
365+ } ) ;
366+ } ) ;
367+
236368 it ( 'configures a preset adapter when the CLI is ready but the ACP layer is missing' , async ( ) => {
237369 probeClientRequirementsMock . mockResolvedValue ( [
238370 {
0 commit comments