- Replace one-shot $derived(.matches) snapshot with $state + addEventListener so the static/animated branch reacts when the user toggles OS reduced-motion at runtime (Felix: non-reactive media query) - Replace bg-[#FAF8F1] raw hex with bg-parchment design token so the SVG background remaps correctly in dark mode (Felix/Markus) Also update TranscriptionPanelHeader.svelte.test.ts to expect role="region" after the HelpPopover ARIA fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
183 lines
5.1 KiB
TypeScript
183 lines
5.1 KiB
TypeScript
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page } from 'vitest/browser';
|
|
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: () => {}
|
|
});
|
|
|
|
await expect.element(page.getByText('Lesen')).toBeInTheDocument();
|
|
await expect.element(page.getByText('Bearbeiten')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should disable Lesen button when hasBlocks is false', async () => {
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'edit',
|
|
hasBlocks: false,
|
|
blockCount: 0,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose: () => {}
|
|
});
|
|
|
|
const lesenBtn = document.querySelector('[data-testid="mode-read"]') as HTMLButtonElement;
|
|
expect(lesenBtn.getAttribute('aria-disabled')).toBe('true');
|
|
});
|
|
|
|
it('should call onModeChange when clicking Bearbeiten', async () => {
|
|
const onModeChange = vi.fn();
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'read',
|
|
hasBlocks: true,
|
|
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');
|
|
});
|
|
|
|
it('should not call onModeChange when clicking disabled Lesen', async () => {
|
|
const onModeChange = vi.fn();
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'edit',
|
|
hasBlocks: false,
|
|
blockCount: 0,
|
|
lastEditedAt: null,
|
|
onModeChange,
|
|
onClose: () => {}
|
|
});
|
|
|
|
const readBtn = document.querySelector('[data-testid="mode-read"]')!;
|
|
readBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
expect(onModeChange).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should call onClose when clicking close button', async () => {
|
|
const onClose = vi.fn();
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'read',
|
|
hasBlocks: true,
|
|
blockCount: 3,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose
|
|
});
|
|
|
|
const closeBtn = document.querySelector('[data-testid="panel-close"]')!;
|
|
closeBtn.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
expect(onClose).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should show singular block count for 1 block', async () => {
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'read',
|
|
hasBlocks: true,
|
|
blockCount: 1,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose: () => {}
|
|
});
|
|
|
|
await expect.element(page.getByText('1 Abschnitt')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show plural block count for multiple blocks', async () => {
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'read',
|
|
hasBlocks: true,
|
|
blockCount: 5,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose: () => {}
|
|
});
|
|
|
|
await expect.element(page.getByText('5 Abschnitte')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should show "0 Abschnitte" when blockCount is 0', async () => {
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'edit',
|
|
hasBlocks: false,
|
|
blockCount: 0,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose: () => {}
|
|
});
|
|
|
|
await expect.element(page.getByText('0 Abschnitte')).toBeInTheDocument();
|
|
});
|
|
|
|
it('should have close button with 44px touch target classes', async () => {
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'read',
|
|
hasBlocks: true,
|
|
blockCount: 3,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose: () => {}
|
|
});
|
|
|
|
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);
|
|
});
|
|
|
|
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: () => {}
|
|
});
|
|
|
|
const statusText = document.querySelector('.hidden.md\\:block');
|
|
expect(statusText).not.toBeNull();
|
|
expect(statusText!.textContent).toContain('2026');
|
|
});
|
|
|
|
it('renders a (?) help chip next to the Read/Edit toggle', async () => {
|
|
render(TranscriptionPanelHeader, {
|
|
mode: 'read',
|
|
hasBlocks: true,
|
|
blockCount: 3,
|
|
lastEditedAt: null,
|
|
onModeChange: () => {},
|
|
onClose: () => {}
|
|
});
|
|
|
|
const helpBtn = document.querySelector('button[aria-expanded]') as HTMLButtonElement;
|
|
expect(helpBtn).not.toBeNull();
|
|
});
|
|
|
|
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());
|
|
});
|
|
});
|