| 1 | import { expect, it, vi } from "vite-plus/test"; |
| 2 | import { createPhoneInteraction, phoneWheelNavigation } from "./phoneInteraction.ts"; |
| 3 | |
| 4 | it("keeps a captured screen gesture separate from orbit and ends it once on cancellation", () => { |
| 5 | const touch = vi.fn(); |
| 6 | const orbit = vi.fn(); |
| 7 | const screenPoint = vi |
| 8 | .fn() |
| 9 | .mockReturnValueOnce({ x: 0.2, y: 0.3 }) |
| 10 | .mockReturnValueOnce({ x: 1, y: 0.6 }); |
| 11 | const input = createPhoneInteraction({ screenPoint, touch, orbit, zoomBy: vi.fn() }); |
| 12 | expect(input.begin(1, { x: 0.4, y: 0.3 })).toBe(true); |
| 13 | expect(input.begin(2, { x: 0.2, y: 0.2 })).toBe(false); |
| 14 | input.move(2, { x: 0.8, y: 0.8 }); |
| 15 | input.move(1, { x: 1.3, y: 0.6 }); |
| 16 | input.end(2); |
| 17 | input.end(); |
| 18 | input.end(1); |
| 19 | expect(touch.mock.calls).toEqual([ |
| 20 | ["begin", { x: 0.2, y: 0.3 }], |
| 21 | ["move", { x: 1, y: 0.6 }], |
| 22 | ["end", { x: 1, y: 0.6 }], |
| 23 | ]); |
| 24 | expect(orbit).not.toHaveBeenCalled(); |
| 25 | expect(screenPoint.mock.calls[1]?.[1]).toBe(true); |
| 26 | }); |
| 27 | |
| 28 | it("orbits without sending touches when dragged outside the screen or with the orbit modifier", () => { |
| 29 | const touch = vi.fn(); |
| 30 | const orbit = vi.fn(); |
| 31 | const input = createPhoneInteraction({ screenPoint: () => null, touch, orbit, zoomBy: vi.fn() }); |
| 32 | input.begin(1, { x: 0.1, y: 0.2 }); |
| 33 | input.move(1, { x: 0.4, y: 0.5 }); |
| 34 | input.end(); |
| 35 | input.begin(2, { x: 0.5, y: 0.5 }, true); |
| 36 | input.move(2, { x: 0.6, y: 0.5 }); |
| 37 | input.end(); |
| 38 | expect(orbit).toHaveBeenCalledTimes(2); |
| 39 | expect(orbit.mock.calls[0]?.[0]).toBeCloseTo(0.3); |
| 40 | expect(touch).not.toHaveBeenCalled(); |
| 41 | }); |
| 42 | |
| 43 | it("blocks navigation during either captured gesture, then resumes without sending device touches", () => { |
| 44 | const touch = vi.fn(); |
| 45 | const orbit = vi.fn(); |
| 46 | const zoomBy = vi.fn(); |
| 47 | const input = createPhoneInteraction({ |
| 48 | screenPoint: () => ({ x: 0.5, y: 0.5 }), |
| 49 | touch, |
| 50 | orbit, |
| 51 | zoomBy, |
| 52 | }); |
| 53 | input.begin(1, { x: 0.5, y: 0.5 }); |
| 54 | expect(input.navigate({ type: "zoom", delta: 0.2 })).toBe(false); |
| 55 | expect(input.navigate({ type: "orbit", x: 0.1, y: 0.2 })).toBe(false); |
| 56 | input.end(); |
| 57 | input.begin(2, { x: 0.5, y: 0.5 }, true); |
| 58 | expect(input.navigate({ type: "zoom", delta: 0.2 })).toBe(false); |
| 59 | input.end(); |
| 60 | expect(input.navigate({ type: "zoom", delta: 0.2 })).toBe(true); |
| 61 | expect(input.navigate({ type: "orbit", x: 0.1, y: 0.2 })).toBe(true); |
| 62 | expect(zoomBy.mock.calls).toEqual([[0.2]]); |
| 63 | expect(orbit.mock.calls).toEqual([[0.1, 0.2]]); |
| 64 | expect(touch.mock.calls.map(([phase]) => phase)).toEqual(["begin", "end"]); |
| 65 | }); |
| 66 | |
| 67 | it("ends wheel orbit without ending a captured screen touch", () => { |
| 68 | const onInteractionActive = vi.fn(); |
| 69 | const input = createPhoneInteraction({ |
| 70 | screenPoint: () => ({ x: 0.5, y: 0.5 }), |
| 71 | touch: vi.fn(), |
| 72 | orbit: vi.fn(), |
| 73 | zoomBy: vi.fn(), |
| 74 | onInteractionActive, |
| 75 | }); |
| 76 | input.endWheel(); |
| 77 | expect(onInteractionActive).toHaveBeenCalledWith(false, "orbit"); |
| 78 | input.begin(1, { x: 0.5, y: 0.5 }); |
| 79 | onInteractionActive.mockClear(); |
| 80 | input.endWheel(); |
| 81 | expect(onInteractionActive).not.toHaveBeenCalled(); |
| 82 | input.end(1); |
| 83 | expect(onInteractionActive).toHaveBeenCalledWith(false, "touch"); |
| 84 | }); |
| 85 | |
| 86 | it("normalizes wheel units, distinguishes pinch, and bounds jumps from coarse scroll wheels", () => { |
| 87 | const wheel = { width: 400, height: 800, deltaX: 16, deltaY: 32, deltaMode: 0, ctrlKey: false }; |
| 88 | expect(phoneWheelNavigation(wheel)).toEqual({ type: "orbit", x: -0.04, y: -0.04 }); |
| 89 | expect(phoneWheelNavigation({ ...wheel, deltaX: 1, deltaY: 2, deltaMode: 1 })).toEqual( |
| 90 | phoneWheelNavigation(wheel), |
| 91 | ); |
| 92 | expect(phoneWheelNavigation({ ...wheel, deltaX: 1, deltaY: 1, deltaMode: 2 })).toEqual({ |
| 93 | type: "orbit", |
| 94 | x: -0.25, |
| 95 | y: -0.25, |
| 96 | }); |
| 97 | expect(phoneWheelNavigation({ ...wheel, ctrlKey: true })).toEqual({ type: "zoom", delta: -0.32 }); |
| 98 | expect(phoneWheelNavigation({ ...wheel, deltaY: -10000, ctrlKey: true })).toEqual({ |
| 99 | type: "zoom", |
| 100 | delta: 1, |
| 101 | }); |
| 102 | expect(phoneWheelNavigation({ ...wheel, width: 0 })).toBeNull(); |
| 103 | expect(phoneWheelNavigation({ ...wheel, deltaY: NaN })).toBeNull(); |
| 104 | expect(phoneWheelNavigation({ ...wheel, deltaMode: 9 })).toBeNull(); |
| 105 | }); |