[#77] fixed delete refresh issue and added more tests

This commit is contained in:
Camille Moussu
2025-09-05 16:41:05 +02:00
parent 0549a77d92
commit 2f071303de
4 changed files with 287 additions and 274 deletions
+272 -272
View File
@@ -1,63 +1,171 @@
import { screen, fireEvent, waitFor } from "@testing-library/react";
import RepeatEvent from "../../src/components/Event/EventRepeat";
import { RepetitionObject } from "../../src/features/Events/EventsTypes";
import { screen, fireEvent, waitFor, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../utils/Renderwithproviders";
import RepeatEvent from "../../src/components/Event/EventRepeat";
import EventPopover from "../../src/features/Events/EventModal";
import { RepetitionObject } from "../../src/features/Events/EventsTypes";
import { DateSelectArg } from "@fullcalendar/core";
import { formatDateToYYYYMMDDTHHMMSS } from "../../src/utils/dateUtils";
import * as eventThunks from "../../src/features/Calendars/CalendarSlice";
import * as apiUtils from "../../src/utils/apiUtils";
const baseRepetition: RepetitionObject = {
freq: "",
interval: 1,
occurrences: 0,
endDate: "",
selectedDays: [],
};
const mockOnClose = jest.fn();
const mockSetSelectedRange = jest.fn();
const mockCalendarRef = { current: { select: jest.fn() } } as any;
const preloadedState = {
user: {
userData: {
sub: "test",
email: "test@test.com",
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
openpaasId: "667037022b752d0026472254",
},
organiserData: {
cn: "test",
cal_address: "test@test.com",
},
},
calendars: {
list: {
"667037022b752d0026472254/cal1": {
id: "667037022b752d0026472254/cal1",
name: "Calendar 1",
color: "#FF0000",
},
"667037022b752d0026472254/cal2": {
id: "667037022b752d0026472254/cal2",
name: "Calendar 2",
color: "#00FF00",
},
},
pending: false,
},
};
const defaultSelectedRange = {
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;
function setupRepeatEvent(props?: Partial<RepetitionObject>, state?: any) {
const setRepetition = jest.fn();
renderWithProviders(
<RepeatEvent
repetition={{ ...baseRepetition, ...props }}
setRepetition={setRepetition}
isOwn={true}
/>,
state
);
return { setRepetition };
}
async function setupEventPopover(
overrides?: Partial<{ start: string; end: string }>
) {
jest
.spyOn(crypto, "randomUUID")
.mockReturnValue("fixed-uuid-with-correct-format");
const originalDateResolvedOptions =
new Intl.DateTimeFormat().resolvedOptions();
jest.spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockReturnValue({
...originalDateResolvedOptions,
timeZone: "UTC",
});
renderWithProviders(
<EventPopover
anchorEl={document.body}
open={true}
onClose={mockOnClose}
selectedRange={defaultSelectedRange}
setSelectedRange={mockSetSelectedRange}
calendarRef={mockCalendarRef}
/>,
preloadedState
);
act(() => {
fireEvent.change(screen.getByLabelText("Title"), {
target: { value: "Meeting" },
});
fireEvent.click(screen.getByLabelText("All day"));
fireEvent.change(screen.getByLabelText("Start"), {
target: {
value: (overrides?.start ?? "2025-07-18T00:00:00.000Z").split("T")[0],
},
});
fireEvent.change(screen.getByLabelText("End"), {
target: {
value: (overrides?.end ?? "2025-07-19T00:00:00.000Z").split("T")[0],
},
});
fireEvent.click(screen.getByText("Show More"));
});
const select = screen.getByLabelText(/repetition/i);
userEvent.click(select);
return jest.spyOn(apiUtils, "api");
}
async function expectRRule(expected: any) {
const spyAPi = jest.spyOn(apiUtils, "api");
act(() => fireEvent.click(screen.getByText("Save")));
await waitFor(() => {
expect(spyAPi).toHaveBeenCalled();
});
const receivedPayload: string =
spyAPi.mock.calls[0][1]?.body?.toString() ?? "";
const [, , [vevent]] = JSON.parse(receivedPayload);
const rrule = vevent[1].find(([name]: any) => name === "rrule");
expect(rrule[3]).toEqual(expected);
}
describe("RepeatEvent", () => {
const baseRepetition: RepetitionObject = {
freq: "",
interval: 1,
occurrences: 0,
endDate: "",
selectedDays: [],
};
const setup = (props?: Partial<RepetitionObject>, preloadedState?: any) => {
const setRepetition = jest.fn();
renderWithProviders(
<RepeatEvent
repetition={{ ...baseRepetition, ...props }}
setRepetition={setRepetition}
isOwn={true}
/>,
preloadedState
);
return { setRepetition };
};
it("renders with no repetition by default", () => {
setup();
setupRepeatEvent();
expect(screen.getByLabelText(/repetition/i)).toBeInTheDocument();
expect(screen.queryByText(/daily/i)).not.toBeInTheDocument();
expect(screen.queryByText(/weekly/i)).not.toBeInTheDocument();
expect(screen.queryByText(/monthly/i)).not.toBeInTheDocument();
expect(screen.queryByText(/yearly/i)).not.toBeInTheDocument();
});
it("allows selecting repetition frequency", async () => {
const { setRepetition } = setup();
const { setRepetition } = setupRepeatEvent();
const select = screen.getByLabelText(/repetition/i);
userEvent.click(select);
userEvent.click(await screen.findByText(/repeat weekly/i));
act(() => {
userEvent.click(select);
});
await waitFor(async () =>
userEvent.click(await screen.findByText(/repeat weekly/i))
);
expect(setRepetition).toHaveBeenCalledWith(
expect.objectContaining({ freq: "weekly" })
);
});
it("renders interval input when frequency is selected", () => {
setup({ freq: "daily", interval: 2 });
setupRepeatEvent({ freq: "daily", interval: 2 });
expect(screen.getByText(/interval/i)).toBeInTheDocument();
expect(screen.getByDisplayValue("2")).toBeInTheDocument();
});
it("updates interval value", () => {
const { setRepetition } = setup({ freq: "daily", interval: 1 });
const { setRepetition } = setupRepeatEvent({ freq: "daily", interval: 1 });
const input = screen.getByDisplayValue("1");
fireEvent.change(input, { target: { value: "5" } });
expect(setRepetition).toHaveBeenCalledWith(
@@ -66,281 +174,173 @@ describe("RepeatEvent", () => {
});
it("toggles day selection for weekly frequency", () => {
const { setRepetition } = setup({ freq: "weekly", selectedDays: [] });
const mondayCheckbox = screen.getByLabelText("MO");
userEvent.click(mondayCheckbox);
const { setRepetition } = setupRepeatEvent({
freq: "weekly",
selectedDays: [],
});
act(() => {
const mondayCheckbox = screen.getByLabelText("MO");
fireEvent.click(mondayCheckbox);
});
expect(setRepetition).toHaveBeenCalledWith(
expect.objectContaining({ selectedDays: ["MO"] })
);
});
});
describe("Repeat Event api calls", () => {
const mockOnClose = jest.fn();
const mockSetSelectedRange = jest.fn();
const mockCalendarRef = { current: { select: jest.fn() } } as any;
const preloadedState = {
user: {
userData: {
sub: "test",
email: "test@test.com",
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
openpaasId: "667037022b752d0026472254",
},
organiserData: {
cn: "test",
cal_address: "test@test.com",
},
},
calendars: {
list: {
"667037022b752d0026472254/cal1": {
id: "667037022b752d0026472254/cal1",
name: "Calendar 1",
color: "#FF0000",
},
"667037022b752d0026472254/cal2": {
id: "667037022b752d0026472254/cal2",
name: "Calendar 2",
color: "#00FF00",
},
},
pending: false,
},
};
const defaultSelectedRange = {
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;
describe("Repeat Event API calls", () => {
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
it("Given XYZ input in the component, data in CalendarEvent is correct", async () => {
renderWithProviders(
<EventPopover
anchorEl={document.body}
open={true}
onClose={mockOnClose}
selectedRange={defaultSelectedRange}
setSelectedRange={mockSetSelectedRange}
calendarRef={mockCalendarRef}
/>,
preloadedState
);
const newEvent = {
title: "Meeting",
start: "2025-07-18T00:00:00.000Z",
end: "2025-07-19T00:00:00.000Z",
allday: false,
uid: "6045c603-11ab-43c5-bc30-0641420bb3a8",
organizer: { cn: "test", cal_address: "test@test.com" },
repetition: { freq: "weekly" },
};
// Fill inputs
fireEvent.change(screen.getByLabelText("Title"), {
target: { value: newEvent.title },
});
fireEvent.click(screen.getByLabelText("All day"));
fireEvent.change(screen.getByLabelText("Start"), {
target: { value: newEvent.start.split("T")[0] },
});
fireEvent.change(screen.getByLabelText("End"), {
target: { value: newEvent.end.split("T")[0] },
});
it("sends correct CalendarEvent payload", async () => {
setupEventPopover();
fireEvent.click(screen.getByText("Show More"));
const select = screen.getByLabelText(/repetition/i);
userEvent.click(select);
userEvent.click(await screen.findByText(/repeat weekly/i));
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
return () => Promise.resolve(payload) as any;
});
.mockImplementation((payload) => () => Promise.resolve(payload) as any);
act(() => fireEvent.click(screen.getByText("Save")));
await waitFor(() => expect(spy).toHaveBeenCalled());
fireEvent.click(screen.getByText("Save"));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const receivedPayload = spy.mock.calls[0][0];
expect(receivedPayload.cal).toEqual(
const received = spy.mock.calls[0][0];
expect(received.cal).toEqual(
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
);
expect(receivedPayload.newEvent.title).toBe(newEvent.title);
expect(received.newEvent.title).toBe("Meeting");
expect(
formatDateToYYYYMMDDTHHMMSS(receivedPayload.newEvent.start).split("T")[0]
).toBe(formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)).split("T")[0]);
formatDateToYYYYMMDDTHHMMSS(received.newEvent.start).split("T")[0]
).toBe("20250718");
expect(
formatDateToYYYYMMDDTHHMMSS(
receivedPayload.newEvent.end || new Date()
).split("T")[0]
).toBe(formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.end)).split("T")[0]);
expect(receivedPayload.newEvent.organizer).toEqual(newEvent.organizer);
expect(receivedPayload.newEvent.repetition).toEqual(newEvent.repetition);
expect(receivedPayload.newEvent.color).toEqual(
formatDateToYYYYMMDDTHHMMSS(received.newEvent.end || new Date()).split(
"T"
)[0]
).toBe("20250719");
expect(received.newEvent.organizer).toEqual(
preloadedState.user.organiserData
);
expect(received.newEvent.repetition).toEqual({ freq: "weekly" });
expect(received.newEvent.color).toEqual(
preloadedState.calendars.list["667037022b752d0026472254/cal1"].color
);
// onClose should be called
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("Given XYZ input in the component, data in api call is correct", async () => {
jest
.spyOn(crypto, "randomUUID")
.mockReturnValue("bfe2c579-acce-456b-8b91-c557bb3b4f89");
renderWithProviders(
<EventPopover
anchorEl={document.body}
open={true}
onClose={mockOnClose}
selectedRange={defaultSelectedRange}
setSelectedRange={mockSetSelectedRange}
calendarRef={mockCalendarRef}
/>,
preloadedState
);
const newEvent = {
title: "Meeting",
start: "2025-07-18T00:00:00.000Z",
end: "2025-07-19T00:00:00.000Z",
allday: false,
uid: "6045c603-11ab-43c5-bc30-0641420bb3a8",
organizer: { cn: "test", cal_address: "test@test.com" },
repetition: { freq: "weekly" },
};
it("sends correct API payload for repeat daily", async () => {
await setupEventPopover();
// Fill inputs
fireEvent.change(screen.getByLabelText("Title"), {
target: { value: newEvent.title },
});
fireEvent.click(screen.getByLabelText("All day"));
fireEvent.change(screen.getByLabelText("Start"), {
target: { value: newEvent.start.split("T")[0] },
});
fireEvent.change(screen.getByLabelText("End"), {
target: { value: newEvent.end.split("T")[0] },
});
userEvent.click(await screen.findByText(/repeat daily/i));
fireEvent.click(screen.getByText("Show More"));
await expectRRule({ freq: "daily" });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
const select = screen.getByLabelText(/repetition/i);
userEvent.click(select);
userEvent.click(await screen.findByText(/repeat weekly/i));
const mondayCheckbox = screen.getByLabelText("MO");
userEvent.click(mondayCheckbox);
it("sends correct API payload for repeat daily with 2 day interval", async () => {
await setupEventPopover();
const radio = screen.getByLabelText(/after/i);
userEvent.click(radio);
userEvent.click(await screen.findByText(/repeat daily/i));
const intervalInput = screen.getByDisplayValue("1");
fireEvent.change(intervalInput, { target: { value: "2" } });
await expectRRule({ freq: "daily", interval: 2 });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("sends correct API payload for repeat daily for 5 repetitions", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat daily/i));
userEvent.click(screen.getByLabelText(/after/i));
const input = screen.getAllByRole("spinbutton")[1];
fireEvent.change(input, { target: { value: "3" } });
const spyAPi = jest.spyOn(apiUtils, "api");
fireEvent.change(input, { target: { value: "5" } });
fireEvent.click(screen.getByText("Save"));
await expectRRule({ freq: "daily", count: 5 });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
await waitFor(() => {
expect(spyAPi).toHaveBeenCalled();
it("sends correct API payload for repeat daily until now+5days", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat daily/i));
userEvent.click(screen.getAllByLabelText(/on/i)[3]);
const untilInput = screen.getByTestId("end-date");
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 5);
fireEvent.change(untilInput, {
target: { value: futureDate.toISOString().split("T")[0] },
});
await expectRRule({
freq: "daily",
until: futureDate.toISOString().split("T")[0],
});
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
const receivedPayload = spyAPi.mock.calls[0][1]?.body;
expect(receivedPayload).toEqual(
JSON.stringify([
"vcalendar",
[],
[
[
"vevent",
[
["uid", {}, "text", "bfe2c579-acce-456b-8b91-c557bb3b4f89"],
["transp", {}, "text", "OPAQUE"],
["dtstart", { tzid: "Europe/Paris" }, "date", "2025-07-18"],
["class", {}, "text", "PUBLIC"],
["x-openpaas-videoconference", {}, "unknown", null],
["summary", {}, "text", "Meeting"],
["dtend", { tzid: "Europe/Paris" }, "date", "2025-07-19"],
[
"organizer",
{ cn: "test" },
"cal-address",
"mailto:test@test.com",
],
[
"rrule",
{},
"recur",
{ freq: "weekly", count: 3, byday: ["MO"] },
],
[
"attendee",
{
partstat: "ACCEPTED",
rsvp: "FALSE",
role: "CHAIR",
cutype: "INDIVIDUAL",
cn: "test",
},
"cal-address",
"mailto:test@test.com",
],
],
[],
],
[
"vtimezone",
[["tzid", {}, "text", "Europe/Paris"]],
[
[
"daylight",
[
["tzoffsetfrom", {}, "utc-offset", "+01:00"],
["tzoffsetto", {}, "utc-offset", "+02:00"],
["tzname", {}, "text", "CEST"],
["dtstart", {}, "date-time", "1970-03-29T02:00:00"],
[
"rrule",
{},
"recur",
{ freq: "YEARLY", bymonth: 3, byday: "-1SU" },
],
],
[],
],
[
"standard",
[
["tzoffsetfrom", {}, "utc-offset", "+02:00"],
["tzoffsetto", {}, "utc-offset", "+01:00"],
["tzname", {}, "text", "CET"],
["dtstart", {}, "date-time", "1970-10-25T03:00:00"],
[
"rrule",
{},
"recur",
{ freq: "YEARLY", bymonth: 10, byday: "-1SU" },
],
],
[],
],
],
],
],
])
);
it("sends correct API payload for repeat weekly on Thursday and Friday", async () => {
await setupEventPopover();
// onClose should be called
userEvent.click(await screen.findByText(/repeat weekly/i));
userEvent.click(screen.getByLabelText("TH"));
userEvent.click(screen.getByLabelText("FR"));
await expectRRule({ freq: "weekly", byday: ["TH", "FR"] });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("sends correct API payload for repeat weekly on Thursday and Friday and an interval of 3 weeks", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat weekly/i));
userEvent.click(screen.getByLabelText("TH"));
userEvent.click(screen.getByLabelText("FR"));
const intervalInput = screen.getByDisplayValue("1");
fireEvent.change(intervalInput, { target: { value: "3" } });
await expectRRule({ freq: "weekly", byday: ["TH", "FR"], interval: 3 });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("sends correct API payload for repeat monthly", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat monthly/i));
await expectRRule({ freq: "monthly" });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("sends correct API payload for repeat monthly and end after 5 occurrences", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat monthly/i));
userEvent.click(screen.getByLabelText(/after/i));
const input = screen.getAllByRole("spinbutton")[1];
fireEvent.change(input, { target: { value: "5" } });
await expectRRule({ freq: "monthly", count: 5 });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("sends correct API payload for repeat yearly", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat yearly/i));
await expectRRule({ freq: "yearly" });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
it("sends correct API payload for repeat yearly, but user first choose to end after 5 occurrences then changed mind and chose to not end", async () => {
await setupEventPopover();
userEvent.click(await screen.findByText(/repeat yearly/i));
userEvent.click(screen.getByLabelText(/after/i));
const input = screen.getAllByRole("spinbutton")[1];
fireEvent.change(input, { target: { value: "5" } });
userEvent.click(screen.getByLabelText(/never/i));
await expectRRule({ freq: "yearly" });
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
});
});
@@ -286,7 +286,6 @@ describe("EventPopover", () => {
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
);
console.log(receivedPayload.newEvent.start);
expect(receivedPayload.newEvent.title).toBe(newEvent.title);
expect(receivedPayload.newEvent.description).toBe(newEvent.description);
expect(
+1
View File
@@ -193,6 +193,7 @@ export default function RepeatEvent({
On
<TextField
type="date"
inputProps={{ "data-testid": "end-date" }}
size="small"
value={repetition.endDate ?? ""}
onChange={(e) =>
+14 -1
View File
@@ -298,7 +298,20 @@ const CalendarSlice = createSlice({
)
.addCase(deleteEventAsync.fulfilled, (state, action) => {
state.pending = false;
delete state.list[action.payload.calId].events[action.payload.eventId];
const [baseId, recurrenceId] = action.payload.eventId.split("/");
if (recurrenceId) {
Object.keys(state.list[action.payload.calId].events).forEach(
(element) => {
if (element.split("/")[0] === baseId) {
delete state.list[action.payload.calId].events[element];
}
}
);
} else {
delete state.list[action.payload.calId].events[
action.payload.eventId
];
}
})
.addCase(getCalendarDetailAsync.pending, (state) => {
state.pending = true;