@@ -91,6 +91,38 @@ describe("App <-> AppBridge integration", () => {
9191 const appCaps = bridge . getAppCapabilities ( ) ;
9292 expect ( appCaps ) . toEqual ( appCapabilities ) ;
9393 } ) ;
94+
95+ it ( "App receives initial hostContext after connect" , async ( ) => {
96+ // Need fresh transports for new bridge
97+ const [ newAppTransport , newBridgeTransport ] =
98+ InMemoryTransport . createLinkedPair ( ) ;
99+
100+ const testHostContext = {
101+ theme : "dark" as const ,
102+ locale : "en-US" ,
103+ viewport : { width : 800 , height : 600 } ,
104+ } ;
105+ const newBridge = new AppBridge (
106+ createMockClient ( ) as Client ,
107+ testHostInfo ,
108+ testHostCapabilities ,
109+ { hostContext : testHostContext } ,
110+ ) ;
111+ const newApp = new App ( testAppInfo , { } , { autoResize : false } ) ;
112+
113+ await newBridge . connect ( newBridgeTransport ) ;
114+ await newApp . connect ( newAppTransport ) ;
115+
116+ const hostContext = newApp . getHostContext ( ) ;
117+ expect ( hostContext ) . toEqual ( testHostContext ) ;
118+
119+ await newAppTransport . close ( ) ;
120+ await newBridgeTransport . close ( ) ;
121+ } ) ;
122+
123+ it ( "getHostContext returns undefined before connect" , ( ) => {
124+ expect ( app . getHostContext ( ) ) . toBeUndefined ( ) ;
125+ } ) ;
94126 } ) ;
95127
96128 describe ( "Host -> App notifications" , ( ) => {
@@ -204,6 +236,128 @@ describe("App <-> AppBridge integration", () => {
204236 ] ) ;
205237 } ) ;
206238
239+ it ( "getHostContext merges updates from onhostcontextchanged" , async ( ) => {
240+ // Need fresh transports for new bridge
241+ const [ newAppTransport , newBridgeTransport ] =
242+ InMemoryTransport . createLinkedPair ( ) ;
243+
244+ // Set up bridge with initial context
245+ const initialContext = {
246+ theme : "light" as const ,
247+ locale : "en-US" ,
248+ } ;
249+ const newBridge = new AppBridge (
250+ createMockClient ( ) as Client ,
251+ testHostInfo ,
252+ testHostCapabilities ,
253+ { hostContext : initialContext } ,
254+ ) ;
255+ const newApp = new App ( testAppInfo , { } , { autoResize : false } ) ;
256+
257+ await newBridge . connect ( newBridgeTransport ) ;
258+
259+ // Set up handler before connecting app
260+ newApp . onhostcontextchanged = ( ) => {
261+ // User handler (can be empty, we're testing getHostContext behavior)
262+ } ;
263+
264+ await newApp . connect ( newAppTransport ) ;
265+
266+ // Verify initial context
267+ expect ( newApp . getHostContext ( ) ) . toEqual ( initialContext ) ;
268+
269+ // Update context
270+ newBridge . setHostContext ( { theme : "dark" , locale : "en-US" } ) ;
271+ await flush ( ) ;
272+
273+ // getHostContext should reflect merged state
274+ const updatedContext = newApp . getHostContext ( ) ;
275+ expect ( updatedContext ?. theme ) . toBe ( "dark" ) ;
276+ expect ( updatedContext ?. locale ) . toBe ( "en-US" ) ;
277+
278+ await newAppTransport . close ( ) ;
279+ await newBridgeTransport . close ( ) ;
280+ } ) ;
281+
282+ it ( "getHostContext updates even without user setting onhostcontextchanged" , async ( ) => {
283+ // Need fresh transports for new bridge
284+ const [ newAppTransport , newBridgeTransport ] =
285+ InMemoryTransport . createLinkedPair ( ) ;
286+
287+ // Set up bridge with initial context
288+ const initialContext = {
289+ theme : "light" as const ,
290+ locale : "en-US" ,
291+ } ;
292+ const newBridge = new AppBridge (
293+ createMockClient ( ) as Client ,
294+ testHostInfo ,
295+ testHostCapabilities ,
296+ { hostContext : initialContext } ,
297+ ) ;
298+ const newApp = new App ( testAppInfo , { } , { autoResize : false } ) ;
299+
300+ await newBridge . connect ( newBridgeTransport ) ;
301+ // Note: We do NOT set app.onhostcontextchanged here
302+ await newApp . connect ( newAppTransport ) ;
303+
304+ // Verify initial context
305+ expect ( newApp . getHostContext ( ) ) . toEqual ( initialContext ) ;
306+
307+ // Update context from bridge
308+ newBridge . setHostContext ( { theme : "dark" , locale : "en-US" } ) ;
309+ await flush ( ) ;
310+
311+ // getHostContext should still update (default handler should work)
312+ const updatedContext = newApp . getHostContext ( ) ;
313+ expect ( updatedContext ?. theme ) . toBe ( "dark" ) ;
314+
315+ await newAppTransport . close ( ) ;
316+ await newBridgeTransport . close ( ) ;
317+ } ) ;
318+
319+ it ( "getHostContext accumulates multiple partial updates" , async ( ) => {
320+ // Need fresh transports for new bridge
321+ const [ newAppTransport , newBridgeTransport ] =
322+ InMemoryTransport . createLinkedPair ( ) ;
323+
324+ const initialContext = {
325+ theme : "light" as const ,
326+ locale : "en-US" ,
327+ viewport : { width : 800 , height : 600 } ,
328+ } ;
329+ const newBridge = new AppBridge (
330+ createMockClient ( ) as Client ,
331+ testHostInfo ,
332+ testHostCapabilities ,
333+ { hostContext : initialContext } ,
334+ ) ;
335+ const newApp = new App ( testAppInfo , { } , { autoResize : false } ) ;
336+
337+ await newBridge . connect ( newBridgeTransport ) ;
338+ await newApp . connect ( newAppTransport ) ;
339+
340+ // Send partial update: only theme changes
341+ newBridge . sendHostContextChange ( { theme : "dark" } ) ;
342+ await flush ( ) ;
343+
344+ // Send another partial update: only viewport changes
345+ newBridge . sendHostContextChange ( { viewport : { width : 1024 , height : 768 } } ) ;
346+ await flush ( ) ;
347+
348+ // getHostContext should have accumulated all updates:
349+ // - locale from initial (unchanged)
350+ // - theme from first partial update
351+ // - viewport from second partial update
352+ const context = newApp . getHostContext ( ) ;
353+ expect ( context ?. theme ) . toBe ( "dark" ) ;
354+ expect ( context ?. locale ) . toBe ( "en-US" ) ;
355+ expect ( context ?. viewport ) . toEqual ( { width : 1024 , height : 768 } ) ;
356+
357+ await newAppTransport . close ( ) ;
358+ await newBridgeTransport . close ( ) ;
359+ } ) ;
360+
207361 it ( "sendResourceTeardown triggers app.onteardown" , async ( ) => {
208362 let teardownCalled = false ;
209363 app . onteardown = async ( ) => {
0 commit comments