[#38]added missing tests
This commit is contained in:
@@ -0,0 +1,117 @@
|
|||||||
|
import { screen, fireEvent } from "@testing-library/react";
|
||||||
|
import { useAppDispatch } from "../../../src/app/hooks";
|
||||||
|
import { createCalendar } from "../../../src/features/Calendars/CalendarSlice";
|
||||||
|
import CalendarPopover from "../../../src/features/Calendars/CalendarModal";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
|
||||||
|
jest.mock("../../../src/app/hooks");
|
||||||
|
|
||||||
|
describe("CalendarPopover", () => {
|
||||||
|
const mockDispatch = jest.fn();
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
(useAppDispatch as unknown as jest.Mock).mockReturnValue(mockDispatch);
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderPopover = (open = true) => {
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: { list: {}, pending: true },
|
||||||
|
};
|
||||||
|
renderWithProviders(
|
||||||
|
<CalendarPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={open}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders popover and inputs", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/Name/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Create a Calendar")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates name and description fields", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
const nameInput = screen.getByLabelText(/Name/i);
|
||||||
|
fireEvent.change(nameInput, { target: { value: "My Calendar" } });
|
||||||
|
expect(nameInput).toHaveValue("My Calendar");
|
||||||
|
|
||||||
|
const descInput = screen.getByLabelText(/Description/i);
|
||||||
|
fireEvent.change(descInput, { target: { value: "Test description" } });
|
||||||
|
expect(descInput).toHaveValue("Test description");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("selects a color when a color button is clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
// There are multiple color buttons; pick the first
|
||||||
|
const colorButtons = screen
|
||||||
|
.getAllByRole("button")
|
||||||
|
.filter((btn) => btn.style.backgroundColor !== "");
|
||||||
|
fireEvent.click(colorButtons[0]);
|
||||||
|
|
||||||
|
// The header background should update (check via inline style)
|
||||||
|
expect(screen.getByText("Create a Calendar").style.backgroundColor).toBe(
|
||||||
|
colorButtons[0].style.backgroundColor
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dispatches createCalendar and calls onClose when Save clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/Name/i), {
|
||||||
|
target: { value: "Test Calendar" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Description/i), {
|
||||||
|
target: { value: "Test Description" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedColor = "#D50000";
|
||||||
|
|
||||||
|
const colorButtons = screen
|
||||||
|
.getAllByRole("button")
|
||||||
|
.filter((btn) => btn.style.backgroundColor !== "");
|
||||||
|
|
||||||
|
fireEvent.click(colorButtons[0]);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Save/i));
|
||||||
|
|
||||||
|
expect(mockDispatch).toHaveBeenCalledWith(
|
||||||
|
createCalendar({
|
||||||
|
name: "Test Calendar",
|
||||||
|
description: "Test Description",
|
||||||
|
color: expectedColor,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
|
||||||
|
// Inputs should be reset (optional check)
|
||||||
|
expect(screen.getByLabelText(/Name/i)).toHaveValue("");
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toHaveValue("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onClose when Cancel clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Cancel/i));
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
import { screen, fireEvent } from "@testing-library/react";
|
||||||
|
import {
|
||||||
|
addEvent,
|
||||||
|
createCalendar,
|
||||||
|
} from "../../../src/features/Calendars/CalendarSlice";
|
||||||
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
|
import EventPopover from "../../../src/features/Events/EventModal";
|
||||||
|
import { DateSelectArg } from "@fullcalendar/core";
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
import { formatDateToYYYYMMDDTHHMMSS } from "../../../src/utils/dateUtils";
|
||||||
|
import preview from "jest-preview";
|
||||||
|
import * as appHooks from "../../../src/app/hooks"; // Import the module
|
||||||
|
|
||||||
|
describe("EventPopover", () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
jest.mock("crypto");
|
||||||
|
const dispatch = jest.fn();
|
||||||
|
jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderPopover = (
|
||||||
|
selectedRange = {
|
||||||
|
startStr: "2025-07-18T09:00",
|
||||||
|
endStr: "2025-07-18T10:00",
|
||||||
|
start: new Date("2025-07-18T09:00"),
|
||||||
|
end: new Date("2025-07-18T10:00"),
|
||||||
|
allDay: false,
|
||||||
|
resource: undefined,
|
||||||
|
} as unknown as DateSelectArg
|
||||||
|
) => {
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
organiserData: {
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "mailto:test@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: {
|
||||||
|
list: {
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
name: "Calendar 1",
|
||||||
|
color: "#FF0000",
|
||||||
|
},
|
||||||
|
"667037022b752d0026472254/cal2": {
|
||||||
|
name: "Calendar 2",
|
||||||
|
color: "#00FF00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pending: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
renderWithProviders(
|
||||||
|
<EventPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
selectedRange={selectedRange}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders correctly with inputs and calendar options", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
expect(screen.getByText(/Create Event/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/title/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Start/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/End/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText(/Location/i)).toBeInTheDocument();
|
||||||
|
preview.debug();
|
||||||
|
// Calendar options
|
||||||
|
const select = screen.getByRole("combobox");
|
||||||
|
fireEvent.mouseDown(select);
|
||||||
|
expect(screen.getByText("Calendar 1")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Calendar 2")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("fills start and end from selectedRange", () => {
|
||||||
|
const selectedRange = {
|
||||||
|
startStr: "2026-07-20T10:00",
|
||||||
|
endStr: "2026-07-20T12:00",
|
||||||
|
start: new Date("2026-07-20T10:00"),
|
||||||
|
end: new Date("2026-07-20T12:00"),
|
||||||
|
allDay: false,
|
||||||
|
resource: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
renderPopover(selectedRange as unknown as DateSelectArg);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText(/Start/i)).toHaveValue("2026-07-20T10:00");
|
||||||
|
expect(screen.getByLabelText(/End/i)).toHaveValue("2026-07-20T12:00");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates inputs on change", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
selectedRange={null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/title/i), {
|
||||||
|
target: { value: "My Event" },
|
||||||
|
});
|
||||||
|
expect(screen.getByLabelText(/title/i)).toHaveValue("My Event");
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/Description/i), {
|
||||||
|
target: { value: "Event Description" },
|
||||||
|
});
|
||||||
|
expect(screen.getByLabelText(/Description/i)).toHaveValue(
|
||||||
|
"Event Description"
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText(/Location/i), {
|
||||||
|
target: { value: "Conference Room" },
|
||||||
|
});
|
||||||
|
expect(screen.getByLabelText(/Location/i)).toHaveValue("Conference Room");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("changes selected calendar", async () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
const select = screen.getByRole("combobox");
|
||||||
|
fireEvent.mouseDown(select); // Open menu
|
||||||
|
|
||||||
|
const option = await screen.findByText("Calendar 2");
|
||||||
|
fireEvent.click(option);
|
||||||
|
|
||||||
|
// The Select control value isn't easy to check directly because MUI Select wraps input,
|
||||||
|
// but we can test by triggering save later
|
||||||
|
});
|
||||||
|
|
||||||
|
it("dispatches addEvent and calls onClose when Save is clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
// Select calendar id
|
||||||
|
const select = screen.getByRole("combobox");
|
||||||
|
fireEvent.mouseDown(select);
|
||||||
|
fireEvent.click(screen.getByText("Calendar 1"));
|
||||||
|
|
||||||
|
// Fill inputs
|
||||||
|
fireEvent.change(screen.getByLabelText(/title/i), {
|
||||||
|
target: { value: "Meeting" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Start/i), {
|
||||||
|
target: { value: "2025-07-18T09:00" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/End/i), {
|
||||||
|
target: { value: "2025-07-18T10:00" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Description/i), {
|
||||||
|
target: { value: "Discuss project" },
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText(/Location/i), {
|
||||||
|
target: { value: "Zoom" },
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Save/i));
|
||||||
|
|
||||||
|
expect(dispatch).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Extract dispatched action argument
|
||||||
|
const dispatchedArg = dispatch.mock.calls[0][0].payload;
|
||||||
|
console.log(dispatchedArg);
|
||||||
|
expect(dispatchedArg.calendarUid).toBe("667037022b752d0026472254/cal1");
|
||||||
|
expect(dispatchedArg.event.title).toBe("Meeting");
|
||||||
|
expect(dispatchedArg.event.description).toBe("Discuss project");
|
||||||
|
expect(dispatchedArg.event.location).toBe("Zoom");
|
||||||
|
expect(dispatchedArg.event.color).toBe("#FF0000");
|
||||||
|
expect(dispatchedArg.event.organizer).toEqual({
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "mailto:test@test.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
// onClose should be called
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onClose when Cancel clicked", () => {
|
||||||
|
renderPopover();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText(/Cancel/i));
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import { parseCalendarEvent } from "../../../src/features/Events/eventUtils";
|
||||||
|
|
||||||
|
describe("parseCalendarEvent", () => {
|
||||||
|
const baseColor = "#00FF00";
|
||||||
|
const calendarId = "calendar-123";
|
||||||
|
|
||||||
|
it("parses a full event correctly", () => {
|
||||||
|
const rawData = [
|
||||||
|
["UID", {}, "text", "event-1"],
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
["DTEND", {}, "date-time", "2025-07-18T10:00:00Z"],
|
||||||
|
["SUMMARY", {}, "text", "Team Meeting"],
|
||||||
|
["DESCRIPTION", {}, "text", "Discuss roadmap"],
|
||||||
|
["LOCATION", {}, "text", "Zoom"],
|
||||||
|
["ORGANIZER", { cn: "Alice" }, "cal-address", "mailto:alice@example.com"],
|
||||||
|
[
|
||||||
|
"ATTENDEE",
|
||||||
|
{ cn: "Bob", partstat: "ACCEPTED" },
|
||||||
|
"cal-address",
|
||||||
|
"mailto:bob@example.com",
|
||||||
|
],
|
||||||
|
["X-OPENPAAS-VIDEOCONFERENCE", {}, "text", "https://meet.link"],
|
||||||
|
["STATUS", {}, "text", "CONFIRMED"],
|
||||||
|
["SEQUENCE", {}, "integer", "2"],
|
||||||
|
["DTSTAMP", {}, "date-time", "2025-07-18T08:00:00Z"],
|
||||||
|
] as unknown as [string, Record<string, string>, string, any];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawData, baseColor, calendarId);
|
||||||
|
|
||||||
|
expect(result.uid).toBe("event-1");
|
||||||
|
expect(result.title).toBe("Team Meeting");
|
||||||
|
expect(result.description).toBe("Discuss roadmap");
|
||||||
|
expect(result.location).toBe("Zoom");
|
||||||
|
expect(result.start).toBe("2025-07-18T09:00:00Z");
|
||||||
|
expect(result.end).toBe("2025-07-18T10:00:00Z");
|
||||||
|
expect(result.stamp).toBe("2025-07-18T08:00:00Z");
|
||||||
|
expect(result.sequence).toBe(2);
|
||||||
|
expect(result.color).toBe(baseColor);
|
||||||
|
expect(result.status).toBe("CONFIRMED");
|
||||||
|
expect(result.x_openpass_videoconference).toBe("https://meet.link");
|
||||||
|
|
||||||
|
expect(result.organizer).toEqual({
|
||||||
|
cn: "Alice",
|
||||||
|
cal_address: "alice@example.com",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.attendee).toEqual([
|
||||||
|
{
|
||||||
|
cn: "Bob",
|
||||||
|
cal_address: "bob@example.com",
|
||||||
|
partstat: "ACCEPTED",
|
||||||
|
rsvp: "",
|
||||||
|
role: "",
|
||||||
|
cutype: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends recurrence-id to UID if present", () => {
|
||||||
|
const rawData: any = [
|
||||||
|
["UID", {}, "text", "event-2"],
|
||||||
|
["RECURRENCE-ID", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawData, baseColor, calendarId);
|
||||||
|
|
||||||
|
expect(result.uid).toBe("event-2/2025-07-18T09:00:00Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns error if UID or start is missing", () => {
|
||||||
|
const rawDataMissingUid: any = [
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawDataMissingUid, baseColor, calendarId);
|
||||||
|
expect(result.error).toMatch(/missing crucial event param/);
|
||||||
|
|
||||||
|
const rawDataMissingStart: any = [["UID", {}, "text", "event-3"]];
|
||||||
|
|
||||||
|
const result2 = parseCalendarEvent(
|
||||||
|
rawDataMissingStart,
|
||||||
|
baseColor,
|
||||||
|
calendarId
|
||||||
|
);
|
||||||
|
expect(result2.error).toMatch(/missing crucial event param/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles optional organizer and attendee fields gracefully", () => {
|
||||||
|
const rawData = [
|
||||||
|
["UID", {}, "text", "event-4"],
|
||||||
|
["DTSTART", {}, "date-time", "2025-07-18T09:00:00Z"],
|
||||||
|
["ATTENDEE", {}, "cal-address", "mailto:john@example.com"],
|
||||||
|
["ORGANIZER", {}, "cal-address", "mailto:jane@example.com"],
|
||||||
|
] as unknown as [string, Record<string, string>, string, any];
|
||||||
|
|
||||||
|
const result = parseCalendarEvent(rawData, baseColor, calendarId);
|
||||||
|
|
||||||
|
expect(result.attendee).toEqual([
|
||||||
|
{
|
||||||
|
cn: "",
|
||||||
|
cal_address: "john@example.com",
|
||||||
|
partstat: "",
|
||||||
|
rsvp: "",
|
||||||
|
role: "",
|
||||||
|
cutype: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.organizer).toEqual({
|
||||||
|
cn: "",
|
||||||
|
cal_address: "jane@example.com",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -45,8 +45,8 @@ describe("HandleLogin", () => {
|
|||||||
const preloadedState = {
|
const preloadedState = {
|
||||||
user: {
|
user: {
|
||||||
userData: {
|
userData: {
|
||||||
sub: "cmoussu",
|
sub: "test",
|
||||||
email: "cmoussu@linagora.com",
|
email: "test@test.com",
|
||||||
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
openpaasId: "667037022b752d0026472254",
|
openpaasId: "667037022b752d0026472254",
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+3
-2070
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,9 @@
|
|||||||
"last 1 safari version"
|
"last 1 safari version"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "24.x"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.28.0",
|
"@babel/preset-env": "^7.28.0",
|
||||||
"@babel/preset-react": "^7.27.1",
|
"@babel/preset-react": "^7.27.1",
|
||||||
|
|||||||
Reference in New Issue
Block a user