* #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>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,46 +1,46 @@
|
||||
import { api } from "@/utils/apiUtils";
|
||||
import { fetchWebSocketTicket } from "@/websocket/api/fetchWebSocketTicket";
|
||||
import { api } from '@/utils/apiUtils'
|
||||
import { fetchWebSocketTicket } from '@/websocket/api/fetchWebSocketTicket'
|
||||
|
||||
jest.mock("@/utils/apiUtils");
|
||||
jest.mock('@/utils/apiUtils')
|
||||
|
||||
describe("fetchWebSocketTicket", () => {
|
||||
describe('fetchWebSocketTicket', () => {
|
||||
const mockTicket = {
|
||||
clientAddress: "127.0.0.1",
|
||||
value: "test-ticket-123",
|
||||
generatedOn: "2025-01-12T10:00:00Z",
|
||||
validUntil: "2025-01-12T11:00:00Z",
|
||||
username: "testuser",
|
||||
};
|
||||
clientAddress: '127.0.0.1',
|
||||
value: 'test-ticket-123',
|
||||
generatedOn: '2025-01-12T10:00:00Z',
|
||||
validUntil: '2025-01-12T11:00:00Z',
|
||||
username: 'testuser'
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should fetch ticket successfully", async () => {
|
||||
(api.post as jest.Mock).mockResolvedValue({
|
||||
it('should fetch ticket successfully', async () => {
|
||||
;(api.post as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => mockTicket,
|
||||
});
|
||||
json: async () => mockTicket
|
||||
})
|
||||
|
||||
const ticket = await fetchWebSocketTicket();
|
||||
const ticket = await fetchWebSocketTicket()
|
||||
|
||||
expect(api.post).toHaveBeenCalledWith("ws/ticket");
|
||||
expect(ticket).toEqual(mockTicket);
|
||||
});
|
||||
expect(api.post).toHaveBeenCalledWith('ws/ticket')
|
||||
expect(ticket).toEqual(mockTicket)
|
||||
})
|
||||
|
||||
it("should throw error when response is not ok", async () => {
|
||||
(api.post as jest.Mock).mockResolvedValue({
|
||||
ok: false,
|
||||
});
|
||||
it('should throw error when response is not ok', async () => {
|
||||
;(api.post as jest.Mock).mockResolvedValue({
|
||||
ok: false
|
||||
})
|
||||
|
||||
await expect(fetchWebSocketTicket()).rejects.toThrow(
|
||||
"Failed to fetch WebSocket ticket"
|
||||
);
|
||||
});
|
||||
'Failed to fetch WebSocket ticket'
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw error when network fails", async () => {
|
||||
(api.post as jest.Mock).mockRejectedValue(new Error("Network error"));
|
||||
it('should throw error when network fails', async () => {
|
||||
;(api.post as jest.Mock).mockRejectedValue(new Error('Network error'))
|
||||
|
||||
await expect(fetchWebSocketTicket()).rejects.toThrow("Network error");
|
||||
});
|
||||
});
|
||||
await expect(fetchWebSocketTicket()).rejects.toThrow('Network error')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,211 +1,210 @@
|
||||
import { fetchWebSocketTicket } from "@/websocket/api/fetchWebSocketTicket";
|
||||
import { createWebSocketConnection } from "@/websocket/connection/createConnection";
|
||||
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import { setupWebsocket } from "./utils/setupWebsocket";
|
||||
import { fetchWebSocketTicket } from '@/websocket/api/fetchWebSocketTicket'
|
||||
import { createWebSocketConnection } from '@/websocket/connection/createConnection'
|
||||
import { WS_INBOUND_EVENTS } from '@/websocket/protocols'
|
||||
import { waitFor } from '@testing-library/dom'
|
||||
import { setupWebsocket } from './utils/setupWebsocket'
|
||||
|
||||
jest.mock("@/websocket/api/fetchWebSocketTicket");
|
||||
jest.mock('@/websocket/api/fetchWebSocketTicket')
|
||||
|
||||
describe("createWebSocketConnection", () => {
|
||||
let mockWebSocket: jest.Mock;
|
||||
let cleanup: () => void;
|
||||
let webSocketInstances: any[] = [];
|
||||
describe('createWebSocketConnection', () => {
|
||||
let mockWebSocket: jest.Mock
|
||||
let cleanup: () => void
|
||||
let webSocketInstances: any[] = []
|
||||
|
||||
const mockTicket = {
|
||||
value: "test-ticket-123",
|
||||
clientAddress: "127.0.0.1",
|
||||
generatedOn: "2025-01-12T10:00:00Z",
|
||||
validUntil: "2025-01-12T11:00:00Z",
|
||||
username: "testuser",
|
||||
};
|
||||
value: 'test-ticket-123',
|
||||
clientAddress: '127.0.0.1',
|
||||
generatedOn: '2025-01-12T10:00:00Z',
|
||||
validUntil: '2025-01-12T11:00:00Z',
|
||||
username: 'testuser'
|
||||
}
|
||||
|
||||
/** ---------- Helpers ---------- */
|
||||
|
||||
const getWs = () => webSocketInstances[0];
|
||||
const getWs = () => webSocketInstances[0]
|
||||
|
||||
const triggerEvent = (ws: any, event: string, payload?: any) => {
|
||||
ws._listeners[event]?.[0]?.(payload);
|
||||
};
|
||||
ws._listeners[event]?.[0]?.(payload)
|
||||
}
|
||||
|
||||
const createAndOpenConnection = async () => {
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
};
|
||||
const promise = createWebSocketConnection(mockCallbacks);
|
||||
onError: jest.fn()
|
||||
}
|
||||
const promise = createWebSocketConnection(mockCallbacks)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webSocketInstances.length).toBe(1);
|
||||
});
|
||||
expect(webSocketInstances.length).toBe(1)
|
||||
})
|
||||
|
||||
triggerEvent(getWs(), WS_INBOUND_EVENTS.CONNECTION_OPENED);
|
||||
const socket = await promise;
|
||||
triggerEvent(getWs(), WS_INBOUND_EVENTS.CONNECTION_OPENED)
|
||||
const socket = await promise
|
||||
|
||||
return { socket, ws: getWs(), promise, mockCallbacks };
|
||||
};
|
||||
return { socket, ws: getWs(), promise, mockCallbacks }
|
||||
}
|
||||
|
||||
/** ---------- Setup ---------- */
|
||||
|
||||
beforeEach(() => {
|
||||
({ webSocketInstances, mockWebSocket, cleanup } = setupWebsocket());
|
||||
window.WEBSOCKET_URL = "wss://calendar.example.com";
|
||||
|
||||
(fetchWebSocketTicket as jest.Mock).mockResolvedValue(mockTicket);
|
||||
});
|
||||
;({ webSocketInstances, mockWebSocket, cleanup } = setupWebsocket())
|
||||
window.WEBSOCKET_URL = 'wss://calendar.example.com'
|
||||
;(fetchWebSocketTicket as jest.Mock).mockResolvedValue(mockTicket)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
delete window.WEBSOCKET_URL;
|
||||
delete window.CALENDAR_BASE_URL;
|
||||
cleanup();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
delete window.WEBSOCKET_URL
|
||||
delete window.CALENDAR_BASE_URL
|
||||
cleanup()
|
||||
})
|
||||
|
||||
/** ---------- Tests ---------- */
|
||||
|
||||
it("throws when WEBSOCKET_URL is not defined", async () => {
|
||||
delete window.WEBSOCKET_URL;
|
||||
it('throws when WEBSOCKET_URL is not defined', async () => {
|
||||
delete window.WEBSOCKET_URL
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
};
|
||||
onMessage: jest.fn()
|
||||
}
|
||||
|
||||
await expect(createWebSocketConnection(mockCallbacks)).rejects.toThrow(
|
||||
"WEBSOCKET_URL is not defined"
|
||||
);
|
||||
});
|
||||
'WEBSOCKET_URL is not defined'
|
||||
)
|
||||
})
|
||||
|
||||
it("fetches WebSocket ticket", async () => {
|
||||
await createAndOpenConnection();
|
||||
expect(fetchWebSocketTicket).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('fetches WebSocket ticket', async () => {
|
||||
await createAndOpenConnection()
|
||||
expect(fetchWebSocketTicket).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("creates WebSocket with correct URL and ticket", async () => {
|
||||
await createAndOpenConnection();
|
||||
it('creates WebSocket with correct URL and ticket', async () => {
|
||||
await createAndOpenConnection()
|
||||
|
||||
expect(mockWebSocket).toHaveBeenCalledWith(
|
||||
"wss://calendar.example.com/ws?ticket=test-ticket-123"
|
||||
);
|
||||
});
|
||||
'wss://calendar.example.com/ws?ticket=test-ticket-123'
|
||||
)
|
||||
})
|
||||
|
||||
it("creates WebSocket with correct URL and ticket without the WEBSOCKET_URL", async () => {
|
||||
delete window.WEBSOCKET_URL;
|
||||
window.CALENDAR_BASE_URL = "https://calendar.example.com";
|
||||
await createAndOpenConnection();
|
||||
it('creates WebSocket with correct URL and ticket without the WEBSOCKET_URL', async () => {
|
||||
delete window.WEBSOCKET_URL
|
||||
window.CALENDAR_BASE_URL = 'https://calendar.example.com'
|
||||
await createAndOpenConnection()
|
||||
|
||||
expect(mockWebSocket).toHaveBeenCalledWith(
|
||||
"wss://calendar.example.com/ws?ticket=test-ticket-123"
|
||||
);
|
||||
});
|
||||
'wss://calendar.example.com/ws?ticket=test-ticket-123'
|
||||
)
|
||||
})
|
||||
|
||||
it("resolves with socket when connection opens", async () => {
|
||||
const { socket, ws } = await createAndOpenConnection();
|
||||
expect(socket).toBe(ws);
|
||||
});
|
||||
it('resolves with socket when connection opens', async () => {
|
||||
const { socket, ws } = await createAndOpenConnection()
|
||||
expect(socket).toBe(ws)
|
||||
})
|
||||
|
||||
it("rejects when connection fails", async () => {
|
||||
it('rejects when connection fails', async () => {
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
};
|
||||
const promise = createWebSocketConnection(mockCallbacks);
|
||||
onMessage: jest.fn()
|
||||
}
|
||||
const promise = createWebSocketConnection(mockCallbacks)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webSocketInstances.length).toBe(1);
|
||||
});
|
||||
expect(webSocketInstances.length).toBe(1)
|
||||
})
|
||||
|
||||
triggerEvent(getWs(), WS_INBOUND_EVENTS.ERROR, new Event("error"));
|
||||
triggerEvent(getWs(), WS_INBOUND_EVENTS.ERROR, new Event('error'))
|
||||
|
||||
await expect(promise).rejects.toThrow("WebSocket connection failed");
|
||||
});
|
||||
await expect(promise).rejects.toThrow('WebSocket connection failed')
|
||||
})
|
||||
|
||||
it("attaches message event listener", async () => {
|
||||
const { ws } = await createAndOpenConnection();
|
||||
it('attaches message event listener', async () => {
|
||||
const { ws } = await createAndOpenConnection()
|
||||
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.MESSAGE]).toBeDefined();
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.MESSAGE].length).toBeGreaterThan(0);
|
||||
});
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.MESSAGE]).toBeDefined()
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.MESSAGE].length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it("attaches close event listener", async () => {
|
||||
const { ws } = await createAndOpenConnection();
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.CONNECTION_CLOSED]).toBeDefined();
|
||||
});
|
||||
it('attaches close event listener', async () => {
|
||||
const { ws } = await createAndOpenConnection()
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.CONNECTION_CLOSED]).toBeDefined()
|
||||
})
|
||||
|
||||
it("handles invalid JSON messages", async () => {
|
||||
const errorSpy = jest.spyOn(console, "error").mockImplementation();
|
||||
it('handles invalid JSON messages', async () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
|
||||
const { ws } = await createAndOpenConnection();
|
||||
const { ws } = await createAndOpenConnection()
|
||||
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: "invalid json" });
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: 'invalid json' })
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
"Failed to parse WebSocket message:",
|
||||
'Failed to parse WebSocket message:',
|
||||
expect.any(Error)
|
||||
);
|
||||
)
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
errorSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("rejects on timeout", async () => {
|
||||
jest.useFakeTimers();
|
||||
it('rejects on timeout', async () => {
|
||||
jest.useFakeTimers()
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
};
|
||||
const promise = createWebSocketConnection(mockCallbacks);
|
||||
onMessage: jest.fn()
|
||||
}
|
||||
const promise = createWebSocketConnection(mockCallbacks)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webSocketInstances.length).toBe(1);
|
||||
});
|
||||
expect(webSocketInstances.length).toBe(1)
|
||||
})
|
||||
|
||||
jest.advanceTimersByTime(10000);
|
||||
jest.advanceTimersByTime(10000)
|
||||
|
||||
await expect(promise).rejects.toThrow("WebSocket connection timed out");
|
||||
await expect(promise).rejects.toThrow('WebSocket connection timed out')
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it("calls onMessage callback when message received", async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection();
|
||||
it('calls onMessage callback when message received', async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection()
|
||||
|
||||
const testMessage = { type: "test", payload: "data" };
|
||||
const testMessage = { type: 'test', payload: 'data' }
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, {
|
||||
data: JSON.stringify(testMessage),
|
||||
});
|
||||
data: JSON.stringify(testMessage)
|
||||
})
|
||||
|
||||
expect(mockCallbacks.onMessage).toHaveBeenCalledWith(testMessage);
|
||||
});
|
||||
expect(mockCallbacks.onMessage).toHaveBeenCalledWith(testMessage)
|
||||
})
|
||||
|
||||
it("does not call onMessage when JSON parsing fails", async () => {
|
||||
const errorSpy = jest.spyOn(console, "error").mockImplementation();
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection();
|
||||
it('does not call onMessage when JSON parsing fails', async () => {
|
||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection()
|
||||
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: "invalid json" });
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: 'invalid json' })
|
||||
|
||||
expect(mockCallbacks.onMessage).not.toHaveBeenCalled();
|
||||
expect(mockCallbacks.onMessage).not.toHaveBeenCalled()
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
"Failed to parse WebSocket message:",
|
||||
'Failed to parse WebSocket message:',
|
||||
expect.any(Error)
|
||||
);
|
||||
)
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
errorSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("calls onClose callback when connection closes", async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection();
|
||||
it('calls onClose callback when connection closes', async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection()
|
||||
|
||||
const closeEvent = new CloseEvent("close", {
|
||||
const closeEvent = new CloseEvent('close', {
|
||||
code: 1000,
|
||||
reason: "Normal closure",
|
||||
});
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.CONNECTION_CLOSED, closeEvent);
|
||||
reason: 'Normal closure'
|
||||
})
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.CONNECTION_CLOSED, closeEvent)
|
||||
|
||||
expect(mockCallbacks.onClose).toHaveBeenCalledWith(closeEvent);
|
||||
});
|
||||
expect(mockCallbacks.onClose).toHaveBeenCalledWith(closeEvent)
|
||||
})
|
||||
|
||||
it("calls onError callback when error occurs", async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection();
|
||||
it('calls onError callback when error occurs', async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection()
|
||||
|
||||
const errorEvent = new Event("error");
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.ERROR, errorEvent);
|
||||
const errorEvent = new Event('error')
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.ERROR, errorEvent)
|
||||
|
||||
expect(mockCallbacks.onError).toHaveBeenCalledWith(errorEvent);
|
||||
});
|
||||
});
|
||||
expect(mockCallbacks.onError).toHaveBeenCalledWith(errorEvent)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
import { getRetryDelay } from "@/utils/getRetryDelay";
|
||||
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";
|
||||
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");
|
||||
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;
|
||||
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
|
||||
});
|
||||
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();
|
||||
});
|
||||
jest.runOnlyPendingTimers()
|
||||
jest.useRealTimers()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("scheduleReconnect", () => {
|
||||
it("should schedule a reconnection with correct delay", () => {
|
||||
describe('scheduleReconnect', () => {
|
||||
it('should schedule a reconnection with correct delay', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -43,26 +43,26 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
})
|
||||
|
||||
expect(mockGetRetryDelay).toHaveBeenCalledWith(0, RECONNECT_CONFIG);
|
||||
expect(reconnectTimeoutRef.current).not.toBeNull();
|
||||
expect(setShouldConnect).not.toHaveBeenCalled();
|
||||
expect(mockGetRetryDelay).toHaveBeenCalledWith(0, RECONNECT_CONFIG)
|
||||
expect(reconnectTimeoutRef.current).not.toBeNull()
|
||||
expect(setShouldConnect).not.toHaveBeenCalled()
|
||||
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
jest.advanceTimersByTime(1000)
|
||||
})
|
||||
|
||||
expect(setShouldConnect).toHaveBeenCalledWith(expect.any(Function));
|
||||
expect(reconnectAttemptsRef.current).toBe(1);
|
||||
});
|
||||
expect(setShouldConnect).toHaveBeenCalledWith(expect.any(Function))
|
||||
expect(reconnectAttemptsRef.current).toBe(1)
|
||||
})
|
||||
|
||||
it("should not schedule reconnection if not authenticated", () => {
|
||||
isAuthenticatedRef = { current: false };
|
||||
it('should not schedule reconnection if not authenticated', () => {
|
||||
isAuthenticatedRef = { current: false }
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -70,19 +70,19 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
})
|
||||
|
||||
expect(reconnectTimeoutRef.current).toBeNull();
|
||||
expect(mockGetRetryDelay).not.toHaveBeenCalled();
|
||||
});
|
||||
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;
|
||||
it('should stop after MAX_RECONNECT_ATTEMPTS', () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation()
|
||||
reconnectAttemptsRef.current = MAX_RECONNECT_ATTEMPTS
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
@@ -91,23 +91,23 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
})
|
||||
|
||||
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Max WebSocket reconnection attempts (${MAX_RECONNECT_ATTEMPTS})`
|
||||
)
|
||||
);
|
||||
expect(reconnectTimeoutRef.current).toBeNull();
|
||||
)
|
||||
expect(reconnectTimeoutRef.current).toBeNull()
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
consoleErrorSpy.mockRestore()
|
||||
})
|
||||
|
||||
it("should increment attempt counter on each reconnection", () => {
|
||||
it('should increment attempt counter on each reconnection', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -115,26 +115,26 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
expect(reconnectAttemptsRef.current).toBe(0);
|
||||
expect(reconnectAttemptsRef.current).toBe(0)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
jest.advanceTimersByTime(1000)
|
||||
})
|
||||
|
||||
expect(reconnectAttemptsRef.current).toBe(1);
|
||||
expect(reconnectAttemptsRef.current).toBe(1)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
jest.advanceTimersByTime(1000)
|
||||
})
|
||||
|
||||
expect(reconnectAttemptsRef.current).toBe(2);
|
||||
});
|
||||
expect(reconnectAttemptsRef.current).toBe(2)
|
||||
})
|
||||
|
||||
it("should toggle setShouldConnect correctly", () => {
|
||||
it('should toggle setShouldConnect correctly', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -142,24 +142,24 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
jest.advanceTimersByTime(1000)
|
||||
})
|
||||
|
||||
expect(setShouldConnect).toHaveBeenCalledTimes(1);
|
||||
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);
|
||||
});
|
||||
});
|
||||
const toggleFn = setShouldConnect.mock.calls[0][0]
|
||||
expect(toggleFn(false)).toBe(true)
|
||||
expect(toggleFn(true)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("clearReconnectTimeout", () => {
|
||||
it("should clear pending timeout", () => {
|
||||
describe('clearReconnectTimeout', () => {
|
||||
it('should clear pending timeout', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -167,29 +167,29 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
})
|
||||
|
||||
expect(reconnectTimeoutRef.current).not.toBeNull();
|
||||
expect(reconnectTimeoutRef.current).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
result.current.clearReconnectTimeout();
|
||||
});
|
||||
result.current.clearReconnectTimeout()
|
||||
})
|
||||
|
||||
expect(reconnectTimeoutRef.current).toBeNull();
|
||||
expect(reconnectTimeoutRef.current).toBeNull()
|
||||
|
||||
// Timeout should not fire
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
jest.advanceTimersByTime(1000)
|
||||
})
|
||||
|
||||
expect(setShouldConnect).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(setShouldConnect).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle multiple clears gracefully", () => {
|
||||
it('should handle multiple clears gracefully', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -197,18 +197,18 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
act(() => {
|
||||
result.current.clearReconnectTimeout();
|
||||
result.current.clearReconnectTimeout();
|
||||
result.current.clearReconnectTimeout();
|
||||
});
|
||||
result.current.clearReconnectTimeout()
|
||||
result.current.clearReconnectTimeout()
|
||||
result.current.clearReconnectTimeout()
|
||||
})
|
||||
|
||||
expect(reconnectTimeoutRef.current).toBeNull();
|
||||
});
|
||||
expect(reconnectTimeoutRef.current).toBeNull()
|
||||
})
|
||||
|
||||
it("should clear timeout before scheduling new one", () => {
|
||||
it('should clear timeout before scheduling new one', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useWebSocketReconnect(
|
||||
reconnectTimeoutRef,
|
||||
@@ -216,35 +216,35 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
// Schedule first reconnection
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
})
|
||||
|
||||
const firstTimeout = reconnectTimeoutRef.current;
|
||||
expect(firstTimeout).not.toBeNull();
|
||||
const firstTimeout = reconnectTimeoutRef.current
|
||||
expect(firstTimeout).not.toBeNull()
|
||||
|
||||
// Schedule second reconnection (should clear first)
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
});
|
||||
result.current.scheduleReconnect()
|
||||
})
|
||||
|
||||
const secondTimeout = reconnectTimeoutRef.current;
|
||||
expect(secondTimeout).not.toBeNull();
|
||||
expect(secondTimeout).not.toBe(firstTimeout);
|
||||
const secondTimeout = reconnectTimeoutRef.current
|
||||
expect(secondTimeout).not.toBeNull()
|
||||
expect(secondTimeout).not.toBe(firstTimeout)
|
||||
|
||||
// Only second timeout should fire
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
jest.advanceTimersByTime(1000)
|
||||
})
|
||||
|
||||
expect(setShouldConnect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
it("should stop reconnecting after MAX_RECONNECT_ATTEMPTS (10)", () => {
|
||||
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
|
||||
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,
|
||||
@@ -252,32 +252,32 @@ describe("useWebSocketReconnect", () => {
|
||||
reconnectAttemptsRef,
|
||||
setShouldConnect
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
// Simulate 10 reconnection attempts
|
||||
for (let i = 0; i < MAX_RECONNECT_ATTEMPTS; i++) {
|
||||
act(() => {
|
||||
result.current.scheduleReconnect();
|
||||
result.current.scheduleReconnect()
|
||||
jest.advanceTimersByTime(
|
||||
mockGetRetryDelay.mock.results[i]?.value || 1000
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
expect(reconnectAttemptsRef.current).toBe(MAX_RECONNECT_ATTEMPTS);
|
||||
expect(setShouldConnect).toHaveBeenCalledTimes(MAX_RECONNECT_ATTEMPTS);
|
||||
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();
|
||||
});
|
||||
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
|
||||
)
|
||||
expect(reconnectTimeoutRef.current).toBeNull()
|
||||
expect(setShouldConnect).toHaveBeenCalledTimes(MAX_RECONNECT_ATTEMPTS) // Should not increment
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
consoleErrorSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
import type { AppDispatch, RootState } from "@/app/store";
|
||||
import { store } from "@/app/store";
|
||||
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services";
|
||||
import { getDisplayedCalendarRange } from "@/utils";
|
||||
import { updateCalendars } from "@/websocket/messaging/updateCalendars";
|
||||
import type { AppDispatch, RootState } from '@/app/store'
|
||||
import { store } from '@/app/store'
|
||||
import { refreshCalendarWithSyncToken } from '@/features/Calendars/services'
|
||||
import { getDisplayedCalendarRange } from '@/utils'
|
||||
import { updateCalendars } from '@/websocket/messaging/updateCalendars'
|
||||
|
||||
jest.mock("@/features/Calendars/services", () => ({
|
||||
refreshCalendarWithSyncToken: jest.fn(),
|
||||
}));
|
||||
jest.mock('@/features/Calendars/services', () => ({
|
||||
refreshCalendarWithSyncToken: jest.fn()
|
||||
}))
|
||||
|
||||
jest.mock("@/utils", () => ({
|
||||
jest.mock('@/utils', () => ({
|
||||
getDisplayedCalendarRange: jest.fn(),
|
||||
findCalendarById: jest.requireActual("@/utils").findCalendarById,
|
||||
}));
|
||||
findCalendarById: jest.requireActual('@/utils').findCalendarById
|
||||
}))
|
||||
|
||||
jest.mock("@/app/store", () => ({
|
||||
jest.mock('@/app/store', () => ({
|
||||
store: {
|
||||
getState: jest.fn(),
|
||||
},
|
||||
}));
|
||||
getState: jest.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
jest.useFakeTimers();
|
||||
const mockDispatch = jest.fn();
|
||||
jest.useFakeTimers()
|
||||
const mockDispatch = jest.fn()
|
||||
const mockRange = {
|
||||
start: new Date("2025-01-15T10:00:00Z"),
|
||||
end: new Date("2025-01-16T10:00:00Z"),
|
||||
};
|
||||
start: new Date('2025-01-15T10:00:00Z'),
|
||||
end: new Date('2025-01-16T10:00:00Z')
|
||||
}
|
||||
const mockState = {
|
||||
calendars: {
|
||||
list: {
|
||||
"cal1/entry1": { id: "cal1/entry1", name: "Calendar 1", syncToken: 1 },
|
||||
"cal2/entry2": { id: "cal2/entry2", name: "Calendar 2", syncToken: 1 },
|
||||
"cal/A": { id: "cal/A", name: "Cal A", syncToken: 1 },
|
||||
"cal/B": { id: "cal/B", name: "Cal B", syncToken: 1 },
|
||||
"cal/C": { id: "cal/C", name: "Cal C", syncToken: 1 },
|
||||
'cal1/entry1': { id: 'cal1/entry1', name: 'Calendar 1', syncToken: 1 },
|
||||
'cal2/entry2': { id: 'cal2/entry2', name: 'Calendar 2', syncToken: 1 },
|
||||
'cal/A': { id: 'cal/A', name: 'Cal A', syncToken: 1 },
|
||||
'cal/B': { id: 'cal/B', name: 'Cal B', syncToken: 1 },
|
||||
'cal/C': { id: 'cal/C', name: 'Cal C', syncToken: 1 }
|
||||
},
|
||||
templist: {},
|
||||
},
|
||||
} as unknown as RootState;
|
||||
templist: {}
|
||||
}
|
||||
} as unknown as RootState
|
||||
const mockAccumulators: {
|
||||
calendarsToRefresh: Map<string, any>;
|
||||
calendarsToHide: Set<string>;
|
||||
debouncedUpdateFn?: (dispatch: AppDispatch) => void;
|
||||
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>;
|
||||
currentDebouncePeriod?: number;
|
||||
calendarsToRefresh: Map<string, any>
|
||||
calendarsToHide: Set<string>
|
||||
debouncedUpdateFn?: (dispatch: AppDispatch) => void
|
||||
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>
|
||||
currentDebouncePeriod?: number
|
||||
} = {
|
||||
calendarsToRefresh: new Map<string, any>(),
|
||||
calendarsToHide: new Set(),
|
||||
shouldRefreshCalendarListRef: { current: false },
|
||||
currentDebouncePeriod: 0,
|
||||
debouncedUpdateFn: undefined,
|
||||
};
|
||||
debouncedUpdateFn: undefined
|
||||
}
|
||||
|
||||
describe("websocket messages storm", () => {
|
||||
describe('websocket messages storm', () => {
|
||||
beforeEach(() => {
|
||||
(refreshCalendarWithSyncToken as unknown as jest.Mock).mockClear();
|
||||
jest.clearAllMocks();
|
||||
jest.clearAllTimers();
|
||||
jest.resetModules();
|
||||
(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
|
||||
(store.getState as jest.Mock).mockReturnValue(mockState);
|
||||
window.WS_DEBOUNCE_PERIOD_MS = 500;
|
||||
mockAccumulators.calendarsToRefresh = new Map<string, any>();
|
||||
mockAccumulators.calendarsToHide = new Set();
|
||||
mockAccumulators.shouldRefreshCalendarListRef.current = false;
|
||||
mockAccumulators.currentDebouncePeriod = 0;
|
||||
;(refreshCalendarWithSyncToken as unknown as jest.Mock).mockClear()
|
||||
jest.clearAllMocks()
|
||||
jest.clearAllTimers()
|
||||
jest.resetModules()
|
||||
;(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange)
|
||||
;(store.getState as jest.Mock).mockReturnValue(mockState)
|
||||
window.WS_DEBOUNCE_PERIOD_MS = 500
|
||||
mockAccumulators.calendarsToRefresh = new Map<string, any>()
|
||||
mockAccumulators.calendarsToHide = new Set()
|
||||
mockAccumulators.shouldRefreshCalendarListRef.current = false
|
||||
mockAccumulators.currentDebouncePeriod = 0
|
||||
|
||||
mockAccumulators.debouncedUpdateFn = undefined;
|
||||
});
|
||||
it("debounces calendar updates during message storm", () => {
|
||||
mockAccumulators.debouncedUpdateFn = undefined
|
||||
})
|
||||
it('debounces calendar updates during message storm', () => {
|
||||
const mockMessage = {
|
||||
"/calendars/cal1/entry1": {
|
||||
syncToken: "ldsk",
|
||||
},
|
||||
};
|
||||
'/calendars/cal1/entry1': {
|
||||
syncToken: 'ldsk'
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 50; i++) {
|
||||
updateCalendars(mockMessage, mockDispatch, mockAccumulators);
|
||||
updateCalendars(mockMessage, mockDispatch, mockAccumulators)
|
||||
}
|
||||
|
||||
// Dispatch called once because of leading edge
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1);
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Trailing edge
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(500)
|
||||
|
||||
// only one call for the last message + leading edge message
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it("debounces calendar updates during message storm with multiple updates", () => {
|
||||
it('debounces calendar updates during message storm with multiple updates', () => {
|
||||
// Send a storm with mixed messages
|
||||
for (let i = 0; i < 50; i++) {
|
||||
if (i % 3 === 0)
|
||||
updateCalendars(
|
||||
{ "/calendars/cal/A": { syncToken: "ldskfjsld" + i } },
|
||||
{ '/calendars/cal/A': { syncToken: 'ldskfjsld' + i } },
|
||||
mockDispatch,
|
||||
mockAccumulators
|
||||
);
|
||||
)
|
||||
else if (i % 3 === 1)
|
||||
updateCalendars(
|
||||
{ "/calendars/cal/B": { syncToken: "ldskfjsld" + i } },
|
||||
{ '/calendars/cal/B': { syncToken: 'ldskfjsld' + i } },
|
||||
mockDispatch,
|
||||
mockAccumulators
|
||||
);
|
||||
)
|
||||
else
|
||||
updateCalendars(
|
||||
{ "/calendars/cal/C": { syncToken: "ldskfjsld" + i } },
|
||||
{ '/calendars/cal/C': { syncToken: 'ldskfjsld' + i } },
|
||||
mockDispatch,
|
||||
mockAccumulators
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
// Dispatch called once because of leading edge
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1);
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Trailing edge
|
||||
jest.advanceTimersByTime(500);
|
||||
jest.advanceTimersByTime(500)
|
||||
|
||||
// Trailing edge updates once per calendar + the original leading edge
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
it("executes immediately when debounce is disabled", () => {
|
||||
window.WS_DEBOUNCE_PERIOD_MS = 0;
|
||||
it('executes immediately when debounce is disabled', () => {
|
||||
window.WS_DEBOUNCE_PERIOD_MS = 0
|
||||
|
||||
updateCalendars(
|
||||
{ "/calendars/cal1/entry1": { syncToken: "abc" } },
|
||||
{ '/calendars/cal1/entry1': { syncToken: 'abc' } },
|
||||
mockDispatch,
|
||||
mockAccumulators
|
||||
);
|
||||
)
|
||||
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function setupWebsocket() {
|
||||
const originalWebSocket = global.WebSocket;
|
||||
const webSocketInstances: any[] = [];
|
||||
const originalWebSocket = global.WebSocket
|
||||
const webSocketInstances: any[] = []
|
||||
const mockWebSocket = jest.fn().mockImplementation((url: string) => {
|
||||
const ws = {
|
||||
url,
|
||||
@@ -9,27 +9,27 @@ export function setupWebsocket() {
|
||||
removeEventListener: jest.fn(),
|
||||
send: jest.fn(),
|
||||
close: jest.fn(),
|
||||
_listeners: {} as Record<string, Function[]>,
|
||||
};
|
||||
_listeners: {} as Record<string, Function[]>
|
||||
}
|
||||
|
||||
ws.addEventListener.mockImplementation((event, handler) => {
|
||||
ws._listeners[event] ??= [];
|
||||
ws._listeners[event].push(handler);
|
||||
});
|
||||
ws._listeners[event] ??= []
|
||||
ws._listeners[event].push(handler)
|
||||
})
|
||||
|
||||
ws.removeEventListener.mockImplementation((event, handler) => {
|
||||
ws._listeners[event] =
|
||||
ws._listeners[event]?.filter((h) => h !== handler) ?? [];
|
||||
});
|
||||
ws._listeners[event]?.filter(h => h !== handler) ?? []
|
||||
})
|
||||
|
||||
webSocketInstances.push(ws);
|
||||
return ws;
|
||||
});
|
||||
webSocketInstances.push(ws)
|
||||
return ws
|
||||
})
|
||||
|
||||
global.WebSocket = mockWebSocket as any;
|
||||
global.WebSocket = mockWebSocket as any
|
||||
const cleanup = () => {
|
||||
global.WebSocket = originalWebSocket;
|
||||
webSocketInstances.length = 0;
|
||||
};
|
||||
return { webSocketInstances, mockWebSocket, cleanup };
|
||||
global.WebSocket = originalWebSocket
|
||||
webSocketInstances.length = 0
|
||||
}
|
||||
return { webSocketInstances, mockWebSocket, cleanup }
|
||||
}
|
||||
|
||||
@@ -1,147 +1,145 @@
|
||||
import { AppDispatch, RootState, store } from "@/app/store";
|
||||
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services/refreshCalendar";
|
||||
import { getDisplayedCalendarRange } from "@/utils/CalendarRangeManager";
|
||||
import { updateCalendars } from "@/websocket/messaging/updateCalendars";
|
||||
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import { AppDispatch, RootState, store } from '@/app/store'
|
||||
import { refreshCalendarWithSyncToken } from '@/features/Calendars/services/refreshCalendar'
|
||||
import { getDisplayedCalendarRange } from '@/utils/CalendarRangeManager'
|
||||
import { updateCalendars } from '@/websocket/messaging/updateCalendars'
|
||||
import { WS_INBOUND_EVENTS } from '@/websocket/protocols'
|
||||
import { waitFor } from '@testing-library/dom'
|
||||
|
||||
jest.mock("@/features/Calendars/services/refreshCalendar");
|
||||
jest.mock("@/utils/CalendarRangeManager");
|
||||
jest.mock("@/app/store", () => ({
|
||||
jest.mock('@/features/Calendars/services/refreshCalendar')
|
||||
jest.mock('@/utils/CalendarRangeManager')
|
||||
jest.mock('@/app/store', () => ({
|
||||
store: {
|
||||
getState: jest.fn(),
|
||||
},
|
||||
}));
|
||||
getState: jest.fn()
|
||||
}
|
||||
}))
|
||||
|
||||
describe("updateCalendars", () => {
|
||||
let mockDispatch: jest.Mock;
|
||||
describe('updateCalendars', () => {
|
||||
let mockDispatch: jest.Mock
|
||||
const mockRange = {
|
||||
start: new Date("2025-01-15T10:00:00Z"),
|
||||
end: new Date("2025-01-16T10:00:00Z"),
|
||||
};
|
||||
start: new Date('2025-01-15T10:00:00Z'),
|
||||
end: new Date('2025-01-16T10:00:00Z')
|
||||
}
|
||||
|
||||
const mockState = {
|
||||
calendars: {
|
||||
list: {
|
||||
"cal1/entry1": { id: "cal1/entry1", name: "Calendar 1", syncToken: 1 },
|
||||
"cal2/entry2": { id: "cal2/entry2", name: "Calendar 2", syncToken: 1 },
|
||||
'cal1/entry1': { id: 'cal1/entry1', name: 'Calendar 1', syncToken: 1 },
|
||||
'cal2/entry2': { id: 'cal2/entry2', name: 'Calendar 2', syncToken: 1 }
|
||||
},
|
||||
templist: {},
|
||||
},
|
||||
} as unknown as RootState;
|
||||
templist: {}
|
||||
}
|
||||
} as unknown as RootState
|
||||
const mockAccumulators: {
|
||||
calendarsToRefresh: Map<string, any>;
|
||||
calendarsToHide: Set<string>;
|
||||
debouncedUpdateFn?: (dispatch: AppDispatch) => void;
|
||||
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>;
|
||||
currentDebouncePeriod?: number;
|
||||
calendarsToRefresh: Map<string, any>
|
||||
calendarsToHide: Set<string>
|
||||
debouncedUpdateFn?: (dispatch: AppDispatch) => void
|
||||
shouldRefreshCalendarListRef: React.MutableRefObject<boolean>
|
||||
currentDebouncePeriod?: number
|
||||
} = {
|
||||
calendarsToRefresh: new Map<string, any>(),
|
||||
calendarsToHide: new Set(),
|
||||
shouldRefreshCalendarListRef: { current: false },
|
||||
currentDebouncePeriod: 0,
|
||||
debouncedUpdateFn: jest.fn(),
|
||||
};
|
||||
debouncedUpdateFn: jest.fn()
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockDispatch = jest.fn();
|
||||
(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
|
||||
(store.getState as jest.Mock).mockReturnValue(mockState);
|
||||
mockAccumulators.calendarsToRefresh = new Map<string, any>();
|
||||
mockAccumulators.calendarsToHide = new Set();
|
||||
mockAccumulators.currentDebouncePeriod = 0;
|
||||
mockAccumulators.shouldRefreshCalendarListRef.current = false;
|
||||
mockAccumulators.debouncedUpdateFn = jest.fn();
|
||||
});
|
||||
jest.clearAllMocks()
|
||||
mockDispatch = jest.fn()
|
||||
;(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange)
|
||||
;(store.getState as jest.Mock).mockReturnValue(mockState)
|
||||
mockAccumulators.calendarsToRefresh = new Map<string, any>()
|
||||
mockAccumulators.calendarsToHide = new Set()
|
||||
mockAccumulators.currentDebouncePeriod = 0
|
||||
mockAccumulators.shouldRefreshCalendarListRef.current = false
|
||||
mockAccumulators.debouncedUpdateFn = jest.fn()
|
||||
})
|
||||
|
||||
it("should not dispatch for non-object messages", () => {
|
||||
updateCalendars(null, mockDispatch, mockAccumulators);
|
||||
updateCalendars("string", mockDispatch, mockAccumulators);
|
||||
updateCalendars(123, mockDispatch, mockAccumulators);
|
||||
it('should not dispatch for non-object messages', () => {
|
||||
updateCalendars(null, mockDispatch, mockAccumulators)
|
||||
updateCalendars('string', mockDispatch, mockAccumulators)
|
||||
updateCalendars(123, mockDispatch, mockAccumulators)
|
||||
|
||||
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
|
||||
});
|
||||
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should dispatch for registered calendars", async () => {
|
||||
it('should dispatch for registered calendars', async () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
|
||||
"/calendars/cal1/entry1",
|
||||
"/calendars/cal2/entry2",
|
||||
],
|
||||
};
|
||||
'/calendars/cal1/entry1',
|
||||
'/calendars/cal2/entry2'
|
||||
]
|
||||
}
|
||||
|
||||
updateCalendars(message, mockDispatch, mockAccumulators);
|
||||
updateCalendars(message, mockDispatch, mockAccumulators)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2)
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should dispatch for calendar path updates", async () => {
|
||||
it('should dispatch for calendar path updates', async () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": { updated: true },
|
||||
};
|
||||
'/calendars/cal1/entry1': { updated: true }
|
||||
}
|
||||
|
||||
updateCalendars(message, mockDispatch, mockAccumulators);
|
||||
await waitFor(() =>
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalled()
|
||||
);
|
||||
updateCalendars(message, mockDispatch, mockAccumulators)
|
||||
await waitFor(() => expect(refreshCalendarWithSyncToken).toHaveBeenCalled())
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
|
||||
calendar: mockState.calendars.list["cal1/entry1"],
|
||||
calendar: mockState.calendars.list['cal1/entry1'],
|
||||
calType: undefined,
|
||||
calendarRange: mockRange,
|
||||
});
|
||||
});
|
||||
calendarRange: mockRange
|
||||
})
|
||||
})
|
||||
|
||||
it("should use displayed calendar range", async () => {
|
||||
it('should use displayed calendar range', async () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": {},
|
||||
};
|
||||
'/calendars/cal1/entry1': {}
|
||||
}
|
||||
|
||||
updateCalendars(message, mockDispatch, mockAccumulators);
|
||||
await waitFor(() => expect(getDisplayedCalendarRange).toHaveBeenCalled());
|
||||
});
|
||||
updateCalendars(message, mockDispatch, mockAccumulators)
|
||||
await waitFor(() => expect(getDisplayedCalendarRange).toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it("should handle temp calendars", async () => {
|
||||
it('should handle temp calendars', async () => {
|
||||
const stateWithTemp = {
|
||||
calendars: {
|
||||
list: {},
|
||||
templist: {
|
||||
"temp1/entry1": {
|
||||
id: "temp1/entry1",
|
||||
name: "Temp Calendar",
|
||||
syncToken: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
'temp1/entry1': {
|
||||
id: 'temp1/entry1',
|
||||
name: 'Temp Calendar',
|
||||
syncToken: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(store.getState as jest.Mock).mockReturnValue(stateWithTemp);
|
||||
;(store.getState as jest.Mock).mockReturnValue(stateWithTemp)
|
||||
|
||||
const message = {
|
||||
"/calendars/temp1/entry1": {},
|
||||
};
|
||||
'/calendars/temp1/entry1': {}
|
||||
}
|
||||
|
||||
updateCalendars(message, mockDispatch, mockAccumulators);
|
||||
updateCalendars(message, mockDispatch, mockAccumulators)
|
||||
|
||||
await waitFor(() =>
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
|
||||
calendar: stateWithTemp.calendars.templist["temp1/entry1"],
|
||||
calType: "temp",
|
||||
calendarRange: mockRange,
|
||||
calendar: stateWithTemp.calendars.templist['temp1/entry1'],
|
||||
calType: 'temp',
|
||||
calendarRange: mockRange
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle invalid calendar paths gracefully", () => {
|
||||
it('should handle invalid calendar paths gracefully', () => {
|
||||
const message = {
|
||||
"/invalid/path": {},
|
||||
"not-a-path": {},
|
||||
};
|
||||
'/invalid/path': {},
|
||||
'not-a-path': {}
|
||||
}
|
||||
|
||||
updateCalendars(message, mockDispatch, mockAccumulators);
|
||||
updateCalendars(message, mockDispatch, mockAccumulators)
|
||||
|
||||
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
import { parseMessage } from "@/websocket/messaging/parseMessage";
|
||||
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
||||
import { parseMessage } from '@/websocket/messaging/parseMessage'
|
||||
import { WS_INBOUND_EVENTS } from '@/websocket/protocols'
|
||||
|
||||
describe("parseMessage", () => {
|
||||
it("should return empty set for non-object messages", () => {
|
||||
const result1 = parseMessage(null);
|
||||
const result2 = parseMessage("string");
|
||||
const result3 = parseMessage(123);
|
||||
describe('parseMessage', () => {
|
||||
it('should return empty set for non-object messages', () => {
|
||||
const result1 = parseMessage(null)
|
||||
const result2 = parseMessage('string')
|
||||
const result3 = parseMessage(123)
|
||||
|
||||
expect(result1.calendarsToRefresh).toEqual(new Set<string>());
|
||||
expect(result2.calendarsToRefresh).toEqual(new Set<string>());
|
||||
expect(result3.calendarsToRefresh).toEqual(new Set<string>());
|
||||
});
|
||||
expect(result1.calendarsToRefresh).toEqual(new Set<string>())
|
||||
expect(result2.calendarsToRefresh).toEqual(new Set<string>())
|
||||
expect(result3.calendarsToRefresh).toEqual(new Set<string>())
|
||||
})
|
||||
|
||||
it("should handle registered event", () => {
|
||||
it('should handle registered event', () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
|
||||
"/calendars/cal1/entry1",
|
||||
"/calendars/cal2/entry2",
|
||||
],
|
||||
};
|
||||
'/calendars/cal1/entry1',
|
||||
'/calendars/cal2/entry2'
|
||||
]
|
||||
}
|
||||
|
||||
const result = parseMessage(message);
|
||||
const result = parseMessage(message)
|
||||
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal1/entry1");
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal2/entry2");
|
||||
expect(result.calendarsToRefresh.size).toBe(2);
|
||||
});
|
||||
expect(result.calendarsToRefresh).toContain('/calendars/cal1/entry1')
|
||||
expect(result.calendarsToRefresh).toContain('/calendars/cal2/entry2')
|
||||
expect(result.calendarsToRefresh.size).toBe(2)
|
||||
})
|
||||
|
||||
it("should handle unregistered event", () => {
|
||||
it('should handle unregistered event', () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_UNREGISTERED]: ["/calendars/cal1/entry1"],
|
||||
};
|
||||
[WS_INBOUND_EVENTS.CLIENT_UNREGISTERED]: ['/calendars/cal1/entry1']
|
||||
}
|
||||
|
||||
const result = parseMessage(message);
|
||||
const result = parseMessage(message)
|
||||
|
||||
expect(result.calendarsToHide).toContain("/calendars/cal1/entry1");
|
||||
});
|
||||
expect(result.calendarsToHide).toContain('/calendars/cal1/entry1')
|
||||
})
|
||||
|
||||
it("should handle calendar path updates", () => {
|
||||
it('should handle calendar path updates', () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": { updated: true },
|
||||
};
|
||||
'/calendars/cal1/entry1': { updated: true }
|
||||
}
|
||||
|
||||
const result = parseMessage(message);
|
||||
const result = parseMessage(message)
|
||||
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal1/entry1");
|
||||
expect(result.calendarsToRefresh.size).toBe(1);
|
||||
});
|
||||
expect(result.calendarsToRefresh).toContain('/calendars/cal1/entry1')
|
||||
expect(result.calendarsToRefresh.size).toBe(1)
|
||||
})
|
||||
|
||||
it("should parse multiple calendar paths", () => {
|
||||
it('should parse multiple calendar paths', () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": {},
|
||||
"/calendars/cal2/entry2": {},
|
||||
};
|
||||
'/calendars/cal1/entry1': {},
|
||||
'/calendars/cal2/entry2': {}
|
||||
}
|
||||
|
||||
const result = parseMessage(message);
|
||||
const result = parseMessage(message)
|
||||
|
||||
expect(result.calendarsToRefresh.size).toBe(2);
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal1/entry1");
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal2/entry2");
|
||||
});
|
||||
expect(result.calendarsToRefresh.size).toBe(2)
|
||||
expect(result.calendarsToRefresh).toContain('/calendars/cal1/entry1')
|
||||
expect(result.calendarsToRefresh).toContain('/calendars/cal2/entry2')
|
||||
})
|
||||
|
||||
it("should handle multiple event types in single message", () => {
|
||||
it('should handle multiple event types in single message', () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: ["/calendars/cal1/entry1"],
|
||||
[WS_INBOUND_EVENTS.CLIENT_UNREGISTERED]: ["/calendars/cal2/entry2"],
|
||||
"/calendars/cal1/entry1": {},
|
||||
};
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: ['/calendars/cal1/entry1'],
|
||||
[WS_INBOUND_EVENTS.CLIENT_UNREGISTERED]: ['/calendars/cal2/entry2'],
|
||||
'/calendars/cal1/entry1': {}
|
||||
}
|
||||
|
||||
const result = parseMessage(message);
|
||||
const result = parseMessage(message)
|
||||
|
||||
expect(result.calendarsToRefresh.size).toBe(1);
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal1/entry1");
|
||||
});
|
||||
});
|
||||
expect(result.calendarsToRefresh.size).toBe(1)
|
||||
expect(result.calendarsToRefresh).toContain('/calendars/cal1/entry1')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
import { registerToCalendars } from "@/websocket/operations/registerToCalendars";
|
||||
import { registerToCalendars } from '@/websocket/operations/registerToCalendars'
|
||||
|
||||
describe("registerToCalendars", () => {
|
||||
let mockSocket: any;
|
||||
describe('registerToCalendars', () => {
|
||||
let mockSocket: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockSocket = {
|
||||
readyState: WebSocket.OPEN,
|
||||
send: jest.fn(),
|
||||
};
|
||||
});
|
||||
send: jest.fn()
|
||||
}
|
||||
})
|
||||
|
||||
it("should send registration message with calendar URIs", () => {
|
||||
const calendarURIs = ["/calendars/cal1", "/calendars/cal2"];
|
||||
it('should send registration message with calendar URIs', () => {
|
||||
const calendarURIs = ['/calendars/cal1', '/calendars/cal2']
|
||||
|
||||
registerToCalendars(mockSocket, calendarURIs);
|
||||
registerToCalendars(mockSocket, calendarURIs)
|
||||
|
||||
expect(mockSocket.send).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
register: calendarURIs,
|
||||
register: calendarURIs
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw error if socket is not open", () => {
|
||||
mockSocket.readyState = WebSocket.CONNECTING;
|
||||
const calendarURIs = ["/calendars/cal1"];
|
||||
it('should throw error if socket is not open', () => {
|
||||
mockSocket.readyState = WebSocket.CONNECTING
|
||||
const calendarURIs = ['/calendars/cal1']
|
||||
|
||||
expect(() => registerToCalendars(mockSocket, calendarURIs)).toThrow(
|
||||
"Cannot register: WebSocket is not open"
|
||||
);
|
||||
});
|
||||
'Cannot register: WebSocket is not open'
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle empty calendar list", () => {
|
||||
registerToCalendars(mockSocket, []);
|
||||
it('should handle empty calendar list', () => {
|
||||
registerToCalendars(mockSocket, [])
|
||||
|
||||
expect(mockSocket.send).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
register: [],
|
||||
register: []
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should log registration", () => {
|
||||
const consoleInfoSpy = jest.spyOn(console, "info").mockImplementation();
|
||||
const calendarURIs = ["/calendars/cal1", "/calendars/cal2"];
|
||||
it('should log registration', () => {
|
||||
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation()
|
||||
const calendarURIs = ['/calendars/cal1', '/calendars/cal2']
|
||||
|
||||
registerToCalendars(mockSocket, calendarURIs);
|
||||
registerToCalendars(mockSocket, calendarURIs)
|
||||
|
||||
expect(consoleInfoSpy).toHaveBeenCalledWith(
|
||||
"Registered to calendars",
|
||||
'Registered to calendars',
|
||||
calendarURIs
|
||||
);
|
||||
)
|
||||
|
||||
consoleInfoSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
consoleInfoSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import { unregisterToCalendars } from "@/websocket/operations/unregisterToCalendars";
|
||||
import { unregisterToCalendars } from '@/websocket/operations/unregisterToCalendars'
|
||||
|
||||
describe("unregisterToCalendars", () => {
|
||||
let mockSocket: any;
|
||||
describe('unregisterToCalendars', () => {
|
||||
let mockSocket: any
|
||||
|
||||
beforeEach(() => {
|
||||
mockSocket = {
|
||||
readyState: WebSocket.OPEN,
|
||||
send: jest.fn(),
|
||||
};
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
send: jest.fn()
|
||||
}
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should send unregistration message with calendar URIs", () => {
|
||||
const calendarURIs = ["/calendars/cal1", "/calendars/cal2"];
|
||||
it('should send unregistration message with calendar URIs', () => {
|
||||
const calendarURIs = ['/calendars/cal1', '/calendars/cal2']
|
||||
|
||||
unregisterToCalendars(mockSocket, calendarURIs);
|
||||
unregisterToCalendars(mockSocket, calendarURIs)
|
||||
|
||||
expect(mockSocket.send).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
unregister: calendarURIs,
|
||||
unregister: calendarURIs
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw error if socket is not open", () => {
|
||||
mockSocket.readyState = WebSocket.CONNECTING;
|
||||
const calendarURIs = ["/calendars/cal1"];
|
||||
it('should throw error if socket is not open', () => {
|
||||
mockSocket.readyState = WebSocket.CONNECTING
|
||||
const calendarURIs = ['/calendars/cal1']
|
||||
|
||||
expect(() => unregisterToCalendars(mockSocket, calendarURIs)).toThrow(
|
||||
"Cannot unregister: WebSocket is not open"
|
||||
);
|
||||
});
|
||||
'Cannot unregister: WebSocket is not open'
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle empty calendar list", () => {
|
||||
unregisterToCalendars(mockSocket, []);
|
||||
it('should handle empty calendar list', () => {
|
||||
unregisterToCalendars(mockSocket, [])
|
||||
|
||||
expect(mockSocket.send).toHaveBeenCalledWith(
|
||||
JSON.stringify({
|
||||
unregister: [],
|
||||
unregister: []
|
||||
})
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
it("should log unregistration", () => {
|
||||
const consoleInfoSpy = jest.spyOn(console, "info").mockImplementation();
|
||||
const calendarURIs = ["/calendars/cal1", "/calendars/cal2"];
|
||||
it('should log unregistration', () => {
|
||||
const consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation()
|
||||
const calendarURIs = ['/calendars/cal1', '/calendars/cal2']
|
||||
|
||||
unregisterToCalendars(mockSocket, calendarURIs);
|
||||
unregisterToCalendars(mockSocket, calendarURIs)
|
||||
|
||||
expect(consoleInfoSpy).toHaveBeenCalledWith(
|
||||
"Unregistered to calendars",
|
||||
'Unregistered to calendars',
|
||||
calendarURIs
|
||||
);
|
||||
)
|
||||
|
||||
consoleInfoSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
consoleInfoSpy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user