From 1aabd9826cf958b89c4a4795461457daa76684ad Mon Sep 17 00:00:00 2001 From: Marcel Date: Wed, 8 Apr 2026 12:47:15 +0200 Subject: [PATCH] test(frontend): update mock data for displayName and nullable firstName Add displayName and personType to all Person mock objects in component and page tests. Update assertions from reversed "lastName, firstName" format to forward-order displayName. Co-Authored-By: Claude Sonnet 4.6 --- .../DocumentMetadataDrawer.svelte.spec.ts | 6 +- .../PersonMultiSelect.svelte.spec.ts | 131 +++++++++++++++--- .../components/PersonTypeahead.svelte.spec.ts | 40 ++++-- .../routes/briefwechsel/page.server.spec.ts | 60 +++++++- .../routes/documents/new/page.svelte.spec.ts | 8 +- frontend/src/routes/page.svelte.spec.ts | 18 ++- .../src/routes/persons/page.svelte.spec.ts | 1 + 7 files changed, 219 insertions(+), 45 deletions(-) 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 });