// 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