[#449] added debounce for websocket updates (#476)

* [#449] added debounce for websocket updates

* [#449] changed behavior to have disableable debounce + changed debounced messages data storage to be handled by websocket Gate
This commit is contained in:
Camille Moussu
2026-01-26 11:15:37 +01:00
committed by GitHub
parent 1fc85e141e
commit dafac94aec
8 changed files with 341 additions and 41 deletions
@@ -1,4 +1,4 @@
import { RootState, store } from "@/app/store";
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";
@@ -29,23 +29,38 @@ describe("updateCalendars", () => {
templist: {},
},
} as unknown as RootState;
const mockAccumulators: {
calendarsToRefresh: Map<string, any>;
calendarsToHide: Set<string>;
debouncedUpdateFn?: (dispatch: AppDispatch) => void;
currentDebouncePeriod?: number;
} = {
calendarsToRefresh: new Map<string, any>(),
calendarsToHide: new Set(),
currentDebouncePeriod: 0,
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.debouncedUpdateFn = jest.fn();
});
it("should not dispatch for non-object messages", () => {
updateCalendars(null, mockDispatch);
updateCalendars("string", mockDispatch);
updateCalendars(123, mockDispatch);
updateCalendars(null, mockDispatch, mockAccumulators);
updateCalendars("string", mockDispatch, mockAccumulators);
updateCalendars(123, mockDispatch, mockAccumulators);
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
});
it("should dispatch for registered calendars", () => {
it("should dispatch for registered calendars", async () => {
const message = {
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
"/calendars/cal1/entry1",
@@ -53,19 +68,22 @@ describe("updateCalendars", () => {
],
};
updateCalendars(message, mockDispatch);
updateCalendars(message, mockDispatch, mockAccumulators);
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2);
await waitFor(() =>
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2)
);
});
it("should dispatch for calendar path updates", () => {
it("should dispatch for calendar path updates", async () => {
const message = {
"/calendars/cal1/entry1": { updated: true },
};
updateCalendars(message, mockDispatch);
expect(refreshCalendarWithSyncToken).toHaveBeenCalled();
updateCalendars(message, mockDispatch, mockAccumulators);
await waitFor(() =>
expect(refreshCalendarWithSyncToken).toHaveBeenCalled()
);
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
calendar: mockState.calendars.list["cal1/entry1"],
calType: undefined,
@@ -73,14 +91,13 @@ describe("updateCalendars", () => {
});
});
it("should use current displayed calendar range", () => {
it("should use displayed calendar range", async () => {
const message = {
"/calendars/cal1/entry1": {},
};
updateCalendars(message, mockDispatch);
expect(getDisplayedCalendarRange).toHaveBeenCalled();
updateCalendars(message, mockDispatch, mockAccumulators);
await waitFor(() => expect(getDisplayedCalendarRange).toHaveBeenCalled());
});
it("should handle temp calendars", async () => {
@@ -103,7 +120,7 @@ describe("updateCalendars", () => {
"/calendars/temp1/entry1": {},
};
updateCalendars(message, mockDispatch);
updateCalendars(message, mockDispatch, mockAccumulators);
await waitFor(() =>
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
@@ -120,7 +137,7 @@ describe("updateCalendars", () => {
"not-a-path": {},
};
updateCalendars(message, mockDispatch);
updateCalendars(message, mockDispatch, mockAccumulators);
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
});