1import { expect, it, vi } from "vite-plus/test";
2import { createPhoneInteraction, phoneWheelNavigation } from "./phoneInteraction.ts";
3
4it("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
28it("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
43it("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
67it("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
86it("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});