From 7fae13ff4ef0fbbd8c90effc7e969d3b741bf06b Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 03:29:32 +0200 Subject: [PATCH] test(annotation): expand AnnotationLayer coverage Container style branches (cursor with/without canDraw, touch-action), drawing pointer flow (canDraw=false skips, pointerdown on existing annotation skips, no preview when not drawing, pointermove without draw, pointerup without draw). 7 new tests, +14 covered branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../annotation/AnnotationLayer.svelte.test.ts | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts b/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts index ce69a890..863a2eda 100644 --- a/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts +++ b/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts @@ -157,4 +157,132 @@ describe('AnnotationLayer', () => { expect(el.classList.contains('annotation-flash')).toBe(false); }); }); + + describe('container style', () => { + it('uses crosshair cursor when canDraw is true', async () => { + render(AnnotationLayer, { + annotations: [], + canDraw: true, + color: '#00c7b1', + onDraw: () => {} + }); + + const wrapper = document.querySelector('[role="presentation"]') as HTMLElement; + expect(wrapper.style.cursor).toContain('crosshair'); + expect(wrapper.style.touchAction).toBe('none'); + }); + + it('omits crosshair cursor when canDraw is false', async () => { + render(AnnotationLayer, { + annotations: [], + canDraw: false, + color: '#00c7b1', + onDraw: () => {} + }); + + const wrapper = document.querySelector('[role="presentation"]') as HTMLElement; + expect(wrapper.style.cursor).not.toContain('crosshair'); + }); + }); + + describe('drawing pointer flow', () => { + it('does not start a draw when canDraw is false', async () => { + render(AnnotationLayer, { + annotations: [], + canDraw: false, + color: '#00c7b1', + onDraw: () => {} + }); + + const wrapper = document.querySelector('[role="presentation"]') as HTMLElement; + (wrapper as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = + () => {}; + + wrapper.dispatchEvent( + new PointerEvent('pointerdown', { + bubbles: true, + clientX: 50, + clientY: 50, + pointerId: 1 + }) + ); + + // No preview rect rendered + const preview = wrapper.querySelector('div[style*="border: 2px dashed"]'); + expect(preview).toBeNull(); + }); + + it('does not start a draw when pointerdown lands on an existing annotation', async () => { + render(AnnotationLayer, { + annotations: [annotation], + canDraw: true, + color: '#00c7b1', + onDraw: () => {} + }); + + const ann = document.querySelector('[data-testid="annotation-ann-1"]') as HTMLElement; + (ann as unknown as { setPointerCapture: (id: number) => void }).setPointerCapture = () => {}; + + // pointerdown bubbles to the layer; layer should refuse to draw because + // closest('[data-annotation]') matches. + ann.dispatchEvent( + new PointerEvent('pointerdown', { + bubbles: true, + clientX: 0, + clientY: 0, + pointerId: 1 + }) + ); + + const preview = document.querySelector('div[style*="border: 2px dashed"]'); + expect(preview).toBeNull(); + }); + + it('renders no preview rect when no draw is in progress', async () => { + render(AnnotationLayer, { + annotations: [], + canDraw: true, + color: '#00c7b1', + onDraw: () => {} + }); + + const preview = document.querySelector('div[style*="border: 2px dashed"]'); + expect(preview).toBeNull(); + }); + + it('handles pointermove without a started draw (early-return)', async () => { + render(AnnotationLayer, { + annotations: [], + canDraw: true, + color: '#00c7b1', + onDraw: () => {} + }); + + const wrapper = document.querySelector('[role="presentation"]') as HTMLElement; + expect(() => + wrapper.dispatchEvent( + new PointerEvent('pointermove', { bubbles: true, clientX: 0, clientY: 0 }) + ) + ).not.toThrow(); + }); + + it('handles pointerup without a started draw (early-return)', async () => { + let drawn = false; + render(AnnotationLayer, { + annotations: [], + canDraw: true, + color: '#00c7b1', + onDraw: () => { + drawn = true; + } + }); + + const wrapper = document.querySelector('[role="presentation"]') as HTMLElement; + wrapper.dispatchEvent( + new PointerEvent('pointerup', { bubbles: true, clientX: 0, clientY: 0 }) + ); + + expect(drawn).toBe(false); + }); + }); });