From 035f9768bd3edd11c1f38014bc0799031b59c5db Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 13 Apr 2026 10:13:57 +0200 Subject: [PATCH] feat(ocr): add OcrProgressBar component with page-based ARIA semantics Progress bar shows brand-mint fill on brand-sand background with smooth transition. Displays page counter with tabular-nums and skipped-pages warning in amber when applicable. Only renders when totalPages > 0. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/lib/components/OcrProgressBar.svelte | 39 +++++++++++++++++ .../components/OcrProgressBar.svelte.spec.ts | 43 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 frontend/src/lib/components/OcrProgressBar.svelte create mode 100644 frontend/src/lib/components/OcrProgressBar.svelte.spec.ts diff --git a/frontend/src/lib/components/OcrProgressBar.svelte b/frontend/src/lib/components/OcrProgressBar.svelte new file mode 100644 index 00000000..2713b3c3 --- /dev/null +++ b/frontend/src/lib/components/OcrProgressBar.svelte @@ -0,0 +1,39 @@ + + +{#if totalPages > 0} +
+
+
+
+ + {currentPage} / {totalPages} + + {#if skippedPages > 0} + + {skippedPages} Seiten übersprungen + + {/if} +
+{/if} diff --git a/frontend/src/lib/components/OcrProgressBar.svelte.spec.ts b/frontend/src/lib/components/OcrProgressBar.svelte.spec.ts new file mode 100644 index 00000000..795850e1 --- /dev/null +++ b/frontend/src/lib/components/OcrProgressBar.svelte.spec.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import OcrProgressBar from './OcrProgressBar.svelte'; + +afterEach(cleanup); + +describe('OcrProgressBar', () => { + it('renders progress bar with correct ARIA attributes', async () => { + render(OcrProgressBar, { currentPage: 2, totalPages: 5 }); + const bar = page.getByRole('progressbar'); + await expect.element(bar).toHaveAttribute('aria-valuenow', '2'); + await expect.element(bar).toHaveAttribute('aria-valuemax', '5'); + }); + + it('hides progress bar when totalPages is zero', async () => { + render(OcrProgressBar, { currentPage: 0, totalPages: 0 }); + await expect.element(page.getByRole('progressbar')).not.toBeInTheDocument(); + }); + + it('fills to 100 percent when current equals total', async () => { + render(OcrProgressBar, { currentPage: 5, totalPages: 5 }); + const fill = page.getByTestId('progress-fill'); + await expect.element(fill).toBeInTheDocument(); + const el = fill.element() as HTMLElement; + expect(el.style.width).toBe('100%'); + }); + + it('shows page counter text', async () => { + render(OcrProgressBar, { currentPage: 3, totalPages: 7 }); + await expect.element(page.getByText('3 / 7')).toBeInTheDocument(); + }); + + it('shows skipped pages warning when skippedPages > 0', async () => { + render(OcrProgressBar, { currentPage: 5, totalPages: 5, skippedPages: 2 }); + await expect.element(page.getByTestId('skipped-warning')).toBeInTheDocument(); + }); + + it('does not show warning when skippedPages is 0', async () => { + render(OcrProgressBar, { currentPage: 3, totalPages: 5, skippedPages: 0 }); + await expect.element(page.getByTestId('skipped-warning')).not.toBeInTheDocument(); + }); +});