diff --git a/frontend/src/lib/components/UploadSuccessBanner.svelte b/frontend/src/lib/components/UploadSuccessBanner.svelte new file mode 100644 index 00000000..e10b2f04 --- /dev/null +++ b/frontend/src/lib/components/UploadSuccessBanner.svelte @@ -0,0 +1,62 @@ + + +
+ +

+ {message} + + {m.upload_banner_cta()} + +

+ +
diff --git a/frontend/src/lib/components/UploadSuccessBanner.svelte.spec.ts b/frontend/src/lib/components/UploadSuccessBanner.svelte.spec.ts new file mode 100644 index 00000000..bed83d9a --- /dev/null +++ b/frontend/src/lib/components/UploadSuccessBanner.svelte.spec.ts @@ -0,0 +1,57 @@ +import { describe, it, expect, afterEach, vi } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; + +import UploadSuccessBanner from './UploadSuccessBanner.svelte'; + +afterEach(() => { + cleanup(); + vi.useRealTimers(); +}); + +describe('UploadSuccessBanner', () => { + it('renders singular copy for count of 1', async () => { + render(UploadSuccessBanner, { count: 1, onClose: () => {} }); + const status = page.getByRole('status'); + await expect.element(status).toBeInTheDocument(); + await expect.element(status).toHaveTextContent(/1 Dokument/); + }); + + it('renders plural copy for count greater than 1', async () => { + render(UploadSuccessBanner, { count: 3, onClose: () => {} }); + await expect.element(page.getByRole('status')).toHaveTextContent(/3 Dokumente/); + }); + + it('exposes role=status with aria-live polite', async () => { + render(UploadSuccessBanner, { count: 1, onClose: () => {} }); + await expect.element(page.getByRole('status')).toHaveAttribute('aria-live', 'polite'); + }); + + it('renders a CTA link to /enrich', async () => { + render(UploadSuccessBanner, { count: 2, onClose: () => {} }); + await expect + .element(page.getByRole('link', { name: /ergänzen/i })) + .toHaveAttribute('href', '/enrich'); + }); + + it('invokes onClose when the close button is clicked', async () => { + const onClose = vi.fn(); + render(UploadSuccessBanner, { count: 1, onClose }); + const button = document.querySelector( + '[data-testid="upload-banner-close"]' + ) as HTMLButtonElement | null; + expect(button).not.toBeNull(); + button?.click(); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('auto-dismisses after 8000ms', async () => { + vi.useFakeTimers(); + const onClose = vi.fn(); + render(UploadSuccessBanner, { count: 1, onClose }); + vi.advanceTimersByTime(7999); + expect(onClose).not.toHaveBeenCalled(); + vi.advanceTimersByTime(2); + expect(onClose).toHaveBeenCalledTimes(1); + }); +});