2026-03-11 20:12:56 +09:00
import { describe , expect , spyOn , test } from "bun:test"
import { disposeCreatedHooks } from "./create-hooks"
import { createPluginDispose } from "./plugin-dispose"
describe ( "createPluginDispose" , ( ) = > {
test ( "#given plugin with active managers and hooks #when dispose() is called #then backgroundManager.shutdown() is called" , async ( ) = > {
// given
const backgroundManager = {
2026-03-11 21:30:04 +09:00
shutdown : async ( ) : Promise < void > = > { } ,
2026-03-11 20:12:56 +09:00
}
const skillMcpManager = {
disconnectAll : async ( ) : Promise < void > = > { } ,
}
2026-03-31 17:33:19 -07:00
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
2026-03-11 20:12:56 +09:00
const shutdownSpy = spyOn ( backgroundManager , "shutdown" )
const dispose = createPluginDispose ( {
backgroundManager ,
skillMcpManager ,
2026-03-31 17:33:19 -07:00
lspManager ,
2026-03-11 20:12:56 +09:00
disposeHooks : ( ) : void = > { } ,
} )
// when
await dispose ( )
// then
expect ( shutdownSpy ) . toHaveBeenCalledTimes ( 1 )
} )
test ( "#given plugin with active MCP connections #when dispose() is called #then skillMcpManager.disconnectAll() is called" , async ( ) = > {
// given
const backgroundManager = {
2026-03-11 21:30:04 +09:00
shutdown : async ( ) : Promise < void > = > { } ,
2026-03-11 20:12:56 +09:00
}
const skillMcpManager = {
disconnectAll : async ( ) : Promise < void > = > { } ,
}
2026-03-31 17:33:19 -07:00
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
2026-03-11 20:12:56 +09:00
const disconnectAllSpy = spyOn ( skillMcpManager , "disconnectAll" )
const dispose = createPluginDispose ( {
backgroundManager ,
skillMcpManager ,
2026-03-31 17:33:19 -07:00
lspManager ,
2026-03-11 20:12:56 +09:00
disposeHooks : ( ) : void = > { } ,
} )
// when
await dispose ( )
// then
expect ( disconnectAllSpy ) . toHaveBeenCalledTimes ( 1 )
} )
test ( "#given plugin with hooks that have dispose #when dispose() is called #then each hook's dispose is called" , async ( ) = > {
// given
2026-03-31 17:33:19 -07:00
const claudeCodeHooks = {
dispose : ( ) : void = > { } ,
}
const commentChecker = {
dispose : ( ) : void = > { } ,
}
2026-03-11 20:12:56 +09:00
const runtimeFallback = {
dispose : ( ) : void = > { } ,
}
const todoContinuationEnforcer = {
dispose : ( ) : void = > { } ,
}
const autoSlashCommand = {
dispose : ( ) : void = > { } ,
}
2026-03-31 17:33:19 -07:00
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
const claudeCodeHooksDisposeSpy = spyOn ( claudeCodeHooks , "dispose" )
const commentCheckerDisposeSpy = spyOn ( commentChecker , "dispose" )
2026-03-11 20:12:56 +09:00
const runtimeFallbackDisposeSpy = spyOn ( runtimeFallback , "dispose" )
const todoContinuationEnforcerDisposeSpy = spyOn ( todoContinuationEnforcer , "dispose" )
const autoSlashCommandDisposeSpy = spyOn ( autoSlashCommand , "dispose" )
const dispose = createPluginDispose ( {
backgroundManager : {
2026-03-11 21:30:04 +09:00
shutdown : async ( ) : Promise < void > = > { } ,
2026-03-11 20:12:56 +09:00
} ,
skillMcpManager : {
disconnectAll : async ( ) : Promise < void > = > { } ,
} ,
2026-03-31 17:33:19 -07:00
lspManager ,
2026-03-11 20:12:56 +09:00
disposeHooks : ( ) : void = > {
disposeCreatedHooks ( {
2026-03-31 17:33:19 -07:00
claudeCodeHooks ,
commentChecker ,
2026-03-11 20:12:56 +09:00
runtimeFallback ,
todoContinuationEnforcer ,
autoSlashCommand ,
} )
} ,
} )
// when
await dispose ( )
// then
2026-03-31 17:33:19 -07:00
expect ( claudeCodeHooksDisposeSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( commentCheckerDisposeSpy ) . toHaveBeenCalledTimes ( 1 )
2026-03-11 20:12:56 +09:00
expect ( runtimeFallbackDisposeSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( todoContinuationEnforcerDisposeSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( autoSlashCommandDisposeSpy ) . toHaveBeenCalledTimes ( 1 )
} )
test ( "#given dispose already called #when dispose() called again #then no errors" , async ( ) = > {
// given
const backgroundManager = {
2026-03-11 21:30:04 +09:00
shutdown : async ( ) : Promise < void > = > { } ,
2026-03-11 20:12:56 +09:00
}
const skillMcpManager = {
disconnectAll : async ( ) : Promise < void > = > { } ,
}
2026-03-31 17:33:19 -07:00
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
2026-03-11 20:12:56 +09:00
const disposeHooks = {
run : ( ) : void = > { } ,
}
const shutdownSpy = spyOn ( backgroundManager , "shutdown" )
const disconnectAllSpy = spyOn ( skillMcpManager , "disconnectAll" )
2026-03-31 17:33:19 -07:00
const stopAllSpy = spyOn ( lspManager , "stopAll" )
2026-03-11 20:12:56 +09:00
const disposeHooksSpy = spyOn ( disposeHooks , "run" )
const dispose = createPluginDispose ( {
backgroundManager ,
skillMcpManager ,
2026-03-31 17:33:19 -07:00
lspManager ,
2026-03-11 20:12:56 +09:00
disposeHooks : disposeHooks.run ,
} )
// when
await dispose ( )
await dispose ( )
// then
2026-03-31 17:33:19 -07:00
expect ( shutdownSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( disconnectAllSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( stopAllSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( disposeHooksSpy ) . toHaveBeenCalledTimes ( 1 )
2026-03-11 20:12:56 +09:00
} )
2026-03-12 00:28:19 +09:00
test ( "#given backgroundManager.shutdown() throws #when dispose() is called #then skillMcpManager.disconnectAll() and disposeHooks() are still called" , async ( ) = > {
// given
const backgroundManager = {
shutdown : async ( ) : Promise < void > = > {
throw new Error ( "shutdown failed" )
} ,
}
const skillMcpManager = {
disconnectAll : async ( ) : Promise < void > = > { } ,
}
2026-03-31 17:33:19 -07:00
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
2026-03-12 00:28:19 +09:00
const disposeHooksCalls : number [ ] = [ ]
const disconnectAllSpy = spyOn ( skillMcpManager , "disconnectAll" )
const dispose = createPluginDispose ( {
backgroundManager ,
skillMcpManager ,
2026-03-31 17:33:19 -07:00
lspManager ,
2026-03-12 00:28:19 +09:00
disposeHooks : ( ) : void = > {
disposeHooksCalls . push ( 1 )
} ,
} )
// when
await dispose ( )
// then
expect ( disconnectAllSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( disposeHooksCalls ) . toHaveLength ( 1 )
} )
test ( "#given skillMcpManager.disconnectAll() throws #when dispose() is called #then disposeHooks() is still called" , async ( ) = > {
// given
const backgroundManager = {
shutdown : async ( ) : Promise < void > = > { } ,
}
const skillMcpManager = {
disconnectAll : async ( ) : Promise < void > = > {
throw new Error ( "disconnectAll failed" )
} ,
}
2026-03-31 17:33:19 -07:00
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
2026-03-12 00:28:19 +09:00
const disposeHooksCalls : number [ ] = [ ]
const shutdownSpy = spyOn ( backgroundManager , "shutdown" )
const dispose = createPluginDispose ( {
backgroundManager ,
skillMcpManager ,
2026-03-31 17:33:19 -07:00
lspManager ,
2026-03-12 00:28:19 +09:00
disposeHooks : ( ) : void = > {
disposeHooksCalls . push ( 1 )
} ,
} )
// when
await dispose ( )
// then
expect ( shutdownSpy ) . toHaveBeenCalledTimes ( 1 )
expect ( disposeHooksCalls ) . toHaveLength ( 1 )
} )
2026-03-31 17:33:19 -07:00
test ( "#given active LSP clients #when dispose runs #then lsp manager is stopped" , async ( ) = > {
// given
const lspManager = {
stopAll : async ( ) : Promise < void > = > { } ,
}
const stopAllSpy = spyOn ( lspManager , "stopAll" )
const dispose = createPluginDispose ( {
backgroundManager : {
shutdown : async ( ) : Promise < void > = > { } ,
} ,
skillMcpManager : {
disconnectAll : async ( ) : Promise < void > = > { } ,
} ,
lspManager ,
disposeHooks : ( ) : void = > { } ,
} )
// when
await dispose ( )
// then
expect ( stopAllSpy ) . toHaveBeenCalledTimes ( 1 )
} )
2026-03-11 20:12:56 +09:00
} )