diff --git a/frontend/src/lib/components/GeschichtenCard.svelte.spec.ts b/frontend/src/lib/components/GeschichtenCard.svelte.spec.ts new file mode 100644 index 00000000..0deeb17c --- /dev/null +++ b/frontend/src/lib/components/GeschichtenCard.svelte.spec.ts @@ -0,0 +1,120 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import GeschichtenCard from './GeschichtenCard.svelte'; + +const makeStory = (id: string, title: string, body: string | null = '

Body

') => ({ + id, + title, + body, + status: 'PUBLISHED' as const, + publishedAt: '2024-04-01T12:00:00', + createdAt: '2024-03-01T12:00:00', + updatedAt: '2024-04-01T12:00:00', + persons: [], + documents: [], + author: { + id: 'u1', + email: 'marcel@example.com', + firstName: 'Marcel', + lastName: 'Raddatz', + enabled: true, + notifyOnReply: false, + notifyOnMention: false, + groups: [], + createdAt: '2024-01-01T00:00:00', + color: '#000' + } +}); + +afterEach(() => cleanup()); + +describe('GeschichtenCard', () => { + it('renders nothing when geschichten is empty', async () => { + render(GeschichtenCard, { + geschichten: [], + personId: 'p1', + personName: 'Franz', + canWrite: true + }); + // No heading, no list — the entire
should not exist + expect( + document.querySelector('section[aria-labelledby="geschichten-card-heading"]') + ).toBeNull(); + }); + + it('renders the section heading and stories when geschichten is non-empty', async () => { + render(GeschichtenCard, { + geschichten: [makeStory('g1', 'Erinnerung an Franz')], + personId: 'p1', + personName: 'Franz', + canWrite: false + }); + await expect.element(page.getByText('Geschichten')).toBeInTheDocument(); + await expect + .element(page.getByRole('link', { name: 'Erinnerung an Franz' })) + .toBeInTheDocument(); + }); + + it('hides the "+ Geschichte schreiben" link when canWrite is false', async () => { + render(GeschichtenCard, { + geschichten: [makeStory('g1', 'A story')], + personId: 'p1', + personName: 'Franz', + canWrite: false + }); + const writeLinks = await page.getByText(/Geschichte schreiben/).all(); + expect(writeLinks).toHaveLength(0); + }); + + it('shows the write-action link only when canWrite is true', async () => { + render(GeschichtenCard, { + geschichten: [makeStory('g1', 'A story')], + personId: 'p1', + personName: 'Franz', + canWrite: true + }); + const link = await page.getByRole('link', { name: /Geschichte schreiben/ }).element(); + expect(link.getAttribute('href')).toBe('/geschichten/new?personId=p1'); + }); + + it('hides the "Alle Geschichten zu …" footer link below the 3-story threshold', async () => { + render(GeschichtenCard, { + geschichten: [makeStory('g1', 'A'), makeStory('g2', 'B')], + personId: 'p1', + personName: 'Franz', + canWrite: false + }); + const overflow = await page.getByText(/Alle Geschichten zu/).all(); + expect(overflow).toHaveLength(0); + }); + + it('shows the footer link at the 3-story threshold (>= 3)', async () => { + render(GeschichtenCard, { + geschichten: [makeStory('g1', 'A'), makeStory('g2', 'B'), makeStory('g3', 'C')], + personId: 'p1', + personName: 'Franz', + canWrite: false + }); + const link = await page.getByRole('link', { name: /Alle Geschichten zu Franz/ }).element(); + expect(link.getAttribute('href')).toBe('/geschichten?personId=p1'); + }); + + it('renders a plain-text excerpt without HTML markup', async () => { + render(GeschichtenCard, { + geschichten: [ + makeStory( + 'g1', + 'Mit HTML', + '

Plain bold story

' + ) + ], + personId: 'p1', + personName: 'Franz', + canWrite: false + }); + // Body excerpt appears once as plain text — no rendered, no script + await expect.element(page.getByText(/Plain bold story/)).toBeInTheDocument(); + expect(document.body.innerHTML).not.toContain('