refactor(notification): rename markRead/markAllRead to optimistic helpers without fetch

Removes raw fetch() calls from the store. optimisticMarkRead(id) and
optimisticMarkAllRead() now only mutate local $state — the actual API
calls move to SvelteKit form actions on /aktivitaeten.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-19 22:59:01 +02:00
committed by marcel
parent 85c13b3d46
commit cdd5bfa318
2 changed files with 49 additions and 24 deletions

View File

@@ -108,12 +108,46 @@ describe('notificationStore (singleton)', () => {
expect(notificationStore.unreadCount).toBe(1); expect(notificationStore.unreadCount).toBe(1);
}); });
it('markAllRead resets unreadCount', async () => { it('optimisticMarkRead marks the notification read and decrements unreadCount without fetching', () => {
mockFetch.mockResolvedValue(new Response(null, { status: 200 })); notificationStore.init();
await notificationStore.markAllRead(); const notification = makeNotification({ id: 'sse-1', read: false });
lastEventSource!.simulate('notification', JSON.stringify(notification));
mockFetch.mockReset(); // clear the fetchUnreadCount call from init
expect(mockFetch).toHaveBeenCalledWith('/api/notifications/read-all', { method: 'POST' }); notificationStore.optimisticMarkRead('sse-1');
expect(notificationStore.notifications[0].read).toBe(true);
expect(notificationStore.unreadCount).toBe(0); expect(notificationStore.unreadCount).toBe(0);
expect(mockFetch).not.toHaveBeenCalled();
});
it('optimisticMarkRead on an already-read notification does not decrement unreadCount below 0', () => {
notificationStore.init();
const notification = makeNotification({ id: 'sse-1', read: true });
lastEventSource!.simulate('notification', JSON.stringify(notification));
notificationStore.optimisticMarkRead('sse-1');
expect(notificationStore.unreadCount).toBe(0);
});
it('optimisticMarkAllRead resets unreadCount and marks all notifications read without fetching', () => {
notificationStore.init();
lastEventSource!.simulate(
'notification',
JSON.stringify(makeNotification({ id: 'n1', read: false }))
);
lastEventSource!.simulate(
'notification',
JSON.stringify(makeNotification({ id: 'n2', read: false }))
);
mockFetch.mockReset();
notificationStore.optimisticMarkAllRead();
expect(notificationStore.unreadCount).toBe(0);
expect(notificationStore.notifications.every((n) => n.read)).toBe(true);
expect(mockFetch).not.toHaveBeenCalled();
}); });
}); });

View File

@@ -35,28 +35,19 @@ async function fetchUnreadCount(): Promise<void> {
} }
} }
async function markRead(notification: NotificationItem): Promise<void> { function optimisticMarkRead(id: string): void {
if (!notification.read) { const notification = notifications.find((n) => n.id === id);
try { if (notification && !notification.read) {
await fetch(`/api/notifications/${notification.id}/read`, { method: 'PATCH' }); notification.read = true;
notification.read = true; unreadCount = Math.max(0, unreadCount - 1);
unreadCount = Math.max(0, unreadCount - 1);
} catch (e) {
console.error('Failed to mark notification as read', e);
}
} }
} }
async function markAllRead(): Promise<void> { function optimisticMarkAllRead(): void {
try { for (const n of notifications) {
await fetch('/api/notifications/read-all', { method: 'POST' }); n.read = true;
for (const n of notifications) {
n.read = true;
}
unreadCount = 0;
} catch (e) {
console.error('Failed to mark all notifications as read', e);
} }
unreadCount = 0;
} }
function init(): void { function init(): void {
@@ -123,8 +114,8 @@ export const notificationStore = {
}, },
fetchNotifications, fetchNotifications,
fetchUnreadCount, fetchUnreadCount,
markRead, optimisticMarkRead,
markAllRead, optimisticMarkAllRead,
init, init,
destroy destroy
}; };