feat(notification): add dismiss-notification and mark-all-read form actions to aktivitaeten
Adds two SvelteKit form actions to /aktivitaeten/+page.server.ts so the notification bell can POST there instead of calling the backend directly from the browser. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
|
import { fail } from '@sveltejs/kit';
|
||||||
import { createApiClient } from '$lib/shared/api.server';
|
import { createApiClient } from '$lib/shared/api.server';
|
||||||
|
import { getErrorMessage } from '$lib/shared/errors';
|
||||||
import type { components, operations } from '$lib/generated/api';
|
import type { components, operations } from '$lib/generated/api';
|
||||||
|
|
||||||
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
type ActivityFeedItemDTO = components['schemas']['ActivityFeedItemDTO'];
|
||||||
@@ -65,3 +67,29 @@ export async function load({ fetch, url }) {
|
|||||||
loadError
|
loadError
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
'dismiss-notification': async ({ request, fetch }) => {
|
||||||
|
const data = await request.formData();
|
||||||
|
const notificationId = data.get('notificationId') as string;
|
||||||
|
const api = createApiClient(fetch);
|
||||||
|
const result = await api.PATCH('/api/notifications/{id}/read', {
|
||||||
|
params: { path: { id: notificationId } }
|
||||||
|
});
|
||||||
|
if (!result.response.ok) {
|
||||||
|
const code = (result.error as unknown as { code?: string })?.code;
|
||||||
|
return fail(result.response.status, { error: getErrorMessage(code) });
|
||||||
|
}
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
|
||||||
|
'mark-all-read': async ({ fetch }) => {
|
||||||
|
const api = createApiClient(fetch);
|
||||||
|
const result = await api.POST('/api/notifications/read-all');
|
||||||
|
if (!result.response.ok) {
|
||||||
|
const code = (result.error as unknown as { code?: string })?.code;
|
||||||
|
return fail(result.response.status, { error: getErrorMessage(code) });
|
||||||
|
}
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { load } from './+page.server';
|
import { load, actions } from './+page.server';
|
||||||
|
|
||||||
const mockApi = {
|
const mockApi = {
|
||||||
GET: vi.fn()
|
GET: vi.fn(),
|
||||||
|
PATCH: vi.fn(),
|
||||||
|
POST: vi.fn()
|
||||||
};
|
};
|
||||||
|
|
||||||
vi.mock('$lib/shared/api.server', () => ({
|
vi.mock('$lib/shared/api.server', () => ({
|
||||||
@@ -173,3 +175,77 @@ describe('aktivitaeten/load — kinds param per filter', () => {
|
|||||||
expect(call[1].params.query.kinds).toHaveLength(2);
|
expect(call[1].params.query.kinds).toHaveLength(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
function makeActionEvent(formData: FormData): any {
|
||||||
|
return {
|
||||||
|
request: new Request('http://localhost/aktivitaeten', { method: 'POST', body: formData }),
|
||||||
|
fetch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('aktivitaeten/actions — dismiss-notification', () => {
|
||||||
|
it('calls PATCH /api/notifications/{id}/read with the form-supplied notificationId', async () => {
|
||||||
|
mockApi.PATCH.mockResolvedValue({ response: { ok: true }, data: {} });
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('notificationId', 'n-abc');
|
||||||
|
|
||||||
|
await actions['dismiss-notification'](makeActionEvent(fd));
|
||||||
|
|
||||||
|
expect(mockApi.PATCH).toHaveBeenCalledWith('/api/notifications/{id}/read', {
|
||||||
|
params: { path: { id: 'n-abc' } }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns { success: true } when the API responds ok', async () => {
|
||||||
|
mockApi.PATCH.mockResolvedValue({ response: { ok: true }, data: {} });
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('notificationId', 'n-abc');
|
||||||
|
|
||||||
|
const result = await actions['dismiss-notification'](makeActionEvent(fd));
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns fail(status, { error }) when the API responds non-ok', async () => {
|
||||||
|
mockApi.PATCH.mockResolvedValue({
|
||||||
|
response: { ok: false, status: 403 },
|
||||||
|
error: { code: 'NOTIFICATION_NOT_FOUND' }
|
||||||
|
});
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.set('notificationId', 'n-abc');
|
||||||
|
|
||||||
|
const result = await actions['dismiss-notification'](makeActionEvent(fd));
|
||||||
|
|
||||||
|
expect(result).toMatchObject({ status: 403 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('aktivitaeten/actions — mark-all-read', () => {
|
||||||
|
it('calls POST /api/notifications/read-all', async () => {
|
||||||
|
mockApi.POST.mockResolvedValue({ response: { ok: true }, data: null });
|
||||||
|
|
||||||
|
await actions['mark-all-read'](makeActionEvent(new FormData()));
|
||||||
|
|
||||||
|
expect(mockApi.POST).toHaveBeenCalledWith('/api/notifications/read-all');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns { success: true } when the API responds ok', async () => {
|
||||||
|
mockApi.POST.mockResolvedValue({ response: { ok: true }, data: null });
|
||||||
|
|
||||||
|
const result = await actions['mark-all-read'](makeActionEvent(new FormData()));
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns fail(status, { error }) when the API responds non-ok', async () => {
|
||||||
|
mockApi.POST.mockResolvedValue({
|
||||||
|
response: { ok: false, status: 500 },
|
||||||
|
error: { code: 'INTERNAL_ERROR' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await actions['mark-all-read'](makeActionEvent(new FormData()));
|
||||||
|
|
||||||
|
expect(result).toMatchObject({ status: 500 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user