Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m54s
CI / OCR Service Tests (pull_request) Successful in 39s
CI / Backend Unit Tests (pull_request) Failing after 2m56s
CI / Unit & Component Tests (push) Failing after 3m6s
CI / Backend Unit Tests (push) Failing after 2m56s
CI / OCR Service Tests (push) Successful in 34s
Felix C2 — `BatchMetadataRequest` controller now uses `@Valid` so future @Size/etc. annotations on the record actually fire. Felix C3 — Auto-clear `$effect` in `+layout.svelte` reads `bulkSelectionStore.size` inside `untrack()` so the effect only re-fires on route change, not on every checkbox toggle. Felix C4 — `BulkDocumentEditLayout` edit-mode hydration loop now lives inside `onMount` (not at top-level script) so the SvelteMap mutation is unambiguously tied to instance lifecycle, matching the pattern used by `WhoWhenSection`/`DescriptionSection` after the cycle-2 fix. Felix C5 — Replaced fully-qualified `java.util.LinkedHashSet` in `DocumentController` with a top-of-file import. Sara coverage — six new spec files / blocks pin the cycle-1 and cycle-2 behaviours that were previously untested: - `WhoWhenSection.svelte.spec.ts` — onMount seeding from initialDateIso / initialLocation; doesn't stomp parent-bound dateIso; hideDate / editMode branch - `DescriptionSection.svelte.spec.ts` — onMount seeding from initialTitle / initialDocumentLocation; doesn't stomp parent-bound values; archive-box / archive-folder fields visible only in editMode - `BulkSelectionBar.svelte.spec.ts` — Esc-scope guard tests for `<dialog>` open and `aria-expanded` popover present - `BulkDocumentEditLayout.svelte.spec.ts` — topbar reads "Massenbearbeitung" + "werden bearbeitet" in edit mode (not the upload-flavoured "hochladen"/"werden erstellt" copy) - `DocumentControllerTest.patchBulk_returns400_whenArchiveBoxExceeds255Chars` — pins the @Size validator on archiveBox via the @Valid wiring Refs #225, PR #331 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
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/stores/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 <dialog> 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();
|
|
}
|
|
});
|
|
});
|