blob: 885943957b65b6f075e2538509ed9f52e642c3f2 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/touch_selection/longpress_drag_selector.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/test/motion_event_test_utils.h"
using ui::test::MockMotionEvent;
namespace ui {
namespace {
const double kSlop = 10.;
} // namespace
class LongPressDragSelectorTest : public testing::Test,
public LongPressDragSelectorClient {
public:
LongPressDragSelectorTest()
: dragging_(false), active_state_changed_(false) {}
~LongPressDragSelectorTest() override {}
void SetSelection(const gfx::PointF& start, const gfx::PointF& end) {
selection_start_ = start;
selection_end_ = end;
}
bool GetAndResetActiveStateChanged() {
bool active_state_changed = active_state_changed_;
active_state_changed_ = false;
return active_state_changed;
}
bool IsDragging() const { return dragging_; }
const gfx::PointF& DragPosition() const { return drag_position_; }
// LongPressDragSelectorClient implementation.
void OnDragBegin(const TouchSelectionDraggable& handler,
const gfx::PointF& drag_position) override {
dragging_ = true;
drag_position_ = drag_position;
}
void OnDragUpdate(const TouchSelectionDraggable& handler,
const gfx::PointF& drag_position) override {
drag_position_ = drag_position;
}
void OnDragEnd(const TouchSelectionDraggable& handler) override {
dragging_ = false;
}
bool IsWithinTapSlop(const gfx::Vector2dF& delta) const override {
return delta.LengthSquared() < (kSlop * kSlop);
}
void OnLongPressDragActiveStateChanged() override {
active_state_changed_ = true;
}
gfx::PointF GetSelectionStart() const override { return selection_start_; }
gfx::PointF GetSelectionEnd() const override { return selection_end_; }
private:
bool dragging_;
bool active_state_changed_;
gfx::PointF drag_position_;
gfx::PointF selection_start_;
gfx::PointF selection_end_;
};
TEST_F(LongPressDragSelectorTest, BasicDrag) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Activate a longpress-triggered selection.
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
EXPECT_TRUE(GetAndResetActiveStateChanged());
// Motion should not be consumed until a selection is detected.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(IsDragging());
// Initiate drag motion. Note that the first move event after activation is
// used to initialize the drag start anchor.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_FALSE(IsDragging());
// The first slop exceeding motion will start the drag. As the motion is
// downward, the end selection point should be moved.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_TRUE(IsDragging());
EXPECT_EQ(selection_end, DragPosition());
// Subsequent motion will extend the selection.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_TRUE(IsDragging());
EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop), DragPosition());
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 3)));
EXPECT_TRUE(IsDragging());
EXPECT_EQ(selection_end + gfx::Vector2dF(0, kSlop * 2), DragPosition());
// Release the touch sequence, ending the drag. The selector will never
// consume the start/end events, only move events after a longpress.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
EXPECT_FALSE(IsDragging());
EXPECT_TRUE(GetAndResetActiveStateChanged());
}
TEST_F(LongPressDragSelectorTest, BasicReverseDrag) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Activate a longpress-triggered selection.
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
EXPECT_TRUE(GetAndResetActiveStateChanged());
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(IsDragging());
// Initiate drag motion.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 5, 0)));
EXPECT_FALSE(IsDragging());
// As the initial motion is leftward, toward the selection start, the
// selection start should be the drag point.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, -kSlop, 0)));
EXPECT_TRUE(IsDragging());
EXPECT_EQ(selection_start, DragPosition());
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop)));
EXPECT_TRUE(IsDragging());
EXPECT_EQ(selection_start + gfx::Vector2dF(kSlop, -kSlop), DragPosition());
// Release the touch sequence, ending the drag.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
EXPECT_FALSE(IsDragging());
EXPECT_TRUE(GetAndResetActiveStateChanged());
}
TEST_F(LongPressDragSelectorTest, NoActiveTouch) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Activate a longpress-triggered selection.
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(GetAndResetActiveStateChanged());
EXPECT_FALSE(IsDragging());
// Start a new touch sequence; it shouldn't initiate selection drag as there
// was no active touch sequence when the longpress selection started.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_FALSE(IsDragging());
EXPECT_EQ(gfx::PointF(), DragPosition());
}
TEST_F(LongPressDragSelectorTest, NoLongPress) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Activate a selection without a preceding longpress.
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(GetAndResetActiveStateChanged());
EXPECT_FALSE(IsDragging());
// Touch movement should not initiate selection drag.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_FALSE(IsDragging());
EXPECT_EQ(gfx::PointF(), DragPosition());
}
TEST_F(LongPressDragSelectorTest, NoValidLongPress) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
SetSelection(selection_start, selection_end);
// Activate a longpress-triggered selection, but at a time before the current
// touch down event.
selector.OnLongPressEvent(
event.GetEventTime() - base::TimeDelta::FromSeconds(1), gfx::PointF());
selector.OnSelectionActivated();
EXPECT_FALSE(GetAndResetActiveStateChanged());
EXPECT_FALSE(IsDragging());
// Activate a longpress-triggered selection, but at a place different than the
// current touch down event.
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF(kSlop, 0));
selector.OnSelectionActivated();
EXPECT_FALSE(GetAndResetActiveStateChanged());
EXPECT_FALSE(IsDragging());
// Touch movement should not initiate selection drag.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_FALSE(IsDragging());
EXPECT_EQ(gfx::PointF(), DragPosition());
}
TEST_F(LongPressDragSelectorTest, NoSelection) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Trigger a longpress. This will notify the client that detection is active,
// but until there's a longpress no drag selection should occur.
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
EXPECT_TRUE(GetAndResetActiveStateChanged());
EXPECT_FALSE(IsDragging());
// Touch movement should not initiate selection drag, as there is no active
// selection.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_FALSE(IsDragging());
EXPECT_EQ(gfx::PointF(), DragPosition());
EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
EXPECT_TRUE(GetAndResetActiveStateChanged());
}
TEST_F(LongPressDragSelectorTest, NoDragMotion) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Activate a longpress-triggered selection.
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
EXPECT_TRUE(GetAndResetActiveStateChanged());
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(IsDragging());
// Touch movement within the slop region should not initiate selection drag.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop / 2)));
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, -kSlop / 2)));
EXPECT_FALSE(IsDragging());
EXPECT_EQ(gfx::PointF(), DragPosition());
EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
EXPECT_TRUE(GetAndResetActiveStateChanged());
}
TEST_F(LongPressDragSelectorTest, SelectionDeactivated) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Activate a longpress-triggered selection.
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
EXPECT_TRUE(GetAndResetActiveStateChanged());
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(IsDragging());
// Start a drag selection.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_TRUE(IsDragging());
// Clearing the selection should force an end to the drag.
selector.OnSelectionDeactivated();
EXPECT_TRUE(GetAndResetActiveStateChanged());
EXPECT_FALSE(IsDragging());
// Subsequent motion should not be consumed.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, 0)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop)));
EXPECT_FALSE(selector.WillHandleTouchEvent(event.MovePoint(0, 0, kSlop * 2)));
EXPECT_FALSE(IsDragging());
EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
}
TEST_F(LongPressDragSelectorTest, DragFast) {
LongPressDragSelector selector(this);
MockMotionEvent event;
// Start a touch sequence.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.PressPoint(0, 0)));
EXPECT_FALSE(GetAndResetActiveStateChanged());
// Activate a longpress-triggered selection.
gfx::PointF selection_start(0, 10);
gfx::PointF selection_end(10, 10);
selector.OnLongPressEvent(event.GetEventTime(), gfx::PointF());
EXPECT_TRUE(GetAndResetActiveStateChanged());
SetSelection(selection_start, selection_end);
selector.OnSelectionActivated();
EXPECT_FALSE(IsDragging());
// Initiate drag motion.
EXPECT_TRUE(selector.WillHandleTouchEvent(event.MovePoint(0, 15, 5)));
EXPECT_FALSE(IsDragging());
// As the initial motion exceeds both endpoints, the closer bound should
// be used for dragging, in this case the selection end.
EXPECT_TRUE(selector.WillHandleTouchEvent(
event.MovePoint(0, 15.f + kSlop * 2.f, 5.f + kSlop)));
EXPECT_TRUE(IsDragging());
EXPECT_EQ(selection_end, DragPosition());
// Release the touch sequence, ending the drag.
EXPECT_FALSE(selector.WillHandleTouchEvent(event.ReleasePoint()));
EXPECT_FALSE(IsDragging());
EXPECT_TRUE(GetAndResetActiveStateChanged());
}
} // namespace ui