@@ -207,6 +207,7 @@ describe('createSocketIoWebSocketManager', () => {
207207 expect ( socket . connectCalls ) . toBe ( 1 ) ;
208208 expect ( socket . disconnectCalls ) . toBe ( 1 ) ;
209209 expect ( socket . listenerCount ( 'runtimeManagerExecuted' ) ) . toBe ( 0 ) ;
210+ expect ( socket . listenerCount ( 'connect' ) ) . toBe ( 0 ) ;
210211 } ) ;
211212
212213 it ( 'subscribes to BFF runtime page topics without cursor' , async ( ) => {
@@ -239,6 +240,62 @@ describe('createSocketIoWebSocketManager', () => {
239240 ] ) ;
240241 } ) ;
241242
243+ it ( 'resubscribes active topics after socket reconnect' , async ( ) => {
244+ const socket = new FakeRuntimeSocket ( ) ;
245+ const manager = createSocketIoWebSocketManager ( {
246+ socketFactory : ( ) => socket ,
247+ } ) ;
248+ const topic = 'tenant.tenant-a.page.orders.instance.instance-1' ;
249+
250+ await manager . connect ( ) ;
251+ await manager . subscribe ( topic , vi . fn ( ) ) ;
252+ socket . receive ( 'connect' , undefined ) ;
253+
254+ expect ( socket . emits ) . toEqual ( [
255+ {
256+ event : 'subscribePage' ,
257+ payload : {
258+ tenantId : 'tenant-a' ,
259+ pageId : 'orders' ,
260+ pageInstanceId : 'instance-1' ,
261+ } ,
262+ } ,
263+ {
264+ event : 'subscribePage' ,
265+ payload : {
266+ tenantId : 'tenant-a' ,
267+ pageId : 'orders' ,
268+ pageInstanceId : 'instance-1' ,
269+ } ,
270+ } ,
271+ ] ) ;
272+ } ) ;
273+
274+ it ( 'resubscribes a topic once when multiple handlers share it' , async ( ) => {
275+ const socket = new FakeRuntimeSocket ( ) ;
276+ const manager = createSocketIoWebSocketManager ( {
277+ socketFactory : ( ) => socket ,
278+ } ) ;
279+ const topic = 'tenant.tenant-a.page.orders.instance.instance-1' ;
280+
281+ await manager . connect ( ) ;
282+ await manager . subscribe ( topic , vi . fn ( ) ) ;
283+ await manager . subscribe ( topic , vi . fn ( ) ) ;
284+ socket . emits . length = 0 ;
285+ socket . receive ( 'connect' , undefined ) ;
286+
287+ expect ( socket . emits ) . toEqual ( [
288+ {
289+ event : 'subscribePage' ,
290+ payload : {
291+ tenantId : 'tenant-a' ,
292+ pageId : 'orders' ,
293+ pageInstanceId : 'instance-1' ,
294+ } ,
295+ } ,
296+ ] ) ;
297+ } ) ;
298+
242299 it ( 'subscribes with replay cursor options' , async ( ) => {
243300 const socket = new FakeRuntimeSocket ( ) ;
244301 const manager = createSocketIoWebSocketManager ( {
@@ -269,6 +326,76 @@ describe('createSocketIoWebSocketManager', () => {
269326 ] ) ;
270327 } ) ;
271328
329+ it ( 'uses the latest replay cursor when resubscribing after reconnect' , async ( ) => {
330+ const socket = new FakeRuntimeSocket ( ) ;
331+ const manager = createSocketIoWebSocketManager ( {
332+ socketFactory : ( ) => socket ,
333+ } ) ;
334+ const topic = 'tenant.tenant-a.page.orders.instance.instance-1' ;
335+ const handler = vi . fn ( ) ;
336+
337+ await subscribeWithOptions ( manager , topic , handler , {
338+ afterReplayId : '42-0' ,
339+ } ) ;
340+ socket . receive ( 'runtimeManagerExecuted' , {
341+ type : 'runtime.manager.executed' ,
342+ topic,
343+ page : {
344+ tenantId : 'tenant-a' ,
345+ pageId : 'orders' ,
346+ pageInstanceId : 'instance-1' ,
347+ } ,
348+ replayId : '43-0' ,
349+ patchState : { } ,
350+ refreshedDatasourceIds : [ ] ,
351+ runActionIds : [ ] ,
352+ } ) ;
353+ socket . emits . length = 0 ;
354+ socket . receive ( 'connect' , undefined ) ;
355+
356+ expect ( socket . emits ) . toEqual ( [
357+ {
358+ event : 'subscribePage' ,
359+ payload : {
360+ tenantId : 'tenant-a' ,
361+ pageId : 'orders' ,
362+ pageInstanceId : 'instance-1' ,
363+ afterReplayId : '43-0' ,
364+ } ,
365+ } ,
366+ ] ) ;
367+ } ) ;
368+
369+ it ( 'falls back to the original replay cursor when no newer event was received' , async ( ) => {
370+ const socket = new FakeRuntimeSocket ( ) ;
371+ const manager = createSocketIoWebSocketManager ( {
372+ socketFactory : ( ) => socket ,
373+ } ) ;
374+
375+ await subscribeWithOptions (
376+ manager ,
377+ 'tenant.tenant-a.page.orders.instance.instance-1' ,
378+ vi . fn ( ) ,
379+ {
380+ afterReplayId : '42-0' ,
381+ } ,
382+ ) ;
383+ socket . emits . length = 0 ;
384+ socket . receive ( 'connect' , undefined ) ;
385+
386+ expect ( socket . emits ) . toEqual ( [
387+ {
388+ event : 'subscribePage' ,
389+ payload : {
390+ tenantId : 'tenant-a' ,
391+ pageId : 'orders' ,
392+ pageInstanceId : 'instance-1' ,
393+ afterReplayId : '42-0' ,
394+ } ,
395+ } ,
396+ ] ) ;
397+ } ) ;
398+
272399 it ( 'dispatches manager events only to matching topic handlers' , async ( ) => {
273400 const socket = new FakeRuntimeSocket ( ) ;
274401 const manager = createSocketIoWebSocketManager ( {
@@ -309,6 +436,8 @@ describe('createSocketIoWebSocketManager', () => {
309436 await manager . connect ( ) ;
310437 await manager . subscribe ( topic , handler ) ;
311438 await manager . unsubscribe ( topic , handler ) ;
439+ socket . emits . length = 0 ;
440+ socket . receive ( 'connect' , undefined ) ;
312441 socket . receive ( 'runtimeManagerExecuted' , {
313442 type : 'runtime.manager.executed' ,
314443 topic,
@@ -323,6 +452,26 @@ describe('createSocketIoWebSocketManager', () => {
323452 } ) ;
324453
325454 expect ( handler ) . not . toHaveBeenCalled ( ) ;
455+ expect ( socket . emits ) . toEqual ( [ ] ) ;
456+ } ) ;
457+
458+ it ( 'clears subscriptions and replay cursors on disconnect' , async ( ) => {
459+ const socket = new FakeRuntimeSocket ( ) ;
460+ const manager = createSocketIoWebSocketManager ( {
461+ socketFactory : ( ) => socket ,
462+ } ) ;
463+ const topic = 'tenant.tenant-a.page.orders.instance.instance-1' ;
464+
465+ await subscribeWithOptions ( manager , topic , vi . fn ( ) , {
466+ afterReplayId : '42-0' ,
467+ } ) ;
468+ await manager . disconnect ( ) ;
469+ socket . emits . length = 0 ;
470+ socket . receive ( 'connect' , undefined ) ;
471+
472+ expect ( socket . emits ) . toEqual ( [ ] ) ;
473+ expect ( socket . listenerCount ( 'runtimeManagerExecuted' ) ) . toBe ( 0 ) ;
474+ expect ( socket . listenerCount ( 'connect' ) ) . toBe ( 0 ) ;
326475 } ) ;
327476} ) ;
328477
0 commit comments