import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import vm from 'node:vm'; import ts from 'typescript'; const source = readFileSync(new URL('../lib/gestures.ts', import.meta.url), 'utf8'); const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS } }); const exports = {}; vm.runInNewContext(compiled.outputText, { exports }); const { swipeDirection, pagePosition } = exports; test('short slow drags spring back in both directions', () => { assert.equal(swipeDirection(-100, -.1, 800), 0); assert.equal(swipeDirection(100, .1, 800), 0); }); test('long drags land on the adjacent page', () => { assert.equal(swipeDirection(-180, 0, 800), 1); assert.equal(swipeDirection(180, 0, 800), -1); }); test('quick flicks commit without requiring a long drag', () => { assert.equal(swipeDirection(-45, -.6, 800), 1); assert.equal(swipeDirection(45, .6, 800), -1); }); test('tiny pointer jitter never changes videos', () => { assert.equal(swipeDirection(-6, -2, 800), 0); assert.equal(swipeDirection(0, 2, 800), 0); }); test('threshold adapts to viewport height', () => { assert.equal(swipeDirection(-130, 0, 600), 1); assert.equal(swipeDirection(-130, 0, 1000), 0); assert.equal(swipeDirection(-130, 0, 0), 0); }); test('every active page has exactly one previous and one next page', () => { for (let active = 0; active < 3; active++) { assert.equal(pagePosition(active, active), 0); assert.equal(pagePosition((active + 1) % 3, active), 1); assert.equal(pagePosition((active + 2) % 3, active), -1); } }); test('recycling at the loop seam preserves the on-screen landing position', () => { for (let active = 0; active < 3; active++) { for (const direction of [-1, 1]) { const next = (active + direction + 3) % 3; // A dragged neighbor reaches y=0 before recycling; it stays at y=0 after. assert.equal(pagePosition(next, active) - direction, 0); assert.equal(pagePosition(next, next), 0); } } });