Files
familienarchiv/frontend/src/routes/documents/new/page.svelte.spec.ts
Marcel b45ec744b2 feat: add PDF annotation feature (#40)
Backend:
- Add ANNOTATE_ALL permission
- Add ANNOTATION_NOT_FOUND and ANNOTATION_OVERLAP error codes
- V10 migration: document_annotations table with page/rect/color/owner
- DocumentAnnotation entity, AnnotationRepository, CreateAnnotationDTO
- AnnotationService: overlap detection (rectangle intersection), ownership enforcement on delete
- AnnotationController: GET (authenticated), POST/DELETE (ANNOTATE_ALL)
- 15 new tests (AnnotationServiceTest, AnnotationControllerTest) — TDD red/green

Frontend:
- AnnotationLayer.svelte: pointer-event drawing, colored rect overlays, delete buttons
- PdfViewer.svelte: annotate toggle, color picker, loads/saves/deletes annotations via API
- Disabled annotate button with tooltip for users without ANNOTATE_ALL
- canAnnotate exposed from layout server, passed to PdfViewer
- errors.ts + de/en/es translations for new error codes
- 3 new unit tests for AnnotationLayer — TDD red/green

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 23:27:21 +01:00

77 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, it } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import Page from './+page.svelte';
afterEach(cleanup);
// ─── Test data ────────────────────────────────────────────────────────────────
const baseData = {
user: undefined,
canWrite: true,
canAnnotate: false,
persons: [],
initialSenderId: '',
initialSenderName: '',
initialReceivers: []
};
// ─── Prefill sender ─────────────────────────────────────────────────────────
describe('New document page sender prefill', () => {
it('shows an empty sender input when no senderId is in the URL', async () => {
render(Page, { data: baseData, form: null });
const input = document.querySelector<HTMLInputElement>('#senderId-search');
expect(input?.value).toBe('');
});
it('shows the sender name in the typeahead input when initialSenderName is set', async () => {
render(Page, {
data: { ...baseData, initialSenderId: 'p1', initialSenderName: 'Hans Müller' },
form: null
});
const input = document.querySelector<HTMLInputElement>('#senderId-search');
expect(input?.value).toBe('Hans Müller');
});
it('sets the hidden senderId input to the prefilled ID', async () => {
render(Page, {
data: { ...baseData, initialSenderId: 'p1', initialSenderName: 'Hans Müller' },
form: null
});
const hidden = document.querySelector<HTMLInputElement>(
'input[type="hidden"][name="senderId"]'
);
expect(hidden?.value).toBe('p1');
});
});
// ─── Prefill receiver ───────────────────────────────────────────────────────
describe('New document page receiver prefill', () => {
it('shows no receiver chips when initialReceivers is empty', async () => {
render(Page, { data: baseData, form: null });
await expect.element(page.getByText('Anna Schmidt')).not.toBeInTheDocument();
});
it('shows a receiver chip when initialReceivers has a person', async () => {
const data = {
...baseData,
initialReceivers: [{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt' }]
};
render(Page, { data, form: null });
await expect.element(page.getByText('Anna Schmidt')).toBeInTheDocument();
});
it('renders a hidden receiverIds input for the prefilled receiver', async () => {
const data = {
...baseData,
initialReceivers: [{ id: 'p2', firstName: 'Anna', lastName: 'Schmidt' }]
};
render(Page, { data, form: null });
const hidden = document.querySelector<HTMLInputElement>('input[name="receiverIds"]');
expect(hidden?.value).toBe('p2');
});
});