From 7d5a34edb718404f553f2232866391a7ce006dac Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 9 May 2026 20:46:51 +0200 Subject: [PATCH] refactor(document): extract DocumentTopBarActions from DocumentTopBar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third Phase 5 split. The desktop action buttons — transcribe, transcribe-stop, edit link, download link — become their own component with a focused props interface (documentId, canWrite, isPdf, transcribeMode bindable, filePath, originalFilename, fileUrl). TDD: 8 tests covering empty render, transcribe button gating (canWrite × isPdf × transcribeMode), stop-transcribe rendering, edit link with documentId href, download link with filePath gating, all hidden when in transcribe mode. After the test was red the component was created. DocumentTopBar dropped from 303 lines to 166. The orchestrator now just composes BackButton, DocumentTopBarTitle, PersonChipRow, OverflowPillButton, the details toggle, DocumentTopBarActions, DocumentMobileMenu, and DocumentMetadataDrawer — each visual region named in one or two words. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../src/lib/document/DocumentTopBar.svelte | 89 ++------------- .../lib/document/DocumentTopBarActions.svelte | 103 ++++++++++++++++++ .../DocumentTopBarActions.svelte.test.ts | 94 ++++++++++++++++ 3 files changed, 207 insertions(+), 79 deletions(-) create mode 100644 frontend/src/lib/document/DocumentTopBarActions.svelte create mode 100644 frontend/src/lib/document/DocumentTopBarActions.svelte.test.ts diff --git a/frontend/src/lib/document/DocumentTopBar.svelte b/frontend/src/lib/document/DocumentTopBar.svelte index 73e57c37..8d8968dc 100644 --- a/frontend/src/lib/document/DocumentTopBar.svelte +++ b/frontend/src/lib/document/DocumentTopBar.svelte @@ -5,6 +5,7 @@ import PersonChipRow from '$lib/person/PersonChipRow.svelte'; import OverflowPillButton from '$lib/shared/primitives/OverflowPillButton.svelte'; import DocumentMetadataDrawer from './DocumentMetadataDrawer.svelte'; import DocumentTopBarTitle from './DocumentTopBarTitle.svelte'; +import DocumentTopBarActions from './DocumentTopBarActions.svelte'; import DocumentMobileMenu from './DocumentMobileMenu.svelte'; import BackButton from '$lib/shared/primitives/BackButton.svelte'; @@ -120,85 +121,15 @@ const overflowPersons = $derived(receivers.slice(2));
- {#if canWrite && isPdf && !transcribeMode} - - {/if} - - {#if transcribeMode} - - {/if} - - {#if canWrite && !transcribeMode} - - - - - {/if} - - {#if doc.filePath && !transcribeMode} - - {/if} + {#if (canWrite && isPdf) || doc.filePath}
diff --git a/frontend/src/lib/document/DocumentTopBarActions.svelte b/frontend/src/lib/document/DocumentTopBarActions.svelte new file mode 100644 index 00000000..6e34eada --- /dev/null +++ b/frontend/src/lib/document/DocumentTopBarActions.svelte @@ -0,0 +1,103 @@ + + +{#if canWrite && isPdf && !transcribeMode} + +{/if} + +{#if transcribeMode} + +{/if} + +{#if canWrite && !transcribeMode} + + + + +{/if} + +{#if filePath && !transcribeMode} + +{/if} diff --git a/frontend/src/lib/document/DocumentTopBarActions.svelte.test.ts b/frontend/src/lib/document/DocumentTopBarActions.svelte.test.ts new file mode 100644 index 00000000..280a3245 --- /dev/null +++ b/frontend/src/lib/document/DocumentTopBarActions.svelte.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import DocumentTopBarActions from './DocumentTopBarActions.svelte'; + +afterEach(cleanup); + +const baseProps = { + documentId: 'd1', + canWrite: false, + isPdf: false, + transcribeMode: false, + filePath: null as string | null, + originalFilename: 'brief.pdf' as string | null, + fileUrl: '' +}; + +describe('DocumentTopBarActions', () => { + it('renders nothing visible when canWrite is false and no file is present', async () => { + render(DocumentTopBarActions, { props: baseProps }); + + await expect + .element(page.getByRole('button', { name: /transkribieren/i })) + .not.toBeInTheDocument(); + await expect.element(page.getByRole('link', { name: /bearbeiten/i })).not.toBeInTheDocument(); + await expect.element(page.getByTitle('Herunterladen')).not.toBeInTheDocument(); + }); + + it('renders the transcribe button when canWrite, isPdf, and not transcribing', async () => { + render(DocumentTopBarActions, { + props: { ...baseProps, canWrite: true, isPdf: true, filePath: 'docs/x.pdf' } + }); + + await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible(); + }); + + it('omits the transcribe button when not a PDF', async () => { + render(DocumentTopBarActions, { + props: { ...baseProps, canWrite: true, isPdf: false, filePath: 'docs/x.jpg' } + }); + + await expect + .element(page.getByRole('button', { name: /transkribieren/i })) + .not.toBeInTheDocument(); + }); + + it('renders the stop-transcribe button when transcribeMode is true', async () => { + render(DocumentTopBarActions, { + props: { + ...baseProps, + canWrite: true, + isPdf: true, + transcribeMode: true, + filePath: 'docs/x.pdf' + } + }); + + await expect.element(page.getByRole('button', { name: /fertig/i })).toBeVisible(); + }); + + it('renders the edit link to the document edit route when canWrite and not transcribing', async () => { + render(DocumentTopBarActions, { + props: { ...baseProps, canWrite: true, documentId: 'doc-42' } + }); + + await expect + .element(page.getByRole('link', { name: /bearbeiten/i })) + .toHaveAttribute('href', '/documents/doc-42/edit'); + }); + + it('hides the edit link when transcribeMode is true', async () => { + render(DocumentTopBarActions, { + props: { ...baseProps, canWrite: true, transcribeMode: true } + }); + + await expect.element(page.getByRole('link', { name: /bearbeiten/i })).not.toBeInTheDocument(); + }); + + it('renders the download link when filePath is set and not in transcribe mode', async () => { + render(DocumentTopBarActions, { + props: { ...baseProps, filePath: 'docs/x.pdf', fileUrl: '/api/docs/x' } + }); + + await expect.element(page.getByTitle('Herunterladen')).toBeVisible(); + }); + + it('hides the download link when transcribeMode is true', async () => { + render(DocumentTopBarActions, { + props: { ...baseProps, filePath: 'docs/x.pdf', fileUrl: '/api/docs/x', transcribeMode: true } + }); + + await expect.element(page.getByTitle('Herunterladen')).not.toBeInTheDocument(); + }); +});