refactor(geschichte): extract delete handler to [id]/+page.svelte, pass via ondelete prop

Moves the confirm-then-delete flow out of StoryReader and JourneyReader into
the single [id]/+page.svelte owner. Both reader components gain an optional
ondelete prop — the delete button calls ondelete?.() so the handler is opt-in
and never duplicated. Tests verify the prop is called on click.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-08 23:24:33 +02:00
parent 91d9dae6fd
commit 4c24bbb002
5 changed files with 67 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
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';
import { createConfirmService, CONFIRM_KEY } from '$lib/shared/services/confirm.svelte.js';
import type { components } from '$lib/generated/api';
@@ -75,8 +75,11 @@ describe('JourneyReader', () => {
props: { geschichte: baseGeschichte({ body: ' ' }), canBlogWrite: false }
});
expect(document.body.textContent?.trim().replace(/\s+/g, ' ')).not.toContain(' ');
// Whitespace-only body must NOT produce a visible intro paragraph.
// The only rendered content should be the empty-state message.
await expect.element(page.getByTestId('journey-empty-state')).toBeVisible();
const paragraphs = document.querySelectorAll('p:not([data-testid])');
expect(paragraphs.length).toBe(0);
});
it('renders empty-state message when items array is empty', async () => {
@@ -148,6 +151,22 @@ describe('JourneyReader', () => {
await expect.element(page.getByText('Diese Lesereise ist noch leer.')).not.toBeInTheDocument();
});
it('clicking delete button calls ondelete prop', async () => {
const ondelete = vi.fn().mockResolvedValue(undefined);
render(JourneyReader, {
context: ctx(),
props: {
geschichte: baseGeschichte({ items: [docItem('i1', 'Brief', 0)] }),
canBlogWrite: true,
ondelete
}
});
await userEvent.click(page.getByRole('button', { name: /löschen/i }));
expect(ondelete).toHaveBeenCalledOnce();
});
it('XSS: Journey body is rendered as plaintext — injected payload does not execute', async () => {
// JourneyReader uses Svelte text interpolation, NOT {@html}.
render(JourneyReader, {