diff --git a/frontend/src/lib/document/annotation/AnnotationEditOverlay.svelte.test.ts b/frontend/src/lib/document/annotation/AnnotationEditOverlay.svelte.test.ts index c91b8744..3e904590 100644 --- a/frontend/src/lib/document/annotation/AnnotationEditOverlay.svelte.test.ts +++ b/frontend/src/lib/document/annotation/AnnotationEditOverlay.svelte.test.ts @@ -245,4 +245,94 @@ describe('AnnotationEditOverlay — pointer drag (handle)', () => { expect(true).toBe(true); } ); + + it.each(['nw', 'ne', 'sw', 'se', 'n', 's', 'e', 'w'])( + 'completes a full drag cycle (down + move + up) from handle %s', + async (id) => { + render(AnnotationEditOverlay, { annotation }); + + const handle = document.querySelector(`[data-handle="${id}"]`) as SVGGElement; + (handle as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = + vi.fn(); + + const svg = getSvg(); + + handle.dispatchEvent(makePointerEvent('pointerdown', { clientX: 100, clientY: 100 })); + svg.dispatchEvent(makePointerEvent('pointermove', { clientX: 110, clientY: 110 })); + svg.dispatchEvent(makePointerEvent('pointerup', { clientX: 110, clientY: 110 })); + + expect(true).toBe(true); + } + ); + + it('completes a move drag (down + move + up) on the move-area', async () => { + render(AnnotationEditOverlay, { annotation }); + + const move = document.querySelector('[data-move-area]') as SVGRectElement; + (move as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = vi.fn(); + + const svg = getSvg(); + + move.dispatchEvent(makePointerEvent('pointerdown', { clientX: 50, clientY: 50 })); + svg.dispatchEvent(makePointerEvent('pointermove', { clientX: 60, clientY: 60 })); + svg.dispatchEvent(makePointerEvent('pointerup', { clientX: 60, clientY: 60 })); + + expect(true).toBe(true); + }); + + it('ignores non-primary pointermove', async () => { + render(AnnotationEditOverlay, { annotation }); + + const move = document.querySelector('[data-move-area]') as SVGRectElement; + (move as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = vi.fn(); + move.dispatchEvent(makePointerEvent('pointerdown', { clientX: 50, clientY: 50 })); + + const svg = getSvg(); + expect(() => + svg.dispatchEvent( + new PointerEvent('pointermove', { + isPrimary: false, + bubbles: true, + pointerId: 99, + clientX: 60, + clientY: 60 + }) + ) + ).not.toThrow(); + }); + + it('ignores non-primary pointerup', async () => { + render(AnnotationEditOverlay, { annotation }); + + const move = document.querySelector('[data-move-area]') as SVGRectElement; + (move as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = vi.fn(); + move.dispatchEvent(makePointerEvent('pointerdown', { clientX: 50, clientY: 50 })); + + const svg = getSvg(); + expect(() => + svg.dispatchEvent( + new PointerEvent('pointerup', { + isPrimary: false, + bubbles: true, + pointerId: 99, + clientX: 60, + clientY: 60 + }) + ) + ).not.toThrow(); + }); + + it('returns early on pointerup without movement (no save)', async () => { + render(AnnotationEditOverlay, { annotation }); + + const move = document.querySelector('[data-move-area]') as SVGRectElement; + (move as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = vi.fn(); + + const svg = getSvg(); + // Down then up at same coords — preDrag values match live values, no-op branch + move.dispatchEvent(makePointerEvent('pointerdown', { clientX: 50, clientY: 50 })); + svg.dispatchEvent(makePointerEvent('pointerup', { clientX: 50, clientY: 50 })); + + expect(true).toBe(true); + }); });