@@ -11,108 +11,129 @@ afterEach(() => {
1111 resetSlashCommandsForTest ( ) ;
1212} ) ;
1313
14+ function isRecord ( value : unknown ) : value is Record < string , unknown > {
15+ return typeof value === "object" && value !== null && ! Array . isArray ( value ) ;
16+ }
17+
18+ function requireRecord ( value : unknown , label : string ) : Record < string , unknown > {
19+ if ( ! isRecord ( value ) ) {
20+ throw new Error ( `expected ${ label } to be an object` ) ;
21+ }
22+ return value ;
23+ }
24+
25+ function requireArray ( value : unknown , label : string ) : unknown [ ] {
26+ if ( ! Array . isArray ( value ) ) {
27+ throw new Error ( `expected ${ label } to be an array` ) ;
28+ }
29+ return value ;
30+ }
31+
32+ function expectRecordFields ( value : unknown , label : string , expected : Record < string , unknown > ) {
33+ const record = requireRecord ( value , label ) ;
34+ for ( const [ key , expectedValue ] of Object . entries ( expected ) ) {
35+ expect ( record [ key ] ) . toEqual ( expectedValue ) ;
36+ }
37+ }
38+
39+ function requireCommandByName ( name : string ) : Record < string , unknown > {
40+ return requireRecord (
41+ SLASH_COMMANDS . find ( ( entry ) => entry . name === name ) ,
42+ `slash command ${ name } ` ,
43+ ) ;
44+ }
45+
46+ function requireCommandByKey ( key : string ) : Record < string , unknown > {
47+ return requireRecord (
48+ SLASH_COMMANDS . find ( ( entry ) => entry . key === key ) ,
49+ `slash command ${ key } ` ,
50+ ) ;
51+ }
52+
53+ function expectParsedSlash ( input : string , commandFields : Record < string , unknown > , args : string ) {
54+ const parsed = requireRecord ( parseSlashCommand ( input ) , `parsed ${ input } ` ) ;
55+ expectRecordFields ( parsed . command , `parsed ${ input } command` , commandFields ) ;
56+ expect ( parsed . args ) . toBe ( args ) ;
57+ }
58+
1459describe ( "parseSlashCommand" , ( ) => {
1560 it ( "parses commands with an optional colon separator" , ( ) => {
16- expect ( parseSlashCommand ( "/think: high" ) ) . toMatchObject ( {
17- command : { name : "think" } ,
18- args : "high" ,
19- } ) ;
20- expect ( parseSlashCommand ( "/think:high" ) ) . toMatchObject ( {
21- command : { name : "think" } ,
22- args : "high" ,
23- } ) ;
24- expect ( parseSlashCommand ( "/help:" ) ) . toMatchObject ( {
25- command : { name : "help" } ,
26- args : "" ,
27- } ) ;
61+ expectParsedSlash ( "/think: high" , { name : "think" } , "high" ) ;
62+ expectParsedSlash ( "/think:high" , { name : "think" } , "high" ) ;
63+ expectParsedSlash ( "/help:" , { name : "help" } , "" ) ;
2864 } ) ;
2965
3066 it ( "still parses space-delimited commands" , ( ) => {
31- expect ( parseSlashCommand ( "/verbose full" ) ) . toMatchObject ( {
32- command : { name : "verbose" } ,
33- args : "full" ,
34- } ) ;
67+ expectParsedSlash ( "/verbose full" , { name : "verbose" } , "full" ) ;
3568 } ) ;
3669
3770 it ( "parses fast commands" , ( ) => {
38- expect ( parseSlashCommand ( "/fast:on" ) ) . toMatchObject ( {
39- command : { name : "fast" } ,
40- args : "on" ,
41- } ) ;
71+ expectParsedSlash ( "/fast:on" , { name : "fast" } , "on" ) ;
4272 } ) ;
4373
4474 it ( "keeps /status on the agent path" , ( ) => {
4575 const status = SLASH_COMMANDS . find ( ( entry ) => entry . name === "status" ) ;
4676 expect ( status ?. executeLocal ) . not . toBe ( true ) ;
47- expect ( parseSlashCommand ( "/status" ) ) . toMatchObject ( {
48- command : { name : "status" } ,
49- args : "" ,
50- } ) ;
77+ expectParsedSlash ( "/status" , { name : "status" } , "" ) ;
5178 } ) ;
5279
5380 it ( "includes shared /tools with shared arg hints" , ( ) => {
54- const tools = SLASH_COMMANDS . find ( ( entry ) => entry . name === "tools" ) ;
55- expect ( tools ) . toMatchObject ( {
81+ const tools = requireCommandByName ( "tools" ) ;
82+ expectRecordFields ( tools , "tools command" , {
5683 key : "tools" ,
5784 description : "List available runtime tools." ,
5885 argOptions : [ "compact" , "verbose" ] ,
5986 executeLocal : false ,
6087 } ) ;
61- expect ( parseSlashCommand ( "/tools verbose" ) ) . toMatchObject ( {
62- command : { name : "tools" } ,
63- args : "verbose" ,
64- } ) ;
88+ expectParsedSlash ( "/tools verbose" , { name : "tools" } , "verbose" ) ;
6589 } ) ;
6690
6791 it ( "parses slash aliases through the shared registry" , ( ) => {
68- const exportCommand = SLASH_COMMANDS . find ( ( entry ) => entry . key === "export-session" ) ;
69- expect ( exportCommand ) . toMatchObject ( {
92+ const exportCommand = requireCommandByKey ( "export-session" ) ;
93+ expectRecordFields ( exportCommand , "export-session command" , {
7094 name : "export-session" ,
7195 aliases : [ "export" ] ,
7296 executeLocal : true ,
7397 } ) ;
74- expect ( parseSlashCommand ( "/export" ) ) . toMatchObject ( {
75- command : { key : "export-session" } ,
76- args : "" ,
77- } ) ;
78- expect ( parseSlashCommand ( "/export-session" ) ) . toMatchObject ( {
79- command : { key : "export-session" } ,
80- args : "" ,
81- } ) ;
82- expect ( parseSlashCommand ( "/side what changed?" ) ) . toMatchObject ( {
83- command : { key : "btw" , name : "btw" , aliases : expect . arrayContaining ( [ "side" ] ) } ,
84- args : "what changed?" ,
85- } ) ;
98+ expectParsedSlash ( "/export" , { key : "export-session" } , "" ) ;
99+ expectParsedSlash ( "/export-session" , { key : "export-session" } , "" ) ;
100+ const side = requireRecord ( parseSlashCommand ( "/side what changed?" ) , "parsed /side" ) ;
101+ expectRecordFields ( side . command , "side command" , { key : "btw" , name : "btw" } ) ;
102+ expect (
103+ requireArray ( requireRecord ( side . command , "side command" ) . aliases , "side aliases" ) ,
104+ ) . toContain ( "side" ) ;
105+ expect ( side . args ) . toBe ( "what changed?" ) ;
86106 } ) ;
87107
88108 it ( "keeps canonical long-form slash names as the primary menu command" , ( ) => {
89- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . key === "verbose" ) ) . toMatchObject ( {
109+ expectRecordFields ( requireCommandByKey ( "verbose" ) , "verbose command" , {
90110 name : "verbose" ,
91111 aliases : [ "v" ] ,
92112 } ) ;
93- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . key === "think" ) ) . toMatchObject ( {
113+ const think = requireCommandByKey ( "think" ) ;
114+ expectRecordFields ( think , "think command" , {
94115 name : "think" ,
95- aliases : expect . arrayContaining ( [ "thinking" , "t" ] ) ,
96116 } ) ;
117+ const aliases = requireArray ( think . aliases , "think aliases" ) ;
118+ expect ( aliases ) . toContain ( "thinking" ) ;
119+ expect ( aliases ) . toContain ( "t" ) ;
97120 } ) ;
98121
99122 it ( "keeps a single local /steer entry with the control-ui metadata" , ( ) => {
100123 const steerEntries = SLASH_COMMANDS . filter ( ( entry ) => entry . name === "steer" ) ;
101124 expect ( steerEntries ) . toHaveLength ( 1 ) ;
102- expect ( steerEntries [ 0 ] ) . toMatchObject ( {
125+ const steer = requireRecord ( steerEntries [ 0 ] , "steer command" ) ;
126+ expectRecordFields ( steer , "steer command" , {
103127 key : "steer" ,
104128 description : "Inject a message into the active run" ,
105129 args : "[id] <message>" ,
106- aliases : expect . arrayContaining ( [ "tell" ] ) ,
107130 executeLocal : true ,
108131 } ) ;
132+ expect ( requireArray ( steer . aliases , "steer aliases" ) ) . toContain ( "tell" ) ;
109133 } ) ;
110134
111135 it ( "keeps focus as a local slash command" , ( ) => {
112- expect ( parseSlashCommand ( "/focus" ) ) . toMatchObject ( {
113- command : { key : "focus" , executeLocal : true } ,
114- args : "" ,
115- } ) ;
136+ expectParsedSlash ( "/focus" , { key : "focus" , executeLocal : true } , "" ) ;
116137 } ) ;
117138
118139 it ( "refreshes runtime commands from commands.list so docks, plugins, and direct skills appear" , async ( ) => {
@@ -154,23 +175,20 @@ describe("parseSlashCommand", () => {
154175 agentId : "main" ,
155176 } ) ;
156177
157- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "dock-discord" ) ) . toMatchObject ( {
178+ expectRecordFields ( requireCommandByName ( "dock-discord" ) , "dock-discord command" , {
158179 aliases : [ "dock_discord" ] ,
159180 category : "tools" ,
160181 executeLocal : false ,
161182 } ) ;
162- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "dreaming" ) ) . toMatchObject ( {
183+ expectRecordFields ( requireCommandByName ( "dreaming" ) , "dreaming command" , {
163184 key : "dreaming" ,
164185 executeLocal : false ,
165186 } ) ;
166- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "prose" ) ) . toMatchObject ( {
187+ expectRecordFields ( requireCommandByName ( "prose" ) , "prose command" , {
167188 key : "prose" ,
168189 executeLocal : false ,
169190 } ) ;
170- expect ( parseSlashCommand ( "/dock_discord" ) ) . toMatchObject ( {
171- command : { name : "dock-discord" } ,
172- args : "" ,
173- } ) ;
191+ expectParsedSlash ( "/dock_discord" , { name : "dock-discord" } , "" ) ;
174192 } ) ;
175193
176194 it ( "does not let remote commands collide with reserved local commands" , async ( ) => {
@@ -200,12 +218,12 @@ describe("parseSlashCommand", () => {
200218 agentId : "main" ,
201219 } ) ;
202220
203- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "redirect" ) ) . toMatchObject ( {
221+ expectRecordFields ( requireCommandByName ( "redirect" ) , "redirect command" , {
204222 key : "redirect" ,
205223 executeLocal : true ,
206224 description : "Abort and restart with a new message" ,
207225 } ) ;
208- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "kill" ) ) . toMatchObject ( {
226+ expectRecordFields ( requireCommandByName ( "kill" ) , "kill command" , {
209227 key : "kill" ,
210228 executeLocal : true ,
211229 description : "Kill a running subagent (or all)." ,
@@ -239,14 +257,12 @@ describe("parseSlashCommand", () => {
239257 agentId : "main" ,
240258 } ) ;
241259
242- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "safe-name" ) ) . toMatchObject ( {
260+ expectRecordFields ( requireCommandByName ( "safe-name" ) , "safe-name command" , {
243261 name : "safe-name" ,
244262 } ) ;
245263 expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "prose now" ) ) . toBeUndefined ( ) ;
246264 expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "bad:alias" ) ) . toBeUndefined ( ) ;
247- expect ( parseSlashCommand ( "/safe-name" ) ) . toMatchObject ( {
248- command : { name : "safe-name" } ,
249- } ) ;
265+ expectParsedSlash ( "/safe-name" , { name : "safe-name" } , "" ) ;
250266 } ) ;
251267
252268 it ( "caps remote command payload size and long metadata before it reaches UI state" , async ( ) => {
@@ -364,11 +380,11 @@ describe("parseSlashCommand", () => {
364380 client : { request } as never ,
365381 agentId : "main" ,
366382 } ) ;
367- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "valid" ) ) . toMatchObject ( {
383+ expectRecordFields ( requireCommandByName ( "valid" ) , "valid command" , {
368384 name : "valid" ,
369385 description : "" ,
370386 } ) ;
371- expect ( SLASH_COMMANDS . find ( ( entry ) => entry . name === "pair" ) ) . toMatchObject ( {
387+ expectRecordFields ( requireCommandByName ( "pair" ) , "pair command" , {
372388 name : "pair" ,
373389 } ) ;
374390 } ) ;
0 commit comments