With create/update returning GeschichteView, no endpoint serves the raw Geschichte entity and springdoc drops its schema. Dashboard modules and the home loader now use GeschichteSummary; GeschichteEditor takes GeschichteView and maps persons into the displayName shape PersonMultiSelect renders — fixing blank person chips on story edit. PersonMultiSelect/Sidebar narrow to Pick<Person, 'id' | 'displayName'>, mirroring the DocumentOption precedent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
101 lines
3.6 KiB
TypeScript
101 lines
3.6 KiB
TypeScript
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page } from 'vitest/browser';
|
|
|
|
import ReaderDraftsModule from './ReaderDraftsModule.svelte';
|
|
import type { components } from '$lib/generated/api';
|
|
|
|
type GeschichteSummary = components['schemas']['GeschichteSummary'];
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
const draft1: GeschichteSummary = {
|
|
id: 'g1',
|
|
title: 'Mein erster Entwurf',
|
|
status: 'DRAFT',
|
|
type: 'STORY',
|
|
updatedAt: '2025-01-02T00:00:00Z'
|
|
};
|
|
|
|
const draft2: GeschichteSummary = {
|
|
id: 'g2',
|
|
title: 'Zweiter Entwurf',
|
|
status: 'DRAFT',
|
|
type: 'STORY',
|
|
createdAt: '2025-02-01T00:00:00Z',
|
|
updatedAt: '2025-02-01T00:00:00Z'
|
|
};
|
|
|
|
describe('ReaderDraftsModule', () => {
|
|
it('renders a link to /geschichten/{id}/edit for each draft', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1, draft2] });
|
|
const link1 = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
|
await expect.element(link1).toHaveAttribute('href', '/geschichten/g1/edit');
|
|
const link2 = page.getByRole('link', { name: /Zweiter Entwurf/ });
|
|
await expect.element(link2).toHaveAttribute('href', '/geschichten/g2/edit');
|
|
});
|
|
|
|
it('shows heading as h3 (not h2)', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
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('shows empty state when drafts is empty', async () => {
|
|
render(ReaderDraftsModule, { drafts: [] });
|
|
const emptyText = page.getByText(/Keine Entwürfe/i);
|
|
await expect.element(emptyText).toBeInTheDocument();
|
|
});
|
|
|
|
it('does not show empty state when drafts are present', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
const emptyText = page.getByText(/Keine Entwürfe/i);
|
|
await expect.element(emptyText).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('card wrapper has mint left-border classes', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
const h3 = page.getByRole('heading', { level: 3 });
|
|
const card = ((await h3.element()) as HTMLElement).closest('div[class]');
|
|
const rootCard = card?.parentElement;
|
|
const cls = rootCard?.className ?? '';
|
|
expect(cls).toMatch(/border-l-\[3px\]/);
|
|
expect(cls).toMatch(/border-l-brand-mint/);
|
|
});
|
|
|
|
it('draft-row link has min-h-[44px] touch target', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
|
const cls = ((await link.element()) as HTMLElement).className;
|
|
expect(cls).toMatch(/min-h-\[44px\]/);
|
|
});
|
|
|
|
it('draft title has text-ink class', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
|
const el = (await link.element()) as HTMLElement;
|
|
const titleEl = el.querySelector('[class*="text-ink"]');
|
|
expect(titleEl).not.toBeNull();
|
|
expect(titleEl?.textContent?.trim()).toBe('Mein erster Entwurf');
|
|
});
|
|
|
|
it('draft meta contains "Entwurf" text', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
|
const el = (await link.element()) as HTMLElement;
|
|
expect(el.textContent).toMatch(/Entwurf/);
|
|
});
|
|
|
|
it('chevron SVG is present in each draft row', async () => {
|
|
render(ReaderDraftsModule, { drafts: [draft1] });
|
|
const link = page.getByRole('link', { name: /Mein erster Entwurf/ });
|
|
const el = (await link.element()) as HTMLElement;
|
|
const svg = el.querySelector('svg');
|
|
expect(svg).not.toBeNull();
|
|
});
|
|
});
|