@@ -56,9 +56,11 @@ describe("WebSocketGate", () => {
5656 const createMockSocket = ( readyState = WebSocket . OPEN ) => ( {
5757 readyState,
5858 close : jest . fn ( ) ,
59+ send : jest . fn ( ) ,
5960 addEventListener : jest . fn ( ) ,
6061 removeEventListener : jest . fn ( ) ,
6162 cleanup : jest . fn ( ) ,
63+ onmessage : null ,
6264 } ) ;
6365
6466 beforeEach ( ( ) => {
@@ -340,19 +342,11 @@ describe("WebSocketGate", () => {
340342 jest . advanceTimersByTime ( 1000 ) ;
341343 } ) ;
342344
343- // Second failure
344- await act ( async ( ) => {
345- onCloseCallback ?.( new CloseEvent ( "close" , { code : 1006 } ) ) ;
346- jest . advanceTimersByTime ( 2000 ) ;
347- } ) ;
348-
349- // Success - should log and reset counter
350- await waitFor ( ( ) => {
351- expect ( consoleLog ) . toHaveBeenCalledWith (
352- "WebSocket connected successfully"
353- ) ;
354- } ) ;
345+ expect ( consoleLog ) . toHaveBeenCalledWith (
346+ expect . stringContaining ( "(attempt 1/10)" )
347+ ) ;
355348
349+ // Success - should reset counter
356350 // Next failure should start from attempt 1 again
357351 await act ( async ( ) => {
358352 onCloseCallback ?.( new CloseEvent ( "close" , { code : 1006 } ) ) ;
@@ -906,4 +900,113 @@ describe("WebSocketGate", () => {
906900 expect ( registerToCalendars ) . not . toHaveBeenCalled ( ) ;
907901 } ) ;
908902 } ) ;
903+
904+ describe ( "Ping/Pong Integration" , ( ) => {
905+ let mockPingCleanup : { stop : jest . Mock ; sendPing : jest . Mock } ;
906+
907+ beforeEach ( ( ) => {
908+ jest . useFakeTimers ( ) ;
909+ mockPingCleanup = {
910+ stop : jest . fn ( ) ,
911+ sendPing : jest . fn ( ) ,
912+ } ;
913+ } ) ;
914+
915+ afterEach ( ( ) => {
916+ jest . runOnlyPendingTimers ( ) ;
917+ jest . useRealTimers ( ) ;
918+ } ) ;
919+
920+ it ( "should trigger reconnection when ping detects dead connection (via socket close)" , async ( ) => {
921+ const consoleWarn = jest . spyOn ( console , "warn" ) . mockImplementation ( ) ;
922+ let onCloseCallback : ( ( event : CloseEvent ) => void ) | undefined ;
923+
924+ ( createWebSocketConnection as jest . Mock ) . mockImplementation (
925+ ( callbacks ) => {
926+ onCloseCallback = callbacks . onClose ;
927+ return Promise . resolve ( mockSocket ) ;
928+ }
929+ ) ;
930+
931+ render ( < TestWrapper store = { store } /> ) ;
932+
933+ await waitFor ( ( ) => {
934+ expect ( createWebSocketConnection ) . toHaveBeenCalledTimes ( 1 ) ;
935+ } ) ;
936+
937+ ( createWebSocketConnection as jest . Mock ) . mockClear ( ) ;
938+
939+ // In the real implementation, when ping detects dead connection,
940+ // it calls socket.close() which triggers the onClose callback
941+ // This simulates that flow
942+ await act ( async ( ) => {
943+ if ( onCloseCallback ) {
944+ onCloseCallback ( new CloseEvent ( "close" , { code : 1006 } ) ) ;
945+ }
946+ } ) ;
947+
948+ // Should schedule reconnection
949+ expect ( consoleWarn ) . toHaveBeenCalledWith (
950+ expect . stringContaining ( "WebSocket closed unexpectedly" )
951+ ) ;
952+
953+ // Advance to trigger reconnection
954+ await act ( async ( ) => {
955+ jest . advanceTimersByTime ( 1500 ) ;
956+ } ) ;
957+
958+ // Should reconnect
959+ await waitFor ( ( ) => {
960+ expect ( createWebSocketConnection ) . toHaveBeenCalledTimes ( 1 ) ;
961+ } ) ;
962+
963+ consoleWarn . mockRestore ( ) ;
964+ } ) ;
965+
966+ it ( "should stop ping monitoring when socket closes normally" , async ( ) => {
967+ let onCloseCallback : ( ( event : CloseEvent ) => void ) | undefined ;
968+
969+ ( createWebSocketConnection as jest . Mock ) . mockImplementation (
970+ ( callbacks ) => {
971+ onCloseCallback = callbacks . onClose ;
972+ return Promise . resolve ( mockSocket ) ;
973+ }
974+ ) ;
975+
976+ render ( < TestWrapper store = { store } /> ) ;
977+
978+ await waitFor ( ( ) => {
979+ expect ( createWebSocketConnection ) . toHaveBeenCalled ( ) ;
980+ } ) ;
981+
982+ // Normal close (code 1000) - like logout or page navigation
983+ await act ( async ( ) => {
984+ if ( onCloseCallback ) {
985+ onCloseCallback ( new CloseEvent ( "close" , { code : 1000 } ) ) ;
986+ }
987+ } ) ;
988+
989+ // Should not attempt reconnection
990+ await act ( async ( ) => {
991+ jest . advanceTimersByTime ( 5000 ) ;
992+ } ) ;
993+
994+ expect ( createWebSocketConnection ) . toHaveBeenCalledTimes ( 1 ) ;
995+ } ) ;
996+
997+ it ( "should cleanup ping monitoring on component unmount" , async ( ) => {
998+ ( createWebSocketConnection as jest . Mock ) . mockResolvedValue ( mockSocket ) ;
999+
1000+ const { unmount } = render ( < TestWrapper store = { store } /> ) ;
1001+
1002+ await waitFor ( ( ) => {
1003+ expect ( createWebSocketConnection ) . toHaveBeenCalled ( ) ;
1004+ } ) ;
1005+
1006+ // Unmount should cleanup
1007+ unmount ( ) ;
1008+
1009+ expect ( mockSocket . close ) . toHaveBeenCalled ( ) ;
1010+ } ) ;
1011+ } ) ;
9091012} ) ;
0 commit comments