From 9b4ffcd652cb1e7bf0fbef820b872aff58e9573d Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 28 Oct 2026 03:06:41 +0100 Subject: [PATCH] fix: return to the event editor with the changes when the recurring-event question is cancelled --- .../calendar/__tests__/calendar-app.test.tsx | 24 +++++++++++++++++++ components/calendar/calendar-app.tsx | 24 ++++++++++++------- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/components/calendar/__tests__/calendar-app.test.tsx b/components/calendar/__tests__/calendar-app.test.tsx index 57035ffd1..1fe0d293b 100644 --- a/components/calendar/__tests__/calendar-app.test.tsx +++ b/components/calendar/__tests__/calendar-app.test.tsx @@ -176,3 +176,27 @@ describe('CalendarApp in the Tasks view', () => { expect(screen.getByRole('dialog', { name: 'tasks.create' })).toBeInTheDocument(); }); }); + +const weekly = makeEvent({ recurrenceRules: [{ '@type': 'RecurrenceRule', frequency: 'weekly', interval: 1 }] as never }); + +function openEditorOn(title = 'Standup') { + fireEvent.click(screen.getAllByRole('button', { name: new RegExp(title) })[0]); + const card = screen.getByRole('dialog', { name: title }); + fireEvent.click(within(card).getByRole('button', { name: 'events.edit' })); +} + +describe('CalendarApp recurring event editor', () => { + beforeEach(() => setUp([weekly])); + + it('goes back to the editor with the changes when the scope question is cancelled', async () => { + renderApp(); + openEditorOn(); + fireEvent.change(screen.getByPlaceholderText('form.title'), { target: { value: 'Standup moved' } }); + fireEvent.click(screen.getByRole('button', { name: 'form.save' })); + const scope = await screen.findByRole('dialog', { name: 'edit_title' }); + fireEvent.click(within(scope).getByRole('button', { name: 'cancel' })); + expect(screen.queryByRole('dialog', { name: 'edit_title' })).toBeNull(); + expect(screen.getByPlaceholderText('form.title')).toHaveValue('Standup moved'); + expect(store.updateEvent).not.toHaveBeenCalled(); + }); +}); diff --git a/components/calendar/calendar-app.tsx b/components/calendar/calendar-app.tsx index c66d92534..d6d1edff9 100644 --- a/components/calendar/calendar-app.tsx +++ b/components/calendar/calendar-app.tsx @@ -101,9 +101,11 @@ import { isEditableEventTarget } from "@/lib/keyboard"; import { isContextMenuOpen } from "@/components/ui/context-menu"; import { useRegisterCommands } from "@/lib/command-registry"; +// `fromEditor`: asked on the way out of the event editor, which stays open +// until a scope is picked, so Cancel goes back to it with the changes. type PendingScopeAction = - | { type: "edit"; event: CalendarEvent; updates: Partial; editor?: EventEditorState; sendScheduling?: boolean } - | { type: "delete"; event: CalendarEvent; sendScheduling?: boolean } + | { type: "edit"; event: CalendarEvent; updates: Partial; editor?: EventEditorState; sendScheduling?: boolean; fromEditor?: boolean } + | { type: "delete"; event: CalendarEvent; sendScheduling?: boolean; fromEditor?: boolean } | { type: "rsvp"; event: CalendarEvent; participantId: string; status: CalendarParticipant["participationStatus"] }; function isRecurringEvent(event: CalendarEvent): boolean { @@ -995,9 +997,8 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = updates: data, editor, sendScheduling: sendSchedulingMessages, + fromEditor: true, }); - setShowEventModal(false); - setEditEvent(null); return; } // false: a plugin stopped it and said why; the form stays open. @@ -1150,9 +1151,8 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = type: "delete", event: eventToDelete, sendScheduling: sendSchedulingMessages || undefined, + fromEditor: showEventModal && editEvent?.id === eventToDelete.id, }); - setShowEventModal(false); - setEditEvent(null); return; } try { @@ -1160,7 +1160,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = } catch { toast.error(t("notifications.event_error")); } - }, [client, deleteEvent, events, editEvent, t]); + }, [client, deleteEvent, events, editEvent, showEventModal, t]); const submitRsvp = useCallback(async ( eventId: string, @@ -1184,10 +1184,16 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = await submitRsvp(event.id, participantId, status, scope === "this" ? "occurrence" : "series"); return; } - const { type, event, sendScheduling } = pendingScopeAction; + const { type, event, sendScheduling, fromEditor } = pendingScopeAction; const updates = type === "edit" ? pendingScopeAction.updates : undefined; const editor = type === "edit" ? pendingScopeAction.editor : undefined; setPendingScopeAction(null); + // The editor the question came from closes once the change went through. + const closeEditor = () => { + if (!fromEditor) return; + setShowEventModal(false); + setEditEvent(null); + }; // false when a plugin stopped the change (it says why): no "updated". let done = true; @@ -1207,6 +1213,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = return; } if (!done) return; + closeEditor(); toast.success(t("notifications.event_updated")); } else { const deleted = await deleteOccurrence(scope, { @@ -1220,6 +1227,7 @@ export function CalendarApp({ linkSegments: routeSegments }: CalendarAppProps = return; } if (!done) return; + closeEditor(); toast.success(t("notifications.event_deleted")); } try {