@@ -69,6 +69,18 @@ describe("command registry", () => {
6969 expect ( def ?. handler ( "" , ctx ) ) . toEqual ( { type : "message" , text : "built-in" } ) ;
7070 } ) ;
7171
72+ it ( "keeps command-specific availability visibility-only" , ( ) => {
73+ registerCommand ( {
74+ name : "unavailable-but-callable" ,
75+ description : "visibility gated" ,
76+ available : ( ) => false ,
77+ handler : ( ) => ( { type : "noop" } ) ,
78+ } ) ;
79+
80+ expect ( listCommands ( ) . map ( ( command ) => command . name ) ) . not . toContain ( "unavailable-but-callable" ) ;
81+ expect ( getCommand ( "unavailable-but-callable" ) ) . toBeDefined ( ) ;
82+ } ) ;
83+
7284 it ( "invokes handler with args and context" , ( ) => {
7385 let receivedArgs = "" ;
7486 let clearCalled = false ;
@@ -102,6 +114,141 @@ describe("registerCommandPlugin", () => {
102114 expect ( getCommand ( "plugin-cmd-a" ) ) . toBeDefined ( ) ;
103115 expect ( getCommand ( "plugin-cmd-b" ) ) . toBeDefined ( ) ;
104116 } ) ;
117+
118+ it ( "re-resolves activation for discovery and execution" , ( ) => {
119+ let active = true ;
120+ registerCommandPlugin (
121+ {
122+ commands : [
123+ {
124+ name : "live-plugin-cmd" ,
125+ description : "live plugin" ,
126+ handler : ( ) => ( { type : "message" , text : "ran" } ) ,
127+ } ,
128+ ] ,
129+ } ,
130+ ( ) => active ,
131+ ) ;
132+
133+ expect ( listCommands ( ) . map ( ( command ) => command . name ) ) . toContain ( "live-plugin-cmd" ) ;
134+ expect ( getCommand ( "live-plugin-cmd" ) ?. handler ( "" , ctx ) ) . toEqual ( {
135+ type : "message" ,
136+ text : "ran" ,
137+ } ) ;
138+
139+ active = false ;
140+ expect ( listCommands ( ) . map ( ( command ) => command . name ) ) . not . toContain ( "live-plugin-cmd" ) ;
141+ expect ( getCommand ( "live-plugin-cmd" ) ) . toBeUndefined ( ) ;
142+
143+ active = true ;
144+ expect ( listCommands ( ) . map ( ( command ) => command . name ) ) . toContain ( "live-plugin-cmd" ) ;
145+ expect ( getCommand ( "live-plugin-cmd" ) ) . toBeDefined ( ) ;
146+ } ) ;
147+
148+ it ( "serves the next candidate when the first plugin deactivates" , ( ) => {
149+ let firstActive = true ;
150+ registerCommandPlugin (
151+ {
152+ commands : [
153+ {
154+ name : "plugin-live-fallback" ,
155+ description : "first" ,
156+ handler : ( ) => ( { type : "noop" } ) ,
157+ } ,
158+ ] ,
159+ } ,
160+ ( ) => firstActive ,
161+ ) ;
162+ registerCommandPlugin ( {
163+ commands : [
164+ {
165+ name : "plugin-live-fallback" ,
166+ description : "second" ,
167+ handler : ( ) => ( { type : "noop" } ) ,
168+ } ,
169+ ] ,
170+ } ) ;
171+
172+ expect ( getCommand ( "plugin-live-fallback" ) ?. description ) . toBe ( "first" ) ;
173+ expect (
174+ listCommands ( ) . find ( ( command ) => command . name === "plugin-live-fallback" ) ?. description ,
175+ ) . toBe ( "first" ) ;
176+
177+ firstActive = false ;
178+ expect ( getCommand ( "plugin-live-fallback" ) ?. description ) . toBe ( "second" ) ;
179+ expect (
180+ listCommands ( ) . find ( ( command ) => command . name === "plugin-live-fallback" ) ?. description ,
181+ ) . toBe ( "second" ) ;
182+ } ) ;
183+
184+ it ( "does not execute a typed slash name after the plugin deactivates" , ( ) => {
185+ let active = true ;
186+ registerCommandPlugin (
187+ {
188+ commands : [
189+ {
190+ name : "typed-after-disable" ,
191+ description : "typed" ,
192+ handler : ( ) => ( { type : "message" , text : "ran" } ) ,
193+ } ,
194+ ] ,
195+ } ,
196+ ( ) => active ,
197+ ) ;
198+
199+ const stalePaletteRow = "typed-after-disable" ;
200+ expect ( getCommand ( stalePaletteRow ) ) . toBeDefined ( ) ;
201+ active = false ;
202+ expect ( getCommand ( stalePaletteRow ) ) . toBeUndefined ( ) ;
203+ } ) ;
204+
205+ it ( "lets an enabled plugin claim a name ahead of a disabled candidate" , ( ) => {
206+ registerCommandPlugin (
207+ {
208+ commands : [
209+ {
210+ name : "plugin-candidate-collision" ,
211+ description : "disabled candidate" ,
212+ handler : ( ) => ( { type : "noop" } ) ,
213+ } ,
214+ ] ,
215+ } ,
216+ ( ) => false ,
217+ ) ;
218+ registerCommandPlugin (
219+ {
220+ commands : [
221+ {
222+ name : "plugin-candidate-collision" ,
223+ description : "enabled candidate" ,
224+ handler : ( ) => ( { type : "noop" } ) ,
225+ } ,
226+ ] ,
227+ } ,
228+ ( ) => true ,
229+ ) ;
230+
231+ expect ( getCommand ( "plugin-candidate-collision" ) ?. description ) . toBe ( "enabled candidate" ) ;
232+ } ) ;
233+
234+ it ( "never lets a plugin collision replace a built-in command" , ( ) => {
235+ registerCommand ( {
236+ name : "built-in-plugin-collision" ,
237+ description : "built-in" ,
238+ handler : ( ) => ( { type : "noop" } ) ,
239+ } ) ;
240+ registerCommandPlugin ( {
241+ commands : [
242+ {
243+ name : "built-in-plugin-collision" ,
244+ description : "plugin" ,
245+ handler : ( ) => ( { type : "noop" } ) ,
246+ } ,
247+ ] ,
248+ } ) ;
249+
250+ expect ( getCommand ( "built-in-plugin-collision" ) ?. description ) . toBe ( "built-in" ) ;
251+ } ) ;
105252} ) ;
106253
107254describe ( "setHiddenCommands" , ( ) => {
0 commit comments