0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 1 | import assert from 'node:assert/strict'; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 2 | import { test } from 'node:test'; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 3 | import { PressTracker, type Timers } from '../src/lib/longpress.ts'; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 4 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 5 | function clock() { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 6 | let now = 0; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 7 | let next = 1; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 8 | const queue = new Map<number, { at: number; fn: () => void }>(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 9 | const timers: Timers = { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 10 | set(fn, ms) { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 11 | const id = next++; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 12 | queue.set(id, { at: now + ms, fn }); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 13 | return id; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 14 | }, |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 15 | clear(handle) { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 16 | queue.delete(handle as number); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 17 | } |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 18 | }; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 19 | const advance = (ms: number) => { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 20 | now += ms; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 21 | for (const [id, t] of [...queue]) { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 22 | if (t.at <= now) { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 23 | queue.delete(id); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 24 | t.fn(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 25 | } |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 26 | } |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 27 | }; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 28 | return { timers, advance }; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 29 | } |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 30 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 31 | function setup() { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 32 | const c = clock(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 33 | const fires: Array<[number, number]> = []; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 34 | const tracker = new PressTracker((x, y) => fires.push([x, y]), c.timers); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 35 | return { ...c, tracker, fires }; |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 36 | } |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 37 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 38 | test('fires with the press origin after the delay', () => { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 39 | const { tracker, advance, fires } = setup(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 40 | tracker.start(10, 20); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 41 | advance(449); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 42 | assert.deepEqual(fires, []); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 43 | advance(1); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 44 | assert.deepEqual(fires, [[10, 20]]); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 45 | assert.equal(tracker.fired, true); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 46 | }); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 47 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 48 | test('release before the delay cancels', () => { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 49 | const { tracker, advance, fires } = setup(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 50 | tracker.start(0, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 51 | advance(200); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 52 | tracker.cancel(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 53 | advance(1000); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 54 | assert.deepEqual(fires, []); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 55 | assert.equal(tracker.fired, false); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 56 | }); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 57 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 58 | test('small jitter is tolerated, a drag cancels', () => { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 59 | const a = setup(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 60 | a.tracker.start(0, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 61 | a.tracker.move(4, 4); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 62 | a.advance(450); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 63 | assert.equal(a.fires.length, 1); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 64 | const b = setup(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 65 | b.tracker.start(0, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 66 | b.tracker.move(9, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 67 | b.advance(450); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 68 | assert.equal(b.fires.length, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 69 | }); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 70 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 71 | test('a new press clears the fired flag', () => { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 72 | const { tracker, advance } = setup(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 73 | tracker.start(0, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 74 | advance(450); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 75 | tracker.start(5, 5); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 76 | assert.equal(tracker.fired, false); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 77 | assert.equal(tracker.pending, true); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 78 | }); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 79 | |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 80 | test('flush fires a pending press once and ignores an idle tracker', () => { |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 81 | const { tracker, advance, fires } = setup(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 82 | tracker.flush(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 83 | assert.equal(fires.length, 0); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 84 | tracker.start(3, 4); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 85 | tracker.flush(); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 86 | advance(1000); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 87 | assert.deepEqual(fires, [[3, 4]]); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 88 | assert.equal(tracker.fired, true); |
0e50e0d137af | atagen | 2026-10-26 05:50 UTC | 89 | }); |