diff --git a/frontend/src/lib/document/DocumentMobileMenu.svelte b/frontend/src/lib/document/DocumentMobileMenu.svelte new file mode 100644 index 00000000..c2892bbd --- /dev/null +++ b/frontend/src/lib/document/DocumentMobileMenu.svelte @@ -0,0 +1,96 @@ + + +
(mobileMenuOpen = false)}> + + + {#if mobileMenuOpen} + + {/if} +
diff --git a/frontend/src/lib/document/DocumentMobileMenu.svelte.test.ts b/frontend/src/lib/document/DocumentMobileMenu.svelte.test.ts new file mode 100644 index 00000000..9b561aa5 --- /dev/null +++ b/frontend/src/lib/document/DocumentMobileMenu.svelte.test.ts @@ -0,0 +1,91 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import DocumentMobileMenu from './DocumentMobileMenu.svelte'; + +afterEach(cleanup); + +const baseProps = { + canWrite: false, + isPdf: false, + transcribeMode: false, + filePath: null as string | null, + originalFilename: 'brief.pdf' as string | null, + fileUrl: '' +}; + +describe('DocumentMobileMenu', () => { + it('renders the kebab trigger button with the more-actions aria-label', async () => { + render(DocumentMobileMenu, { props: { ...baseProps, filePath: 'docs/x.pdf' } }); + + await expect.element(page.getByRole('button', { name: /weitere aktionen/i })).toBeVisible(); + }); + + it('starts with the dropdown closed (aria-expanded=false)', async () => { + render(DocumentMobileMenu, { props: { ...baseProps, filePath: 'docs/x.pdf' } }); + + await expect + .element(page.getByRole('button', { name: /weitere aktionen/i })) + .toHaveAttribute('aria-expanded', 'false'); + }); + + it('opens the dropdown when the trigger is clicked', async () => { + render(DocumentMobileMenu, { props: { ...baseProps, filePath: 'docs/x.pdf' } }); + + await page.getByRole('button', { name: /weitere aktionen/i }).click(); + + await expect + .element(page.getByRole('button', { name: /weitere aktionen/i })) + .toHaveAttribute('aria-expanded', 'true'); + }); + + it('shows the transcribe action inside the open menu when canWrite, isPdf, and not in transcribe mode', async () => { + render(DocumentMobileMenu, { + props: { ...baseProps, canWrite: true, isPdf: true, filePath: 'docs/x.pdf' } + }); + + await page.getByRole('button', { name: /weitere aktionen/i }).click(); + + await expect.element(page.getByRole('button', { name: /transkribieren/i })).toBeVisible(); + }); + + it('hides the transcribe action when already in transcribeMode', async () => { + render(DocumentMobileMenu, { + props: { + ...baseProps, + canWrite: true, + isPdf: true, + transcribeMode: true, + filePath: 'docs/x.pdf' + } + }); + + await page.getByRole('button', { name: /weitere aktionen/i }).click(); + + await expect + .element(page.getByRole('button', { name: /transkribieren/i })) + .not.toBeInTheDocument(); + }); + + it('shows the download link inside the open menu when filePath is present', async () => { + render(DocumentMobileMenu, { + props: { ...baseProps, filePath: 'docs/x.pdf', fileUrl: '/api/docs/x' } + }); + + await page.getByRole('button', { name: /weitere aktionen/i }).click(); + + await expect.element(page.getByRole('link', { name: /herunterladen/i })).toBeVisible(); + }); + + it('omits the download link when filePath is null', async () => { + render(DocumentMobileMenu, { + props: { ...baseProps, canWrite: true, isPdf: true } + }); + + await page.getByRole('button', { name: /weitere aktionen/i }).click(); + + await expect + .element(page.getByRole('link', { name: /herunterladen/i })) + .not.toBeInTheDocument(); + }); +}); diff --git a/frontend/src/lib/document/DocumentTopBar.svelte b/frontend/src/lib/document/DocumentTopBar.svelte index bc3e90d5..73e57c37 100644 --- a/frontend/src/lib/document/DocumentTopBar.svelte +++ b/frontend/src/lib/document/DocumentTopBar.svelte @@ -1,11 +1,11 @@ -{#snippet transcribeBtn(mobile: boolean)} - -{/snippet} - -{#snippet transcribeStopBtn(mobile: boolean)} - -{/snippet} - -{#snippet downloadLink(mobile: boolean)} - { - if (mobile) mobileMenuOpen = false; - }} - class={mobile - ? 'flex items-center gap-2 rounded px-3 py-2 text-[16px] text-ink transition hover:bg-muted focus-visible:ring-2 focus-visible:ring-primary' - : 'hidden rounded border border-transparent bg-muted p-1.5 text-ink transition hover:bg-accent focus-visible:ring-2 focus-visible:ring-primary md:block'} - title={m.doc_download_title()} - > - - {#if mobile}{m.doc_download_title()}{/if} - -{/snippet} -
@@ -180,7 +98,9 @@ let mobileMenuOpen = $state(false); onclick={() => (detailsOpen = !detailsOpen)} aria-expanded={detailsOpen} aria-label={m.doc_details_toggle()} - class="ml-2 inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded border px-3 py-1 font-sans text-sm font-semibold transition-colors {detailsOpen ? 'border-primary bg-primary text-primary-fg' : 'border-line text-ink-2 hover:bg-muted hover:text-ink'}" + class="ml-2 inline-flex min-h-[44px] shrink-0 items-center gap-1.5 rounded border px-3 py-1 font-sans text-sm font-semibold transition-colors {detailsOpen + ? 'border-primary bg-primary text-primary-fg' + : 'border-line text-ink-2 hover:bg-muted hover:text-ink'}" > {m.doc_details_toggle()}
{#if canWrite && isPdf && !transcribeMode} - {@render transcribeBtn(false)} + {/if} {#if transcribeMode} - {@render transcribeStopBtn(false)} + {/if} {#if canWrite && !transcribeMode} @@ -225,47 +185,31 @@ let mobileMenuOpen = $state(false); {/if} {#if doc.filePath && !transcribeMode} - {@render downloadLink(false)} + {/if} - {#if (canWrite && isPdf) || doc.filePath} -
(mobileMenuOpen = false)} - > - - - {#if mobileMenuOpen} - - {/if} +
+
{/if}