@@ -53,6 +53,84 @@ test('switching between popup and sidebar preserves App mode', async ({ page })
5353 await expect ( page . locator ( 'popup-mode' ) ) . toBeVisible ( ) ;
5454} ) ;
5555
56+ test ( 'sidebar App mode keeps the map framed when the chat rail closes and reopens' , async ( { page } ) => {
57+ await page . setViewportSize ( { width : 1280 , height : 800 } ) ;
58+ await openDemo ( page , '/sidebar?appmode=on' ) ;
59+
60+ const sidebar = page . locator ( 'chat-sidebar' ) ;
61+ const map = page . locator ( 'sidebar-mode app-map-canvas' ) ;
62+ const panel = page . locator ( '.chat-sidebar__panel' ) ;
63+
64+ await expect ( sidebar ) . toHaveAttribute ( 'data-open' , 'true' ) ;
65+ await expect ( panel ) . toHaveAttribute ( 'data-open' , 'true' ) ;
66+ await expect ( map ) . toBeVisible ( ) ;
67+
68+ async function layoutState ( ) : Promise < {
69+ contentWidth : number ;
70+ contentRight : number ;
71+ mapWidth : number ;
72+ mapRight : number ;
73+ marginRight : string ;
74+ rootClaim : string | null ;
75+ occupyRight : string ;
76+ } > {
77+ return page . evaluate ( ( ) => {
78+ const contentEl = document . querySelector ( '.chat-sidebar__content' ) ;
79+ const mapEl = document . querySelector ( 'sidebar-mode app-map-canvas' ) ;
80+ if ( ! contentEl || ! mapEl ) throw new Error ( 'App-mode map layout elements missing' ) ;
81+ const contentRect = contentEl . getBoundingClientRect ( ) ;
82+ const mapRect = mapEl . getBoundingClientRect ( ) ;
83+ const contentStyle = getComputedStyle ( contentEl ) ;
84+ const root = document . documentElement ;
85+ const rootStyle = getComputedStyle ( root ) ;
86+ return {
87+ contentWidth : contentRect . width ,
88+ contentRight : contentRect . right ,
89+ mapWidth : mapRect . width ,
90+ mapRight : mapRect . right ,
91+ marginRight : contentStyle . marginRight ,
92+ rootClaim : root . getAttribute ( 'data-threadplane-chat-sidebar' ) ,
93+ occupyRight : rootStyle . getPropertyValue ( '--tplane-chat-occupy-right' ) . trim ( ) ,
94+ } ;
95+ } ) ;
96+ }
97+
98+ await expect . poll ( async ( ) => parseFloat ( ( await layoutState ( ) ) . marginRight ) ) . toBeGreaterThan ( 300 ) ;
99+ const openLayout = await layoutState ( ) ;
100+ expect ( openLayout . rootClaim ) . toBe ( 'open' ) ;
101+ expect ( parseFloat ( openLayout . marginRight ) ) . toBeGreaterThan ( 300 ) ;
102+ expect ( openLayout . contentWidth ) . toBeGreaterThan ( 500 ) ;
103+ expect ( openLayout . mapWidth ) . toBeCloseTo ( openLayout . contentWidth , 0 ) ;
104+ expect ( openLayout . mapRight ) . toBeCloseTo ( openLayout . contentRight , 0 ) ;
105+
106+ await page . getByRole ( 'button' , { name : 'Close chat' } ) . click ( ) ;
107+ await expect ( sidebar ) . toHaveAttribute ( 'data-open' , 'false' ) ;
108+ await expect ( panel ) . toHaveAttribute ( 'data-open' , 'false' ) ;
109+ await expect ( page . getByRole ( 'button' , { name : 'Open chat' } ) ) . toBeVisible ( ) ;
110+ await expect . poll ( async ( ) => parseFloat ( ( await layoutState ( ) ) . marginRight ) ) . toBe ( 0 ) ;
111+
112+ const closedLayout = await layoutState ( ) ;
113+ expect ( closedLayout . rootClaim ) . toBeNull ( ) ;
114+ expect ( closedLayout . occupyRight ) . toBe ( '0px' ) ;
115+ expect ( parseFloat ( closedLayout . marginRight ) ) . toBe ( 0 ) ;
116+ expect ( closedLayout . contentWidth ) . toBeGreaterThan ( openLayout . contentWidth ) ;
117+ expect ( closedLayout . mapWidth ) . toBeCloseTo ( closedLayout . contentWidth , 0 ) ;
118+ expect ( closedLayout . mapRight ) . toBeCloseTo ( closedLayout . contentRight , 0 ) ;
119+
120+ await page . getByRole ( 'button' , { name : 'Open chat' } ) . click ( ) ;
121+ await expect ( sidebar ) . toHaveAttribute ( 'data-open' , 'true' ) ;
122+ await expect ( panel ) . toHaveAttribute ( 'data-open' , 'true' ) ;
123+ await expect . poll ( async ( ) => parseFloat ( ( await layoutState ( ) ) . marginRight ) ) . toBeGreaterThan ( 300 ) ;
124+
125+ const reopenedLayout = await layoutState ( ) ;
126+ expect ( reopenedLayout . rootClaim ) . toBe ( 'open' ) ;
127+ expect ( parseFloat ( reopenedLayout . marginRight ) ) . toBeGreaterThan ( 300 ) ;
128+ expect ( reopenedLayout . contentWidth ) . toBeGreaterThan ( 500 ) ;
129+ expect ( reopenedLayout . contentWidth ) . toBeLessThan ( closedLayout . contentWidth ) ;
130+ expect ( reopenedLayout . mapWidth ) . toBeCloseTo ( reopenedLayout . contentWidth , 0 ) ;
131+ expect ( reopenedLayout . mapRight ) . toBeCloseTo ( reopenedLayout . contentRight , 0 ) ;
132+ } ) ;
133+
56134// Embed is full-chat with no background, so it can't host App mode: choosing
57135// Embed from App mode turns App mode off.
58136test ( 'choosing embed from App mode turns App mode off and shows the full chat' , async ( { page } ) => {
0 commit comments