diff --git a/frontend/src/lib/stores/bulkSelection.svelte.spec.ts b/frontend/src/lib/stores/bulkSelection.svelte.spec.ts new file mode 100644 index 00000000..cde4814a --- /dev/null +++ b/frontend/src/lib/stores/bulkSelection.svelte.spec.ts @@ -0,0 +1,53 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { bulkSelectionStore } from './bulkSelection.svelte'; + +describe('bulkSelectionStore', () => { + afterEach(() => bulkSelectionStore.clear()); + + it('starts empty', () => { + expect(bulkSelectionStore.size).toBe(0); + }); + + it('toggle adds an id when absent', () => { + bulkSelectionStore.toggle('a'); + expect(bulkSelectionStore.has('a')).toBe(true); + expect(bulkSelectionStore.size).toBe(1); + }); + + it('toggle removes an id when present', () => { + bulkSelectionStore.add('a'); + bulkSelectionStore.toggle('a'); + expect(bulkSelectionStore.has('a')).toBe(false); + }); + + it('add and remove update size', () => { + bulkSelectionStore.add('a'); + bulkSelectionStore.add('b'); + expect(bulkSelectionStore.size).toBe(2); + bulkSelectionStore.remove('a'); + expect(bulkSelectionStore.size).toBe(1); + expect(bulkSelectionStore.has('b')).toBe(true); + }); + + it('add is idempotent', () => { + bulkSelectionStore.add('a'); + bulkSelectionStore.add('a'); + expect(bulkSelectionStore.size).toBe(1); + }); + + it('setAll replaces the selection', () => { + bulkSelectionStore.add('a'); + bulkSelectionStore.add('b'); + bulkSelectionStore.setAll(['c', 'd', 'e']); + expect(bulkSelectionStore.size).toBe(3); + expect(bulkSelectionStore.has('a')).toBe(false); + expect(bulkSelectionStore.has('c')).toBe(true); + }); + + it('clear empties the selection', () => { + bulkSelectionStore.add('a'); + bulkSelectionStore.add('b'); + bulkSelectionStore.clear(); + expect(bulkSelectionStore.size).toBe(0); + }); +}); diff --git a/frontend/src/lib/stores/bulkSelection.svelte.ts b/frontend/src/lib/stores/bulkSelection.svelte.ts new file mode 100644 index 00000000..1d1f7863 --- /dev/null +++ b/frontend/src/lib/stores/bulkSelection.svelte.ts @@ -0,0 +1,36 @@ +import { SvelteSet } from 'svelte/reactivity'; + +// Live accumulator. Selection persists across pagination and route changes +// within /documents and /enrich. Cleared on successful bulk save or via +// "Alles aufheben". The store is module-singleton — there is only ever one +// bulk-edit selection per browser session. +const selectedIds = new SvelteSet(); + +export const bulkSelectionStore = { + get ids(): SvelteSet { + return selectedIds; + }, + get size(): number { + return selectedIds.size; + }, + has(id: string): boolean { + return selectedIds.has(id); + }, + toggle(id: string): void { + if (selectedIds.has(id)) selectedIds.delete(id); + else selectedIds.add(id); + }, + add(id: string): void { + selectedIds.add(id); + }, + remove(id: string): void { + selectedIds.delete(id); + }, + setAll(ids: Iterable): void { + selectedIds.clear(); + for (const id of ids) selectedIds.add(id); + }, + clear(): void { + selectedIds.clear(); + } +};