fix(reminders): keine Dauermeldung ohne localStorage, Mail nach Zeitaenderung, null-Pruefung

- Merkliste zusaetzlich im Arbeitsspeicher (sonst alle 10 s dieselbe Meldung)
- Aendern der Faelligkeit atomar gegen gleichzeitiges Faelligwerden, setzt Mail-Spur zurueck
- UpdateReminderDto lehnt null ab (400 statt 500)
- keine Mails an deaktivierte Benutzer
- Spaeter erinnern beschriftet heute/morgen nach dem berechneten Zeitpunkt
- Bearbeiten schickt dueAt nur bei geaenderter Zeit

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 03:20:16 +02:00
parent be1e0035e0
commit c2e4467dd8
14 changed files with 442 additions and 78 deletions
@@ -17,12 +17,13 @@ vi.mock('@/lib/reminders-api', async () => {
});
vi.mock('@/lib/reminder-notify', async () => {
const actual = await vi.importActual<typeof import('@/lib/reminder-notify')>('@/lib/reminder-notify');
const actual =
await vi.importActual<typeof import('@/lib/reminder-notify')>('@/lib/reminder-notify');
return { ...actual, showReminderNotification: vi.fn(async () => undefined) };
});
import { showReminderNotification } from '@/lib/reminder-notify';
import { ReminderRequestError, listReminders } from '@/lib/reminders-api';
import { resetNotifiedMemoryForTests, showReminderNotification } from '@/lib/reminder-notify';
import { listReminders, ReminderRequestError } from '@/lib/reminders-api';
import { ReminderNotifier } from './reminder-notifier';
const mockList = listReminders as ReturnType<typeof vi.fn>;
@@ -53,6 +54,7 @@ beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(NOW);
window.localStorage.clear();
resetNotifiedMemoryForTests();
mockList.mockReset();
mockShow.mockClear();
});
@@ -77,7 +79,9 @@ describe('ReminderNotifier', () => {
});
it('meldet nichts, solange nichts faellig ist', async () => {
mockList.mockResolvedValue([reminder({ dueAt: new Date(NOW.getTime() + 3600_000).toISOString() })]);
mockList.mockResolvedValue([
reminder({ dueAt: new Date(NOW.getTime() + 3600_000).toISOString() }),
]);
render(<ReminderNotifier />);
await advance(30_000);
expect(mockShow).not.toHaveBeenCalled();
@@ -85,7 +89,10 @@ describe('ReminderNotifier', () => {
it('nimmt die Beschreibung als Text, gekuerzt auf 200 Zeichen', async () => {
mockList.mockResolvedValue([
reminder({ dueAt: new Date(NOW.getTime() - 1000).toISOString(), description: 'x'.repeat(300) }),
reminder({
dueAt: new Date(NOW.getTime() - 1000).toISOString(),
description: 'x'.repeat(300),
}),
]);
render(<ReminderNotifier />);
await advance(0);