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>
77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
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');
|
||
});
|
||
});
|