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:
@@ -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());
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user