@@ -32,18 +32,32 @@ describe("permission prompt navigation", () => {
3232 expect ( findPermOptionByKey ( "i" ) ?. value ) . toBe ( "explain" ) ;
3333 } ) ;
3434
35- it ( "debounces rapid menu navigation" , ( ) => {
36- const canNavigateMenu = TuiApp . prototype [ "canNavigateMenu" ] as ( this : { menuNavDebounceUntil : number } , now ?: number ) => boolean ;
37- const state = { menuNavDebounceUntil : 0 } ;
35+ it ( "deduplicates rapid repeated menu navigation in the same direction " , ( ) => {
36+ const canNavigateMenu = TuiApp . prototype [ "canNavigateMenu" ] as ( this : { lastMenuNavDirection : "up" | "down" | null ; lastMenuNavAt : number } , direction : "up" | "down" , now ?: number ) => boolean ;
37+ const state = { lastMenuNavDirection : null , lastMenuNavAt : 0 } ;
3838
3939 vi . spyOn ( Date , "now" )
4040 . mockReturnValueOnce ( 100 )
4141 . mockReturnValueOnce ( 120 )
4242 . mockReturnValueOnce ( 220 ) ;
4343
44- expect ( canNavigateMenu . call ( state ) ) . toBe ( true ) ;
45- expect ( canNavigateMenu . call ( state ) ) . toBe ( false ) ;
46- expect ( canNavigateMenu . call ( state ) ) . toBe ( true ) ;
44+ expect ( canNavigateMenu . call ( state , "down" ) ) . toBe ( true ) ;
45+ expect ( canNavigateMenu . call ( state , "down" ) ) . toBe ( false ) ;
46+ expect ( canNavigateMenu . call ( state , "down" ) ) . toBe ( true ) ;
47+ } ) ;
48+
49+ it ( "allows immediate direction changes in menu navigation" , ( ) => {
50+ const canNavigateMenu = TuiApp . prototype [ "canNavigateMenu" ] as ( this : { lastMenuNavDirection : "up" | "down" | null ; lastMenuNavAt : number } , direction : "up" | "down" , now ?: number ) => boolean ;
51+ const state = { lastMenuNavDirection : null , lastMenuNavAt : 0 } ;
52+
53+ vi . spyOn ( Date , "now" )
54+ . mockReturnValueOnce ( 100 )
55+ . mockReturnValueOnce ( 120 )
56+ . mockReturnValueOnce ( 140 ) ;
57+
58+ expect ( canNavigateMenu . call ( state , "down" ) ) . toBe ( true ) ;
59+ expect ( canNavigateMenu . call ( state , "up" ) ) . toBe ( true ) ;
60+ expect ( canNavigateMenu . call ( state , "down" ) ) . toBe ( true ) ;
4761 } ) ;
4862
4963 it ( "submits input idea even while a tool call is waiting" , ( ) => {
@@ -56,6 +70,7 @@ describe("permission prompt navigation", () => {
5670 const state = {
5771 processing : true ,
5872 permissionExplainMode : true ,
73+ pendingImages : [ ] ,
5974 pendingPermissionContext : { toolName : "write_file" , args : { path : "/tmp/test.json" } } ,
6075 editor : { setText : vi . fn ( ) } ,
6176 resolvePermissionChoice,
@@ -104,4 +119,54 @@ describe("permission prompt navigation", () => {
104119 expect ( state . processing ) . toBe ( true ) ;
105120 expect ( state . editor . disableSubmit ) . toBe ( false ) ;
106121 } ) ;
122+
123+ it ( "submits image-only messages" , ( ) => {
124+ const handleSubmit = TuiApp . prototype [ "handleSubmit" ] as ( this : any , text : string ) => void ;
125+ const prompt = vi . fn ( ) . mockResolvedValue ( undefined ) ;
126+ const state = {
127+ pendingImages : [ { type : "image" , data : "abcd" , mimeType : "image/png" } ] ,
128+ processing : false ,
129+ permissionExplainMode : false ,
130+ editor : { setText : vi . fn ( ) } ,
131+ deps : {
132+ agent : { prompt } ,
133+ modelSupportsImages : true ,
134+ modelNeedsOcr : false ,
135+ } ,
136+ conversation : { addInfo : vi . fn ( ) } ,
137+ addUserMessage : vi . fn ( ) ,
138+ setProcessing : vi . fn ( ) ,
139+ updateImageStatus : vi . fn ( ) ,
140+ addError : vi . fn ( ) ,
141+ stop : vi . fn ( ) ,
142+ } ;
143+
144+ handleSubmit . call ( state , "" ) ;
145+
146+ expect ( state . editor . setText ) . toHaveBeenCalledWith ( "" ) ;
147+ expect ( state . addUserMessage ) . toHaveBeenCalledWith ( expect . stringContaining ( "1 image(s) attached" ) ) ;
148+ expect ( state . setProcessing ) . toHaveBeenCalledWith ( true ) ;
149+ expect ( state . pendingImages ) . toEqual ( [ ] ) ;
150+ expect ( state . updateImageStatus ) . toHaveBeenCalled ( ) ;
151+ expect ( prompt ) . toHaveBeenCalledWith ( "" , [
152+ { type : "image" , data : "abcd" , mimeType : "image/png" } ,
153+ ] ) ;
154+ } ) ;
155+
156+ it ( "keeps empty submit as a no-op when there is no text or image" , ( ) => {
157+ const handleSubmit = TuiApp . prototype [ "handleSubmit" ] as ( this : any , text : string ) => void ;
158+ const state = {
159+ pendingImages : [ ] ,
160+ editor : { setText : vi . fn ( ) } ,
161+ deps : { agent : { prompt : vi . fn ( ) } } ,
162+ addUserMessage : vi . fn ( ) ,
163+ setProcessing : vi . fn ( ) ,
164+ } ;
165+
166+ handleSubmit . call ( state , "" ) ;
167+
168+ expect ( state . editor . setText ) . not . toHaveBeenCalled ( ) ;
169+ expect ( state . addUserMessage ) . not . toHaveBeenCalled ( ) ;
170+ expect ( state . setProcessing ) . not . toHaveBeenCalled ( ) ;
171+ } ) ;
107172} ) ;
0 commit comments