#598 resource field in event (#634)

Co-authored-by: lethemanh <lethemanh@lethemanhs-MacBook-Pro.local>
This commit is contained in:
lethemanh
2026-03-17 20:11:12 +07:00
committed by GitHub
parent e4fb209147
commit 387e64d58c
21 changed files with 503 additions and 91 deletions
@@ -65,6 +65,14 @@ describe("Event Preview Display", () => {
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
},
{
cn: "Projector Room",
cal_address: "room1@test.com",
partstat: "ACCEPTED",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "RESOURCE",
},
],
},
event2: {
@@ -163,6 +171,8 @@ describe("Event Preview Display", () => {
expect(screen.getByText(/ 10:00/)).toBeInTheDocument();
expect(screen.getByText("Calendar")).toBeInTheDocument();
expect(screen.getByText("Projector Room")).toBeInTheDocument();
});
it("calls onClose when Cancel clicked", () => {
renderWithProviders(
+137 -55
View File
@@ -81,6 +81,22 @@ describe("EventPopover", () => {
},
],
},
{
id: "room1@example.com",
objectType: "resource",
emailAddresses: [
{
value: "room1@example.com",
type: "default",
},
],
names: [
{
displayName: "Room 1",
type: "default",
},
],
},
];
(api.post as jest.Mock).mockReturnValue({
json: jest.fn().mockResolvedValue(mockUsers),
@@ -211,71 +227,137 @@ describe("EventPopover", () => {
});
it("adds a attendee", async () => {
jest.useFakeTimers();
jest
.spyOn(calendarsApi, "getCalendars")
.mockReturnValue({ json: jest.fn() });
renderPopover();
fireEvent.change(screen.getByLabelText("event.form.title"), {
target: { value: "newEvent" },
});
const select = screen.getByLabelText("peopleSearch.label");
try {
jest
.spyOn(calendarsApi, "getCalendars")
.mockReturnValue({ json: jest.fn() });
renderPopover();
fireEvent.change(screen.getByLabelText("event.form.title"), {
target: { value: "newEvent" },
});
const select = screen.getByLabelText("peopleSearch.label");
act(() => {
select.focus();
fireEvent.mouseDown(select);
userEvent.type(select, "john");
});
await act(async () => {
jest.advanceTimersByTime(400);
});
await waitFor(() => expect(api.post).toHaveBeenCalledTimes(1));
act(() => {
select.focus();
fireEvent.mouseDown(select);
userEvent.type(select, "john");
});
await act(async () => {
jest.advanceTimersByTime(400);
});
await waitFor(() => expect(api.post).toHaveBeenCalledTimes(1));
await waitFor(() => {
expect(screen.getByText("John Doe")).toBeInTheDocument();
});
await act(async () => {
userEvent.click(screen.getByText("John Doe"));
});
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
const promise = Promise.resolve(payload);
(promise as any).unwrap = () => promise;
return () => promise as any;
await waitFor(() => {
expect(screen.getByText("John Doe")).toBeInTheDocument();
});
await act(async () => {
userEvent.click(screen.getByText("John Doe"));
});
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
const promise = Promise.resolve(payload);
(promise as any).unwrap = () => promise;
return () => promise as any;
});
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
const receivedPayload = spy.mock.calls[0][0];
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
expect(receivedPayload.cal).toEqual(
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
);
const receivedPayload = spy.mock.calls[0][0];
expect(receivedPayload.newEvent.attendee).toHaveLength(2);
expect(receivedPayload.newEvent.attendee).toStrictEqual([
{
cn: "test",
cal_address: "test@test.com",
partstat: "ACCEPTED",
rsvp: "FALSE",
role: "CHAIR",
cutype: "INDIVIDUAL",
},
{
cn: "John Doe",
cal_address: "john@example.com",
expect(receivedPayload.cal).toEqual(
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
);
expect(receivedPayload.newEvent.attendee).toHaveLength(2);
expect(receivedPayload.newEvent.attendee).toStrictEqual([
{
cn: "test",
cal_address: "test@test.com",
partstat: "ACCEPTED",
rsvp: "FALSE",
role: "CHAIR",
cutype: "INDIVIDUAL",
},
{
cn: "John Doe",
cal_address: "john@example.com",
partstat: "NEEDS-ACTION",
rsvp: "FALSE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
},
]);
} finally {
jest.useRealTimers();
}
});
it("adds a resource", async () => {
jest.useFakeTimers();
try {
renderPopover();
fireEvent.change(screen.getByLabelText("event.form.title"), {
target: { value: "newEventWithResource" },
});
fireEvent.click(
screen.getByRole("button", { name: "common.moreOptions" })
);
const resourceCombobox = screen.getByPlaceholderText(
"resourceSearch.placeholder"
);
act(() => {
resourceCombobox.focus();
fireEvent.mouseDown(resourceCombobox);
});
await userEvent.type(resourceCombobox, "room");
await act(async () => {
jest.advanceTimersByTime(400);
});
await waitFor(() => {
expect(screen.getByText("Room 1")).toBeInTheDocument();
});
await userEvent.click(screen.getByText("Room 1"));
const spy = jest
.spyOn(eventThunks, "putEventAsync")
.mockImplementation((payload) => {
const promise = Promise.resolve(payload);
(promise as any).unwrap = () => promise;
return () => promise as any;
});
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
await waitFor(() => {
expect(spy).toHaveBeenCalled();
});
const receivedPayload = spy.mock.calls[0][0];
expect(receivedPayload.newEvent.attendee).toHaveLength(2); // Organizer + 1 resource
expect(receivedPayload.newEvent.attendee[1]).toStrictEqual({
cn: "Room 1",
cal_address: "room1@example.com",
partstat: "NEEDS-ACTION",
rsvp: "FALSE",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "INDIVIDUAL",
},
]);
cutype: "RESOURCE",
});
} finally {
jest.useRealTimers();
}
});
it("dispatches putEventAsync and calls onClose when Save is clicked", async () => {
@@ -157,6 +157,89 @@ describe("EventUpdateModal Timezone Handling", () => {
// Verify the timezone is still preserved (should be Asia/Bangkok)
expect(titleInput).toHaveValue("Updated Event");
});
it("preserves resources when editing an event", async () => {
const eventDateUTC = new Date("2025-01-15T07:00:00.000Z");
const eventData = {
uid: "test-event-resource",
title: "Resource Event",
calId: "667037022b752d0026472254/cal1",
start: eventDateUTC.toISOString(),
end: new Date(eventDateUTC.getTime() + 3600000).toISOString(),
timezone: "Asia/Bangkok",
allday: false,
organizer: { cn: "test", cal_address: "test@test.com" },
attendee: [
{ cn: "test", cal_address: "test@test.com" },
{
cn: "Conference Room",
cal_address: "room@test.com",
partstat: "ACCEPTED",
rsvp: "TRUE",
role: "REQ-PARTICIPANT",
cutype: "RESOURCE",
},
],
};
const stateWithEvent = {
...preloadedState,
calendars: {
...preloadedState.calendars,
list: {
"667037022b752d0026472254/cal1": {
...preloadedState.calendars.list["667037022b752d0026472254/cal1"],
events: {
"test-event-resource": eventData,
},
},
},
},
};
const mockPutEvent = jest.spyOn(EventApi, "putEvent").mockResolvedValue({
status: 201,
url: `/calendars/667037022b752d0026472254/cal1/test-event-resource.ics`,
} as any);
renderWithProviders(
<EventUpdateModal
open={true}
onClose={mockOnClose}
calId={"667037022b752d0026472254/cal1"}
eventId={"test-event-resource"}
eventData={eventData}
/>,
stateWithEvent
);
// Edit the title
const titleInput = screen.getByDisplayValue("Resource Event");
fireEvent.change(titleInput, {
target: { value: "Updated Resource Event" },
});
// Click Save
const saveButton = screen.getByRole("button", { name: "actions.save" });
await act(async () => {
fireEvent.click(saveButton);
});
await waitFor(() => {
expect(mockPutEvent).toHaveBeenCalled();
});
const putEventCall = mockPutEvent.mock.calls[0][0];
expect(putEventCall.title).toBe("Updated Resource Event");
// Check that the resource is still in the attendee list!
const attendees = putEventCall.attendee;
const resource = attendees.find((a: any) => a.cutype === "RESOURCE");
expect(resource).toBeDefined();
expect(resource!.cn).toBe("Conference Room");
expect(resource!.cal_address).toBe("room@test.com");
});
});
describe("EventUpdateModal Recurring to Non-Recurring Conversion", () => {