import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { PalmRejector } from './palm'; const ev = (type: string, pointerType: string, extra: Partial = {}) => ({ type, pointerType, pointerId: pointerType === 'pen' ? 1 : 2, width: 10, height: 10, ...extra }) as PointerEvent; const touch = (extra: Partial = {}) => ev('pointerdown', 'touch', extra); describe('palm rejection', () => { beforeEach(() => vi.useFakeTimers()); afterEach(() => vi.useRealTimers()); /** Pen writes a stroke, hovers, then leaves range; `ms` later a finger lands. */ function afterPen(ms: number, { leave = true } = {}) { const palm = new PalmRejector(); for (const t of ['pointermove', 'pointerdown', 'pointermove', 'pointerup', 'pointermove']) palm.track(ev(t, 'pen')); if (leave) palm.track(ev('pointerleave', 'pen')); vi.advanceTimersByTime(ms); return palm; } it('accepts fingers before any pen is used', () => { const palm = new PalmRejector(); expect(palm.penSeen).toBe(false); expect(palm.evaluate(touch(), 'draw').accept).toBe(true); }); it('rejects every touch while the pen is down', () => { const palm = new PalmRejector(); palm.track(ev('pointerdown', 'pen')); vi.advanceTimersByTime(1000); expect(palm.evaluate(touch(), 'navigate').accept).toBe(false); expect(palm.evaluate(touch(), 'draw').accept).toBe(false); }); it('lets fingers navigate between pen strokes but not draw', () => { const palm = afterPen(300); expect(palm.penSeen).toBe(true); expect(palm.evaluate(touch(), 'navigate').accept).toBe(true); expect(palm.evaluate(touch(), 'draw')).toEqual({ accept: false, reason: 'pen session active' }); }); it('lets fingers navigate while the pen hovers, but not draw', () => { const palm = afterPen(6000, { leave: false }); expect(palm.evaluate(touch(), 'navigate').accept).toBe(true); expect(palm.evaluate(touch(), 'draw')).toEqual({ accept: false, reason: 'pen hovering' }); }); it('still rejects palms and touches right after the pen', () => { expect(afterPen(50).evaluate(touch(), 'navigate').accept).toBe(false); expect(afterPen(300).evaluate(touch({ width: 60, height: 60 }), 'navigate').accept).toBe(false); }); it('allows finger ink again once the pen session ends', () => { expect(afterPen(5100).evaluate(touch(), 'draw').accept).toBe(true); }); });