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:
lenhanphung
2025-11-25 21:30:58 +07:00
committed by GitHub
parent 25dc016688
commit 9151ff5563
12 changed files with 1324 additions and 240 deletions
+41 -13
View File
@@ -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", () => {