fix: preserve form data on API error and fix test cases (#338)
- Add temp storage utility (eventFormTempStorage.ts) to save/restore form data - Implement error handling: close modal immediately, reopen on API failure with saved data - Add event listeners in Calendar.tsx and EventDisplayPreview.tsx to reopen modals on error - Fix calendar change logic in EventUpdateModal: use oldCalendar for update before move - Update all test mocks to support .unwrap() method for Redux thunks - Add sessionStorage.clear() in test beforeEach hooks - Fix test timing issues with act() wrappers and increased timeouts - Mock putEventAsync in TempUpdate test to ensure updateTempCalendar is called - Fix update modal not reopening when API fails for recurring events (solo/all) - Move update modal reopen logic from EventDisplayPreview to Calendar.tsx - Use sessionStorage to persist update modal info across component unmounts - Handle typeOfAction matching for recurring events (undefined vs solo/all) - Fix error handling in updateEventInstanceAsync and updateSeriesAsync - Ensure all API failures properly dispatch eventModalError event
This commit is contained in:
@@ -25,6 +25,7 @@ describe("Event Preview Display", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
sessionStorage.clear();
|
||||
(window as any).MAIL_SPA_URL = null;
|
||||
});
|
||||
|
||||
@@ -355,7 +356,9 @@ describe("Event Preview Display", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
const rsvpState = {
|
||||
@@ -414,7 +417,9 @@ describe("Event Preview Display", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
const rsvpState = {
|
||||
@@ -471,7 +476,9 @@ describe("Event Preview Display", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
const rsvpState = {
|
||||
@@ -1694,10 +1701,18 @@ describe("Event Full Display", () => {
|
||||
it("saves event and moves it when calendar is changed", async () => {
|
||||
const spyPut = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => () => Promise.resolve(payload) as any);
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
const spyMove = jest
|
||||
.spyOn(eventThunks, "moveEventAsync")
|
||||
.mockImplementation((payload) => () => Promise.resolve(payload) as any);
|
||||
.mockImplementation((payload) => {
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
const spyRemove = jest.spyOn(eventThunks, "removeEvent");
|
||||
|
||||
const testDate = new Date("2025-01-15T10:00:00.000Z");
|
||||
@@ -1751,17 +1766,30 @@ describe("Event Full Display", () => {
|
||||
const option = await screen.findByText("Calendar Two");
|
||||
fireEvent.click(option);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(spyPut).toHaveBeenCalled();
|
||||
await act(async () => {
|
||||
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(spyMove).toHaveBeenCalled();
|
||||
});
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(spyPut).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
expect(spyRemove).toHaveBeenCalled();
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(spyMove).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(spyRemove).toHaveBeenCalled();
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
});
|
||||
|
||||
it("edit modal displays event time in original event timezone", () => {
|
||||
|
||||
@@ -9,6 +9,10 @@ import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||
jest.mock("../../../src/utils/apiUtils");
|
||||
|
||||
describe("EventPopover", () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
const mockOnClose = jest.fn();
|
||||
const mockSetSelectedRange = jest.fn();
|
||||
const mockCalendarRef = { current: { select: jest.fn() } } as any;
|
||||
@@ -224,7 +228,9 @@ describe("EventPopover", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
|
||||
@@ -290,7 +296,9 @@ describe("EventPopover", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "actions.save" }));
|
||||
|
||||
@@ -9,6 +9,17 @@ import {
|
||||
EventHandlersProps,
|
||||
} from "../../../src/components/Calendar/handlers/eventHandlers";
|
||||
import EventPreviewModal from "../../../src/features/Events/EventDisplayPreview";
|
||||
|
||||
jest.mock("../../../src/components/Event/utils/eventUtils", () => {
|
||||
const actual = jest.requireActual(
|
||||
"../../../src/components/Event/utils/eventUtils"
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
refreshCalendars: jest.fn(() => Promise.resolve()),
|
||||
refreshSingularCalendar: jest.fn(() => Promise.resolve()),
|
||||
};
|
||||
});
|
||||
import preview from "jest-preview";
|
||||
const mockOnClose = jest.fn();
|
||||
const day = new Date("2025-03-15T10:00:00Z");
|
||||
@@ -476,7 +487,9 @@ describe("RSVP to Recurring Event", () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "updateEventInstanceAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
await act(async () =>
|
||||
@@ -936,7 +949,9 @@ describe("handleRSVP function", () => {
|
||||
} = require("../../../src/components/Event/eventHandlers/eventHandlers");
|
||||
|
||||
jest.spyOn(eventThunks, "putEventAsync").mockImplementation((payload) => {
|
||||
return () => Promise.resolve(payload) as any;
|
||||
const promise = Promise.resolve(payload);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
const nonRecurringEvent = {
|
||||
@@ -1116,8 +1131,10 @@ describe("Event URL handling for recurring events", () => {
|
||||
const moveEventSpy = jest
|
||||
.spyOn(eventThunks, "moveEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () =>
|
||||
Promise.resolve({ calId: payload.cal.id, events: [] }) as any;
|
||||
const result = { calId: payload.cal.id, events: [] };
|
||||
const promise = Promise.resolve(result);
|
||||
(promise as any).unwrap = () => promise;
|
||||
return () => promise as any;
|
||||
});
|
||||
|
||||
const twoCalState = {
|
||||
|
||||
@@ -13,6 +13,7 @@ describe("EventUpdateModal Timezone Handling", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
|
||||
const preloadedState = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { jest } from "@jest/globals";
|
||||
import { ThunkDispatch } from "@reduxjs/toolkit";
|
||||
import "@testing-library/jest-dom";
|
||||
import { screen, waitFor, fireEvent } from "@testing-library/react";
|
||||
import { screen, waitFor, fireEvent, act } from "@testing-library/react";
|
||||
import * as appHooks from "../../../src/app/hooks";
|
||||
import * as eventUtils from "../../../src/components/Event/utils/eventUtils";
|
||||
import * as userApi from "../../../src/features/User/userAPI";
|
||||
@@ -51,9 +51,16 @@ describe("Update tempcalendars called with correct params", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
const dispatch = jest.fn() as ThunkDispatch<any, any, any>;
|
||||
const dispatch = jest.fn((thunk) => {
|
||||
if (typeof thunk === "function") {
|
||||
return thunk(dispatch, () => ({}), undefined);
|
||||
}
|
||||
return thunk;
|
||||
}) as ThunkDispatch<any, any, any>;
|
||||
jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch);
|
||||
refreshCalendarsSpy = jest.spyOn(eventUtils, "refreshCalendars");
|
||||
refreshCalendarsSpy = jest
|
||||
.spyOn(eventUtils, "refreshCalendars")
|
||||
.mockResolvedValue();
|
||||
updateTempCalendarSpy = jest.spyOn(calendarUtils, "updateTempCalendar");
|
||||
refreshSingularCalendarSpy = jest.spyOn(
|
||||
eventUtils,
|
||||
@@ -214,6 +221,29 @@ describe("Update tempcalendars called with correct params", () => {
|
||||
resource: undefined,
|
||||
} as unknown as DateSelectArg;
|
||||
jest.spyOn(userApi, "searchUsers");
|
||||
|
||||
// Mock putEventAsync to return success with unwrap
|
||||
const putEventAsyncMock = jest
|
||||
.spyOn(eventThunks, "putEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
const action = {
|
||||
type: "calendars/putEvent/fulfilled",
|
||||
payload: {
|
||||
calId: payload.cal.id,
|
||||
events: [],
|
||||
calType: payload.calType,
|
||||
},
|
||||
unwrap: () =>
|
||||
Promise.resolve({
|
||||
calId: payload.cal.id,
|
||||
events: [],
|
||||
calType: payload.calType,
|
||||
}),
|
||||
};
|
||||
const promise = Promise.resolve(action) as any;
|
||||
return () => promise;
|
||||
});
|
||||
|
||||
renderWithProviders(
|
||||
<EventPopover
|
||||
anchorEl={null}
|
||||
@@ -238,22 +268,26 @@ describe("Update tempcalendars called with correct params", () => {
|
||||
fireEvent.keyDown(attendeeInput, { key: "Enter", code: "Enter" });
|
||||
|
||||
const saveButton = screen.getByText(/save/i);
|
||||
fireEvent.click(saveButton);
|
||||
await act(async () => {
|
||||
fireEvent.click(saveButton);
|
||||
});
|
||||
|
||||
await waitFor(() =>
|
||||
expect(updateTempCalendarSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
temp1: expect.objectContaining({ id: "temp1" }),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "New Event",
|
||||
}),
|
||||
expect.any(Function),
|
||||
expect.objectContaining({
|
||||
start: expect.any(Date),
|
||||
end: expect.any(Date),
|
||||
})
|
||||
)
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(updateTempCalendarSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
temp1: expect.objectContaining({ id: "temp1" }),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
title: "New Event",
|
||||
}),
|
||||
expect.any(Function),
|
||||
expect.objectContaining({
|
||||
start: expect.any(Date),
|
||||
end: expect.any(Date),
|
||||
})
|
||||
),
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
Reference in New Issue
Block a user