116 lines
4.5 KiB
TypeScript
116 lines
4.5 KiB
TypeScript
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page } from 'vitest/browser';
|
|
|
|
import ReaderRecentStories from './ReaderRecentStories.svelte';
|
|
import type { components } from '$lib/generated/api';
|
|
|
|
type Geschichte = components['schemas']['Geschichte'];
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
const story1: Geschichte = {
|
|
id: 'g1',
|
|
title: 'Die Familie Müller',
|
|
body: '<p>Dies ist eine sehr lange Geschichte über die Familie Müller. Sie lebten in Bayern und hatten viele Kinder. Das war früher so üblich in diesen Gebieten.</p>',
|
|
status: 'PUBLISHED',
|
|
createdAt: '2025-01-01T00:00:00Z',
|
|
updatedAt: '2025-01-01T00:00:00Z',
|
|
publishedAt: '2025-01-01T00:00:00Z'
|
|
};
|
|
|
|
const longBodyStory: Geschichte = {
|
|
id: 'g2',
|
|
title: 'Sehr lange Geschichte',
|
|
body: '<p>' + 'A'.repeat(200) + '</p>',
|
|
status: 'PUBLISHED',
|
|
createdAt: '2025-02-01T00:00:00Z',
|
|
updatedAt: '2025-02-01T00:00:00Z',
|
|
publishedAt: '2025-02-01T00:00:00Z'
|
|
};
|
|
|
|
describe('ReaderRecentStories', () => {
|
|
it('renders a link to /geschichten/{id} for each story', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const link = page.getByRole('link', { name: /Die Familie Müller/ });
|
|
await expect.element(link).toHaveAttribute('href', '/geschichten/g1');
|
|
});
|
|
|
|
it('truncates body excerpt to 150 characters and strips HTML', async () => {
|
|
render(ReaderRecentStories, { stories: [longBodyStory] });
|
|
const excerpt = page.getByText(/A{100,150}/);
|
|
await expect.element(excerpt).toBeInTheDocument();
|
|
const text = ((await excerpt.element()) as HTMLElement).textContent;
|
|
expect(text!.replace(/…$/, '').length).toBeLessThanOrEqual(150);
|
|
});
|
|
|
|
it('shows empty state when stories array is empty', async () => {
|
|
render(ReaderRecentStories, { stories: [] });
|
|
const links = page.getByRole('link');
|
|
await expect.element(links).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('renders "Alle Geschichten" link pointing to /geschichten', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const allLink = page.getByRole('link', { name: /Alle Geschichten/i });
|
|
await expect.element(allLink).toHaveAttribute('href', '/geschichten');
|
|
});
|
|
|
|
it('exposes a focus-visible ring on the "Alle Geschichten" link', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const allLink = page.getByRole('link', { name: /Alle Geschichten/i });
|
|
const cls = ((await allLink.element()) as HTMLElement).className;
|
|
expect(cls).toMatch(/focus-visible:ring-2/);
|
|
expect(cls).toMatch(/focus-visible:ring-brand-navy/);
|
|
});
|
|
|
|
it('meets the 44px touch target on the "Alle Geschichten" link', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const allLink = page.getByRole('link', { name: /Alle Geschichten/i });
|
|
const cls = ((await allLink.element()) as HTMLElement).className;
|
|
expect(cls).toMatch(/min-h-\[44px\]/);
|
|
});
|
|
|
|
it('card-head contains an h3 (not h2)', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const h3 = page.getByRole('heading', { level: 3 });
|
|
await expect.element(h3).toBeInTheDocument();
|
|
const h2 = page.getByRole('heading', { level: 2 });
|
|
await expect.element(h2).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('card-head div has border-b and border-line classes', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const h3 = page.getByRole('heading', { level: 3 });
|
|
const cardHead = ((await h3.element()) as HTMLElement).parentElement;
|
|
expect(cardHead?.className).toMatch(/border-b/);
|
|
expect(cardHead?.className).toMatch(/border-line/);
|
|
});
|
|
|
|
it('"Alle Geschichten" link is inside the card-head (sibling of h3)', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const h3 = page.getByRole('heading', { level: 3 });
|
|
const cardHead = ((await h3.element()) as HTMLElement).parentElement;
|
|
const allLink = cardHead?.querySelector('a');
|
|
expect(allLink).not.toBeNull();
|
|
expect(allLink?.textContent?.trim()).toMatch(/Alle Geschichten/i);
|
|
});
|
|
|
|
it('story-row link has min-h-[44px] touch target', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const link = page.getByRole('link', { name: /Die Familie Müller/ });
|
|
const cls = ((await link.element()) as HTMLElement).className;
|
|
expect(cls).toMatch(/min-h-\[44px\]/);
|
|
});
|
|
|
|
it('excerpt has text-ink-2 class', async () => {
|
|
render(ReaderRecentStories, { stories: [story1] });
|
|
const link = page.getByRole('link', { name: /Die Familie Müller/ });
|
|
const el = (await link.element()) as HTMLElement;
|
|
const excerptEl = el.querySelector('p');
|
|
expect(excerptEl?.className).toMatch(/text-ink-2/);
|
|
});
|
|
});
|