import { afterEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render } from 'vitest-browser-svelte'; import { page } from 'vitest/browser'; import { goto } from '$app/navigation'; import BulkSelectionBar from './BulkSelectionBar.svelte'; import { bulkSelectionStore } from '$lib/document/bulkSelection.svelte'; vi.mock('$app/navigation', () => ({ goto: vi.fn() })); afterEach(() => { cleanup(); vi.mocked(goto).mockClear(); bulkSelectionStore.clear(); }); describe('BulkSelectionBar', () => { it('does not render when canWrite is false', async () => { bulkSelectionStore.add('a'); render(BulkSelectionBar, { canWrite: false }); await expect.element(page.getByTestId('bulk-selection-bar')).not.toBeInTheDocument(); }); it('does not render when selection is empty', async () => { render(BulkSelectionBar, { canWrite: true }); await expect.element(page.getByTestId('bulk-selection-bar')).not.toBeInTheDocument(); }); it('renders with the current selection count', async () => { bulkSelectionStore.add('a'); bulkSelectionStore.add('b'); render(BulkSelectionBar, { canWrite: true }); await expect.element(page.getByTestId('bulk-selection-count')).toHaveTextContent('2'); }); it('uses the singular plural form for count=1 (not "1 Dokumente")', async () => { bulkSelectionStore.add('only'); render(BulkSelectionBar, { canWrite: true }); await expect .element(page.getByTestId('bulk-selection-count')) .toHaveTextContent('1 Dokument ausgewählt'); }); it('uses the plural form for count=2', async () => { bulkSelectionStore.add('a'); bulkSelectionStore.add('b'); render(BulkSelectionBar, { canWrite: true }); await expect .element(page.getByTestId('bulk-selection-count')) .toHaveTextContent('2 Dokumente ausgewählt'); }); it('clear button empties the store', async () => { bulkSelectionStore.add('a'); bulkSelectionStore.add('b'); render(BulkSelectionBar, { canWrite: true }); await page.getByTestId('bulk-clear-all').click(); expect(bulkSelectionStore.size).toBe(0); }); it('Massenbearbeitung navigates to /documents/bulk-edit', async () => { bulkSelectionStore.add('a'); render(BulkSelectionBar, { canWrite: true }); await page.getByTestId('bulk-edit-open').click(); expect(vi.mocked(goto)).toHaveBeenCalledWith('/documents/bulk-edit'); }); it('selection count region announces via aria-live=polite', async () => { bulkSelectionStore.add('a'); render(BulkSelectionBar, { canWrite: true }); await expect .element(page.getByTestId('bulk-selection-count')) .toHaveAttribute('aria-live', 'polite'); }); it('Escape clears the selection while the bar is visible', async () => { bulkSelectionStore.add('a'); bulkSelectionStore.add('b'); render(BulkSelectionBar, { canWrite: true }); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await expect.poll(() => bulkSelectionStore.size).toBe(0); }); it('Escape is a no-op when the bar is hidden (no selection)', async () => { render(BulkSelectionBar, { canWrite: true }); window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); // Nothing to clear, no error. expect(bulkSelectionStore.size).toBe(0); }); it('Escape does not clear when an open is present (Leonie B6 scope guard)', async () => { bulkSelectionStore.add('a'); bulkSelectionStore.add('b'); render(BulkSelectionBar, { canWrite: true }); // Simulate a ConfirmDialog being open in front of the bar. const overlay = document.createElement('dialog'); overlay.setAttribute('open', ''); document.body.appendChild(overlay); try { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); // Escape is captured by the dialog, not the bar — selection survives. expect(bulkSelectionStore.size).toBe(2); } finally { overlay.remove(); } }); it('Escape does not clear when an aria-expanded popover is present', async () => { bulkSelectionStore.add('a'); render(BulkSelectionBar, { canWrite: true }); const trigger = document.createElement('button'); trigger.setAttribute('aria-expanded', 'true'); document.body.appendChild(trigger); try { window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); expect(bulkSelectionStore.size).toBe(1); } finally { trigger.remove(); } }); });