// 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/touch_handle.h"

#include "base/memory/ptr_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/test/motion_event_test_utils.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/touch_selection/touch_handle_orientation.h"

using ui::test::MockMotionEvent;

namespace ui {
namespace {

const int kDefaultTapDurationMs = 200;
const double kDefaultTapSlop = 10.;
const float kDefaultDrawableSize = 10.f;
const gfx::RectF kDefaultViewportRect(0, 0, 560, 1200);

struct MockDrawableData {
  MockDrawableData()
      : orientation(TouchHandleOrientation::UNDEFINED),
        alpha(0.f),
        enabled(false),
        visible(false),
        rect(0, 0, kDefaultDrawableSize, kDefaultDrawableSize) {}
  TouchHandleOrientation orientation;
  float alpha;
  bool mirror_horizontal;
  bool mirror_vertical;
  bool enabled;
  bool visible;
  gfx::RectF rect;
};

class MockTouchHandleDrawable : public TouchHandleDrawable {
 public:
  explicit MockTouchHandleDrawable(MockDrawableData* data) : data_(data) {}
  ~MockTouchHandleDrawable() override {}

  void SetEnabled(bool enabled) override { data_->enabled = enabled; }

  void SetOrientation(TouchHandleOrientation orientation,
                      bool mirror_vertical,
                      bool mirror_horizontal) override {
    data_->orientation = orientation;
    data_->mirror_horizontal = mirror_horizontal;
    data_->mirror_vertical = mirror_vertical;
  }

  void SetOrigin(const gfx::PointF& origin) override {
    data_->rect.set_origin(origin);
  }

  void SetAlpha(float alpha) override {
    data_->alpha = alpha;
    data_->visible = alpha > 0;
  }

  // TODO(AviD): Add unittests for non-zero values of padding ratio once the
  // code refactoring is completed.
  float GetDrawableHorizontalPaddingRatio() const override { return 0; }

  gfx::RectF GetVisibleBounds() const override {
    return data_->rect;
  }

 private:
  MockDrawableData* data_;
};

}  // namespace

class TouchHandleTest : public testing::Test, public TouchHandleClient {
 public:
  TouchHandleTest()
      : dragging_(false),
        dragged_(false),
        tapped_(false),
        needs_animate_(false) {}

  ~TouchHandleTest() override {}

  // TouchHandleClient implementation.
  void OnDragBegin(const TouchSelectionDraggable& handler,
                   const gfx::PointF& drag_position) override {
    dragging_ = true;
  }

  void OnDragUpdate(const TouchSelectionDraggable& handler,
                    const gfx::PointF& drag_position) override {
    dragged_ = true;
    drag_position_ = drag_position;
  }

  void OnDragEnd(const TouchSelectionDraggable& handler) override {
    dragging_ = false;
  }

  bool IsWithinTapSlop(const gfx::Vector2dF& delta) const override {
    return delta.LengthSquared() < (kDefaultTapSlop * kDefaultTapSlop);
  }

  void OnHandleTapped(const TouchHandle& handle) override { tapped_ = true; }

  void SetNeedsAnimate() override { needs_animate_ = true; }

  std::unique_ptr<TouchHandleDrawable> CreateDrawable() override {
    return std::make_unique<MockTouchHandleDrawable>(&drawable_data_);
  }

  base::TimeDelta GetMaxTapDuration() const override {
    return base::TimeDelta::FromMilliseconds(kDefaultTapDurationMs);
  }

  bool IsAdaptiveHandleOrientationEnabled() const override {
    // Enable adaptive handle orientation by default for unittests
    return true;
  }

  void Animate(TouchHandle& handle) {
    needs_animate_ = false;
    base::TimeTicks now = base::TimeTicks::Now();
    while (handle.Animate(now))
      now += base::TimeDelta::FromMilliseconds(16);
  }

  bool GetAndResetHandleDragged() {
    bool dragged = dragged_;
    dragged_ = false;
    return dragged;
  }

  bool GetAndResetHandleTapped() {
    bool tapped = tapped_;
    tapped_ = false;
    return tapped;
  }

  bool GetAndResetNeedsAnimate() {
    bool needs_animate = needs_animate_;
    needs_animate_ = false;
    return needs_animate;
  }

  void UpdateHandleFocus(TouchHandle& handle,
                         gfx::PointF& top,
                         gfx::PointF& bottom) {
    handle.SetFocus(top, bottom);
    handle.UpdateHandleLayout();
  }

  void UpdateHandleOrientation(TouchHandle& handle,
                               TouchHandleOrientation orientation) {
    handle.SetOrientation(orientation);
    handle.UpdateHandleLayout();
  }

  void UpdateHandleVisibility(TouchHandle& handle,
                              bool visible,
                              TouchHandle::AnimationStyle animation_style) {
    handle.SetVisible(visible, animation_style);
    handle.UpdateHandleLayout();
  }

  void UpdateViewportRect(TouchHandle& handle, gfx::RectF viewport_rect) {
    handle.SetViewportRect(viewport_rect);
    handle.UpdateHandleLayout();
  }

  bool IsDragging() const { return dragging_; }
  const gfx::PointF& DragPosition() const { return drag_position_; }
  bool NeedsAnimate() const { return needs_animate_; }

  const MockDrawableData& drawable() { return drawable_data_; }

 private:
  gfx::PointF drag_position_;
  bool dragging_;
  bool dragged_;
  bool tapped_;
  bool needs_animate_;

  MockDrawableData drawable_data_;
};

TEST_F(TouchHandleTest, Visibility) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  EXPECT_FALSE(drawable().visible);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);
  EXPECT_TRUE(drawable().visible);
  EXPECT_EQ(1.f, drawable().alpha);

  UpdateHandleVisibility(handle, false, TouchHandle::ANIMATION_NONE);
  EXPECT_FALSE(drawable().visible);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);
  EXPECT_TRUE(drawable().visible);
  EXPECT_EQ(1.f, drawable().alpha);
}

TEST_F(TouchHandleTest, VisibilityAnimation) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  ASSERT_FALSE(NeedsAnimate());
  ASSERT_FALSE(drawable().visible);
  ASSERT_EQ(0.f, drawable().alpha);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_TRUE(NeedsAnimate());
  EXPECT_FALSE(drawable().visible);
  EXPECT_EQ(0.f, drawable().alpha);

  Animate(handle);
  EXPECT_TRUE(drawable().visible);
  EXPECT_EQ(1.f, drawable().alpha);

  ASSERT_FALSE(NeedsAnimate());
  UpdateHandleVisibility(handle, false, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_TRUE(NeedsAnimate());
  EXPECT_TRUE(drawable().visible);
  EXPECT_EQ(1.f, drawable().alpha);

  Animate(handle);
  EXPECT_FALSE(drawable().visible);
  EXPECT_EQ(0.f, drawable().alpha);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);
  EXPECT_EQ(1.f, drawable().alpha);
  EXPECT_FALSE(GetAndResetNeedsAnimate());
  UpdateHandleVisibility(handle, false, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_EQ(1.f, drawable().alpha);
  EXPECT_TRUE(GetAndResetNeedsAnimate());
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_EQ(1.f, drawable().alpha);
  EXPECT_FALSE(GetAndResetNeedsAnimate());
}

TEST_F(TouchHandleTest, Orientation) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  EXPECT_EQ(TouchHandleOrientation::CENTER, drawable().orientation);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);
  UpdateHandleOrientation(handle, TouchHandleOrientation::LEFT);
  EXPECT_EQ(TouchHandleOrientation::LEFT, drawable().orientation);

  UpdateHandleOrientation(handle, TouchHandleOrientation::RIGHT);
  EXPECT_EQ(TouchHandleOrientation::RIGHT, drawable().orientation);

  UpdateHandleOrientation(handle, TouchHandleOrientation::CENTER);
  EXPECT_EQ(TouchHandleOrientation::CENTER, drawable().orientation);
}

TEST_F(TouchHandleTest, Position) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  const gfx::Vector2dF koffset_vector(kDefaultDrawableSize / 2.f, 0);
  gfx::PointF focus_top;
  gfx::PointF focus_bottom;
  EXPECT_EQ(gfx::PointF() - koffset_vector, drawable().rect.origin());

  focus_top = gfx::PointF(7.3f, -3.7f);
  focus_bottom = gfx::PointF(7.3f, -2.7f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_EQ(focus_bottom - koffset_vector, drawable().rect.origin());

  focus_top = gfx::PointF(-7.3f, 3.7f);
  focus_bottom = gfx::PointF(-7.3f, 4.7f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_EQ(focus_bottom - koffset_vector, drawable().rect.origin());
}

TEST_F(TouchHandleTest, PositionNotUpdatedWhileFadingOrInvisible) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);
  ASSERT_TRUE(drawable().visible);
  ASSERT_FALSE(NeedsAnimate());

  const gfx::Vector2dF koffset_vector(kDefaultDrawableSize / 2.f, 0);
  gfx::PointF old_focus_top(7.3f, -3.7f);
  gfx::PointF old_focus_bottom(7.3f, -2.7f);
  UpdateHandleFocus(handle, old_focus_top, old_focus_bottom);
  ASSERT_EQ(old_focus_bottom - koffset_vector, drawable().rect.origin());

  UpdateHandleVisibility(handle, false, TouchHandle::ANIMATION_SMOOTH);
  ASSERT_TRUE(NeedsAnimate());

  gfx::PointF new_position_top(3.7f, -3.7f);
  gfx::PointF new_position_bottom(3.7f, -2.7f);
  UpdateHandleFocus(handle, new_position_top, new_position_bottom);
  EXPECT_EQ(old_focus_bottom - koffset_vector, drawable().rect.origin());
  EXPECT_TRUE(NeedsAnimate());

  // While the handle is fading, the new position should not take affect.
  base::TimeTicks now = base::TimeTicks::Now();
  while (handle.Animate(now)) {
    EXPECT_EQ(old_focus_bottom - koffset_vector, drawable().rect.origin());
    now += base::TimeDelta::FromMilliseconds(16);
  }

  // Even after the animation terminates, the new position will not be pushed.
  EXPECT_EQ(old_focus_bottom - koffset_vector, drawable().rect.origin());

  // As soon as the handle becomes visible, the new position will be pushed.
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_EQ(new_position_bottom - koffset_vector, drawable().rect.origin());
}

TEST_F(TouchHandleTest, Enabled) {
  // A newly created handle defaults to enabled.
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  EXPECT_TRUE(drawable().enabled);

  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_TRUE(GetAndResetNeedsAnimate());
  EXPECT_EQ(0.f, drawable().alpha);
  handle.SetEnabled(false);
  EXPECT_FALSE(drawable().enabled);

  // Dragging should not be allowed while the handle is disabled.
  base::TimeTicks event_time = base::TimeTicks::Now();
  const float kOffset = kDefaultDrawableSize / 2.f;
  MockMotionEvent event(
      MockMotionEvent::ACTION_DOWN, event_time, kOffset, kOffset);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));

  // Disabling mid-animation should cancel the animation.
  handle.SetEnabled(true);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_TRUE(drawable().enabled);
  EXPECT_EQ(0.f, drawable().alpha);
  // Since alpha value is 0, visibility of drawable will be false.
  EXPECT_FALSE(drawable().visible);
  EXPECT_TRUE(GetAndResetNeedsAnimate());
  handle.SetEnabled(false);
  EXPECT_FALSE(drawable().enabled);
  EXPECT_FALSE(drawable().visible);
  EXPECT_FALSE(handle.Animate(base::TimeTicks::Now()));

  // Disabling mid-drag should cancel the drag.
  handle.SetEnabled(true);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());
  handle.SetEnabled(false);
  EXPECT_FALSE(IsDragging());
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
}

TEST_F(TouchHandleTest, Drag) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);

  base::TimeTicks event_time = base::TimeTicks::Now();
  const float kOffset = kDefaultDrawableSize / 2.f;

  // The handle must be visible to trigger drag.
  MockMotionEvent event(
      MockMotionEvent::ACTION_DOWN, event_time, kOffset, kOffset);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  // ACTION_DOWN must fall within the drawable region to trigger drag.
  event = MockMotionEvent(MockMotionEvent::ACTION_DOWN, event_time, 50, 50);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());

  // Only ACTION_DOWN will trigger drag.
  event = MockMotionEvent(
      MockMotionEvent::ACTION_MOVE, event_time, kOffset, kOffset);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());

  // Start the drag.
  event = MockMotionEvent(
      MockMotionEvent::ACTION_DOWN, event_time, kOffset, kOffset);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  event = MockMotionEvent(
      MockMotionEvent::ACTION_MOVE, event_time, kOffset + 10, kOffset + 15);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(GetAndResetHandleDragged());
  EXPECT_TRUE(IsDragging());
  EXPECT_EQ(gfx::PointF(10, 15), DragPosition());

  event = MockMotionEvent(
      MockMotionEvent::ACTION_MOVE, event_time, kOffset - 10, kOffset - 15);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(GetAndResetHandleDragged());
  EXPECT_TRUE(IsDragging());
  EXPECT_EQ(gfx::PointF(-10, -15), DragPosition());

  event = MockMotionEvent(MockMotionEvent::ACTION_UP);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(GetAndResetHandleDragged());
  EXPECT_FALSE(IsDragging());

  // Non-ACTION_DOWN events after the drag has terminated should not be handled.
  event = MockMotionEvent(MockMotionEvent::ACTION_CANCEL);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
}

TEST_F(TouchHandleTest, DragDefersOrientationChange) {
  TouchHandle handle(this, TouchHandleOrientation::RIGHT, kDefaultViewportRect);
  ASSERT_EQ(drawable().orientation, TouchHandleOrientation::RIGHT);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  MockMotionEvent event(MockMotionEvent::ACTION_DOWN);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  // Orientation changes will be deferred until the drag ends.
  UpdateHandleOrientation(handle, TouchHandleOrientation::LEFT);
  EXPECT_EQ(TouchHandleOrientation::RIGHT, drawable().orientation);

  event = MockMotionEvent(MockMotionEvent::ACTION_MOVE);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(GetAndResetHandleDragged());
  EXPECT_TRUE(IsDragging());
  EXPECT_EQ(TouchHandleOrientation::RIGHT, drawable().orientation);

  event = MockMotionEvent(MockMotionEvent::ACTION_UP);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(GetAndResetHandleDragged());
  EXPECT_FALSE(IsDragging());
  EXPECT_EQ(TouchHandleOrientation::LEFT, drawable().orientation);
}

TEST_F(TouchHandleTest, DragDefersFade) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  MockMotionEvent event(MockMotionEvent::ACTION_DOWN);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  // Fade will be deferred until the drag ends.
  UpdateHandleVisibility(handle, false, TouchHandle::ANIMATION_SMOOTH);
  EXPECT_FALSE(NeedsAnimate());
  EXPECT_TRUE(drawable().visible);
  EXPECT_EQ(1.f, drawable().alpha);

  event = MockMotionEvent(MockMotionEvent::ACTION_MOVE);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(NeedsAnimate());
  EXPECT_TRUE(drawable().visible);

  event = MockMotionEvent(MockMotionEvent::ACTION_UP);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());
  EXPECT_TRUE(NeedsAnimate());

  Animate(handle);
  EXPECT_FALSE(drawable().visible);
  EXPECT_EQ(0.f, drawable().alpha);
}

TEST_F(TouchHandleTest, DragTargettingUsesTouchSize) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  gfx::PointF focus_top(kDefaultDrawableSize / 2, 0);
  gfx::PointF focus_bottom(kDefaultDrawableSize / 2, 0);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  base::TimeTicks event_time = base::TimeTicks::Now();
  const float kTouchSize = 24.f;
  const float kOffset = kDefaultDrawableSize + kTouchSize / 2.001f;

  MockMotionEvent event(
      MockMotionEvent::ACTION_DOWN, event_time, kOffset, 0);
  event.SetTouchMajor(0.f);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());

  event.SetTouchMajor(kTouchSize / 2.f);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());

  event.SetTouchMajor(kTouchSize);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  event.SetTouchMajor(kTouchSize * 2.f);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  // The touch hit test region should be circular.
  event = MockMotionEvent(
      MockMotionEvent::ACTION_DOWN, event_time, kOffset, kOffset);
  event.SetTouchMajor(kTouchSize);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());

  event.SetTouchMajor(kTouchSize * std::sqrt(2.f) - 0.1f);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());

  event.SetTouchMajor(kTouchSize * std::sqrt(2.f) + 0.1f);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  // Ensure a touch size of 0 can still register a hit.
  event = MockMotionEvent(MockMotionEvent::ACTION_DOWN,
                          event_time,
                          kDefaultDrawableSize / 2.f,
                          kDefaultDrawableSize / 2.f);
  event.SetTouchMajor(0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  // Touches centered above the handle region should never register a hit, even
  // if the touch region intersects the handle region.
  event = MockMotionEvent(MockMotionEvent::ACTION_DOWN, event_time,
                          kDefaultDrawableSize / 2.f, -kTouchSize / 3.f);
  event.SetTouchMajor(kTouchSize);
  EXPECT_FALSE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(IsDragging());
}

TEST_F(TouchHandleTest, Tap) {
  TouchHandle handle(this, TouchHandleOrientation::CENTER,
                     kDefaultViewportRect);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  base::TimeTicks event_time = base::TimeTicks::Now();

  // ACTION_CANCEL shouldn't trigger a tap.
  MockMotionEvent event(MockMotionEvent::ACTION_DOWN, event_time, 0, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  event_time += base::TimeDelta::FromMilliseconds(50);
  event = MockMotionEvent(MockMotionEvent::ACTION_CANCEL, event_time, 0, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(GetAndResetHandleTapped());

  // Long press shouldn't trigger a tap.
  event = MockMotionEvent(MockMotionEvent::ACTION_DOWN, event_time, 0, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  event_time += 2 * GetMaxTapDuration();
  event = MockMotionEvent(MockMotionEvent::ACTION_UP, event_time, 0, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(GetAndResetHandleTapped());

  // Only a brief tap within the slop region should trigger a tap.
  event = MockMotionEvent(MockMotionEvent::ACTION_DOWN, event_time, 0, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  event_time += GetMaxTapDuration() / 2;
  event = MockMotionEvent(
      MockMotionEvent::ACTION_MOVE, event_time, kDefaultTapSlop / 2.f, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  event = MockMotionEvent(
      MockMotionEvent::ACTION_UP, event_time, kDefaultTapSlop / 2.f, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(GetAndResetHandleTapped());

  // Moving beyond the slop region shouldn't trigger a tap.
  event = MockMotionEvent(MockMotionEvent::ACTION_DOWN, event_time, 0, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  event_time += GetMaxTapDuration() / 2;
  event = MockMotionEvent(
      MockMotionEvent::ACTION_MOVE, event_time, kDefaultTapSlop * 2.f, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  event = MockMotionEvent(
      MockMotionEvent::ACTION_UP, event_time, kDefaultTapSlop * 2.f, 0);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(GetAndResetHandleTapped());
}

TEST_F(TouchHandleTest, MirrorFocusChange) {
  TouchHandle handle(this, TouchHandleOrientation::LEFT, kDefaultViewportRect);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  gfx::PointF focus_top;
  gfx::PointF focus_bottom;
  EXPECT_EQ(gfx::PointF(), drawable().rect.origin());

  // Moving the selection to the bottom of the screen
  // should mirror the handle vertically.
  focus_top = gfx::PointF(17.3f, 1199.0f);
  focus_bottom = gfx::PointF(17.3f, 1200.0f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_TRUE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);

  // Moving the left handle to the left edge of the viewport
  // should mirror the handle horizontally as well.
  focus_top = gfx::PointF(2.3f, 1199.0f);
  focus_bottom = gfx::PointF(2.3f, 1200.0f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_TRUE(drawable().mirror_vertical);
  EXPECT_TRUE(drawable().mirror_horizontal);

  // When the selection is not at the bottom, only the
  // horizontal mirror flag should be true.
  focus_top = gfx::PointF(2.3f, 7.3f);
  focus_bottom = gfx::PointF(2.3f, 8.3f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_FALSE(drawable().mirror_vertical);
  EXPECT_TRUE(drawable().mirror_horizontal);

  // When selection handles intersects the viewport fully,
  // both mirror values should be false.
  focus_top = gfx::PointF(23.3f, 7.3f);
  focus_bottom = gfx::PointF(23.3f, 8.3f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_FALSE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);

  // Horizontal mirror should be true for Right handle when
  // the handle is at theright edge of the viewport.
  UpdateHandleOrientation(handle, TouchHandleOrientation::RIGHT);
  focus_top = gfx::PointF(560.0f, 7.3f);
  focus_bottom = gfx::PointF(560.0f, 8.3f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_FALSE(drawable().mirror_vertical);
  EXPECT_TRUE(drawable().mirror_horizontal);
}

TEST_F(TouchHandleTest, DragDefersMirrorChange) {
  TouchHandle handle(this, TouchHandleOrientation::RIGHT, kDefaultViewportRect);
  ASSERT_EQ(drawable().orientation, TouchHandleOrientation::RIGHT);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  base::TimeTicks event_time = base::TimeTicks::Now();
  const float kOffset = kDefaultDrawableSize / 2.f;

  // Start the drag.
  MockMotionEvent event(MockMotionEvent::ACTION_DOWN, event_time, kOffset,
                        kOffset);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_TRUE(IsDragging());

  UpdateHandleOrientation(handle, TouchHandleOrientation::LEFT);
  gfx::PointF focus_top(17.3f, 1199.0f);
  gfx::PointF focus_bottom(17.3f, 1200.0f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_FALSE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);

  // Mirror flag changes will be deferred until the drag ends.
  event = MockMotionEvent(MockMotionEvent::ACTION_UP);
  EXPECT_TRUE(handle.WillHandleTouchEvent(event));
  EXPECT_FALSE(GetAndResetHandleDragged());
  EXPECT_FALSE(IsDragging());
  EXPECT_TRUE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);
}

TEST_F(TouchHandleTest, ViewportSizeChange) {
  TouchHandle handle(this, TouchHandleOrientation::RIGHT, kDefaultViewportRect);
  ASSERT_EQ(drawable().orientation, TouchHandleOrientation::RIGHT);
  UpdateHandleVisibility(handle, true, TouchHandle::ANIMATION_NONE);

  gfx::PointF focus_top;
  gfx::PointF focus_bottom;
  EXPECT_EQ(gfx::PointF(), drawable().rect.origin());

  focus_top = gfx::PointF(230.0f, 599.0f);
  focus_bottom = gfx::PointF(230.0f, 600.0f);
  UpdateHandleFocus(handle, focus_top, focus_bottom);
  EXPECT_FALSE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);

  UpdateViewportRect(handle, gfx::RectF(0, 0, 560, 600));
  EXPECT_TRUE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);

  UpdateViewportRect(handle, gfx::RectF(0, 0, 230, 600));
  EXPECT_TRUE(drawable().mirror_vertical);
  EXPECT_TRUE(drawable().mirror_horizontal);

  UpdateViewportRect(handle, kDefaultViewportRect);
  EXPECT_FALSE(drawable().mirror_vertical);
  EXPECT_FALSE(drawable().mirror_horizontal);
}

}  // namespace ui
