1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/touch_selection/longpress_drag_selector.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "ui/events/test/motion_event_test_utils.h"
9
10 using ui::test::MockMotionEvent;
11
12 namespace ui {
13 namespace {
14
15 const double kSlop = 10.;
16
17 } // namespace
18
19 class LongPressDragSelectorTest : public testing::Test,
20 public LongPressDragSelectorClient {
21 public:
LongPressDragSelectorTest()22 LongPressDragSelectorTest()
23 : dragging_(false), active_state_changed_(false) {}
24
~LongPressDragSelectorTest()25 ~LongPressDragSelectorTest() override {}
26
SetSelection(const gfx::PointF & start,const gfx::PointF & end)27 void SetSelection(const gfx::PointF& start, const gfx::PointF& end) {
28 selection_start_ = start;
29 selection_end_ = end;
30 }
31
GetAndResetActiveStateChanged()32 bool GetAndResetActiveStateChanged() {
33 bool active_state_changed = active_state_changed_;
34 active_state_changed_ = false;
35 return active_state_changed;
36 }
37
IsDragging() const38 bool IsDragging() const { return dragging_; }
DragPosition() const39 const gfx::PointF& DragPosition() const { return drag_position_; }
40
41 // LongPressDragSelectorClient implementation.
OnDragBegin(const TouchSelectionDraggable & handler,const gfx::PointF & drag_position)42 void OnDragBegin(const TouchSelectionDraggable& handler,
43 const gfx::PointF& drag_position) override {
44 dragging_ = true;
45 drag_position_ = drag_position;
46 }
47
OnDragUpdate(const TouchSelectionDraggable & handler,const gfx::PointF & drag_position)48 void OnDragUpdate(const TouchSelectionDraggable& handler,
49 const gfx::PointF& drag_position) override {
50 drag_position_ = drag_position;
51 }
52
OnDragEnd(const TouchSelectionDraggable & handler)53 void OnDragEnd(const TouchSelectionDraggable& handler) override {
54 dragging_ = false;
55 }
56
IsWithinTapSlop(const gfx::Vector2dF & delta) const57 bool IsWithinTapSlop(const gfx::Vector2dF& delta) const override {
58 return delta.LengthSquared() < (kSlop * kSlop);
59 }
60
OnLongPressDragActiveStateChanged()61 void OnLongPressDragActiveStateChanged() override {
62 active_state_changed_ = true;
63 }
64
GetSelectionStart() const65 gfx::PointF GetSelectionStart() const override { return selection_start_; }
66
GetSelectionEnd() const67 gfx::PointF GetSelectionEnd() const override { return selection_end_; }
68
69 private:
70 bool dragging_;
71 bool active_state_changed_;
72 gfx::PointF drag_position_;
73
74 gfx::PointF selection_start_;
75 gfx::PointF selection_end_;
76 };
77
TEST_F(LongPressDragSelectorTest,BasicDrag)78 TEST_F(LongPressDragSelectorTest, BasicDrag) {
79 LongPressDragSelector selector(this);
80 MockMotionEvent event;
81
82 // Start a touch sequence.
83 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
84 EXPECT_FALSE(GetAndResetActiveStateChanged());
85
86 // Activate a longpress-triggered selection.
87 gfx::PointF selection_start(0, 10);
88 gfx::PointF selection_end(10, 10);
89 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
90 EXPECT_FALSE(GetAndResetActiveStateChanged());
91
92 // Motion should not be consumed until a selection is detected.
93 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
94 SetSelection(selection_start, selection_end);
95 selector.OnSelectionActivated();
96 EXPECT_TRUE(GetAndResetActiveStateChanged());
97 EXPECT_FALSE(IsDragging());
98
99 // Initiate drag motion. Note that the first move event after activation is
100 // used to initialize the drag start anchor.
101 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
102 EXPECT_FALSE(IsDragging());
103
104 // The first slop exceeding motion will start the drag. As the motion is
105 // downward, the end selection point should be moved.
106 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
107 EXPECT_TRUE(IsDragging());
108 EXPECT_EQ(selection_end, DragPosition());
109
110 // Subsequent motion will extend the selection.
111 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
112 EXPECT_TRUE(IsDragging());
113 EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop), DragPosition());
114 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 3)));
115 EXPECT_TRUE(IsDragging());
116 EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop * 2), DragPosition());
117
118 // Release the touch sequence, ending the drag. The selector will never
119 // consume the start/end events, only move events after a longpress.
120 EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
121 EXPECT_FALSE(IsDragging());
122 EXPECT_TRUE(GetAndResetActiveStateChanged());
123 }
124
TEST_F(LongPressDragSelectorTest,BasicReverseDrag)125 TEST_F(LongPressDragSelectorTest, BasicReverseDrag) {
126 LongPressDragSelector selector(this);
127 MockMotionEvent event;
128
129 // Start a touch sequence.
130 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
131 EXPECT_FALSE(GetAndResetActiveStateChanged());
132
133 // Activate a longpress-triggered selection.
134 gfx::PointF selection_start(0, 10);
135 gfx::PointF selection_end(10, 10);
136 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
137 EXPECT_FALSE(GetAndResetActiveStateChanged());
138 SetSelection(selection_start, selection_end);
139 selector.OnSelectionActivated();
140 EXPECT_TRUE(GetAndResetActiveStateChanged());
141 EXPECT_FALSE(IsDragging());
142
143 // Initiate drag motion.
144 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 5, 0)));
145 EXPECT_FALSE(IsDragging());
146
147 // As the initial motion is leftward, toward the selection start, the
148 // selection start should be the drag point.
149 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, -kSlop, 0)));
150 EXPECT_TRUE(IsDragging());
151 EXPECT_EQ(selection_start, DragPosition());
152
153 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop)));
154 EXPECT_TRUE(IsDragging());
155 EXPECT_EQ(selection_start + gfx::Vector2dF(kSlop, -kSlop), DragPosition());
156
157 // Release the touch sequence, ending the drag.
158 EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
159 EXPECT_FALSE(IsDragging());
160 EXPECT_TRUE(GetAndResetActiveStateChanged());
161 }
162
TEST_F(LongPressDragSelectorTest,NoActiveTouch)163 TEST_F(LongPressDragSelectorTest, NoActiveTouch) {
164 LongPressDragSelector selector(this);
165 MockMotionEvent event;
166
167 // Activate a longpress-triggered selection.
168 gfx::PointF selection_start(0, 10);
169 gfx::PointF selection_end(10, 10);
170 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
171 SetSelection(selection_start, selection_end);
172 selector.OnSelectionActivated();
173 EXPECT_FALSE(GetAndResetActiveStateChanged());
174 EXPECT_FALSE(IsDragging());
175
176 // Start a new touch sequence; it shouldn't initiate selection drag as there
177 // was no active touch sequence when the longpress selection started.
178 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
179 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
180 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
181 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
182 EXPECT_FALSE(IsDragging());
183 EXPECT_EQ(gfx::PointF(), DragPosition());
184 }
185
TEST_F(LongPressDragSelectorTest,NoLongPress)186 TEST_F(LongPressDragSelectorTest, NoLongPress) {
187 LongPressDragSelector selector(this);
188 MockMotionEvent event;
189
190 // Start a touch sequence.
191 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
192 EXPECT_FALSE(GetAndResetActiveStateChanged());
193
194 // Activate a selection without a preceding longpress.
195 gfx::PointF selection_start(0, 10);
196 gfx::PointF selection_end(10, 10);
197 SetSelection(selection_start, selection_end);
198 selector.OnSelectionActivated();
199 EXPECT_FALSE(GetAndResetActiveStateChanged());
200 EXPECT_FALSE(IsDragging());
201
202 // Touch movement should not initiate selection drag.
203 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
204 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
205 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
206 EXPECT_FALSE(IsDragging());
207 EXPECT_EQ(gfx::PointF(), DragPosition());
208 }
209
TEST_F(LongPressDragSelectorTest,NoValidLongPress)210 TEST_F(LongPressDragSelectorTest, NoValidLongPress) {
211 LongPressDragSelector selector(this);
212 MockMotionEvent event;
213
214 // Start a touch sequence.
215 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
216 EXPECT_FALSE(GetAndResetActiveStateChanged());
217
218 gfx::PointF selection_start(0, 10);
219 gfx::PointF selection_end(10, 10);
220 SetSelection(selection_start, selection_end);
221
222 // Activate a longpress-triggered selection, but at a time before the current
223 // touch down event.
224 selector.OnLongPressEvent(
225 event.GetEventTime() - base::TimeDelta::FromSeconds(1), gfx::PointF());
226 selector.OnSelectionActivated();
227 EXPECT_FALSE(GetAndResetActiveStateChanged());
228 EXPECT_FALSE(IsDragging());
229
230 // Activate a longpress-triggered selection, but at a place different than the
231 // current touch down event.
232 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF(kSlop, 0));
233 selector.OnSelectionActivated();
234 EXPECT_FALSE(GetAndResetActiveStateChanged());
235 EXPECT_FALSE(IsDragging());
236
237 // Touch movement should not initiate selection drag.
238 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
239 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
240 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
241 EXPECT_FALSE(IsDragging());
242 EXPECT_EQ(gfx::PointF(), DragPosition());
243 }
244
TEST_F(LongPressDragSelectorTest,NoSelection)245 TEST_F(LongPressDragSelectorTest, NoSelection) {
246 LongPressDragSelector selector(this);
247 MockMotionEvent event;
248
249 // Start a touch sequence.
250 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
251 EXPECT_FALSE(GetAndResetActiveStateChanged());
252
253 // Trigger a longpress.
254 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
255 EXPECT_FALSE(GetAndResetActiveStateChanged());
256 EXPECT_FALSE(IsDragging());
257
258 // Touch movement should not initiate selection drag, as there is no active
259 // selection.
260 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
261 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
262 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
263 EXPECT_FALSE(IsDragging());
264 EXPECT_EQ(gfx::PointF(), DragPosition());
265
266 EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
267 }
268
TEST_F(LongPressDragSelectorTest,NoDragMotion)269 TEST_F(LongPressDragSelectorTest, NoDragMotion) {
270 LongPressDragSelector selector(this);
271 MockMotionEvent event;
272
273 // Start a touch sequence.
274 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
275 EXPECT_FALSE(GetAndResetActiveStateChanged());
276
277 // Activate a longpress-triggered selection.
278 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
279 EXPECT_FALSE(GetAndResetActiveStateChanged());
280 gfx::PointF selection_start(0, 10);
281 gfx::PointF selection_end(10, 10);
282 SetSelection(selection_start, selection_end);
283 selector.OnSelectionActivated();
284 EXPECT_TRUE(GetAndResetActiveStateChanged());
285 EXPECT_FALSE(IsDragging());
286
287 // Touch movement within the slop region should not initiate selection drag.
288 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
289 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop / 2)));
290 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop / 2)));
291 EXPECT_FALSE(IsDragging());
292 EXPECT_EQ(gfx::PointF(), DragPosition());
293
294 EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
295 EXPECT_TRUE(GetAndResetActiveStateChanged());
296 }
297
TEST_F(LongPressDragSelectorTest,SelectionDeactivated)298 TEST_F(LongPressDragSelectorTest, SelectionDeactivated) {
299 LongPressDragSelector selector(this);
300 MockMotionEvent event;
301
302 // Start a touch sequence.
303 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
304 EXPECT_FALSE(GetAndResetActiveStateChanged());
305
306 // Activate a longpress-triggered selection.
307 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
308 EXPECT_FALSE(GetAndResetActiveStateChanged());
309 gfx::PointF selection_start(0, 10);
310 gfx::PointF selection_end(10, 10);
311 SetSelection(selection_start, selection_end);
312 selector.OnSelectionActivated();
313 EXPECT_TRUE(GetAndResetActiveStateChanged());
314 EXPECT_FALSE(IsDragging());
315
316 // Start a drag selection.
317 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
318 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
319 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
320 EXPECT_TRUE(IsDragging());
321
322 // Clearing the selection should force an end to the drag.
323 selector.OnSelectionDeactivated();
324 EXPECT_TRUE(GetAndResetActiveStateChanged());
325 EXPECT_FALSE(IsDragging());
326
327 // Subsequent motion should not be consumed.
328 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
329 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
330 EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
331 EXPECT_FALSE(IsDragging());
332 EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
333 }
334
TEST_F(LongPressDragSelectorTest,DragFast)335 TEST_F(LongPressDragSelectorTest, DragFast) {
336 LongPressDragSelector selector(this);
337 MockMotionEvent event;
338
339 // Start a touch sequence.
340 EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
341 EXPECT_FALSE(GetAndResetActiveStateChanged());
342
343 // Activate a longpress-triggered selection.
344 gfx::PointF selection_start(0, 10);
345 gfx::PointF selection_end(10, 10);
346 selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
347 EXPECT_FALSE(GetAndResetActiveStateChanged());
348 SetSelection(selection_start, selection_end);
349 selector.OnSelectionActivated();
350 EXPECT_TRUE(GetAndResetActiveStateChanged());
351 EXPECT_FALSE(IsDragging());
352
353 // Initiate drag motion.
354 EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 15, 5)));
355 EXPECT_FALSE(IsDragging());
356
357 // As the initial motion exceeds both endpoints, the closer bound should
358 // be used for dragging, in this case the selection end.
359 EXPECT_TRUE(selector.WillHandleTouchEvent(
360 event.MovePoint(0, 15.f + kSlop * 2.f, 5.f + kSlop)));
361 EXPECT_TRUE(IsDragging());
362 EXPECT_EQ(selection_end, DragPosition());
363
364 // Release the touch sequence, ending the drag.
365 EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
366 EXPECT_FALSE(IsDragging());
367 EXPECT_TRUE(GetAndResetActiveStateChanged());
368 }
369
TEST_F(LongPressDragSelectorTest,ScrollAfterLongPress)370 TEST_F(LongPressDragSelectorTest, ScrollAfterLongPress) {
371 LongPressDragSelector selector(this);
372 MockMotionEvent event;
373 gfx::PointF touch_point(0, 0);
374
375 // Start a touch sequence.
376 EXPECT_FALSE(selector.WillHandleTouchEvent(
377 event.PressPoint(touch_point.x(), touch_point.y())));
378
379 // Long-press and hold down.
380 selector.OnLongPressEvent(event.GetEventTime(), touch_point);
381
382 // Scroll the page. This should cancel long-press drag gesture.
383 touch_point.Offset(0, 2 * kSlop);
384 EXPECT_FALSE(selector.WillHandleTouchEvent(
385 event.MovePoint(0, touch_point.x(), touch_point.y())));
386 selector.OnScrollBeginEvent();
387
388 // Now if the selection is activated, because long-press drag gesture was
389 // canceled, active state of the long-press selector should not change.
390 selector.OnSelectionActivated();
391 EXPECT_FALSE(GetAndResetActiveStateChanged());
392
393 // Release the touch sequence.
394 selector.WillHandleTouchEvent(event.ReleasePoint());
395 }
396
397 } // namespace ui
398