Files
workavia-calendar-front/__test__/features/websocket/useWebSocketReconnect.test.tsx
T
lethemanh cadfa70e60 #708 apply strictier linting rules (#717)
* #708 apply strictier linting rules and fix simple eslint bugs

* #708 fix eslint errors relate to promise

* #708 fix eslint import/no-extraneous-dependencies

* #708 fix eslint errors of react-hook

* #708 enable eslint check for typescript

---------

Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
2026-04-01 17:15:10 +02:00

284 lines
7.7 KiB
TypeScript

import { getRetryDelay } from '@/utils/getRetryDelay'
import {
MAX_RECONNECT_ATTEMPTS,
RECONNECT_CONFIG,
useWebSocketReconnect
} from '@/websocket/connection/lifecycle/useWebSocketReconnect'
import { act, renderHook } from '@testing-library/react'
import { MutableRefObject } from 'react'
// Mock the retry delay utility
jest.mock('@/utils/getRetryDelay')
const mockGetRetryDelay = getRetryDelay as jest.MockedFunction<
typeof getRetryDelay
>
describe('useWebSocketReconnect', () => {
let reconnectTimeoutRef: MutableRefObject<NodeJS.Timeout | null>
let isAuthenticatedRef: MutableRefObject<boolean>
let reconnectAttemptsRef: MutableRefObject<number>
let setShouldConnect: jest.Mock
beforeEach(() => {
jest.useFakeTimers()
reconnectTimeoutRef = { current: null }
isAuthenticatedRef = { current: true }
reconnectAttemptsRef = { current: 0 }
setShouldConnect = jest.fn()
mockGetRetryDelay.mockReturnValue(1000) // Default 1 second delay
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
jest.clearAllMocks()
})
describe('scheduleReconnect', () => {
it('should schedule a reconnection with correct delay', () => {
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef, // isAuthenticated
reconnectAttemptsRef,
setShouldConnect
)
)
act(() => {
result.current.scheduleReconnect()
})
expect(mockGetRetryDelay).toHaveBeenCalledWith(0, RECONNECT_CONFIG)
expect(reconnectTimeoutRef.current).not.toBeNull()
expect(setShouldConnect).not.toHaveBeenCalled()
act(() => {
jest.advanceTimersByTime(1000)
})
expect(setShouldConnect).toHaveBeenCalledWith(expect.any(Function))
expect(reconnectAttemptsRef.current).toBe(1)
})
it('should not schedule reconnection if not authenticated', () => {
isAuthenticatedRef = { current: false }
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef, // Not authenticated
reconnectAttemptsRef,
setShouldConnect
)
)
act(() => {
result.current.scheduleReconnect()
})
expect(reconnectTimeoutRef.current).toBeNull()
expect(mockGetRetryDelay).not.toHaveBeenCalled()
})
it('should stop after MAX_RECONNECT_ATTEMPTS', () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
reconnectAttemptsRef.current = MAX_RECONNECT_ATTEMPTS
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
act(() => {
result.current.scheduleReconnect()
})
expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringContaining(
`Max WebSocket reconnection attempts (${MAX_RECONNECT_ATTEMPTS})`
)
)
expect(reconnectTimeoutRef.current).toBeNull()
consoleErrorSpy.mockRestore()
})
it('should increment attempt counter on each reconnection', () => {
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
expect(reconnectAttemptsRef.current).toBe(0)
act(() => {
result.current.scheduleReconnect()
jest.advanceTimersByTime(1000)
})
expect(reconnectAttemptsRef.current).toBe(1)
act(() => {
result.current.scheduleReconnect()
jest.advanceTimersByTime(1000)
})
expect(reconnectAttemptsRef.current).toBe(2)
})
it('should toggle setShouldConnect correctly', () => {
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
act(() => {
result.current.scheduleReconnect()
jest.advanceTimersByTime(1000)
})
expect(setShouldConnect).toHaveBeenCalledTimes(1)
// Test the toggle function
const toggleFn = setShouldConnect.mock.calls[0][0]
expect(toggleFn(false)).toBe(true)
expect(toggleFn(true)).toBe(false)
})
})
describe('clearReconnectTimeout', () => {
it('should clear pending timeout', () => {
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
act(() => {
result.current.scheduleReconnect()
})
expect(reconnectTimeoutRef.current).not.toBeNull()
act(() => {
result.current.clearReconnectTimeout()
})
expect(reconnectTimeoutRef.current).toBeNull()
// Timeout should not fire
act(() => {
jest.advanceTimersByTime(1000)
})
expect(setShouldConnect).not.toHaveBeenCalled()
})
it('should handle multiple clears gracefully', () => {
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
act(() => {
result.current.clearReconnectTimeout()
result.current.clearReconnectTimeout()
result.current.clearReconnectTimeout()
})
expect(reconnectTimeoutRef.current).toBeNull()
})
it('should clear timeout before scheduling new one', () => {
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
// Schedule first reconnection
act(() => {
result.current.scheduleReconnect()
})
const firstTimeout = reconnectTimeoutRef.current
expect(firstTimeout).not.toBeNull()
// Schedule second reconnection (should clear first)
act(() => {
result.current.scheduleReconnect()
})
const secondTimeout = reconnectTimeoutRef.current
expect(secondTimeout).not.toBeNull()
expect(secondTimeout).not.toBe(firstTimeout)
// Only second timeout should fire
act(() => {
jest.advanceTimersByTime(1000)
})
expect(setShouldConnect).toHaveBeenCalledTimes(1)
})
})
it('should stop reconnecting after MAX_RECONNECT_ATTEMPTS (10)', () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
const { result } = renderHook(() =>
useWebSocketReconnect(
reconnectTimeoutRef,
isAuthenticatedRef,
reconnectAttemptsRef,
setShouldConnect
)
)
// Simulate 10 reconnection attempts
for (let i = 0; i < MAX_RECONNECT_ATTEMPTS; i++) {
act(() => {
result.current.scheduleReconnect()
jest.advanceTimersByTime(
mockGetRetryDelay.mock.results[i]?.value || 1000
)
})
}
expect(reconnectAttemptsRef.current).toBe(MAX_RECONNECT_ATTEMPTS)
expect(setShouldConnect).toHaveBeenCalledTimes(MAX_RECONNECT_ATTEMPTS)
// Try to schedule one more reconnection - should fail
act(() => {
result.current.scheduleReconnect()
})
expect(consoleErrorSpy).toHaveBeenCalledWith(
`Max WebSocket reconnection attempts (${MAX_RECONNECT_ATTEMPTS}) reached. Giving up.`
)
expect(reconnectTimeoutRef.current).toBeNull()
expect(setShouldConnect).toHaveBeenCalledTimes(MAX_RECONNECT_ATTEMPTS) // Should not increment
consoleErrorSpy.mockRestore()
})
})