@@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
22
33vi . mock ( 'electron' , ( ) => import ( '@/test/electron-mock' ) )
44
5+ import { MAX_BROWSER_TABS } from '@sim/browser-protocol'
56import { BrowserWindow } from 'electron'
67
78type SessionModule = typeof import ( '@/main/browser-agent/session' )
@@ -15,7 +16,9 @@ interface MockView {
1516 setWindowOpenHandler : ReturnType < typeof vi . fn >
1617 loadURL : ReturnType < typeof vi . fn >
1718 setBackgroundThrottling : ReturnType < typeof vi . fn >
19+ capturePage : ReturnType < typeof vi . fn >
1820 }
21+ setBackgroundColor : ReturnType < typeof vi . fn >
1922 setBounds : ReturnType < typeof vi . fn >
2023 setVisible : ReturnType < typeof vi . fn >
2124}
@@ -32,15 +35,21 @@ function mainWindowMock() {
3235 return win as unknown as BrowserWindow
3336}
3437
35- async function freshSession ( win : BrowserWindow | null ) : Promise < SessionModule > {
38+ async function freshSession (
39+ win : BrowserWindow | null ,
40+ eventOverrides : Partial < import ( '@/main/browser-agent/session' ) . AgentSessionEvents > = { }
41+ ) : Promise < SessionModule > {
3642 vi . resetModules ( )
3743 const session = await import ( '@/main/browser-agent/session' )
3844 session . initSession (
3945 {
4046 onSessionClosed : vi . fn ( ) ,
4147 onTabCreated : vi . fn ( ) ,
4248 onActiveTabChanged : vi . fn ( ) ,
49+ onTabsChanged : vi . fn ( ) ,
50+ onTabThemeChanged : vi . fn ( ) ,
4351 onDownloadBlocked : vi . fn ( ) ,
52+ ...eventOverrides ,
4453 } ,
4554 ( ) => win
4655 )
@@ -76,6 +85,32 @@ describe('browser-agent session', () => {
7685 expect ( contents . setBackgroundThrottling ) . toHaveBeenLastCalledWith ( true )
7786 } )
7887
88+ it ( 'updates the native backdrop when Sim changes browser theme' , ( ) => {
89+ const tab = session . ensureTab ( )
90+ const view = tab . view as unknown as MockView
91+
92+ session . setBrowserTheme ( 'dark' )
93+ expect ( session . getBrowserTheme ( ) ) . toBe ( 'dark' )
94+ expect ( view . setBackgroundColor ) . toHaveBeenLastCalledWith ( '#0c0c0c' )
95+
96+ session . setBrowserTheme ( 'light' )
97+ expect ( view . setBackgroundColor ) . toHaveBeenLastCalledWith ( '#ffffff' )
98+ } )
99+
100+ it ( 'propagates theme changes to every existing tab' , async ( ) => {
101+ const onTabThemeChanged = vi . fn ( )
102+ const themedSession = await freshSession ( win , { onTabThemeChanged } )
103+ const first = themedSession . ensureTab ( )
104+ const second = themedSession . addTab ( )
105+
106+ themedSession . setBrowserTheme ( 'dark' )
107+
108+ expect ( onTabThemeChanged . mock . calls ) . toEqual ( [
109+ [ first . view . webContents , 'dark' ] ,
110+ [ second . view . webContents , 'dark' ] ,
111+ ] )
112+ } )
113+
79114 it ( 'requireTab refuses when no page is open yet' , ( ) => {
80115 expect ( ( ) => session . requireTab ( ) ) . toThrow ( / N o p a g e i s o p e n y e t / )
81116 } )
@@ -98,6 +133,18 @@ describe('browser-agent session', () => {
98133 expect ( ( ) => session . closeTab ( '999' ) ) . toThrow ( / N o t a b w i t h i d 9 9 9 / )
99134 } )
100135
136+ it ( 'limits the browser session to five open tabs' , ( ) => {
137+ session . ensureTab ( )
138+ for ( let index = 1 ; index < MAX_BROWSER_TABS ; index ++ ) {
139+ session . addTab ( )
140+ }
141+
142+ expect ( session . listTabs ( ) ) . toHaveLength ( MAX_BROWSER_TABS )
143+ expect ( ( ) => session . addTab ( ) ) . toThrow (
144+ `The browser supports up to ${ MAX_BROWSER_TABS } open tabs.`
145+ )
146+ } )
147+
101148 it ( 'embeds the active view in the MAIN window only while panel bounds are reported' , ( ) => {
102149 const tab = session . ensureTab ( )
103150 const view = tab . view as unknown as MockView
@@ -136,7 +183,37 @@ describe('browser-agent session', () => {
136183 } )
137184 } )
138185
139- it ( 'hardens every tab: agent partition default-denies permissions and popups collapse into the same view' , ( ) => {
186+ it ( 'keeps an occluded view attached, captures its frame, and toggles visibility' , async ( ) => {
187+ const tab = session . ensureTab ( )
188+ const view = tab . view as unknown as MockView
189+ const content = (
190+ win as unknown as {
191+ contentView : {
192+ addChildView : ReturnType < typeof vi . fn >
193+ removeChildView : ReturnType < typeof vi . fn >
194+ }
195+ }
196+ ) . contentView
197+ session . setPanelBounds ( { x : 100 , y : 50 , width : 800 , height : 600 } )
198+ content . removeChildView . mockClear ( )
199+ view . setVisible . mockClear ( )
200+
201+ session . setPanelOccluded ( true )
202+
203+ expect ( content . removeChildView ) . not . toHaveBeenCalled ( )
204+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
205+ await vi . waitFor ( ( ) => {
206+ expect ( win . webContents . send ) . toHaveBeenCalledWith ( 'browser-agent:panel-snapshot' , {
207+ dataUrl : 'data:image/png;base64,c2lt' ,
208+ tabId : tab . id ,
209+ } )
210+ } )
211+
212+ session . setPanelOccluded ( false )
213+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( true )
214+ } )
215+
216+ it ( 'hardens every tab and keeps http popups inside a new internal tab' , ( ) => {
140217 const tab = session . ensureTab ( )
141218 const contents = ( tab . view as unknown as MockView ) . webContents
142219 expect ( contents . session . setPermissionRequestHandler ) . toHaveBeenCalled ( )
@@ -146,7 +223,11 @@ describe('browser-agent session', () => {
146223 url : string
147224 } ) => { action : string }
148225 expect ( openHandler ( { url : 'https://example.com/popup' } ) ) . toEqual ( { action : 'deny' } )
149- expect ( contents . loadURL ) . toHaveBeenCalledWith ( 'https://example.com/popup' )
226+ expect ( session . listTabs ( ) ) . toHaveLength ( 2 )
227+ const popupContents = ( session . activeTab ( ) ?. view as unknown as MockView | undefined )
228+ ?. webContents
229+ expect ( popupContents ?. loadURL ) . toHaveBeenCalledWith ( 'https://example.com/popup' )
230+ expect ( contents . loadURL ) . not . toHaveBeenCalledWith ( 'https://example.com/popup' )
150231 // Non-http(s) popups are denied without navigating anywhere.
151232 contents . loadURL . mockClear ( )
152233 expect ( openHandler ( { url : 'file:///etc/passwd' } ) ) . toEqual ( { action : 'deny' } )
0 commit comments