• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2024 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {ZonedInteractionHandler} from './zoned_interaction_handler';
16
17describe('ZonedInteractionHandler', () => {
18  let zih: ZonedInteractionHandler;
19  let div: HTMLElement;
20
21  beforeEach(() => {
22    // Create a DOM element
23    div = document.createElement('div');
24    div.style.width = '100px';
25    div.style.height = '100px';
26    document.body.appendChild(div);
27    zih = new ZonedInteractionHandler(div);
28  });
29
30  // Utility functions for simulating mouse events
31  function mouseup(x: number, y: number) {
32    simulateMouseEvent('mouseup', x, y);
33  }
34
35  function mousedown(x: number, y: number) {
36    simulateMouseEvent('mousedown', x, y);
37  }
38
39  function mousemove(x: number, y: number) {
40    simulateMouseEvent('mousemove', x, y);
41  }
42
43  function simulateMouseEvent(kind: string, x: number, y: number) {
44    div.dispatchEvent(
45      new MouseEvent(kind, {
46        bubbles: true,
47        clientX: x,
48        clientY: y,
49      }),
50    );
51  }
52
53  test('overlapping zones', () => {
54    zih.update([
55      {
56        id: 'foo',
57        area: {x: 50, y: 50, width: 50, height: 50},
58        cursor: 'grab',
59      },
60      {
61        id: 'bar',
62        area: {x: 0, y: 0, width: 100, height: 100},
63        cursor: 'pointer',
64      },
65    ]);
66
67    mousemove(30, 30); // inside 'bar'
68    expect(div.style.cursor).toBe('pointer');
69
70    mousemove(70, 70); // inside 'foo'
71    expect(div.style.cursor).toBe('grab');
72  });
73
74  test('click', () => {
75    const handleMouseClick = jest.fn(() => {});
76
77    zih.update([
78      {
79        id: 'foo',
80        area: {x: 0, y: 0, width: 60, height: 60},
81        onClick: handleMouseClick,
82      },
83    ]);
84
85    // Simulate a mouse click
86    mousedown(50, 50);
87    mouseup(50, 50);
88
89    expect(handleMouseClick).toHaveBeenCalled();
90
91    handleMouseClick.mockClear();
92
93    // Simulate a mouse down then a mouseup outside the zone
94    mousedown(50, 50);
95    mouseup(80, 80);
96
97    expect(handleMouseClick).not.toHaveBeenCalled();
98  });
99
100  test('drag', () => {
101    const handleDrag = jest.fn(() => {});
102    const handleDragEnd = jest.fn(() => {});
103
104    zih.update([
105      {
106        id: 'foo',
107        area: {x: 0, y: 0, width: 100, height: 100},
108        drag: {
109          cursorWhileDragging: 'grabbing',
110          onDrag: handleDrag,
111          onDragEnd: handleDragEnd,
112        },
113      },
114    ]);
115
116    // Simulate a mouse drag start
117    mousedown(0, 0);
118    expect(div.style.cursor).toBe('grabbing');
119
120    // Simulate a mouse drag move
121    mousemove(50, 0);
122
123    expect(handleDrag).toHaveBeenCalled();
124
125    // Simulate a drag end
126    mouseup(60, 0);
127    expect(handleDragEnd).toHaveBeenCalled();
128  });
129
130  test('drag with minimum distance', () => {
131    const handleDrag = jest.fn();
132    const handleDragEnd = jest.fn();
133
134    zih.update([
135      {
136        id: 'dragZone',
137        area: {x: 0, y: 0, width: 100, height: 100},
138        drag: {
139          minDistance: 20,
140          onDrag: handleDrag,
141          onDragEnd: handleDragEnd,
142        },
143      },
144    ]);
145
146    // Simulate drag start
147    mousedown(10, 10);
148
149    // Move within the minimum distance
150    mousemove(15, 15);
151    expect(handleDrag).not.toHaveBeenCalled();
152
153    // Move beyond the minimum distance
154    mousemove(40, 40);
155    expect(handleDrag).toHaveBeenCalled();
156
157    // End the drag
158    mouseup(50, 50);
159    expect(handleDragEnd).toHaveBeenCalled();
160  });
161
162  test('onWheel', () => {
163    const handleWheel = jest.fn();
164
165    zih.update([
166      {
167        id: 'foo',
168        area: {x: 0, y: 0, width: 100, height: 100},
169        onWheel: handleWheel,
170      },
171    ]);
172
173    // Simulate a wheel event inside the zone
174    div.dispatchEvent(
175      new WheelEvent('wheel', {
176        bubbles: true,
177        clientX: 50,
178        clientY: 50,
179        deltaX: 5,
180        deltaY: 10,
181      }),
182    );
183
184    expect(handleWheel).toHaveBeenCalled();
185    expect(handleWheel.mock.calls[0][0]).toMatchObject({
186      position: {x: 50, y: 50},
187      deltaX: 5,
188      deltaY: 10,
189    });
190  });
191
192  test('key modifiers', () => {
193    const handleMouseClick = jest.fn();
194
195    zih.update([
196      {
197        id: 'modifierZone',
198        area: {x: 0, y: 0, width: 100, height: 100},
199        keyModifier: 'shift',
200        onClick: handleMouseClick,
201      },
202    ]);
203
204    // Attempt click without holding the modifier key
205    mousedown(50, 50);
206    mouseup(50, 50);
207    expect(handleMouseClick).not.toHaveBeenCalled();
208
209    // Simulate holding down the shift key and clicking
210    document.dispatchEvent(new KeyboardEvent('keydown', {shiftKey: true}));
211    mousedown(50, 50);
212    mouseup(50, 50);
213    expect(handleMouseClick).toHaveBeenCalled();
214
215    // Simulate releasing the shift key
216    document.dispatchEvent(new KeyboardEvent('keyup', {shiftKey: false}));
217    mousedown(50, 50);
218    mouseup(50, 50);
219    expect(handleMouseClick).toHaveBeenCalledTimes(1); // No additional call
220  });
221
222  test('move zone during drag', () => {
223    const handleDrag = jest.fn();
224    const handleDragEnd = jest.fn();
225
226    zih.update([
227      {
228        id: 'dragZone',
229        area: {x: 0, y: 0, width: 100, height: 100},
230        drag: {
231          onDrag: handleDrag,
232          onDragEnd: handleDragEnd,
233        },
234      },
235    ]);
236
237    // Start a drag
238    mousedown(10, 10);
239
240    // Update zones while dragging
241    zih.update([
242      {
243        id: 'dragZone',
244        area: {x: 0, y: 0, width: 10, height: 10},
245        drag: {
246          onDrag: handleDrag,
247          onDragEnd: handleDragEnd,
248        },
249      },
250    ]);
251
252    // Continue dragging - drags are sticky, so even if we drag outside of the
253    // zone, the drag persists
254    mousemove(50, 50);
255    expect(handleDrag).toHaveBeenCalled();
256
257    // End drag
258    mouseup(60, 60);
259    expect(handleDragEnd).toHaveBeenCalled();
260  });
261
262  test('click and move but stay in zone', () => {
263    const handleMouseClick = jest.fn(() => {});
264
265    zih.update([
266      {
267        id: 'foo',
268        area: {x: 0, y: 0, width: 60, height: 60},
269        onClick: handleMouseClick,
270      },
271    ]);
272
273    // Simulate a mouse click where the cursor has moved a little by remains
274    // inside the zone with the click event handler.
275    mousedown(30, 30);
276    mouseup(50, 50);
277
278    expect(handleMouseClick).toHaveBeenCalled();
279  });
280
281  test('click and move out of zone', () => {
282    const handleMouseClick = jest.fn(() => {});
283
284    zih.update([
285      {
286        id: 'foo',
287        area: {x: 0, y: 0, width: 60, height: 60},
288        onClick: handleMouseClick,
289      },
290    ]);
291
292    // Simulate a mouse click where the cursor has moved outside of the zone.
293    mousedown(50, 50);
294    mouseup(80, 80);
295
296    expect(handleMouseClick).not.toHaveBeenCalled();
297  });
298
299  afterEach(() => {
300    document.body.removeChild(div);
301  });
302});
303