fix: say so when the filters stay off after turning on the auto-reply

This commit is contained in:
Linus Rath
2026-10-29 04:31:18 +01:00
parent fc9c164d3d
commit 54aa88425a
5 changed files with 40 additions and 6 deletions
+3
View File
@@ -616,6 +616,9 @@
"your_server": "Ihrem Server"
}
},
"notifications": {
"vacation_filters_off": "Abwesenheits-Einstellungen gespeichert, aber Ihre Filter sind ausgeschaltet. Speichern Sie noch einmal, um sie wieder einzuschalten."
},
"offline": {
"banner": "Sie sind offline",
"discarded_changes": "{count, plural, one {# Offline-Änderung wurde} other {# Offline-Änderungen wurden}} verworfen, weil sich der Mailserver geändert hat",
+3
View File
@@ -661,6 +661,9 @@
"your_server": "your server"
}
},
"notifications": {
"vacation_filters_off": "Out of office settings saved, but your filters are switched off. Save again to turn them back on."
},
"offline": {
"banner": "You are offline",
"discarded_changes": "{count, plural, one {# offline change was} other {# offline changes were}} discarded because the mail server changed",
+3 -1
View File
@@ -192,7 +192,9 @@ export function VacationSettings() {
Alert.alert(t('notifications.vacation_saved', 'Vacation responder settings saved'));
} catch (err) {
Alert.alert(
err instanceof VacationFiltersError
err instanceof VacationFiltersError && err.filtersOff
? t('notifications.vacation_filters_off', 'Out of office settings saved, but your filters are switched off. Save again to turn them back on.')
: err instanceof VacationFiltersError
? t('notifications.vacation_filters_save_failed', 'Out of office settings saved, but the forwarding and reply recipients could not be saved')
: t('notifications.vacation_save_failed', 'Failed to save vacation responder settings'),
err instanceof Error ? err.message : undefined,
+16 -1
View File
@@ -21,7 +21,7 @@ vi.mock('../filter-store', () => ({
import * as vacation from '../../api/vacation';
import * as sieve from '../../api/sieve';
import * as filters from '../filter-store';
import { useVacationStore } from '../vacation-store';
import { useVacationStore, VacationFiltersError } from '../vacation-store';
const api = vi.mocked(vacation);
const sieveApi = vi.mocked(sieve);
@@ -164,6 +164,21 @@ describe('vacation-store and the filters script (webmail 198a3c0d)', () => {
);
});
// Turning the auto-reply on activates Stalwart's vacation script, which
// switches the filters off until the filters script includes it.
it('reports filters left off when the auto-reply was turned on', async () => {
sieveApi.isSieveSupported.mockReturnValue(true);
filterStore.syncVacationWithFilters.mockRejectedValueOnce(new Error('boom'));
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
const saved = useVacationStore.getState().save({ isEnabled: true });
await expect(saved).rejects.toBeInstanceOf(VacationFiltersError);
await expect(saved).rejects.toMatchObject({ filtersOff: true });
expect(api.setVacationResponse).toHaveBeenCalledWith({ isEnabled: true }, undefined);
expect(useVacationStore.getState()).toMatchObject({ isEnabled: true, isSaving: false });
warn.mockRestore();
});
it('reports a forwarding that could not be saved', async () => {
sieveApi.isSieveSupported.mockReturnValue(true);
filterStore.syncVacationWithFilters.mockRejectedValueOnce(new Error('boom'));
+15 -4
View File
@@ -19,9 +19,17 @@ export type VacationForwardSettings = Pick<VacationForward, 'enabled' | 'to' | '
* or still run, and the auto-reply may go to the wrong senders.
*/
export class VacationFiltersError extends Error {
constructor(cause: unknown) {
/**
* The auto-reply was turned on and the filters script could not be made
* to run next to it: Stalwart's vacation script took over, and no filter
* runs until the card is saved again.
*/
readonly filtersOff: boolean;
constructor(cause: unknown, filtersOff = false) {
super(cause instanceof Error ? cause.message : 'Failed to save the forwarding');
this.name = 'VacationFiltersError';
this.filtersOff = filtersOff;
}
}
@@ -169,6 +177,9 @@ export const useVacationStore = create<VacationState>((set, get) => ({
let filtersError: VacationFiltersError | null = null;
let filtersState: Partial<VacationState> = {};
const needsSync = vacationUpdates.isEnabled !== undefined || forwardSettings !== undefined || audience !== undefined;
// Turning the auto-reply on activates Stalwart's vacation script, which
// switches the filters off until the sync includes it in them again.
const turnedOn = vacationUpdates.isEnabled === true;
if (needsSync && isSieveSupported(accountId)) {
let synced = false;
try {
@@ -180,9 +191,9 @@ export const useVacationStore = create<VacationState>((set, get) => ({
console.warn('[vacation] Failed to keep filters active next to the vacation response:', err);
// Forwarding that is or was on may now not run, or still run; an
// auto-reply meant for some senders may go to everyone, or the other
// way round.
if (forwardSettings?.enabled || state.forward?.enabled || audience || state.audience) {
filtersError = new VacationFiltersError(err);
// way round; the filters may be off.
if (turnedOn || forwardSettings?.enabled || state.forward?.enabled || audience || state.audience) {
filtersError = new VacationFiltersError(err, turnedOn);
}
}
// What the filters script holds now, and whether it runs. After a