@@ -119,14 +119,157 @@ describe('panel chat scope', () => {
119119 } )
120120 } )
121121
122- it ( 'does not hide the native page behind a replacement captured at stale bounds' , async ( ) => {
123- const { win } = showPanel ( panel )
122+ it ( 'force-hides the native page when its replacement was captured at stale bounds' , async ( ) => {
123+ const { win, view } = showPanel ( panel )
124124 const scopeId = panel . getActivePanelScopeId ( )
125125
126126 await expect ( panel . capturePanelSnapshot ( win , scopeId ) ) . resolves . not . toBeNull ( )
127127 panel . setPanelBounds ( { x : 399 , y : 64 , width : 601 , height : 786 } , win )
128+ vi . mocked ( view . setVisible ) . mockClear ( )
129+
130+ expect ( panel . setPanelOccluded ( true , win , scopeId ) ) . toBe ( false )
131+ expect ( view . setVisible ) . not . toHaveBeenCalled ( )
132+
133+ expect ( panel . setPanelOccluded ( true , win , scopeId , true ) ) . toBe ( true )
134+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
135+ } )
136+
137+ it ( 'force-hides the native page without a captured replacement frame' , ( ) => {
138+ const { win, view } = showPanel ( panel )
139+ const scopeId = panel . getActivePanelScopeId ( )
140+ vi . mocked ( view . setVisible ) . mockClear ( )
128141
129142 expect ( panel . setPanelOccluded ( true , win , scopeId ) ) . toBe ( false )
143+ expect ( view . setVisible ) . not . toHaveBeenCalled ( )
144+
145+ expect ( panel . setPanelOccluded ( true , win , scopeId , true ) ) . toBe ( true )
146+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
147+ } )
148+
149+ it ( 'applies a forced hide before the panel reports its first bounds' , ( ) => {
150+ const win = new BrowserWindow ( )
151+ const view = new WebContentsView ( )
152+ const active = { id : 'tab-1' , scopeId : 'chat-test' , view, pinned : false }
153+ panel . initPanel ( {
154+ getMainWindow : ( ) => win ,
155+ activeTab : ( ) => active ,
156+ backgroundColor : ( ) => '#0c0c0c' ,
157+ ensureInitialTab : ( ) => { } ,
158+ onViewDetached : ( ) => { } ,
159+ } )
160+ panel . activatePanelScope ( 'chat-test' )
161+
162+ expect ( panel . setPanelOccluded ( true , win , 'chat-test' ) ) . toBe ( false )
163+ expect ( panel . setPanelOccluded ( true , win , 'chat-test' , true ) ) . toBe ( true )
164+
165+ panel . setPanelBounds ( PANEL_RECT , win )
166+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
167+ } )
168+
169+ it ( 'transfers a hidden lease to a focused window before its bounds arrive' , ( ) => {
170+ const { win, view } = showPanel ( panel )
171+ const other = new BrowserWindow ( )
172+ const scopeId = panel . getActivePanelScopeId ( )
173+ vi . mocked ( other . isFocused ) . mockReturnValue ( true )
174+ vi . mocked ( view . setVisible ) . mockClear ( )
175+
176+ expect ( panel . setPanelOccluded ( true , other , scopeId ) ) . toBe ( false )
177+ expect ( panel . setPanelOccluded ( true , other , scopeId , true ) ) . toBe ( true )
178+ expect ( win . contentView . removeChildView ) . toHaveBeenCalledWith ( view )
179+
180+ panel . setPanelBounds ( PANEL_RECT , other )
181+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
182+ expect ( panel . setPanelOccluded ( false , other , scopeId ) ) . toBe ( true )
183+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( true )
184+ expect ( panel . setPanelOccluded ( false , win , scopeId ) ) . toBe ( true )
185+ } )
186+
187+ it ( 'acknowledges forced occlusion in an unfocused window that has no local native view' , ( ) => {
188+ const { win, view } = showPanel ( panel )
189+ const other = new BrowserWindow ( )
190+ const scopeId = panel . getActivePanelScopeId ( )
191+ vi . mocked ( other . isFocused ) . mockReturnValue ( false )
192+ vi . mocked ( view . setVisible ) . mockClear ( )
193+
194+ expect ( panel . setPanelOccluded ( true , other , scopeId , true ) ) . toBe ( true )
195+ expect ( view . setVisible ) . not . toHaveBeenCalled ( )
196+ expect ( panel . setPanelOccluded ( false , other , scopeId ) ) . toBe ( true )
197+ expect ( view . setVisible ) . not . toHaveBeenCalled ( )
198+ expect ( panel . setPanelOccluded ( false , win , scopeId ) ) . toBe ( true )
199+ } )
200+
201+ it ( 'acknowledges cross-scope modal leases only for a window without the singleton view' , ( ) => {
202+ const { win, view } = showPanel ( panel )
203+ const other = new BrowserWindow ( )
204+ vi . mocked ( other . isFocused ) . mockReturnValue ( false )
205+ vi . mocked ( view . setVisible ) . mockClear ( )
206+
207+ expect ( panel . setPanelOccluded ( true , other , 'chat-in-other-window' , true ) ) . toBe ( true )
208+ expect ( panel . setPanelOccluded ( false , other , 'chat-in-other-window' ) ) . toBe ( true )
209+ expect ( view . setVisible ) . not . toHaveBeenCalled ( )
210+
211+ vi . mocked ( other . isFocused ) . mockReturnValue ( true )
212+ expect ( panel . setPanelOccluded ( true , other , 'focused-other-chat' , true ) ) . toBe ( false )
213+
214+ // A stale scope from the real owner must never mutate or falsely
215+ // acknowledge the currently hosted native surface.
216+ expect ( panel . setPanelOccluded ( true , win , 'stale-owner-chat' , true ) ) . toBe ( false )
217+ expect ( panel . setPanelOccluded ( false , win , 'stale-owner-chat' ) ) . toBe ( false )
218+ expect ( view . setVisible ) . not . toHaveBeenCalled ( )
219+ } )
220+
221+ it ( 'releases the old window occlusion lease when panel ownership moves' , async ( ) => {
222+ const { win, view } = showPanel ( panel )
223+ const other = new BrowserWindow ( )
224+ const scopeId = panel . getActivePanelScopeId ( )
225+
226+ await expect ( panel . capturePanelSnapshot ( win , scopeId ) ) . resolves . not . toBeNull ( )
227+ expect ( panel . setPanelOccluded ( true , win , scopeId ) ) . toBe ( true )
228+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
229+
230+ panel . setPanelBounds ( PANEL_RECT , other )
231+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( true )
232+ // The displaced renderer can retire its stale local snapshot without
233+ // changing the new owner's already-visible native view.
234+ expect ( panel . setPanelOccluded ( false , win , scopeId ) ) . toBe ( true )
235+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( true )
236+ } )
237+
238+ it ( 'lets a displaced window retire its snapshot without revealing the current modal lease' , async ( ) => {
239+ const { win, view } = showPanel ( panel )
240+ const other = new BrowserWindow ( )
241+ const scopeId = panel . getActivePanelScopeId ( )
242+
243+ await expect ( panel . capturePanelSnapshot ( win , scopeId ) ) . resolves . not . toBeNull ( )
244+ expect ( panel . setPanelOccluded ( true , win , scopeId ) ) . toBe ( true )
245+
246+ vi . mocked ( other . isFocused ) . mockReturnValue ( true )
247+ expect ( panel . setPanelOccluded ( true , other , scopeId , true ) ) . toBe ( true )
248+ panel . setPanelBounds ( PANEL_RECT , other )
249+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
250+
251+ expect ( panel . setPanelOccluded ( false , win , scopeId ) ) . toBe ( true )
252+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
253+ expect ( panel . setPanelOccluded ( false , other , scopeId ) ) . toBe ( true )
254+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( true )
255+ } )
256+
257+ it ( 'lets the prior scope retire after a focused window establishes the next hidden lease' , ( ) => {
258+ const { win, view } = showPanel ( panel )
259+ const other = new BrowserWindow ( )
260+ const previousScope = panel . getActivePanelScopeId ( )
261+ const nextScope = 'chat-in-focused-window'
262+ vi . mocked ( other . isFocused ) . mockReturnValue ( true )
263+
264+ panel . activatePanelScope ( nextScope )
265+ expect ( panel . setPanelOccluded ( true , other , nextScope , true ) ) . toBe ( true )
266+ panel . setPanelBounds ( PANEL_RECT , other , undefined , nextScope )
267+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
268+
269+ expect ( panel . setPanelOccluded ( false , win , previousScope ) ) . toBe ( true )
270+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( false )
271+ expect ( panel . setPanelOccluded ( false , other , nextScope ) ) . toBe ( true )
272+ expect ( view . setVisible ) . toHaveBeenLastCalledWith ( true )
130273 } )
131274
132275 it ( 'treats an unpainted blank tab as a valid backdrop snapshot' , async ( ) => {
@@ -152,6 +295,7 @@ describe('panel chat scope', () => {
152295 vi . mocked ( view . setVisible ) . mockClear ( )
153296
154297 expect ( panel . setPanelOccluded ( true , win , 'some-other-chat' ) ) . toBe ( false )
298+ expect ( panel . setPanelOccluded ( true , win , 'some-other-chat' , true ) ) . toBe ( false )
155299 expect ( view . setVisible ) . not . toHaveBeenCalled ( )
156300 } )
157301} )
0 commit comments