diff --git a/frontend/src/lib/components/DocumentMetadataDrawer.svelte.spec.ts b/frontend/src/lib/components/DocumentMetadataDrawer.svelte.spec.ts index 7265a9a9..aeda6bbe 100644 --- a/frontend/src/lib/components/DocumentMetadataDrawer.svelte.spec.ts +++ b/frontend/src/lib/components/DocumentMetadataDrawer.svelte.spec.ts @@ -5,10 +5,10 @@ import DocumentMetadataDrawer from './DocumentMetadataDrawer.svelte'; afterEach(cleanup); -const sender = { id: 's1', firstName: 'Karl', lastName: 'Müller' }; +const sender = { id: 's1', firstName: 'Karl', lastName: 'Müller', displayName: 'Karl Müller' }; const receivers = [ - { id: 'r1', firstName: 'Anna', lastName: 'Schmidt' }, - { id: 'r2', firstName: 'Hans', lastName: 'Weber' } + { id: 'r1', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' }, + { id: 'r2', firstName: 'Hans', lastName: 'Weber', displayName: 'Hans Weber' } ]; const tags = [ { id: 't1', name: 'Familienbrief' }, diff --git a/frontend/src/lib/components/PersonMultiSelect.svelte.spec.ts b/frontend/src/lib/components/PersonMultiSelect.svelte.spec.ts index 1a132943..08c2ba28 100644 --- a/frontend/src/lib/components/PersonMultiSelect.svelte.spec.ts +++ b/frontend/src/lib/components/PersonMultiSelect.svelte.spec.ts @@ -7,9 +7,21 @@ const waitForDebounce = () => new Promise((r) => setTimeout(r, 350)); const tick = () => new Promise((r) => setTimeout(r, 0)); const PERSONS = [ - { id: '1', firstName: 'Max', lastName: 'Mustermann' }, - { id: '2', firstName: 'Anna', lastName: 'Musterfrau' }, - { id: '3', firstName: 'Karl', lastName: 'König' } + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + }, + { + id: '2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' + }, + { id: '3', firstName: 'Karl', lastName: 'König', displayName: 'Karl König', personType: 'PERSON' } ]; function mockFetch(persons = PERSONS) { @@ -45,8 +57,20 @@ describe('PersonMultiSelect – rendering', () => { it('renders pre-selected persons as chips', async () => { render(PersonMultiSelect, { selectedPersons: [ - { id: '1', firstName: 'Max', lastName: 'Mustermann' }, - { id: '2', firstName: 'Anna', lastName: 'Musterfrau' } + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + }, + { + id: '2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' + } ] }); await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); @@ -57,8 +81,20 @@ describe('PersonMultiSelect – rendering', () => { it('renders hidden inputs for each selected person', async () => { render(PersonMultiSelect, { selectedPersons: [ - { id: '1', firstName: 'Max', lastName: 'Mustermann' }, - { id: '2', firstName: 'Anna', lastName: 'Musterfrau' } + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + }, + { + id: '2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' + } ] }); await tick(); @@ -70,7 +106,15 @@ describe('PersonMultiSelect – rendering', () => { it('hides the placeholder when persons are selected', async () => { render(PersonMultiSelect, { - selectedPersons: [{ id: '1', firstName: 'Max', lastName: 'Mustermann' }] + selectedPersons: [ + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + } + ] }); await expect.element(page.getByPlaceholder('Namen tippen...')).not.toBeInTheDocument(); }); @@ -85,7 +129,7 @@ describe('PersonMultiSelect – selecting persons', () => { const input = page.getByRole('textbox'); await input.fill('Mu'); await waitForDebounce(); - await page.getByText('Mustermann, Max').click(); + await page.getByText('Max Mustermann').click(); await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); await expect.element(input).toHaveValue(''); await page.screenshot({ @@ -100,11 +144,11 @@ describe('PersonMultiSelect – selecting persons', () => { await input.fill('Mu'); await waitForDebounce(); - await page.getByText('Mustermann, Max').click(); + await page.getByText('Max Mustermann').click(); await input.fill('Mu'); await waitForDebounce(); - await page.getByText('Musterfrau, Anna').click(); + await page.getByText('Anna Musterfrau').click(); await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); await expect.element(page.getByText('Anna Musterfrau')).toBeInTheDocument(); @@ -116,22 +160,41 @@ describe('PersonMultiSelect – selecting persons', () => { it('filters already-selected persons from search results', async () => { mockFetch(); render(PersonMultiSelect, { - selectedPersons: [{ id: '1', firstName: 'Max', lastName: 'Mustermann' }] + selectedPersons: [ + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + } + ] }); const input = page.getByRole('textbox'); await input.fill('Mu'); await waitForDebounce(); - await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument(); - await expect.element(page.getByText('Musterfrau, Anna')).toBeInTheDocument(); + // Chip still shows "Max Mustermann" but the dropdown item (role=button) must be filtered out + await expect + .element(page.getByRole('button', { name: 'Max Mustermann' })) + .not.toBeInTheDocument(); + await expect.element(page.getByRole('button', { name: 'Anna Musterfrau' })).toBeInTheDocument(); }); it('selects a result with Enter key', async () => { - mockFetch([{ id: '1', firstName: 'Max', lastName: 'Mustermann' }]); + mockFetch([ + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + } + ]); render(PersonMultiSelect, { selectedPersons: [] }); const input = page.getByRole('textbox'); await input.fill('Ma'); await waitForDebounce(); - await page.getByText('Mustermann, Max').click(); + await page.getByText('Max Mustermann').click(); await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); }); }); @@ -142,8 +205,20 @@ describe('PersonMultiSelect – removing persons', () => { it('removes a chip when its × button is clicked', async () => { render(PersonMultiSelect, { selectedPersons: [ - { id: '1', firstName: 'Max', lastName: 'Mustermann' }, - { id: '2', firstName: 'Anna', lastName: 'Musterfrau' } + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + }, + { + id: '2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' + } ] }); // Buttons have aria-label="Entfernen" @@ -156,8 +231,20 @@ describe('PersonMultiSelect – removing persons', () => { it('removes the corresponding hidden input when a chip is removed', async () => { render(PersonMultiSelect, { selectedPersons: [ - { id: '1', firstName: 'Max', lastName: 'Mustermann' }, - { id: '2', firstName: 'Anna', lastName: 'Musterfrau' } + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + }, + { + id: '2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' + } ] }); await page.getByRole('button', { name: 'Entfernen' }).first().click(); @@ -177,9 +264,9 @@ describe('PersonMultiSelect – click outside', () => { const input = page.getByRole('textbox'); await input.fill('Mu'); await waitForDebounce(); - await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); document.body.click(); await tick(); - await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/lib/components/PersonTypeahead.svelte.spec.ts b/frontend/src/lib/components/PersonTypeahead.svelte.spec.ts index 440fb9cb..4ec6a142 100644 --- a/frontend/src/lib/components/PersonTypeahead.svelte.spec.ts +++ b/frontend/src/lib/components/PersonTypeahead.svelte.spec.ts @@ -7,8 +7,20 @@ const waitForDebounce = () => new Promise((r) => setTimeout(r, 350)); const tick = () => new Promise((r) => setTimeout(r, 0)); const PERSONS = [ - { id: '1', firstName: 'Max', lastName: 'Mustermann' }, - { id: '2', firstName: 'Anna', lastName: 'Musterfrau' } + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + }, + { + id: '2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' + } ]; function mockFetchWithPersons(persons = PERSONS) { @@ -76,8 +88,8 @@ describe('PersonTypeahead – search', () => { const input = page.getByPlaceholder('Namen tippen...'); await input.fill('Mu'); await waitForDebounce(); - await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument(); - await expect.element(page.getByText('Musterfrau, Anna')).toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); + await expect.element(page.getByText('Anna Musterfrau')).toBeInTheDocument(); await page.screenshot({ path: 'test-results/screenshots/person-typeahead-open.png' }); }); @@ -105,7 +117,7 @@ describe('PersonTypeahead – search', () => { const input = page.getByPlaceholder('Namen tippen...'); await input.fill('Ma'); await waitForDebounce(); - await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).not.toBeInTheDocument(); }); }); @@ -122,7 +134,7 @@ describe('PersonTypeahead – selection', () => { await tick(); await expect.element(input).toHaveValue('Max Mustermann'); await expect - .element(page.getByRole('button', { name: 'Mustermann, Max' })) + .element(page.getByRole('button', { name: 'Max Mustermann' })) .not.toBeInTheDocument(); await page.screenshot({ path: 'test-results/screenshots/person-typeahead-selected.png' }); }); @@ -152,7 +164,15 @@ describe('PersonTypeahead – selection', () => { }); it('selects a result with Enter key', async () => { - mockFetchWithPersons([{ id: '1', firstName: 'Max', lastName: 'Mustermann' }]); + mockFetchWithPersons([ + { + id: '1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' + } + ]); render(PersonTypeahead, { name: 'senderId', label: 'Absender' }); const input = page.getByPlaceholder('Namen tippen...'); await input.fill('Ma'); @@ -218,7 +238,7 @@ describe('PersonTypeahead – correspondent mode', () => { (document.querySelector('input[placeholder="Namen tippen..."]') as HTMLInputElement).focus(); await waitForDebounce(); - await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); }); it('uses correspondents endpoint with q param when typing', async () => { @@ -259,9 +279,9 @@ describe('PersonTypeahead – click outside', () => { const input = page.getByPlaceholder('Namen tippen...'); await input.fill('Mu'); await waitForDebounce(); - await expect.element(page.getByText('Mustermann, Max')).toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).toBeInTheDocument(); document.body.click(); await tick(); - await expect.element(page.getByText('Mustermann, Max')).not.toBeInTheDocument(); + await expect.element(page.getByText('Max Mustermann')).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/routes/briefwechsel/page.server.spec.ts b/frontend/src/routes/briefwechsel/page.server.spec.ts index f8dab9ee..151436b6 100644 --- a/frontend/src/routes/briefwechsel/page.server.spec.ts +++ b/frontend/src/routes/briefwechsel/page.server.spec.ts @@ -54,7 +54,15 @@ describe('korrespondenz load — senderId set, no receiverId', () => { const docs = [{ id: 'd1', title: 'Testbrief' }]; const GET = mockApi([ { ok: true, data: docs }, - { ok: true, data: { firstName: 'Hans', lastName: 'Müller' } } + { + ok: true, + data: { + firstName: 'Hans', + lastName: 'Müller', + displayName: 'Hans Müller', + personType: 'PERSON' + } + } ]); const result = await load({ @@ -76,8 +84,24 @@ describe('korrespondenz load — senderId and receiverId set', () => { it('calls conversation, sender person, and receiver person endpoints', async () => { const GET = mockApi([ { ok: true, data: [] }, - { ok: true, data: { firstName: 'Hans', lastName: 'Müller' } }, - { ok: true, data: { firstName: 'Anna', lastName: 'Schmidt' } } + { + ok: true, + data: { + firstName: 'Hans', + lastName: 'Müller', + displayName: 'Hans Müller', + personType: 'PERSON' + } + }, + { + ok: true, + data: { + firstName: 'Anna', + lastName: 'Schmidt', + displayName: 'Anna Schmidt', + personType: 'PERSON' + } + } ]); const result = await load({ @@ -98,7 +122,15 @@ describe('korrespondenz load — canWrite', () => { it('derives canWrite true from WRITE_ALL permission', async () => { mockApi([ { ok: true, data: [] }, - { ok: true, data: { firstName: 'Hans', lastName: 'Müller' } } + { + ok: true, + data: { + firstName: 'Hans', + lastName: 'Müller', + displayName: 'Hans Müller', + personType: 'PERSON' + } + } ]); const result = await load({ @@ -113,7 +145,15 @@ describe('korrespondenz load — canWrite', () => { it('derives canWrite false when user lacks WRITE_ALL', async () => { mockApi([ { ok: true, data: [] }, - { ok: true, data: { firstName: 'Hans', lastName: 'Müller' } } + { + ok: true, + data: { + firstName: 'Hans', + lastName: 'Müller', + displayName: 'Hans Müller', + personType: 'PERSON' + } + } ]); const result = await load({ @@ -132,7 +172,15 @@ describe('korrespondenz load — backend error', () => { it('throws when the conversation endpoint returns non-ok', async () => { mockApi([ { ok: false, status: 500 }, - { ok: true, data: { firstName: 'Hans', lastName: 'Müller' } } + { + ok: true, + data: { + firstName: 'Hans', + lastName: 'Müller', + displayName: 'Hans Müller', + personType: 'PERSON' + } + } ]); await expect( diff --git a/frontend/src/routes/documents/new/page.svelte.spec.ts b/frontend/src/routes/documents/new/page.svelte.spec.ts index dcc3b387..dcdfbdef 100644 --- a/frontend/src/routes/documents/new/page.svelte.spec.ts +++ b/frontend/src/routes/documents/new/page.svelte.spec.ts @@ -58,7 +58,9 @@ describe('New document page – receiver prefill', () => { it('shows a receiver chip when initialReceivers has a person', async () => { const data = { ...baseData, - initialReceivers: [{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt' }] + initialReceivers: [ + { id: 'p2', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' } + ] }; render(Page, { data, form: null }); await expect.element(page.getByText('Anna Schmidt')).toBeInTheDocument(); @@ -67,7 +69,9 @@ describe('New document page – receiver prefill', () => { it('renders a hidden receiverIds input for the prefilled receiver', async () => { const data = { ...baseData, - initialReceivers: [{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt' }] + initialReceivers: [ + { id: 'p2', firstName: 'Anna', lastName: 'Schmidt', displayName: 'Anna Schmidt' } + ] }; render(Page, { data, form: null }); const hidden = document.querySelector('input[name="receiverIds"]'); diff --git a/frontend/src/routes/page.svelte.spec.ts b/frontend/src/routes/page.svelte.spec.ts index a13c3822..28f58d3f 100644 --- a/frontend/src/routes/page.svelte.spec.ts +++ b/frontend/src/routes/page.svelte.spec.ts @@ -39,8 +39,22 @@ const makeDoc = (overrides = {}) => ({ status: 'UPLOADED' as const, documentDate: '2024-03-15', location: 'Berlin', - sender: { id: 'p1', firstName: 'Max', lastName: 'Mustermann' }, - receivers: [{ id: 'p2', firstName: 'Anna', lastName: 'Musterfrau' }], + sender: { + id: 'p1', + firstName: 'Max', + lastName: 'Mustermann', + displayName: 'Max Mustermann', + personType: 'PERSON' as const + }, + receivers: [ + { + id: 'p2', + firstName: 'Anna', + lastName: 'Musterfrau', + displayName: 'Anna Musterfrau', + personType: 'PERSON' as const + } + ], tags: [{ id: 't1', name: 'Familie' }], filePath: '/files/testbrief.pdf', createdAt: '2024-03-15T10:00:00Z', diff --git a/frontend/src/routes/persons/page.svelte.spec.ts b/frontend/src/routes/persons/page.svelte.spec.ts index 113df55d..6c975a4e 100644 --- a/frontend/src/routes/persons/page.svelte.spec.ts +++ b/frontend/src/routes/persons/page.svelte.spec.ts @@ -11,6 +11,7 @@ const makePerson = (overrides = {}) => ({ id: '1', firstName: 'Max', lastName: 'Mustermann', + displayName: 'Max Mustermann', documentCount: 0, ...overrides });