From 9572b062f1b7635fab9aa69b8df834d0eb964429 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sat, 9 May 2026 19:55:05 +0200 Subject: [PATCH] refactor(test): use getByRole instead of data-testid in TranscriptionPanelHeader test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Felix's review on issue #496, tests should query observable behaviour via ARIA roles, not test-only data-testid attributes. Replaces every 'document.querySelector([data-testid=...])' with 'page.getByRole(...)'. The disabled-button click test uses force: true so Playwright bypasses its enabled-check — the behaviour under test is precisely that the click is ignored. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../TranscriptionPanelHeader.svelte.test.ts | 180 ++++++------------ 1 file changed, 59 insertions(+), 121 deletions(-) diff --git a/frontend/src/lib/document/transcription/TranscriptionPanelHeader.svelte.test.ts b/frontend/src/lib/document/transcription/TranscriptionPanelHeader.svelte.test.ts index d60b5e60..b512e8ef 100644 --- a/frontend/src/lib/document/transcription/TranscriptionPanelHeader.svelte.test.ts +++ b/frontend/src/lib/document/transcription/TranscriptionPanelHeader.svelte.test.ts @@ -5,178 +5,116 @@ import TranscriptionPanelHeader from './TranscriptionPanelHeader.svelte'; afterEach(cleanup); -describe('TranscriptionPanelHeader', () => { - it('should render Lesen and Bearbeiten buttons', async () => { - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} - }); +const baseProps = { + mode: 'read' as const, + hasBlocks: true, + blockCount: 3, + lastEditedAt: null, + onModeChange: () => {}, + onClose: () => {} +}; - await expect.element(page.getByText('Lesen')).toBeInTheDocument(); - await expect.element(page.getByText('Bearbeiten')).toBeInTheDocument(); +describe('TranscriptionPanelHeader', () => { + it('renders the Lesen and Bearbeiten toggle buttons', async () => { + render(TranscriptionPanelHeader, baseProps); + + await expect.element(page.getByRole('button', { name: /lesen/i })).toBeVisible(); + await expect.element(page.getByRole('button', { name: /bearbeiten/i })).toBeVisible(); }); - it('should disable Lesen button when hasBlocks is false', async () => { + it('marks the Lesen button as aria-disabled when hasBlocks is false', async () => { render(TranscriptionPanelHeader, { + ...baseProps, mode: 'edit', hasBlocks: false, - blockCount: 0, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} + blockCount: 0 }); - const lesenBtn = document.querySelector('[data-testid="mode-read"]') as HTMLButtonElement; - expect(lesenBtn.getAttribute('aria-disabled')).toBe('true'); + await expect + .element(page.getByRole('button', { name: /lesen/i })) + .toHaveAttribute('aria-disabled', 'true'); }); - it('should call onModeChange when clicking Bearbeiten', async () => { + it('calls onModeChange("edit") when the Bearbeiten button is clicked', async () => { const onModeChange = vi.fn(); - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: null, - onModeChange, - onClose: () => {} - }); + render(TranscriptionPanelHeader, { ...baseProps, onModeChange }); + + await page.getByRole('button', { name: /bearbeiten/i }).click(); - const editBtn = document.querySelector('[data-testid="mode-edit"]')!; - editBtn.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(onModeChange).toHaveBeenCalledWith('edit'); }); - it('should not call onModeChange when clicking disabled Lesen', async () => { + it('does not call onModeChange when the disabled Lesen button is clicked', async () => { const onModeChange = vi.fn(); render(TranscriptionPanelHeader, { + ...baseProps, mode: 'edit', hasBlocks: false, blockCount: 0, - lastEditedAt: null, - onModeChange, - onClose: () => {} + onModeChange }); - const readBtn = document.querySelector('[data-testid="mode-read"]')!; - readBtn.dispatchEvent(new MouseEvent('click', { bubbles: true })); + await page.getByRole('button', { name: /lesen/i }).click({ force: true }); + expect(onModeChange).not.toHaveBeenCalled(); }); - it('should call onClose when clicking close button', async () => { + it('calls onClose when the close button is clicked', async () => { const onClose = vi.fn(); - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: null, - onModeChange: () => {}, - onClose - }); + render(TranscriptionPanelHeader, { ...baseProps, onClose }); - const closeBtn = document.querySelector('[data-testid="panel-close"]')!; - closeBtn.dispatchEvent(new MouseEvent('click', { bubbles: true })); - expect(onClose).toHaveBeenCalled(); + await page.getByRole('button', { name: /panel schließen/i }).click(); + + expect(onClose).toHaveBeenCalledOnce(); }); - it('should show singular block count for 1 block', async () => { - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 1, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} - }); + it('shows the singular section label when blockCount is 1', async () => { + render(TranscriptionPanelHeader, { ...baseProps, blockCount: 1 }); - await expect.element(page.getByText('1 Abschnitt')).toBeInTheDocument(); + await expect.element(page.getByText('1 Abschnitt')).toBeVisible(); }); - it('should show plural block count for multiple blocks', async () => { - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 5, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} - }); + it('shows the plural section label when blockCount is greater than 1', async () => { + render(TranscriptionPanelHeader, { ...baseProps, blockCount: 5 }); - await expect.element(page.getByText('5 Abschnitte')).toBeInTheDocument(); + await expect.element(page.getByText('5 Abschnitte')).toBeVisible(); }); - it('should show "0 Abschnitte" when blockCount is 0', async () => { + it('shows "0 Abschnitte" when blockCount is 0', async () => { render(TranscriptionPanelHeader, { - mode: 'edit', + ...baseProps, hasBlocks: false, blockCount: 0, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} + mode: 'edit' }); - await expect.element(page.getByText('0 Abschnitte')).toBeInTheDocument(); + await expect.element(page.getByText('0 Abschnitte')).toBeVisible(); }); - it('should have close button with 44px touch target classes', async () => { + it('renders the formatted last-edit date when lastEditedAt is provided', async () => { render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} + ...baseProps, + lastEditedAt: '2026-04-07T10:00:00Z' }); - const closeBtn = document.querySelector('[data-testid="panel-close"]') as HTMLElement; - expect(closeBtn.classList.contains('h-11')).toBe(true); - expect(closeBtn.classList.contains('w-11')).toBe(true); + await expect.element(page.getByText(/2026/)).toBeVisible(); }); - it('should show formatted date when lastEditedAt is provided', async () => { - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: '2026-04-07T10:00:00Z', - onModeChange: () => {}, - onClose: () => {} - }); + it('renders the help popover trigger', async () => { + render(TranscriptionPanelHeader, baseProps); - const statusText = document.querySelector('.hidden.md\\:block'); - expect(statusText).not.toBeNull(); - expect(statusText!.textContent).toContain('2026'); + await expect + .element(page.getByRole('button', { name: /lese- und bearbeitungsmodus/i })) + .toBeVisible(); }); - it('renders a (?) help chip next to the Read/Edit toggle', async () => { - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} - }); + it('opens the help popover when the help trigger is clicked', async () => { + render(TranscriptionPanelHeader, baseProps); - const helpBtn = document.querySelector('button[aria-expanded]') as HTMLButtonElement; - expect(helpBtn).not.toBeNull(); - }); + await page.getByRole('button', { name: /lese- und bearbeitungsmodus/i }).click(); - it('opens a help popover with mode explanation when the chip is clicked', async () => { - render(TranscriptionPanelHeader, { - mode: 'read', - hasBlocks: true, - blockCount: 3, - lastEditedAt: null, - onModeChange: () => {}, - onClose: () => {} - }); - - const helpBtn = document.querySelector('button[aria-expanded]') as HTMLButtonElement; - helpBtn.dispatchEvent(new MouseEvent('click', { bubbles: true })); - await vi.waitFor(() => expect(document.querySelector('[role="region"]')).not.toBeNull()); + await expect + .element(page.getByRole('button', { name: /lese- und bearbeitungsmodus/i })) + .toHaveAttribute('aria-expanded', 'true'); }); });