444 use websocket to trigger reloads (#458)
* [#444] added refresh on sync token calendar update * [#444] added refresh on register and tests * [#444] extracted logic for useEffects and updates * [#444] refactored code structure to use absolute paths
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
import { cleanup, render, waitFor, act } from "@testing-library/react";
|
||||
import { Provider } from "react-redux";
|
||||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import { createWebSocketConnection } from "../../../src/websocket/createWebSocketConnection";
|
||||
import { registerToCalendars } from "../../../src/websocket/ws/registerToCalendars";
|
||||
import { unregisterToCalendars } from "../../../src/websocket/ws/unregisterToCalendars";
|
||||
import { WebSocketGate } from "../../../src/websocket/WebSocketGate";
|
||||
import { setSelectedCalendars } from "../../../src/utils/storage/setSelectedCalendars";
|
||||
import { createWebSocketConnection } from "@/websocket/connection/createConnection";
|
||||
import { registerToCalendars } from "@/websocket/operations/registerToCalendars";
|
||||
import { unregisterToCalendars } from "@/websocket/operations/unregisterToCalendars";
|
||||
import { WebSocketGate } from "@/websocket/WebSocketGate";
|
||||
import { setSelectedCalendars } from "@/utils/storage/setSelectedCalendars";
|
||||
|
||||
jest.mock("../../../src/websocket/createWebSocketConnection");
|
||||
jest.mock("../../../src/websocket/ws/registerToCalendars");
|
||||
jest.mock("../../../src/websocket/ws/unregisterToCalendars");
|
||||
jest.mock("@/websocket/connection/createConnection");
|
||||
jest.mock("@/websocket/operations/registerToCalendars");
|
||||
jest.mock("@/websocket/operations/unregisterToCalendars");
|
||||
|
||||
describe("WebSocketGate", () => {
|
||||
let store: any;
|
||||
@@ -101,7 +101,7 @@ describe("WebSocketGate", () => {
|
||||
});
|
||||
|
||||
describe("Socket Connection Management", () => {
|
||||
it("should add close event listener on socket connection", async () => {
|
||||
it("should create connection with callbacks", async () => {
|
||||
(createWebSocketConnection as jest.Mock).mockResolvedValue(mockSocket);
|
||||
|
||||
render(
|
||||
@@ -111,22 +111,25 @@ describe("WebSocketGate", () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSocket.addEventListener).toHaveBeenCalledWith(
|
||||
"close",
|
||||
expect.any(Function)
|
||||
expect(createWebSocketConnection).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onMessage: expect.any(Function),
|
||||
onClose: expect.any(Function),
|
||||
onError: expect.any(Function),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle socket close event", async () => {
|
||||
let closeHandler: Function;
|
||||
mockSocket.addEventListener = jest.fn((event, handler) => {
|
||||
if (event === "close") {
|
||||
closeHandler = handler;
|
||||
}
|
||||
});
|
||||
it("should handle socket close via callback", async () => {
|
||||
let onCloseCallback: Function | undefined;
|
||||
|
||||
(createWebSocketConnection as jest.Mock).mockResolvedValue(mockSocket);
|
||||
(createWebSocketConnection as jest.Mock).mockImplementation(
|
||||
(callbacks) => {
|
||||
onCloseCallback = callbacks.onClose;
|
||||
return Promise.resolve(mockSocket);
|
||||
}
|
||||
);
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
@@ -135,11 +138,12 @@ describe("WebSocketGate", () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockSocket.addEventListener).toHaveBeenCalled();
|
||||
expect(createWebSocketConnection).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// Simulate close event
|
||||
await act(async () => {
|
||||
closeHandler!();
|
||||
onCloseCallback?.(new CloseEvent("close"));
|
||||
});
|
||||
|
||||
// Verify that subsequent calendar changes don't try to register
|
||||
@@ -349,7 +353,7 @@ describe("WebSocketGate", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
"Failed to update calendar registrations:",
|
||||
"Failed to register calendar:",
|
||||
expect.any(Error)
|
||||
);
|
||||
});
|
||||
@@ -443,8 +447,9 @@ describe("WebSocketGate", () => {
|
||||
});
|
||||
|
||||
// Wait a bit to ensure the effect would have run if it was going to
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
jest.useFakeTimers();
|
||||
jest.advanceTimersByTime(100);
|
||||
jest.useRealTimers();
|
||||
expect(registerToCalendars).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -477,7 +482,7 @@ describe("WebSocketGate", () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleError).toHaveBeenCalledWith(
|
||||
"Failed to update calendar registrations:",
|
||||
"Failed to unregister calendar:",
|
||||
expect.any(Error)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { api } from "../../../../src/utils/apiUtils";
|
||||
import { fetchWebSocketTicket } from "../../../../src/websocket/api/fetchWebSocketTicket";
|
||||
import { api } from "@/utils/apiUtils";
|
||||
import { fetchWebSocketTicket } from "@/websocket/api/fetchWebSocketTicket";
|
||||
|
||||
jest.mock("../../../../src/utils/apiUtils");
|
||||
jest.mock("@/utils/apiUtils");
|
||||
|
||||
describe("fetchWebSocketTicket", () => {
|
||||
const mockTicket = {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
import { fetchWebSocketTicket } from "../../../src/websocket/api/fetchWebSocketTicket";
|
||||
import { createWebSocketConnection } from "../../../src/websocket/createWebSocketConnection";
|
||||
import { WS_INBOUND_EVENTS } from "../../../src/websocket/protocols";
|
||||
import { fetchWebSocketTicket } from "@/websocket/api/fetchWebSocketTicket";
|
||||
import { createWebSocketConnection } from "@/websocket/connection/createConnection";
|
||||
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
||||
import { setupWebsocket } from "./utils/setupWebsocket";
|
||||
|
||||
jest.mock("../../../src/websocket/api/fetchWebSocketTicket");
|
||||
jest.mock("@/websocket/api/fetchWebSocketTicket");
|
||||
|
||||
describe("createWebSocketConnection", () => {
|
||||
let mockWebSocket: jest.Mock;
|
||||
@@ -28,7 +28,12 @@ describe("createWebSocketConnection", () => {
|
||||
};
|
||||
|
||||
const createAndOpenConnection = async () => {
|
||||
const promise = createWebSocketConnection();
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
onError: jest.fn(),
|
||||
};
|
||||
const promise = createWebSocketConnection(mockCallbacks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webSocketInstances.length).toBe(1);
|
||||
@@ -37,7 +42,7 @@ describe("createWebSocketConnection", () => {
|
||||
triggerEvent(getWs(), WS_INBOUND_EVENTS.CONNECTION_OPENED);
|
||||
const socket = await promise;
|
||||
|
||||
return { socket, ws: getWs(), promise };
|
||||
return { socket, ws: getWs(), promise, mockCallbacks };
|
||||
};
|
||||
|
||||
/** ---------- Setup ---------- */
|
||||
@@ -60,8 +65,11 @@ describe("createWebSocketConnection", () => {
|
||||
|
||||
it("throws when WEBSOCKET_URL is not defined", async () => {
|
||||
delete (window as any).WEBSOCKET_URL;
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
};
|
||||
|
||||
await expect(createWebSocketConnection()).rejects.toThrow(
|
||||
await expect(createWebSocketConnection(mockCallbacks)).rejects.toThrow(
|
||||
"WEBSOCKET_URL is not defined"
|
||||
);
|
||||
});
|
||||
@@ -95,7 +103,10 @@ describe("createWebSocketConnection", () => {
|
||||
});
|
||||
|
||||
it("rejects when connection fails", async () => {
|
||||
const promise = createWebSocketConnection();
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
};
|
||||
const promise = createWebSocketConnection(mockCallbacks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webSocketInstances.length).toBe(1);
|
||||
@@ -118,23 +129,6 @@ describe("createWebSocketConnection", () => {
|
||||
expect(ws._listeners[WS_INBOUND_EVENTS.CONNECTION_CLOSED]).toBeDefined();
|
||||
});
|
||||
|
||||
it("parses and logs incoming messages", async () => {
|
||||
const logSpy = jest.spyOn(console, "log").mockImplementation();
|
||||
|
||||
const { ws } = await createAndOpenConnection();
|
||||
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, {
|
||||
data: JSON.stringify({ type: "test", payload: "data" }),
|
||||
});
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith("WebSocket message received:", {
|
||||
type: "test",
|
||||
payload: "data",
|
||||
});
|
||||
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("handles invalid JSON messages", async () => {
|
||||
const errorSpy = jest.spyOn(console, "error").mockImplementation();
|
||||
|
||||
@@ -149,4 +143,69 @@ describe("createWebSocketConnection", () => {
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("rejects on timeout", async () => {
|
||||
jest.useFakeTimers();
|
||||
const mockCallbacks = {
|
||||
onMessage: jest.fn(),
|
||||
};
|
||||
const promise = createWebSocketConnection(mockCallbacks);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(webSocketInstances.length).toBe(1);
|
||||
});
|
||||
|
||||
jest.advanceTimersByTime(10000);
|
||||
|
||||
await expect(promise).rejects.toThrow("WebSocket connection timed out");
|
||||
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it("calls onMessage callback when message received", async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection();
|
||||
|
||||
const testMessage = { type: "test", payload: "data" };
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, {
|
||||
data: JSON.stringify(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();
|
||||
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: "invalid json" });
|
||||
|
||||
expect(mockCallbacks.onMessage).not.toHaveBeenCalled();
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
"Failed to parse WebSocket message:",
|
||||
expect.any(Error)
|
||||
);
|
||||
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("calls onClose callback when connection closes", async () => {
|
||||
const { ws, mockCallbacks } = await createAndOpenConnection();
|
||||
|
||||
const closeEvent = new CloseEvent("close", {
|
||||
code: 1000,
|
||||
reason: "Normal closure",
|
||||
});
|
||||
triggerEvent(ws, WS_INBOUND_EVENTS.CONNECTION_CLOSED, closeEvent);
|
||||
|
||||
expect(mockCallbacks.onClose).toHaveBeenCalledWith(closeEvent);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
expect(mockCallbacks.onError).toHaveBeenCalledWith(errorEvent);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { updateCalendars } from "@/websocket/messaging/updateCalendars";
|
||||
import { refreshCalendarWithSyncToken } from "@/features/Calendars/services/refreshCalendar";
|
||||
import { RootState, store } from "@/app/store";
|
||||
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
||||
import { getDisplayedCalendarRange } from "@/utils/CalendarRangeManager";
|
||||
import { waitFor } from "@testing-library/dom";
|
||||
|
||||
jest.mock("@/features/Calendars/services/refreshCalendar");
|
||||
jest.mock("@/utils/CalendarRangeManager");
|
||||
jest.mock("@/app/store", () => ({
|
||||
store: {
|
||||
getState: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("updateCalendars", () => {
|
||||
let mockDispatch: jest.Mock;
|
||||
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 },
|
||||
},
|
||||
templist: {},
|
||||
},
|
||||
} as unknown as RootState;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockDispatch = jest.fn();
|
||||
(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
|
||||
(store.getState as jest.Mock).mockReturnValue(mockState);
|
||||
});
|
||||
|
||||
it("should not dispatch for non-object messages", () => {
|
||||
updateCalendars(null, mockDispatch);
|
||||
updateCalendars("string", mockDispatch);
|
||||
updateCalendars(123, mockDispatch);
|
||||
|
||||
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should dispatch for registered calendars", () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
|
||||
"/calendars/cal1/entry1",
|
||||
"/calendars/cal2/entry2",
|
||||
],
|
||||
};
|
||||
|
||||
updateCalendars(message, mockDispatch);
|
||||
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("should dispatch for calendar path updates", () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": { updated: true },
|
||||
};
|
||||
|
||||
updateCalendars(message, mockDispatch);
|
||||
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalled();
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
|
||||
calendar: mockState.calendars.list["cal1/entry1"],
|
||||
calType: undefined,
|
||||
calendarRange: mockRange,
|
||||
});
|
||||
});
|
||||
|
||||
it("should use current displayed calendar range", () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": {},
|
||||
};
|
||||
|
||||
updateCalendars(message, mockDispatch);
|
||||
|
||||
expect(getDisplayedCalendarRange).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle temp calendars", async () => {
|
||||
const stateWithTemp = {
|
||||
calendars: {
|
||||
list: {},
|
||||
templist: {
|
||||
"temp1/entry1": {
|
||||
id: "temp1/entry1",
|
||||
name: "Temp Calendar",
|
||||
syncToken: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
(store.getState as jest.Mock).mockReturnValue(stateWithTemp);
|
||||
|
||||
const message = {
|
||||
"/calendars/temp1/entry1": {},
|
||||
};
|
||||
|
||||
updateCalendars(message, mockDispatch);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(refreshCalendarWithSyncToken).toHaveBeenCalledWith({
|
||||
calendar: stateWithTemp.calendars.templist["temp1/entry1"],
|
||||
calType: "temp",
|
||||
calendarRange: mockRange,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle invalid calendar paths gracefully", () => {
|
||||
const message = {
|
||||
"/invalid/path": {},
|
||||
"not-a-path": {},
|
||||
};
|
||||
|
||||
updateCalendars(message, mockDispatch);
|
||||
|
||||
expect(refreshCalendarWithSyncToken).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
||||
import { parseMessage } from "@/websocket/messaging/parseMessage";
|
||||
|
||||
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>());
|
||||
});
|
||||
|
||||
it("should handle registered event", () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: [
|
||||
"/calendars/cal1/entry1",
|
||||
"/calendars/cal2/entry2",
|
||||
],
|
||||
};
|
||||
|
||||
const result = parseMessage(message);
|
||||
|
||||
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", () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_UNREGISTERED]: ["/calendars/cal1/entry1"],
|
||||
};
|
||||
|
||||
const result = parseMessage(message);
|
||||
|
||||
expect(result.calendarsToHide).toContain("/calendars/cal1/entry1");
|
||||
});
|
||||
|
||||
it("should handle calendar path updates", () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": { updated: true },
|
||||
};
|
||||
|
||||
const result = parseMessage(message);
|
||||
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal1/entry1");
|
||||
expect(result.calendarsToRefresh.size).toBe(1);
|
||||
});
|
||||
|
||||
it("should parse multiple calendar paths", () => {
|
||||
const message = {
|
||||
"/calendars/cal1/entry1": {},
|
||||
"/calendars/cal2/entry2": {},
|
||||
};
|
||||
|
||||
const result = parseMessage(message);
|
||||
|
||||
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", () => {
|
||||
const message = {
|
||||
[WS_INBOUND_EVENTS.CLIENT_REGISTERED]: ["/calendars/cal1/entry1"],
|
||||
[WS_INBOUND_EVENTS.CLIENT_UNREGISTERED]: ["/calendars/cal2/entry2"],
|
||||
"/calendars/cal1/entry1": {},
|
||||
};
|
||||
|
||||
const result = parseMessage(message);
|
||||
|
||||
expect(result.calendarsToRefresh.size).toBe(1);
|
||||
expect(result.calendarsToRefresh).toContain("/calendars/cal1/entry1");
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { registerToCalendars } from "../../../../src/websocket/ws/registerToCalendars";
|
||||
import { registerToCalendars } from "@/websocket/operations/registerToCalendars";
|
||||
|
||||
describe("registerToCalendars", () => {
|
||||
let mockSocket: any;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/websocket/__tests__/unregisterToCalendars.test.ts
|
||||
|
||||
import { unregisterToCalendars } from "../../../../src/websocket/ws/unregisterToCalendars";
|
||||
import { unregisterToCalendars } from "@/websocket/operations/unregisterToCalendars";
|
||||
|
||||
describe("unregisterToCalendars", () => {
|
||||
let mockSocket: any;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
getDisplayedDate,
|
||||
setDisplayedDateAndRange,
|
||||
calendarRangeManager,
|
||||
getDisplayedCalendarRange,
|
||||
} from "../../src/utils/CalendarRangeManager";
|
||||
|
||||
describe("CalendarRangeManager", () => {
|
||||
beforeEach(() => {
|
||||
setDisplayedDateAndRange(new Date());
|
||||
});
|
||||
|
||||
it("should return a singleton instance", () => {
|
||||
const instance1 = calendarRangeManager;
|
||||
const instance2 = calendarRangeManager;
|
||||
|
||||
expect(instance1).toStrictEqual(instance2);
|
||||
});
|
||||
|
||||
it("should compute and return calendar range when date is set", () => {
|
||||
const testDate = new Date("2025-06-15T10:00:00Z");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
|
||||
const range = getDisplayedCalendarRange();
|
||||
expect(range.start).toBeInstanceOf(Date);
|
||||
expect(range.end).toBeInstanceOf(Date);
|
||||
expect(range.start.getTime()).toBeLessThanOrEqual(testDate.getTime());
|
||||
expect(range.end.getTime()).toBeGreaterThanOrEqual(testDate.getTime());
|
||||
});
|
||||
|
||||
it("should get the default date", () => {
|
||||
const date = getDisplayedDate();
|
||||
expect(date).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it("should set and get a date", () => {
|
||||
const testDate = new Date("2025-01-15T10:00:00Z");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
|
||||
const retrievedDate = getDisplayedDate();
|
||||
expect(retrievedDate).toStrictEqual(testDate);
|
||||
});
|
||||
|
||||
it("should persist date across multiple calls", () => {
|
||||
const testDate = new Date("2025-06-20T15:30:00Z");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
|
||||
const date1 = getDisplayedDate();
|
||||
const date2 = getDisplayedDate();
|
||||
|
||||
expect(date1).toStrictEqual(date2);
|
||||
expect(date1).toStrictEqual(testDate);
|
||||
});
|
||||
|
||||
it("should update date when set multiple times", () => {
|
||||
const date1 = new Date("2025-01-01T00:00:00Z");
|
||||
const date2 = new Date("2025-12-31T23:59:59Z");
|
||||
|
||||
setDisplayedDateAndRange(date1);
|
||||
expect(getDisplayedDate()).toStrictEqual(date1);
|
||||
|
||||
setDisplayedDateAndRange(date2);
|
||||
expect(getDisplayedDate()).toStrictEqual(date2);
|
||||
});
|
||||
|
||||
it("should maintain shared state (singleton behavior)", () => {
|
||||
const testDate = new Date("2025-03-01");
|
||||
setDisplayedDateAndRange(testDate);
|
||||
|
||||
// Verify CalendarRangeManager reflects the same state
|
||||
expect(calendarRangeManager.getDate()).toStrictEqual(testDate);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { findCalendarById } from "../../src/utils/findCalendarById";
|
||||
import { Calendar } from "../../src/features/Calendars/CalendarTypes";
|
||||
import { RootState } from "../../src/app/store";
|
||||
|
||||
describe("findCalendarById", () => {
|
||||
const mockCalendar1: Calendar = {
|
||||
id: "cal1",
|
||||
name: "Personal",
|
||||
color: { light: "#FF0000" },
|
||||
} as unknown as Calendar;
|
||||
|
||||
const mockCalendar2: Calendar = {
|
||||
id: "cal2",
|
||||
name: "Work",
|
||||
color: { light: "#0000FF" },
|
||||
} as unknown as Calendar;
|
||||
|
||||
const mockTempCalendar: Calendar = {
|
||||
id: "temp1",
|
||||
name: "Temporary",
|
||||
color: { light: "#00FF00" },
|
||||
} as unknown as Calendar;
|
||||
|
||||
const mockState = {
|
||||
calendars: {
|
||||
list: {
|
||||
cal1: mockCalendar1,
|
||||
cal2: mockCalendar2,
|
||||
},
|
||||
templist: {
|
||||
temp1: mockTempCalendar,
|
||||
},
|
||||
},
|
||||
} as unknown as Partial<RootState>;
|
||||
|
||||
it("should find calendar in main list", () => {
|
||||
const result = findCalendarById(mockState, "cal1");
|
||||
|
||||
expect(result?.calendar).toEqual(mockCalendar1);
|
||||
expect(result?.type).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should find calendar in temp list", () => {
|
||||
const result = findCalendarById(mockState, "temp1");
|
||||
|
||||
expect(result?.calendar).toEqual(mockTempCalendar);
|
||||
expect(result?.type).toBe("temp");
|
||||
});
|
||||
|
||||
it("should not return calendar for non-existent id", () => {
|
||||
const result = findCalendarById(mockState, "nonexistent");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should not return calendar for empty string", () => {
|
||||
const result = findCalendarById(mockState, "");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should prioritize main list over temp list", () => {
|
||||
const stateWithDuplicate = {
|
||||
calendars: {
|
||||
list: {
|
||||
dup1: mockCalendar1,
|
||||
},
|
||||
templist: {
|
||||
dup1: mockTempCalendar,
|
||||
},
|
||||
},
|
||||
} as unknown as Partial<RootState>;
|
||||
|
||||
const result = findCalendarById(stateWithDuplicate, "dup1");
|
||||
|
||||
expect(result?.calendar).toEqual(mockCalendar1);
|
||||
expect(result?.type).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle undefined list or templist", () => {
|
||||
const stateWithPartialCalendars = {
|
||||
calendars: {
|
||||
list: undefined,
|
||||
templist: { temp1: mockTempCalendar },
|
||||
},
|
||||
} as unknown as Partial<RootState>;
|
||||
|
||||
const result = findCalendarById(stateWithPartialCalendars, "temp1");
|
||||
expect(result?.calendar).toEqual(mockTempCalendar);
|
||||
});
|
||||
|
||||
it("should handle missing calendars state", () => {
|
||||
const emptyState = {};
|
||||
|
||||
const result = findCalendarById(emptyState, "cal1");
|
||||
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user