From 95df639d7be02f8db5a27861d63a729ec34122f4 Mon Sep 17 00:00:00 2001 From: SaelixCode Date: Tue, 21 Jul 2026 14:09:58 -0400 Subject: [PATCH] fix: correct contradictory toggle-sequence copy in schedule-repair toast The blocking toast told operators to toggle the weekly window "off then on" to confirm clearing a corrupt schedule, but the toggle starts off for a corrupt rule, so that sequence leaves it on and trips the no-selected-day validation instead. The correct, tested sequence is on then off, matching the inline hint below the toggle. Also add a regression test confirming the invalid-schedule save gate resets cleanly across edit sessions on different rules. --- .../NotificationSuppressionSection.tsx | 2 +- .../NotificationSuppressionSection.test.tsx | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/settings/NotificationSuppressionSection.tsx b/frontend/src/components/settings/NotificationSuppressionSection.tsx index ecc9bb65..67ee170c 100644 --- a/frontend/src/components/settings/NotificationSuppressionSection.tsx +++ b/frontend/src/components/settings/NotificationSuppressionSection.tsx @@ -282,7 +282,7 @@ export function NotificationSuppressionSection({ if (!formName.trim()) { toast.error('Name is required.'); return; } if (formScheduleInvalid && !formScheduleTouched) { toast.error( - "This rule's stored weekly window could not be read. Turn the weekly window on to set a new schedule, or toggle it off then on to confirm clearing it.", + "This rule's stored weekly window could not be read. Turn the weekly window on to set a new schedule, or toggle it on then off to confirm clearing it.", ); return; } diff --git a/frontend/src/components/settings/__tests__/NotificationSuppressionSection.test.tsx b/frontend/src/components/settings/__tests__/NotificationSuppressionSection.test.tsx index c5b88f4d..d52875cc 100644 --- a/frontend/src/components/settings/__tests__/NotificationSuppressionSection.test.tsx +++ b/frontend/src/components/settings/__tests__/NotificationSuppressionSection.test.tsx @@ -368,4 +368,29 @@ describe('NotificationSuppressionSection', () => { }); }); }); + + it('does not carry the invalid-schedule save gate over to a later edit of a different, valid rule', async () => { + mockListRules([corruptScheduleRule, scheduledRule]); + render(); + await waitFor(() => expect(screen.getByText('Corrupt window')).toBeInTheDocument()); + + const editButtons = screen.getAllByTitle('Edit'); + await userEvent.click(editButtons[0]); + await waitFor(() => expect(screen.getByRole('dialog', { name: /Edit mute rule/i })).toBeInTheDocument()); + await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + + await userEvent.click(screen.getAllByTitle('Edit')[1]); + await waitFor(() => expect(screen.getByRole('dialog', { name: /Edit mute rule/i })).toBeInTheDocument()); + await userEvent.click(screen.getByRole('button', { name: /Create|Update/i })); + + await waitFor(() => { + const put = mockedFetch.mock.calls.find( + ([url, opts]) => + url === '/notification-suppression-rules/42' && (opts as { method?: string })?.method === 'PUT', + ); + expect(put).toBeTruthy(); + }); + expect(toast.error).not.toHaveBeenCalledWith(expect.stringContaining('could not be read')); + }); });