refactor(test): use getByRole instead of data-testid in TranscriptionPanelHeader test

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 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-09 19:55:05 +02:00
parent 891e1f1cf2
commit eb2ec6a71a

View File

@@ -5,178 +5,116 @@ import TranscriptionPanelHeader from './TranscriptionPanelHeader.svelte';
afterEach(cleanup); afterEach(cleanup);
describe('TranscriptionPanelHeader', () => { const baseProps = {
it('should render Lesen and Bearbeiten buttons', async () => { mode: 'read' as const,
render(TranscriptionPanelHeader, { hasBlocks: true,
mode: 'read', blockCount: 3,
hasBlocks: true, lastEditedAt: null,
blockCount: 3, onModeChange: () => {},
lastEditedAt: null, onClose: () => {}
onModeChange: () => {}, };
onClose: () => {}
});
await expect.element(page.getByText('Lesen')).toBeInTheDocument(); describe('TranscriptionPanelHeader', () => {
await expect.element(page.getByText('Bearbeiten')).toBeInTheDocument(); 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, { render(TranscriptionPanelHeader, {
...baseProps,
mode: 'edit', mode: 'edit',
hasBlocks: false, hasBlocks: false,
blockCount: 0, blockCount: 0
lastEditedAt: null,
onModeChange: () => {},
onClose: () => {}
}); });
const lesenBtn = document.querySelector('[data-testid="mode-read"]') as HTMLButtonElement; await expect
expect(lesenBtn.getAttribute('aria-disabled')).toBe('true'); .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(); const onModeChange = vi.fn();
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, { ...baseProps, onModeChange });
mode: 'read',
hasBlocks: true, await page.getByRole('button', { name: /bearbeiten/i }).click();
blockCount: 3,
lastEditedAt: null,
onModeChange,
onClose: () => {}
});
const editBtn = document.querySelector('[data-testid="mode-edit"]')!;
editBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(onModeChange).toHaveBeenCalledWith('edit'); 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(); const onModeChange = vi.fn();
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, {
...baseProps,
mode: 'edit', mode: 'edit',
hasBlocks: false, hasBlocks: false,
blockCount: 0, blockCount: 0,
lastEditedAt: null, onModeChange
onModeChange,
onClose: () => {}
}); });
const readBtn = document.querySelector('[data-testid="mode-read"]')!; await page.getByRole('button', { name: /lesen/i }).click({ force: true });
readBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(onModeChange).not.toHaveBeenCalled(); 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(); const onClose = vi.fn();
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, { ...baseProps, onClose });
mode: 'read',
hasBlocks: true,
blockCount: 3,
lastEditedAt: null,
onModeChange: () => {},
onClose
});
const closeBtn = document.querySelector('[data-testid="panel-close"]')!; await page.getByRole('button', { name: /panel schließen/i }).click();
closeBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
expect(onClose).toHaveBeenCalled(); expect(onClose).toHaveBeenCalledOnce();
}); });
it('should show singular block count for 1 block', async () => { it('shows the singular section label when blockCount is 1', async () => {
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, { ...baseProps, blockCount: 1 });
mode: 'read',
hasBlocks: true,
blockCount: 1,
lastEditedAt: null,
onModeChange: () => {},
onClose: () => {}
});
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 () => { it('shows the plural section label when blockCount is greater than 1', async () => {
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, { ...baseProps, blockCount: 5 });
mode: 'read',
hasBlocks: true,
blockCount: 5,
lastEditedAt: null,
onModeChange: () => {},
onClose: () => {}
});
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, { render(TranscriptionPanelHeader, {
mode: 'edit', ...baseProps,
hasBlocks: false, hasBlocks: false,
blockCount: 0, blockCount: 0,
lastEditedAt: null, mode: 'edit'
onModeChange: () => {},
onClose: () => {}
}); });
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, { render(TranscriptionPanelHeader, {
mode: 'read', ...baseProps,
hasBlocks: true, lastEditedAt: '2026-04-07T10:00:00Z'
blockCount: 3,
lastEditedAt: null,
onModeChange: () => {},
onClose: () => {}
}); });
const closeBtn = document.querySelector('[data-testid="panel-close"]') as HTMLElement; await expect.element(page.getByText(/2026/)).toBeVisible();
expect(closeBtn.classList.contains('h-11')).toBe(true);
expect(closeBtn.classList.contains('w-11')).toBe(true);
}); });
it('should show formatted date when lastEditedAt is provided', async () => { it('renders the help popover trigger', async () => {
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, baseProps);
mode: 'read',
hasBlocks: true,
blockCount: 3,
lastEditedAt: '2026-04-07T10:00:00Z',
onModeChange: () => {},
onClose: () => {}
});
const statusText = document.querySelector('.hidden.md\\:block'); await expect
expect(statusText).not.toBeNull(); .element(page.getByRole('button', { name: /lese- und bearbeitungsmodus/i }))
expect(statusText!.textContent).toContain('2026'); .toBeVisible();
}); });
it('renders a (?) help chip next to the Read/Edit toggle', async () => { it('opens the help popover when the help trigger is clicked', async () => {
render(TranscriptionPanelHeader, { render(TranscriptionPanelHeader, baseProps);
mode: 'read',
hasBlocks: true,
blockCount: 3,
lastEditedAt: null,
onModeChange: () => {},
onClose: () => {}
});
const helpBtn = document.querySelector('button[aria-expanded]') as HTMLButtonElement; await page.getByRole('button', { name: /lese- und bearbeitungsmodus/i }).click();
expect(helpBtn).not.toBeNull();
});
it('opens a help popover with mode explanation when the chip is clicked', async () => { await expect
render(TranscriptionPanelHeader, { .element(page.getByRole('button', { name: /lese- und bearbeitungsmodus/i }))
mode: 'read', .toHaveAttribute('aria-expanded', 'true');
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());
}); });
}); });