141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
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 = '<p>Body</p>') => ({
|
|
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 <section> 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();
|
|
// The whole row is one link to the story; matching on the title text via
|
|
// a partial regex tolerates trailing author/date metadata in the
|
|
// accessible name.
|
|
const link = await page
|
|
.getByRole('link', { name: /Erinnerung an Franz/ })
|
|
.first()
|
|
.element();
|
|
expect(link.getAttribute('href')).toBe('/geschichten/g1');
|
|
});
|
|
|
|
it('makes the entire story row a single clickable link', async () => {
|
|
render(GeschichtenCard, {
|
|
geschichten: [makeStory('g1', 'A title', '<p>Some body excerpt text</p>')],
|
|
personId: 'p1',
|
|
personName: 'Franz',
|
|
canWrite: false
|
|
});
|
|
// The body-excerpt text is inside the same <a> as the title.
|
|
const links = await page.getByRole('link', { name: /A title/ }).all();
|
|
expect(links.length).toBeGreaterThan(0);
|
|
const linkEl = await links[0].element();
|
|
expect(linkEl.tagName).toBe('A');
|
|
expect(linkEl.textContent).toContain('Some body excerpt text');
|
|
});
|
|
|
|
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',
|
|
'<p>Plain <strong>bold</strong> story</p><script>alert(1)</script>'
|
|
)
|
|
],
|
|
personId: 'p1',
|
|
personName: 'Franz',
|
|
canWrite: false
|
|
});
|
|
// Body excerpt appears once as plain text — no <strong> rendered, no script
|
|
await expect.element(page.getByText(/Plain bold story/)).toBeInTheDocument();
|
|
expect(document.body.innerHTML).not.toContain('<script>');
|
|
});
|
|
});
|