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("@/utils", () => ({ getDisplayedCalendarRange: jest.fn(), findCalendarById: jest.requireActual("@/utils").findCalendarById, })); jest.mock("@/app/store", () => ({ store: { getState: 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"), }; 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 }, }, templist: {}, }, } as unknown as RootState; const mockAccumulators: { calendarsToRefresh: Map; calendarsToHide: Set; debouncedUpdateFn?: (dispatch: AppDispatch) => void; shouldRefreshCalendarListRef: React.MutableRefObject; currentDebouncePeriod?: number; } = { calendarsToRefresh: new Map(), calendarsToHide: new Set(), shouldRefreshCalendarListRef: { current: false }, currentDebouncePeriod: 0, debouncedUpdateFn: undefined, }; 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(); mockAccumulators.calendarsToHide = new Set(); mockAccumulators.shouldRefreshCalendarListRef.current = false; mockAccumulators.currentDebouncePeriod = 0; mockAccumulators.debouncedUpdateFn = undefined; }); it("debounces calendar updates during message storm", () => { const mockMessage = { "/calendars/cal1/entry1": { syncToken: "ldsk", }, }; for (let i = 0; i < 50; i++) { updateCalendars(mockMessage, mockDispatch, mockAccumulators); } // Dispatch called once because of leading edge expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1); // Trailing edge jest.advanceTimersByTime(500); // only one call for the last message + leading edge message expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2); }); 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 } }, mockDispatch, mockAccumulators ); else if (i % 3 === 1) updateCalendars( { "/calendars/cal/B": { syncToken: "ldskfjsld" + i } }, mockDispatch, mockAccumulators ); else updateCalendars( { "/calendars/cal/C": { syncToken: "ldskfjsld" + i } }, mockDispatch, mockAccumulators ); } // Dispatch called once because of leading edge expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1); // Trailing edge jest.advanceTimersByTime(500); // Trailing edge updates once per calendar + the original leading edge expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(4); }); it("executes immediately when debounce is disabled", () => { window.WS_DEBOUNCE_PERIOD_MS = 0; updateCalendars( { "/calendars/cal1/entry1": { syncToken: "abc" } }, mockDispatch, mockAccumulators ); expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(1); }); });