Files
familienarchiv/frontend/src/lib/document/annotation/AnnotationLayer.svelte.spec.ts
Marcel ae1688319e test(annotation): replace synchronous query().toBeNull() with async not.toBeInTheDocument()
Svelte defers DOM updates to microtasks; .query() is a synchronous
snapshot that can fire before the element disappears — making the
absence assertions in AnnotationShape and AnnotationLayer non-deterministic.

Sweeps all 4 instances across both spec files (Sara's ≤5 threshold).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 10:21:24 +02:00

126 lines
3.5 KiB
TypeScript

import { describe, it, expect, afterEach } from 'vitest';
import { cleanup, render } from 'vitest-browser-svelte';
import { page } from 'vitest/browser';
import AnnotationLayer from './AnnotationLayer.svelte';
afterEach(cleanup);
type Annotation = {
id: string;
documentId: string;
pageNumber: number;
x: number;
y: number;
width: number;
height: number;
color: string;
createdAt: string;
};
function makeAnnotation(id = 'ann-1', color = '#00C7B1'): Annotation {
return {
id,
documentId: 'doc-1',
pageNumber: 1,
x: 0.1,
y: 0.1,
width: 0.3,
height: 0.2,
color,
createdAt: new Date().toISOString()
};
}
describe('AnnotationLayer', () => {
it('renders a colored element for each annotation', async () => {
render(AnnotationLayer, {
annotations: [makeAnnotation('ann-1'), makeAnnotation('ann-2')],
canDraw: false,
color: '#00C7B1',
onDraw: () => {}
});
await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
await expect.element(page.getByTestId('annotation-ann-2')).toBeInTheDocument();
});
it('has crosshair cursor when canDraw is true', async () => {
render(AnnotationLayer, {
annotations: [],
canDraw: true,
color: '#00C7B1',
onDraw: () => {}
});
const container = document.querySelector('[role="presentation"]')!;
expect(container.getAttribute('style')).toContain('cursor: crosshair');
});
it('does not have crosshair cursor when canDraw is false', async () => {
render(AnnotationLayer, {
annotations: [],
canDraw: false,
color: '#00C7B1',
onDraw: () => {}
});
const container = document.querySelector('[role="presentation"]')!;
expect(container.getAttribute('style')).not.toContain('cursor: crosshair');
});
it('dims non-active annotations when activeAnnotationId is set', async () => {
render(AnnotationLayer, {
annotations: [makeAnnotation('ann-1'), makeAnnotation('ann-2')],
canDraw: false,
color: '#00C7B1',
activeAnnotationId: 'ann-1',
onDraw: () => {}
});
const active = page.getByTestId('annotation-ann-1').element();
const dimmed = page.getByTestId('annotation-ann-2').element();
expect(active.style.opacity).toBe('1');
expect(dimmed.style.opacity).toBe('0.3');
});
it('shows all annotations at full opacity when no activeAnnotationId', async () => {
render(AnnotationLayer, {
annotations: [makeAnnotation('ann-1'), makeAnnotation('ann-2')],
canDraw: false,
color: '#00C7B1',
onDraw: () => {}
});
const el1 = page.getByTestId('annotation-ann-1').element();
const el2 = page.getByTestId('annotation-ann-2').element();
expect(el1.style.opacity).toBe('1');
expect(el2.style.opacity).toBe('1');
});
it('does not show delete button when annotation is not hovered or active', async () => {
render(AnnotationLayer, {
annotations: [makeAnnotation('ann-1')],
canDraw: true,
color: '#00C7B1',
onDraw: () => {}
});
await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
});
it('does not show delete button when canDraw is false even if annotation is active', async () => {
render(AnnotationLayer, {
annotations: [makeAnnotation('ann-1')],
canDraw: false,
color: '#00C7B1',
activeAnnotationId: 'ann-1',
onDraw: () => {}
});
await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
});
});