diff --git a/frontend/src/routes/geschichten/[id]/+page.svelte b/frontend/src/routes/geschichten/[id]/+page.svelte
index 7297ee66..b691bc0e 100644
--- a/frontend/src/routes/geschichten/[id]/+page.svelte
+++ b/frontend/src/routes/geschichten/[id]/+page.svelte
@@ -5,6 +5,7 @@ import { formatDate } from '$lib/shared/utils/date';
import { formatAuthorDisplayName } from '$lib/geschichte/utils';
import { getConfirmService } from '$lib/shared/services/confirm.svelte';
import { csrfFetch } from '$lib/shared/cookies';
+import { parseBackendError, getErrorMessage } from '$lib/shared/errors';
import BackButton from '$lib/shared/primitives/BackButton.svelte';
import StoryReader from '$lib/geschichte/StoryReader.svelte';
import JourneyReader from '$lib/geschichte/JourneyReader.svelte';
@@ -24,7 +25,10 @@ const authorName = $derived(formatAuthorDisplayName(g.author));
const confirm = getConfirmService();
+let deleteError = $state(null);
+
async function handleDelete() {
+ deleteError = null;
const ok = await confirm.confirm({
title: m.geschichte_delete_confirm_title(),
body: m.geschichte_delete_confirm_body(),
@@ -36,6 +40,9 @@ async function handleDelete() {
const res = await csrfFetch(`/api/geschichten/${g.id}`, { method: 'DELETE' });
if (res.ok) {
goto('/geschichten');
+ } else {
+ const err = await parseBackendError(res);
+ deleteError = getErrorMessage(err?.code);
}
}
@@ -65,6 +72,15 @@ async function handleDelete() {
+ {#if deleteError}
+
+ {deleteError}
+
+ {/if}
+
{#if isJourney}
{:else}
diff --git a/frontend/src/routes/geschichten/[id]/page.svelte.test.ts b/frontend/src/routes/geschichten/[id]/page.svelte.test.ts
index 10172d12..6dc213a4 100644
--- a/frontend/src/routes/geschichten/[id]/page.svelte.test.ts
+++ b/frontend/src/routes/geschichten/[id]/page.svelte.test.ts
@@ -1,8 +1,28 @@
-import { describe, it, expect, afterEach } from 'vitest';
+import { describe, it, expect, vi, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
-import { page } from 'vitest/browser';
+import { page, userEvent } from 'vitest/browser';
+
+vi.mock('$app/navigation', () => ({
+ beforeNavigate: () => {},
+ afterNavigate: () => {},
+ goto: vi.fn(),
+ invalidate: vi.fn(),
+ invalidateAll: vi.fn(),
+ preloadCode: vi.fn(),
+ preloadData: vi.fn(),
+ pushState: vi.fn(),
+ replaceState: vi.fn(),
+ disableScrollHandling: vi.fn(),
+ onNavigate: () => () => {}
+}));
+
+vi.mock('$lib/shared/cookies', () => ({
+ csrfFetch: vi.fn()
+}));
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
+import { csrfFetch } from '$lib/shared/cookies';
+import { goto } from '$app/navigation';
import type { components } from '$lib/generated/api';
const { default: GeschichtePage } = await import('./+page.svelte');
@@ -205,4 +225,50 @@ describe('geschichten/[id] page', () => {
await expect.element(page.getByText(/Im Jahr 1923/)).toBeVisible();
await expect.element(page.getByText(/Diese Lesereise ist noch leer/)).not.toBeInTheDocument();
});
+
+ it('delete success: navigates to /geschichten after confirmed DELETE returns ok', async () => {
+ vi.mocked(csrfFetch).mockResolvedValue(new Response(null, { status: 200 }));
+ const confirmService = createConfirmService();
+ render(GeschichtePage, {
+ context: new Map([[CONFIRM_KEY, confirmService]]),
+ props: { data: baseData({ canBlogWrite: true }) }
+ });
+
+ // Trigger delete — opens confirm dialog
+ const deleteBtn = page.getByRole('button', { name: /löschen/i });
+ userEvent.click(deleteBtn);
+
+ // Settle the confirmation dialog
+ confirmService.settle(true);
+
+ // Wait for the async delete to complete, then check goto was called
+ await vi.waitFor(() => {
+ expect(vi.mocked(goto)).toHaveBeenCalledWith('/geschichten');
+ });
+ });
+
+ it('delete failure: shows error message when DELETE returns non-ok', async () => {
+ vi.mocked(csrfFetch).mockResolvedValue(
+ new Response(JSON.stringify({ code: 'FORBIDDEN' }), {
+ status: 403,
+ headers: { 'Content-Type': 'application/json' }
+ })
+ );
+ const confirmService = createConfirmService();
+ render(GeschichtePage, {
+ context: new Map([[CONFIRM_KEY, confirmService]]),
+ props: { data: baseData({ canBlogWrite: true }) }
+ });
+
+ // Trigger delete — opens confirm dialog
+ const deleteBtn = page.getByRole('button', { name: /löschen/i });
+ userEvent.click(deleteBtn);
+
+ // Settle the confirmation dialog
+ confirmService.settle(true);
+
+ // Wait for the error to appear inline
+ await expect.element(page.getByRole('alert')).toBeVisible();
+ expect(vi.mocked(goto)).not.toHaveBeenCalled();
+ });
});