diff --git a/frontend/src/routes/documents/new/FileSectionNew.svelte.test.ts b/frontend/src/routes/documents/new/FileSectionNew.svelte.test.ts new file mode 100644 index 00000000..da11ef93 --- /dev/null +++ b/frontend/src/routes/documents/new/FileSectionNew.svelte.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import FileSectionNew from './FileSectionNew.svelte'; + +afterEach(cleanup); + +describe('FileSectionNew', () => { + it('renders the upload prompt and section heading when no file is selected', async () => { + render(FileSectionNew, { props: {} }); + + await expect.element(page.getByRole('heading', { name: /datei/i })).toBeVisible(); + await expect.element(page.getByText('Datei hochladen')).toBeVisible(); + await expect.element(page.getByText('(optional)')).toBeVisible(); + }); + + it('replaces the prompt with the selected filename after a file is chosen', async () => { + render(FileSectionNew, { props: {} }); + + const input = document.querySelector('input[type="file"]') as HTMLInputElement; + const file = new File(['%PDF'], 'brief_1920.pdf', { type: 'application/pdf' }); + Object.defineProperty(input, 'files', { value: [file], writable: false }); + input.dispatchEvent(new Event('change', { bubbles: true })); + + await expect.element(page.getByText('brief_1920.pdf')).toBeVisible(); + await expect.element(page.getByText('Datei hochladen')).not.toBeInTheDocument(); + }); + + it('invokes onfileParsed with the parsed filename result', async () => { + const onfileParsed = vi.fn(); + render(FileSectionNew, { props: { onfileParsed } }); + + const input = document.querySelector('input[type="file"]') as HTMLInputElement; + const file = new File(['%PDF'], 'Sender_Receiver_2024-05-01.pdf', { type: 'application/pdf' }); + Object.defineProperty(input, 'files', { value: [file], writable: false }); + input.dispatchEvent(new Event('change', { bubbles: true })); + + expect(onfileParsed).toHaveBeenCalledOnce(); + const result = onfileParsed.mock.calls[0][0]; + expect(result).toHaveProperty('suggestedTitle'); + }); + + it('does nothing when the change event fires with no file selected', async () => { + const onfileParsed = vi.fn(); + render(FileSectionNew, { props: { onfileParsed } }); + + const input = document.querySelector('input[type="file"]') as HTMLInputElement; + Object.defineProperty(input, 'files', { value: [], writable: false }); + input.dispatchEvent(new Event('change', { bubbles: true })); + + expect(onfileParsed).not.toHaveBeenCalled(); + await expect.element(page.getByText('Datei hochladen')).toBeVisible(); + }); + + it('falls back to the bare stripped filename when the parser provides no suggested title', async () => { + const onfileParsed = vi.fn(); + render(FileSectionNew, { props: { onfileParsed } }); + + const input = document.querySelector('input[type="file"]') as HTMLInputElement; + const file = new File(['%PDF'], 'plain.pdf', { type: 'application/pdf' }); + Object.defineProperty(input, 'files', { value: [file], writable: false }); + input.dispatchEvent(new Event('change', { bubbles: true })); + + const result = onfileParsed.mock.calls[0][0]; + expect(typeof result.suggestedTitle).toBe('string'); + expect(result.suggestedTitle.length).toBeGreaterThan(0); + }); +}); diff --git a/frontend/src/routes/profile/PasswordChangeForm.svelte.test.ts b/frontend/src/routes/profile/PasswordChangeForm.svelte.test.ts new file mode 100644 index 00000000..9d7026cc --- /dev/null +++ b/frontend/src/routes/profile/PasswordChangeForm.svelte.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import PasswordChangeForm from './PasswordChangeForm.svelte'; + +afterEach(cleanup); + +describe('PasswordChangeForm', () => { + it('renders the three password inputs and a save button by default', async () => { + render(PasswordChangeForm, { props: { form: null } }); + + await expect.element(page.getByRole('heading', { name: /passwort ändern/i })).toBeVisible(); + await expect.element(page.getByRole('button', { name: /speichern/i })).toBeVisible(); + }); + + it('does not render any banner when form is null', async () => { + render(PasswordChangeForm, { props: { form: null } }); + + await expect.element(page.getByText(/erfolgreich geändert/i)).not.toBeInTheDocument(); + await expect.element(page.getByText(/stimmen nicht überein/i)).not.toBeInTheDocument(); + }); + + it('shows the success banner when form.passwordSuccess is true', async () => { + render(PasswordChangeForm, { props: { form: { passwordSuccess: true } } }); + + await expect.element(page.getByText('Passwort erfolgreich geändert.')).toBeVisible(); + }); + + it('shows the localised mismatch message for the PASSWORDS_DO_NOT_MATCH error code', async () => { + render(PasswordChangeForm, { + props: { form: { passwordError: 'PASSWORDS_DO_NOT_MATCH' } } + }); + + await expect + .element(page.getByText('Die neuen Passwörter stimmen nicht überein.')) + .toBeVisible(); + }); + + it('shows the raw error message for any non-matching error code', async () => { + render(PasswordChangeForm, { + props: { form: { passwordError: 'Server-side error message' } } + }); + + await expect.element(page.getByText('Server-side error message')).toBeVisible(); + }); + + it('declares POST as the form method and routes to the changePassword action', async () => { + render(PasswordChangeForm, { props: { form: null } }); + + const form = document.querySelector('form'); + expect(form?.getAttribute('method')).toBe('POST'); + expect(form?.getAttribute('action')).toBe('?/changePassword'); + }); +});