feat(ui): add automation settings and safe AI review presentation

This commit is contained in:
cesnimda
2026-11-03 19:28:02 +01:00
parent a88310481f
commit e2327c1fa2
121 changed files with 4445 additions and 47 deletions
+25 -6
View File
@@ -319,12 +319,31 @@ test('[PRES-S03] Rules draft updates do not refetch an unrelated notification pr
});
function preferenceCopy(key: TranslationKey) { return translations[document.documentElement.lang === 'nb-NO' ? 'nb' : 'en'][key]; }
test.each(['en', 'nb'] as const)('%s Automation reuses the saved notification preference and explicit save', async language => {
localStorage.setItem('uiLanguage', language); renderView();
fireEvent.click(screen.getByRole('tab', { name: preferenceCopy('settingsTabAutomation') }));
const panel = within(screen.getByRole('tabpanel', { name: preferenceCopy('settingsTabAutomation') }));
const toggle = await panel.findByRole('switch', { name: preferenceCopy('settingsNotificationsFollowUpReminders') });
expect(toggle).toBeChecked(); fireEvent.click(toggle); expect(mockedApi.put).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole('tab', { name: preferenceCopy('settingsTabNotifications') }));
expect(await notificationPanel().findByRole('switch', { name: preferenceCopy('settingsNotificationsFollowUpReminders') })).not.toBeChecked();
fireEvent.click(notificationPanel().getByRole('button', { name: preferenceCopy('settingsNotificationsSave') }));
await waitFor(() => expect(mockedApi.put).toHaveBeenCalledWith('/notification-settings', { emailFollowUpRemindersEnabled: false }));
});
test('Automation AI entry moves focus into existing settings without an AI request', async () => {
renderView(); fireEvent.click(screen.getByRole('tab', { name: preferenceCopy('settingsTabAutomation') }));
fireEvent.click(await screen.findByRole('button', { name: preferenceCopy('settingsAutomationAi') }));
await waitFor(() => expect(document.getElementById('settings-ai-controls')).toHaveFocus());
expect(mockedApi.post).not.toHaveBeenCalled();
});
function notificationPanel() { return within(screen.getByRole('tabpanel', { name: preferenceCopy('settingsTabNotifications') })); }
function notificationReads() { return mockedApi.get.mock.calls.filter(([url]) => url === '/notification-settings'); }
function notificationWrites() { return mockedApi.put.mock.calls.filter(([url]) => url === '/notification-settings'); }
async function openNotifications() {
fireEvent.click(screen.getByRole('tab', { name: preferenceCopy('settingsTabNotifications') }));
return notificationPanel().findByRole('checkbox', { name: preferenceCopy('settingsNotificationsFollowUpReminders') });
return notificationPanel().findByRole('switch', { name: preferenceCopy('settingsNotificationsFollowUpReminders') });
}
async function preferenceLanguage(language: 'en' | 'nb') {
await act(async () => { window.dispatchEvent(new StorageEvent('storage', { key: 'uiLanguage', newValue: language })); });
@@ -345,7 +364,7 @@ test('[P7-09 preferences] initial notification failure offers a single GET Retry
act(() => { retry.focus(); fireEvent.click(retry); fireEvent.click(retry); });
expect(notificationReads()).toHaveLength(2); expect(mockedApi.put).not.toHaveBeenCalled();
await act(async () => pending.resolve({ data: { emailFollowUpRemindersEnabled: false } }));
expect(await notificationPanel().findByRole('checkbox', { name: translations.en.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(await notificationPanel().findByRole('switch', { name: translations.en.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(notificationPanel().queryByRole('button', { name: 'Retry' })).not.toBeInTheDocument();
expect(notificationPanel().getByRole('group', { name: translations.en.settingsNotificationsTitle })).toHaveFocus();
expect(notificationReads()).toHaveLength(2); expect(mockedApi.put).not.toHaveBeenCalled();
@@ -359,7 +378,7 @@ test('[P7-09 preferences] notification drafts survive internal sections and loca
const retained = await openNotifications();
expect(retained).not.toBeChecked(); expect(notificationReads()).toHaveLength(reads);
expect(notificationPanel().getByRole('status')).toHaveTextContent(translations.nb.settingsNotificationsDraftScope);
await preferenceLanguage('en'); expect(notificationPanel().getByRole('checkbox', { name: translations.en.settingsNotificationsFollowUpReminders })).not.toBeChecked();
await preferenceLanguage('en'); expect(notificationPanel().getByRole('switch', { name: translations.en.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(notificationReads()).toHaveLength(reads); expect(mockedApi.put).not.toHaveBeenCalled();
});
@@ -375,7 +394,7 @@ test('[P7-09 preferences] one held notification save acknowledges its snapshot a
expect(await openNotifications()).toBeChecked();
expect(notificationPanel().getByRole('status')).toHaveTextContent(translations.en.assetsUnsavedChanges);
fireEvent.click(notificationPanel().getByRole('button', { name: translations.en.assetsDiscardChanges }));
expect(notificationPanel().getByRole('checkbox', { name: translations.en.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(notificationPanel().getByRole('switch', { name: translations.en.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(notificationPanel().queryByText(translations.en.settingsNotificationsDraftScope)).not.toBeInTheDocument();
expect(notificationReads()).toHaveLength(1); expect(notificationWrites()).toHaveLength(1);
});
@@ -387,7 +406,7 @@ test('[P7-09 preferences] failed notification save keeps its current-locale draf
expect(await notificationPanel().findByRole('alert')).toHaveTextContent(translations.en.settingsNotificationsSaveFailed);
const reads = notificationReads().length; await preferenceLanguage('nb');
expect(notificationPanel().getByRole('alert')).toHaveTextContent(translations.nb.settingsNotificationsSaveFailed);
expect(notificationPanel().getByRole('checkbox', { name: translations.nb.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(notificationPanel().getByRole('switch', { name: translations.nb.settingsNotificationsFollowUpReminders })).not.toBeChecked();
expect(notificationPanel().queryByRole('button', { name: translations.nb.retry })).not.toBeInTheDocument();
fireEvent.click(notificationPanel().getByRole('button', { name: translations.nb.settingsNotificationsSave }));
await waitFor(() => expect(notificationPanel().queryByRole('alert')).not.toBeInTheDocument());
@@ -403,7 +422,7 @@ test('[P7-09 preferences] discarding a notification draft leaves the independent
renderView(); fireEvent.change(await openRules(), { target: { value: '5' } });
fireEvent.click(await openNotifications());
fireEvent.click(notificationPanel().getByRole('button', { name: translations.en.assetsDiscardChanges }));
expect(notificationPanel().getByRole('checkbox', { name: translations.en.settingsNotificationsFollowUpReminders })).toBeChecked();
expect(notificationPanel().getByRole('switch', { name: translations.en.settingsNotificationsFollowUpReminders })).toBeChecked();
expect(notificationPanel().queryByText(translations.en.settingsNotificationsDraftScope)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('tab', { name: 'Follow-ups' }));
expect(screen.getByLabelText('Applied: follow-up days')).toHaveValue(5);