[#7] added MOVE support for changing calendar for event + missing tests for this PR
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
import {
|
||||||
|
putEvent,
|
||||||
|
moveEvent,
|
||||||
|
deleteEvent,
|
||||||
|
} from "../../../src/features/Events/EventApi";
|
||||||
|
import { calendarEventToJCal } from "../../../src/features/Events/eventUtils";
|
||||||
|
import { clientConfig } from "../../../src/features/User/oidcAuth";
|
||||||
|
import { api } from "../../../src/utils/apiUtils";
|
||||||
|
clientConfig.url = "https://example.com";
|
||||||
|
|
||||||
|
jest.mock("../../../src/utils/apiUtils");
|
||||||
|
|
||||||
|
const day = new Date();
|
||||||
|
|
||||||
|
const mockEvent = {
|
||||||
|
uid: "event1",
|
||||||
|
title: "Test Event",
|
||||||
|
timezone: "UTC",
|
||||||
|
calId: "667037022b752d0026472254/cal1",
|
||||||
|
URL: "/calendars/667037022b752d0026472254/667037022b752d0026472254/cal1.ics",
|
||||||
|
start: day,
|
||||||
|
end: day,
|
||||||
|
status: "PUBLIC",
|
||||||
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
rsvp: "TRUE",
|
||||||
|
role: "REQ-PARTICIPANT",
|
||||||
|
cutype: "INDIVIDUAL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cn: "John",
|
||||||
|
cal_address: "john@test.com",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
rsvp: "TRUE",
|
||||||
|
role: "REQ-PARTICIPANT",
|
||||||
|
cutype: "INDIVIDUAL",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("eventApi", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("putEvent sends PUT request with JCal body", async () => {
|
||||||
|
const mockResponse = { status: 201, url: "/dav/cals/test.ics" };
|
||||||
|
(api as unknown as jest.Mock).mockReturnValue(mockResponse);
|
||||||
|
const result = await putEvent(mockEvent);
|
||||||
|
const expectedResult = calendarEventToJCal(mockEvent);
|
||||||
|
expect(api).toHaveBeenCalledWith(
|
||||||
|
"dav/calendars/667037022b752d0026472254/667037022b752d0026472254/cal1.ics",
|
||||||
|
expect.objectContaining({
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "content-type": "text/calendar; charset=utf-8" },
|
||||||
|
body: JSON.stringify(expectedResult),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(result).toBe(mockResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("putEvent logs when status is 201", async () => {
|
||||||
|
const mockResponse = { status: 201, url: "/dav/cals/test.ics" };
|
||||||
|
(api as unknown as jest.Mock).mockReturnValue(mockResponse);
|
||||||
|
const logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
|
||||||
|
|
||||||
|
await putEvent(mockEvent);
|
||||||
|
expect(logSpy).toHaveBeenCalledWith("PUT (201) :", "/dav/cals/test.ics");
|
||||||
|
|
||||||
|
logSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("moveEvent sends MOVE request with destination header", async () => {
|
||||||
|
const mockResponse = { status: 204 };
|
||||||
|
(api as unknown as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue(mockResponse),
|
||||||
|
});
|
||||||
|
const result = await moveEvent(mockEvent);
|
||||||
|
|
||||||
|
expect(api).toHaveBeenCalledWith(
|
||||||
|
"dav/calendars/667037022b752d0026472254/667037022b752d0026472254/cal1.ics",
|
||||||
|
expect.objectContaining({
|
||||||
|
method: "MOVE",
|
||||||
|
headers: {
|
||||||
|
destination:
|
||||||
|
"/calendars/667037022b752d0026472254/667037022b752d0026472254/cal1.ics",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleteEvent sends DELETE request and returns json response", async () => {
|
||||||
|
const mockResponse = { ok: true };
|
||||||
|
(api as unknown as jest.Mock).mockReturnValue({
|
||||||
|
json: jest.fn().mockResolvedValue(mockResponse),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await deleteEvent("/calendars/test.ics");
|
||||||
|
|
||||||
|
expect(api).toHaveBeenCalledWith("dav/calendars/test.ics", {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
||||||
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
||||||
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||||
import EventPopover from "../../../src/features/Events/EventModal";
|
import EventDisplayModal, {
|
||||||
import { DateSelectArg } from "@fullcalendar/core";
|
InfoRow,
|
||||||
import preview from "jest-preview";
|
stringAvatar,
|
||||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../../src/utils/dateUtils";
|
stringToColor,
|
||||||
|
} from "../../../src/features/Events/EventDisplay";
|
||||||
import EventPreviewModal from "../../../src/features/Events/EventDisplayPreview";
|
import EventPreviewModal from "../../../src/features/Events/EventDisplayPreview";
|
||||||
|
|
||||||
describe("Event Display", () => {
|
describe("Event Preview Display", () => {
|
||||||
const mockOnClose = jest.fn();
|
const mockOnClose = jest.fn();
|
||||||
const day = new Date();
|
const day = new Date();
|
||||||
const RealDateToLocaleString = Date.prototype.toLocaleString;
|
const RealDateToLocaleString = Date.prototype.toLocaleString;
|
||||||
@@ -109,7 +110,6 @@ describe("Event Display", () => {
|
|||||||
const dayOfMonth = day.getDate().toString();
|
const dayOfMonth = day.getDate().toString();
|
||||||
|
|
||||||
expect(screen.getByText("Test Event")).toBeInTheDocument();
|
expect(screen.getByText("Test Event")).toBeInTheDocument();
|
||||||
preview.debug();
|
|
||||||
expect(screen.getByText(new RegExp(weekday, "i"))).toBeInTheDocument();
|
expect(screen.getByText(new RegExp(weekday, "i"))).toBeInTheDocument();
|
||||||
expect(screen.getByText(new RegExp(month, "i"))).toBeInTheDocument();
|
expect(screen.getByText(new RegExp(month, "i"))).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
@@ -475,3 +475,577 @@ describe("Event Display", () => {
|
|||||||
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Event Full Display", () => {
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
const day = new Date();
|
||||||
|
const RealDateToLocaleString = Date.prototype.toLocaleString;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
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": {
|
||||||
|
id: "667037022b752d0026472254/cal1",
|
||||||
|
name: "First Calendar",
|
||||||
|
color: "#FF0000",
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
id: "event1",
|
||||||
|
title: "Test Event",
|
||||||
|
calId: "667037022b752d0026472254/cal1",
|
||||||
|
start: day.toISOString(),
|
||||||
|
end: day.toISOString(),
|
||||||
|
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
rsvp: "TRUE",
|
||||||
|
role: "REQ-PARTICIPANT",
|
||||||
|
cutype: "INDIVIDUAL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cn: "John",
|
||||||
|
cal_address: "john@test.com",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
rsvp: "TRUE",
|
||||||
|
role: "REQ-PARTICIPANT",
|
||||||
|
cutype: "INDIVIDUAL",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"otherCal/cal": {
|
||||||
|
id: "otherCal/cal",
|
||||||
|
name: "Calendar 1",
|
||||||
|
color: "#FF0000",
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
id: "event1",
|
||||||
|
calId: "otherCal/cal",
|
||||||
|
title: "Test Event Other cal",
|
||||||
|
start: day.toISOString(),
|
||||||
|
end: day.toISOString(),
|
||||||
|
organizer: { cn: "john", cal_address: "john@test.com" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pending: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders correctly event data", () => {
|
||||||
|
jest
|
||||||
|
.spyOn(Date.prototype, "toLocaleString")
|
||||||
|
.mockImplementation(function (
|
||||||
|
this: Date,
|
||||||
|
locales?: Intl.LocalesArgument,
|
||||||
|
options?: Intl.DateTimeFormatOptions | undefined
|
||||||
|
): string {
|
||||||
|
return RealDateToLocaleString.call(this, "en-UK", options);
|
||||||
|
});
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
day.setHours(day.getHours() + 2);
|
||||||
|
const date = day.toISOString().slice(0, 16);
|
||||||
|
|
||||||
|
expect(screen.getByDisplayValue("Test Event")).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getAllByDisplayValue(new RegExp(date, "i"))[0]
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getAllByDisplayValue(new RegExp(date, "i")).length
|
||||||
|
).toBeLessThanOrEqual(2);
|
||||||
|
|
||||||
|
expect(screen.getByText("First Calendar")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("calls onClose when Cancel clicked", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getAllByTestId("CloseIcon")[0]);
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("Shows delete button only when calendar is own", () => {
|
||||||
|
// Renders the other cal event
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"otherCal/cal"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
expect(screen.queryByTestId("DeleteIcon")).not.toBeInTheDocument();
|
||||||
|
// Renders the personnal cal event
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
expect(screen.queryByTestId("DeleteIcon")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("calls delete when Delete clicked", async () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "deleteEventAsync")
|
||||||
|
.mockImplementation((payload) => {
|
||||||
|
return () => Promise.resolve(payload) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("DeleteIcon"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const receivedPayload = spy.mock.calls[0][0];
|
||||||
|
|
||||||
|
expect(receivedPayload).toEqual({
|
||||||
|
calId: "667037022b752d0026472254/cal1",
|
||||||
|
eventId: "event1",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("renders RSVP buttons when user is an attendee", () => {
|
||||||
|
const rsvpStateIsOrga = {
|
||||||
|
...preloadedState,
|
||||||
|
calendars: {
|
||||||
|
...preloadedState.calendars,
|
||||||
|
list: {
|
||||||
|
...preloadedState.calendars.list,
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
...preloadedState.calendars.list[
|
||||||
|
"667037022b752d0026472254/cal1"
|
||||||
|
].events.event1,
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
cn: "Test User",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
cn: "Test Organizer",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: {
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
rsvpStateIsOrga
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByRole("button", { name: "Accept" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: "Maybe" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("button", { name: "Decline" })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("doesnt renders RSVP buttons when user isnt an attendee", () => {
|
||||||
|
const rsvpStateIsOrga = {
|
||||||
|
...preloadedState,
|
||||||
|
calendars: {
|
||||||
|
...preloadedState.calendars,
|
||||||
|
list: {
|
||||||
|
...preloadedState.calendars.list,
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
...preloadedState.calendars.list[
|
||||||
|
"667037022b752d0026472254/cal1"
|
||||||
|
].events.event1,
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
cn: "Test Organizer",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: {
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
rsvpStateIsOrga
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: "Accept" })
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: "Maybe" })
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.queryByRole("button", { name: "Decline" })
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles RSVP Accept click", async () => {
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "putEventAsync")
|
||||||
|
.mockImplementation((payload) => {
|
||||||
|
return () => Promise.resolve(payload) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rsvpState = {
|
||||||
|
...preloadedState,
|
||||||
|
calendars: {
|
||||||
|
...preloadedState.calendars,
|
||||||
|
list: {
|
||||||
|
...preloadedState.calendars.list,
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
...preloadedState.calendars.list[
|
||||||
|
"667037022b752d0026472254/cal1"
|
||||||
|
].events.event1,
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
cn: "Test User",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: {
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
rsvpState
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Accept" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedEvent = spy.mock.calls[0][0].newEvent;
|
||||||
|
expect(updatedEvent.attendee[0].partstat).toBe("ACCEPTED");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles RSVP Maybe click", async () => {
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "putEventAsync")
|
||||||
|
.mockImplementation((payload) => {
|
||||||
|
return () => Promise.resolve(payload) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rsvpState = {
|
||||||
|
...preloadedState,
|
||||||
|
calendars: {
|
||||||
|
...preloadedState.calendars,
|
||||||
|
list: {
|
||||||
|
...preloadedState.calendars.list,
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
...preloadedState.calendars.list[
|
||||||
|
"667037022b752d0026472254/cal1"
|
||||||
|
].events.event1,
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
cn: "Test User",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: {
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
rsvpState
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Maybe" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedEvent = spy.mock.calls[0][0].newEvent;
|
||||||
|
expect(updatedEvent.attendee[0].partstat).toBe("TENTATIVE");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles RSVP Decline click", async () => {
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "putEventAsync")
|
||||||
|
.mockImplementation((payload) => {
|
||||||
|
return () => Promise.resolve(payload) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
const rsvpState = {
|
||||||
|
...preloadedState,
|
||||||
|
calendars: {
|
||||||
|
...preloadedState.calendars,
|
||||||
|
list: {
|
||||||
|
...preloadedState.calendars.list,
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
...preloadedState.calendars.list[
|
||||||
|
"667037022b752d0026472254/cal1"
|
||||||
|
].events.event1,
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
cn: "Test User",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: {
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
rsvpState
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "Decline" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedEvent = spy.mock.calls[0][0].newEvent;
|
||||||
|
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
||||||
|
});
|
||||||
|
test("toggle Show More reveals extra fields", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
fireEvent.click(screen.getByText("Show More"));
|
||||||
|
expect(screen.getByLabelText("Alarm")).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText("Repetition")).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText("Visibility")).toBeInTheDocument();
|
||||||
|
fireEvent.click(screen.getByText("Show Less"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("can edit title when user is organizer", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
const titleField = screen.getByLabelText("Title");
|
||||||
|
fireEvent.change(titleField, { target: { value: "New Title" } });
|
||||||
|
expect(screen.getByDisplayValue("New Title")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
test("calendar select is disabled when not organizer", () => {
|
||||||
|
const rsvpState = {
|
||||||
|
...preloadedState,
|
||||||
|
calendars: {
|
||||||
|
...preloadedState.calendars,
|
||||||
|
list: {
|
||||||
|
...preloadedState.calendars.list,
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
|
||||||
|
events: {
|
||||||
|
event1: {
|
||||||
|
...preloadedState.calendars.list[
|
||||||
|
"667037022b752d0026472254/cal1"
|
||||||
|
].events.event1,
|
||||||
|
attendee: [
|
||||||
|
{
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
cn: "Test User",
|
||||||
|
partstat: "NEEDS-ACTION",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
organizer: {
|
||||||
|
cal_address: "organizer@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
rsvpState
|
||||||
|
);
|
||||||
|
expect(screen.getByLabelText("Calendar")).toHaveClass("Mui-disabled");
|
||||||
|
});
|
||||||
|
test("toggle all-day updates end date correctly", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<EventDisplayModal
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
const allDayCheckbox = screen.getByLabelText("All day");
|
||||||
|
fireEvent.click(allDayCheckbox);
|
||||||
|
expect(allDayCheckbox).toBeChecked();
|
||||||
|
const date = day.toISOString().split("T")[0];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getAllByDisplayValue(new RegExp(date, "i"))[0]
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Helper functions", () => {
|
||||||
|
test("stringToColor generates consistent color", () => {
|
||||||
|
expect(stringToColor("Alice")).toMatch(/^#[0-9a-f]{6}$/);
|
||||||
|
expect(stringToColor("Alice")).toBe(stringToColor("Alice"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("stringAvatar returns correct props", () => {
|
||||||
|
const result = stringAvatar("Alice");
|
||||||
|
expect(result.children).toBe("A");
|
||||||
|
expect(result.sx.bgcolor).toMatch(/^#/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("InfoRow renders text and link if url is valid", () => {
|
||||||
|
renderWithProviders(
|
||||||
|
<InfoRow
|
||||||
|
icon={<span>ico</span>}
|
||||||
|
text="Meeting"
|
||||||
|
data="https://example.com"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Meeting").closest("a")).toHaveAttribute(
|
||||||
|
"href",
|
||||||
|
"https://example.com"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { CalendarEvent } from "../Events/EventsTypes";
|
|||||||
import { getCalendar, getCalendars } from "./CalendarApi";
|
import { getCalendar, getCalendars } from "./CalendarApi";
|
||||||
import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
|
import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
|
||||||
import { parseCalendarEvent } from "../Events/eventUtils";
|
import { parseCalendarEvent } from "../Events/eventUtils";
|
||||||
import { deleteEvent, putEvent } from "../Events/EventApi";
|
import { deleteEvent, moveEvent, putEvent } from "../Events/EventApi";
|
||||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../utils/dateUtils";
|
import { formatDateToYYYYMMDDTHHMMSS } from "../../utils/dateUtils";
|
||||||
|
|
||||||
export const getCalendarsListAsync = createAsyncThunk<
|
export const getCalendarsListAsync = createAsyncThunk<
|
||||||
@@ -92,6 +92,32 @@ export const putEventAsync = createAsyncThunk<
|
|||||||
events,
|
events,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
export const moveEventAsync = createAsyncThunk<
|
||||||
|
{ calId: string; events: CalendarEvent[] }, // Return type
|
||||||
|
{ cal: Calendars; newEvent: CalendarEvent } // Arg type
|
||||||
|
>("calendars/moveEvent", async ({ cal, newEvent }) => {
|
||||||
|
const response = await moveEvent(newEvent);
|
||||||
|
const calEvents = (await getCalendar(cal.id, {
|
||||||
|
start: formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)),
|
||||||
|
end: formatDateToYYYYMMDDTHHMMSS(
|
||||||
|
new Date(new Date(newEvent.start).getTime() + 86400000)
|
||||||
|
),
|
||||||
|
})) as Record<string, any>;
|
||||||
|
const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap(
|
||||||
|
(eventdata: any) => {
|
||||||
|
const vevents = eventdata.data[2] as any[][];
|
||||||
|
const eventURL = eventdata._links.self.href;
|
||||||
|
return vevents.map((vevent: any[]) => {
|
||||||
|
return parseCalendarEvent(vevent[1], cal.color ?? "", cal.id, eventURL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
calId: cal.id,
|
||||||
|
events,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export const deleteEventAsync = createAsyncThunk<
|
export const deleteEventAsync = createAsyncThunk<
|
||||||
{ calId: string; eventId: string }, // Return type
|
{ calId: string; eventId: string }, // Return type
|
||||||
@@ -207,6 +233,32 @@ const CalendarSlice = createSlice({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.addCase(
|
||||||
|
moveEventAsync.fulfilled,
|
||||||
|
(
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ calId: string; events: CalendarEvent[] }>
|
||||||
|
) => {
|
||||||
|
state.pending = false;
|
||||||
|
if (!state.list[action.payload.calId]) {
|
||||||
|
state.list[action.payload.calId] = {
|
||||||
|
id: action.payload.calId,
|
||||||
|
events: {},
|
||||||
|
} as Calendars;
|
||||||
|
}
|
||||||
|
action.payload.events.forEach((event) => {
|
||||||
|
state.list[action.payload.calId].events[event.uid] = event;
|
||||||
|
});
|
||||||
|
Object.keys(state.list[action.payload.calId].events).forEach((id) => {
|
||||||
|
state.list[action.payload.calId].events[id].color =
|
||||||
|
state.list[action.payload.calId].color;
|
||||||
|
state.list[action.payload.calId].events[id].calId =
|
||||||
|
action.payload.calId;
|
||||||
|
state.list[action.payload.calId].events[id].timezone =
|
||||||
|
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
.addCase(deleteEventAsync.fulfilled, (state, action) => {
|
.addCase(deleteEventAsync.fulfilled, (state, action) => {
|
||||||
state.pending = false;
|
state.pending = false;
|
||||||
delete state.list[action.payload.calId].events[action.payload.eventId];
|
delete state.list[action.payload.calId].events[action.payload.eventId];
|
||||||
@@ -220,6 +272,9 @@ const CalendarSlice = createSlice({
|
|||||||
.addCase(putEventAsync.pending, (state) => {
|
.addCase(putEventAsync.pending, (state) => {
|
||||||
state.pending = true;
|
state.pending = true;
|
||||||
})
|
})
|
||||||
|
.addCase(moveEventAsync.pending, (state) => {
|
||||||
|
state.pending = true;
|
||||||
|
})
|
||||||
.addCase(deleteEventAsync.pending, (state) => {
|
.addCase(deleteEventAsync.pending, (state) => {
|
||||||
state.pending = true;
|
state.pending = true;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,9 +16,19 @@ export async function putEvent(event: CalendarEvent) {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function moveEvent(event: CalendarEvent) {
|
||||||
|
const response = await api(`dav${event.URL}`, {
|
||||||
|
method: "MOVE",
|
||||||
|
headers: {
|
||||||
|
destination: event.URL,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteEvent(eventURL: string) {
|
export async function deleteEvent(eventURL: string) {
|
||||||
const response = await api(`dav${eventURL}`, {
|
const response = await api(`dav${eventURL}`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
}).json();
|
});
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { deleteEventAsync, putEventAsync } from "../Calendars/CalendarSlice";
|
import {
|
||||||
|
deleteEventAsync,
|
||||||
|
moveEventAsync,
|
||||||
|
putEventAsync,
|
||||||
|
} from "../Calendars/CalendarSlice";
|
||||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||||
import AttendeeSelector from "../../components/Attendees/AttendeeSearch";
|
import AttendeeSelector from "../../components/Attendees/AttendeeSearch";
|
||||||
import {
|
import {
|
||||||
@@ -86,7 +90,7 @@ export default function EventDisplayModal({
|
|||||||
const [alarm, setAlarm] = useState("");
|
const [alarm, setAlarm] = useState("");
|
||||||
const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC");
|
const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC");
|
||||||
const [timezone, setTimezone] = useState(event?.timezone ?? "UTC");
|
const [timezone, setTimezone] = useState(event?.timezone ?? "UTC");
|
||||||
|
const [newCalId, setNewCalId] = useState(event.calId);
|
||||||
const [calendarid, setCalendarid] = useState(
|
const [calendarid, setCalendarid] = useState(
|
||||||
event?.calId.split("/")[0] === user.userData.openpaasId
|
event?.calId.split("/")[0] === user.userData.openpaasId
|
||||||
? userPersonnalCalendars.findIndex((cal) => cal.id === calId)
|
? userPersonnalCalendars.findIndex((cal) => cal.id === calId)
|
||||||
@@ -169,6 +173,12 @@ export default function EventDisplayModal({
|
|||||||
newEvent,
|
newEvent,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (newCalId !== calId) {
|
||||||
|
dispatch(
|
||||||
|
moveEventAsync({ cal: userPersonnalCalendars[calendarid], newEvent })
|
||||||
|
);
|
||||||
|
}
|
||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -279,9 +289,11 @@ export default function EventDisplayModal({
|
|||||||
labelId="calendar-select-label"
|
labelId="calendar-select-label"
|
||||||
value={calendarid.toString()}
|
value={calendarid.toString()}
|
||||||
label="Calendar"
|
label="Calendar"
|
||||||
onChange={(e: SelectChangeEvent) =>
|
onChange={(e: SelectChangeEvent) => {
|
||||||
setCalendarid(Number(e.target.value))
|
const newId = Number(e.target.value);
|
||||||
}
|
setCalendarid(newId);
|
||||||
|
setNewCalId(userPersonnalCalendars[newId].id);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{calList}
|
{calList}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -325,11 +337,6 @@ export default function EventDisplayModal({
|
|||||||
const endDate = new Date(end);
|
const endDate = new Date(end);
|
||||||
const startDate = new Date(start);
|
const startDate = new Date(start);
|
||||||
setAllDay(!allday);
|
setAllDay(!allday);
|
||||||
console.log(
|
|
||||||
endDate.getDate() === startDate.getDate(),
|
|
||||||
endDate.getDate(),
|
|
||||||
startDate.getDate()
|
|
||||||
);
|
|
||||||
if (endDate.getDate() === startDate.getDate()) {
|
if (endDate.getDate() === startDate.getDate()) {
|
||||||
endDate.setDate(startDate.getDate() + 1);
|
endDate.setDate(startDate.getDate() + 1);
|
||||||
setEnd(formatLocalDateTime(endDate));
|
setEnd(formatLocalDateTime(endDate));
|
||||||
@@ -390,7 +397,6 @@ export default function EventDisplayModal({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newAttendeesList = [...attendees];
|
const newAttendeesList = [...attendees];
|
||||||
setAttendees(newAttendeesList.splice(idx, 1));
|
setAttendees(newAttendeesList.splice(idx, 1));
|
||||||
console.log(attendees, newAttendeesList);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CloseIcon fontSize="small" />
|
<CloseIcon fontSize="small" />
|
||||||
@@ -496,18 +502,19 @@ export default function EventDisplayModal({
|
|||||||
|
|
||||||
<CardActions>
|
<CardActions>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<IconButton
|
{isOwn && (
|
||||||
size="small"
|
<IconButton
|
||||||
onClick={() => {
|
size="small"
|
||||||
onClose({}, "backdropClick");
|
onClick={() => {
|
||||||
dispatch(
|
onClose({}, "backdropClick");
|
||||||
deleteEventAsync({ calId, eventId, eventURL: event.URL })
|
dispatch(
|
||||||
);
|
deleteEventAsync({ calId, eventId, eventURL: event.URL })
|
||||||
}}
|
);
|
||||||
>
|
}}
|
||||||
<DeleteIcon fontSize="small" />
|
>
|
||||||
</IconButton>
|
<DeleteIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
<Button size="small" onClick={() => setShowMore(!showMore)}>
|
<Button size="small" onClick={() => setShowMore(!showMore)}>
|
||||||
{showMore ? "Show Less" : "Show More"}
|
{showMore ? "Show Less" : "Show More"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -324,13 +324,6 @@ function formatEnd(start: Date, end: Date, allday?: boolean) {
|
|||||||
startDate.getDate() === endDate.getDate();
|
startDate.getDate() === endDate.getDate();
|
||||||
|
|
||||||
if (allday) {
|
if (allday) {
|
||||||
console.log(
|
|
||||||
endDate.toLocaleDateString(undefined, {
|
|
||||||
year: "numeric",
|
|
||||||
month: "short",
|
|
||||||
day: "numeric",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return sameDay
|
return sameDay
|
||||||
? null
|
? null
|
||||||
: endDate.toLocaleDateString(undefined, {
|
: endDate.toLocaleDateString(undefined, {
|
||||||
|
|||||||
@@ -233,14 +233,8 @@ function EventPopover({
|
|||||||
const endDate = new Date(end);
|
const endDate = new Date(end);
|
||||||
const startDate = new Date(start);
|
const startDate = new Date(start);
|
||||||
setAllDay(!allday);
|
setAllDay(!allday);
|
||||||
console.log(
|
|
||||||
endDate.getDate() === startDate.getDate(),
|
|
||||||
endDate.getDate(),
|
|
||||||
startDate.getDate()
|
|
||||||
);
|
|
||||||
if (endDate.getDate() === startDate.getDate()) {
|
if (endDate.getDate() === startDate.getDate()) {
|
||||||
endDate.setDate(startDate.getDate() + 1);
|
endDate.setDate(startDate.getDate() + 1);
|
||||||
console.log("formatedd", formatLocalDateTime(endDate));
|
|
||||||
setEnd(formatLocalDateTime(endDate));
|
setEnd(formatLocalDateTime(endDate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +252,6 @@ function EventPopover({
|
|||||||
),
|
),
|
||||||
allDay: allday,
|
allDay: allday,
|
||||||
};
|
};
|
||||||
console.log(newRange, selectedRange);
|
|
||||||
setSelectedRange(newRange);
|
setSelectedRange(newRange);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -126,11 +126,6 @@ export function calendarEventToJCal(event: CalendarEvent): any[] {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (event.end) {
|
if (event.end) {
|
||||||
console.log(
|
|
||||||
event.end,
|
|
||||||
event.start,
|
|
||||||
event.end.getTime() === event.start.getTime()
|
|
||||||
);
|
|
||||||
if (event.allday && event.end.getTime() === event.start.getTime()) {
|
if (event.allday && event.end.getTime() === event.start.getTime()) {
|
||||||
event.end.setDate(event.start.getDate() + 1);
|
event.end.setDate(event.start.getDate() + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user