@@ -13,8 +13,10 @@ interface MockView {
1313 setPermissionRequestHandler : ReturnType < typeof vi . fn >
1414 setPermissionCheckHandler : ReturnType < typeof vi . fn >
1515 }
16+ on : ReturnType < typeof vi . fn >
1617 setWindowOpenHandler : ReturnType < typeof vi . fn >
1718 loadURL : ReturnType < typeof vi . fn >
19+ focus : ReturnType < typeof vi . fn >
1820 isDestroyed : ReturnType < typeof vi . fn >
1921 setBackgroundThrottling : ReturnType < typeof vi . fn >
2022 capturePage : ReturnType < typeof vi . fn >
@@ -75,6 +77,78 @@ describe('browser-agent session', () => {
7577 expect ( session . listTabs ( ) [ 0 ] ) . toMatchObject ( { tabId : first . id , active : true } )
7678 } )
7779
80+ it ( 'normalizes browser shortcuts to Command on macOS and Control elsewhere' , ( ) => {
81+ const input = {
82+ type : 'keyDown' ,
83+ key : 'l' ,
84+ isAutoRepeat : false ,
85+ isComposing : false ,
86+ shift : false ,
87+ control : false ,
88+ alt : false ,
89+ meta : true ,
90+ }
91+
92+ expect ( session . browserShortcutForInput ( input , 'darwin' ) ) . toBe ( 'focus-omnibox' )
93+ expect ( session . browserShortcutForInput ( input , 'win32' ) ) . toBeNull ( )
94+ expect ( session . browserShortcutForInput ( { ...input , meta : false , control : true } , 'win32' ) ) . toBe (
95+ 'focus-omnibox'
96+ )
97+ expect ( session . browserShortcutForInput ( { ...input , key : 't' } , 'darwin' ) ) . toBe ( 'new-tab' )
98+ expect ( session . browserShortcutForInput ( { ...input , key : 'w' } , 'darwin' ) ) . toBe ( 'close-tab' )
99+ expect (
100+ session . browserShortcutForInput ( { ...input , key : 't' , shift : true } , 'darwin' )
101+ ) . toBeNull ( )
102+ } )
103+
104+ it ( 'handles browser shortcuts from a focused native tab' , ( ) => {
105+ session . setPanelBounds ( { x : 100 , y : 50 , width : 800 , height : 600 } )
106+ const first = session . requireTab ( )
107+ const firstContents = ( first . view as unknown as MockView ) . webContents
108+ const beforeInput = firstContents . on . mock . calls . find (
109+ ( [ eventName ] ) => eventName === 'before-input-event'
110+ ) ?. [ 1 ] as
111+ | ( ( event : { preventDefault : ( ) => void } , input : Record < string , unknown > ) => void )
112+ | undefined
113+ const event = { preventDefault : vi . fn ( ) }
114+ const input = {
115+ type : 'keyDown' ,
116+ key : 'l' ,
117+ isAutoRepeat : false ,
118+ isComposing : false ,
119+ shift : false ,
120+ control : process . platform !== 'darwin' ,
121+ alt : false ,
122+ meta : process . platform === 'darwin' ,
123+ }
124+
125+ beforeInput ?.( event , input )
126+ expect ( event . preventDefault ) . toHaveBeenCalled ( )
127+ expect ( win . webContents . focus ) . toHaveBeenCalled ( )
128+ expect ( win . webContents . send ) . toHaveBeenLastCalledWith ( 'browser-agent:focus-omnibox' , 'select' )
129+
130+ beforeInput ?.( event , { ...input , key : 't' } )
131+ expect ( session . listTabs ( ) ) . toHaveLength ( 2 )
132+ expect ( win . webContents . send ) . toHaveBeenLastCalledWith ( 'browser-agent:focus-omnibox' , 'clear' )
133+
134+ const second = session . activeTab ( )
135+ expect ( second ) . not . toBeNull ( )
136+ const secondContents = ( second ?. view as unknown as MockView ) . webContents
137+ const secondBeforeInput = secondContents . on . mock . calls . find (
138+ ( [ eventName ] ) => eventName === 'before-input-event'
139+ ) ?. [ 1 ] as
140+ | ( ( event : { preventDefault : ( ) => void } , input : Record < string , unknown > ) => void )
141+ | undefined
142+ secondBeforeInput ?.( event , { ...input , key : 'w' } )
143+ expect ( session . listTabs ( ) ) . toHaveLength ( 1 )
144+ expect ( firstContents . focus ) . toHaveBeenCalled ( )
145+
146+ beforeInput ?.( event , { ...input , key : 'w' } )
147+ expect ( session . listTabs ( ) ) . toHaveLength ( 1 )
148+ expect ( session . listTabs ( ) [ 0 ] . tabId ) . not . toBe ( first . id )
149+ expect ( win . webContents . send ) . toHaveBeenLastCalledWith ( 'browser-agent:focus-omnibox' , 'clear' )
150+ } )
151+
78152 it ( 'only disables hidden-page throttling while browser automation is active' , ( ) => {
79153 const tab = session . ensureTab ( )
80154 const contents = ( tab . view as unknown as MockView ) . webContents
@@ -167,6 +241,20 @@ describe('browser-agent session', () => {
167241 expect ( removeChildView ) . toHaveBeenCalledWith ( tab . view )
168242 } )
169243
244+ it ( 'creates one real default tab when the browser panel becomes visible' , ( ) => {
245+ expect ( session . listTabs ( ) ) . toHaveLength ( 0 )
246+
247+ session . setPanelBounds ( { x : 100 , y : 50 , width : 800 , height : 600 } )
248+
249+ expect ( session . listTabs ( ) ) . toHaveLength ( 1 )
250+ expect ( session . getTabsState ( ) . activeTabId ) . toBe ( session . listTabs ( ) [ 0 ] . tabId )
251+
252+ const firstTabId = session . listTabs ( ) [ 0 ] . tabId
253+ session . closeTab ( firstTabId )
254+ expect ( session . listTabs ( ) ) . toHaveLength ( 1 )
255+ expect ( session . listTabs ( ) [ 0 ] . tabId ) . not . toBe ( firstTabId )
256+ } )
257+
170258 it ( 'clears a stale attachment without touching a destroyed host window' , ( ) => {
171259 const tab = session . ensureTab ( )
172260 session . setPanelBounds ( { x : 100 , y : 50 , width : 800 , height : 600 } )
0 commit comments