chromium/chrome/browser/ui/webui/ash/mako/mako_bubble_event_handler_unittest.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/ash/mako/mako_bubble_event_handler.h"

#include "chrome/test/views/chrome_views_test_base.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/events/event.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/rect.h"

namespace ash {
namespace {

using testing::_;
using testing::VariantWith;

class DelegateForTest : public ash::MakoBubbleEventHandler::Delegate {
 public:
  virtual ~DelegateForTest() = default;

  const std::optional<SkRegion> GetDraggableRegion() override {
    SkRegion draggable_region;
    draggable_region.op(SkIRect::MakeLTRB(100, 100, 200, 200),
                        SkRegion::kUnion_Op);
    return draggable_region;
  }

  const gfx::Rect GetWidgetBoundsInScreen() override {
    return gfx::Rect(
        /*x=*/50,
        /*y=*/50,
        /*width=*/300,
        /*height=*/300);
  }

  void SetWidgetBoundsConstrained(const gfx::Rect bounds) override {
    widget_bounds_updated = true;
    widget_bounds = bounds;
  }

  void SetCursor(const ui::Cursor& cursor) override {}

  bool IsDraggingEnabled() override { return true; }

  bool IsResizingEnabled() override { return true; }

  bool widget_bounds_updated = false;
  gfx::Rect widget_bounds;
};

enum EventLocation {
  DRAGGABLE_REGION,
  TOP,
  BOTTOM,
  LEFT,
  RIGHT,
  TOP_LEFT,
  TOP_RIGHT,
  BOTTOM_LEFT,
  BOTTOM_RIGHT,
  OTHER
};

gfx::PointF MakeLocation(EventLocation location) {
  switch (location) {
    case EventLocation::DRAGGABLE_REGION:
      return gfx::PointF(150, 150);
    case EventLocation::TOP:
      return gfx::PointF(150, 2);
    case EventLocation::BOTTOM:
      return gfx::PointF(150, 298);
    case EventLocation::LEFT:
      return gfx::PointF(2, 150);
    case EventLocation::RIGHT:
      return gfx::PointF(298, 150);
    case EventLocation::TOP_LEFT:
      return gfx::PointF(2, 2);
    case EventLocation::TOP_RIGHT:
      return gfx::PointF(298, 2);
    case EventLocation::BOTTOM_LEFT:
      return gfx::PointF(2, 298);
    case EventLocation::BOTTOM_RIGHT:
      return gfx::PointF(298, 298);
    case EventLocation::OTHER:
      return gfx::PointF(50, 50);
    default:
      return gfx::PointF(0, 0);
  }
}

std::unique_ptr<ui::TouchEvent> MakeTouchEvent(ui::EventType type,
                                               EventLocation location) {
  return std::make_unique<ui::TouchEvent>(
      /*type=*/type,
      /*location=*/
      MakeLocation(location),
      /*root_location=*/gfx::PointF(0, 0),
      /*time_stamp=*/base::TimeTicks::Now(),
      /*pointer_details=*/ui::PointerDetails());
}

std::unique_ptr<ui::MouseEvent> MakeMouseEvent(ui::EventType type,
                                               EventLocation location) {
  return std::make_unique<ui::MouseEvent>(
      /*type=*/type,
      /*location=*/
      MakeLocation(location),
      /*root_location=*/gfx::PointF(0, 0),
      /*time_stamp=*/base::TimeTicks::Now(),
      /*flag=*/0,
      /*changed_button_flags=*/0,
      /*pointer_details=*/ui::PointerDetails());
}

class MakoBubbleEventHandlerTest : public ChromeViewsTestBase {
 public:
  MakoBubbleEventHandlerTest() : handler_(&delegate_) {}

 protected:
  DelegateForTest delegate_;
  ash::MakoBubbleEventHandler handler_;
};

TEST_F(MakoBubbleEventHandlerTest, TouchPressedEventStartsDragging) {
  std::unique_ptr<ui::TouchEvent> event = MakeTouchEvent(
      /*type=*/ui::EventType::kTouchPressed,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnTouchEvent(event.get());

  EXPECT_THAT(handler_.get_state_for_testing(),
              VariantWith<ash::MakoBubbleEventHandler::DraggingState>(_));
}

TEST_F(MakoBubbleEventHandlerTest, TouchPressedEventIsIgnored) {
  std::unique_ptr<ui::TouchEvent> event = MakeTouchEvent(
      /*type=*/ui::EventType::kTouchPressed,
      /*location=*/EventLocation::OTHER);

  handler_.OnTouchEvent(event.get());

  EXPECT_THAT(handler_.get_state_for_testing(),
              VariantWith<ash::MakoBubbleEventHandler::InitialState>(_));
}

TEST_F(MakoBubbleEventHandlerTest, MousePressedEventStartsDragging) {
  std::unique_ptr<ui::MouseEvent> event = MakeMouseEvent(
      /*type=*/ui::EventType::kMousePressed,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnMouseEvent(event.get());

  EXPECT_THAT(handler_.get_state_for_testing(),
              VariantWith<ash::MakoBubbleEventHandler::DraggingState>(_));
}

TEST_F(MakoBubbleEventHandlerTest, MousePressedEventIsIgnored) {
  std::unique_ptr<ui::MouseEvent> event = MakeMouseEvent(
      /*type=*/ui::EventType::kMousePressed,
      /*location=*/EventLocation::OTHER);

  handler_.OnMouseEvent(event.get());

  EXPECT_THAT(handler_.get_state_for_testing(),
              VariantWith<ash::MakoBubbleEventHandler::InitialState>(_));
}

TEST_F(MakoBubbleEventHandlerTest, TouchMovedEventProcceedsDragging) {
  // In this test, the widget was first positioned at (x=50,y=50,w=300,h=300).
  // Mouse drags from (140, 140) to (150, 150) and so the widget should be
  // moved to (x=60, y=60, w=300, h=300).
  handler_.set_state_for_testing(MakoBubbleEventHandler::DraggingState{
      .original_bounds_in_screen = gfx::Rect(
          /*x=*/50,
          /*y=*/50,
          /*width=*/300,
          /*height=*/300),
      // The original pointer position is relative to screen, so it's 140 + 50.
      .original_pointer_pos = gfx::Vector2d(/*x=*/140 + 50, /*y=*/140 + 50),
  });
  std::unique_ptr<ui::TouchEvent> event = MakeTouchEvent(
      /*type=*/ui::EventType::kTouchMoved,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnTouchEvent(event.get());

  EXPECT_TRUE(delegate_.widget_bounds_updated);
  EXPECT_EQ(delegate_.widget_bounds, gfx::Rect(
                                         /*x=*/60,
                                         /*y=*/60,
                                         /*width=*/300,
                                         /*height=*/300));
}

TEST_F(MakoBubbleEventHandlerTest, TouchMovedEventIsIgnored) {
  handler_.set_state_for_testing(MakoBubbleEventHandler::InitialState{});
  std::unique_ptr<ui::TouchEvent> event = MakeTouchEvent(
      /*type=*/ui::EventType::kTouchMoved,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnTouchEvent(event.get());

  EXPECT_FALSE(delegate_.widget_bounds_updated);
}

TEST_F(MakoBubbleEventHandlerTest, MouseDraggedEventProcceedsDragging) {
  // In this test, the widget was first positioned at (x=50,y=50,w=300,h=300).
  // Mouse drags from (140, 140) to (150, 150) and so the widget should be
  // moved to (x=60, y=60, w=300, h=300).
  handler_.set_state_for_testing(MakoBubbleEventHandler::DraggingState{
      .original_bounds_in_screen = gfx::Rect(
          /*x=*/50,
          /*y=*/50,
          /*width=*/300,
          /*height=*/300),
      // The original pointer position is relative to screen, so it's 140 + 50.
      .original_pointer_pos = gfx::Vector2d(/*x=*/140 + 50, /*y=*/140 + 50)});
  std::unique_ptr<ui::MouseEvent> event = MakeMouseEvent(
      /*type=*/ui::EventType::kMouseDragged,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnMouseEvent(event.get());

  EXPECT_TRUE(delegate_.widget_bounds_updated);
  EXPECT_EQ(delegate_.widget_bounds, gfx::Rect(
                                         /*x=*/60,
                                         /*y=*/60,
                                         /*width=*/300,
                                         /*height=*/300));
}

TEST_F(MakoBubbleEventHandlerTest, MouseDraggedEventIsIgnored) {
  handler_.set_state_for_testing(MakoBubbleEventHandler::InitialState{});
  std::unique_ptr<ui::MouseEvent> event = MakeMouseEvent(
      /*type=*/ui::EventType::kMouseDragged,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnMouseEvent(event.get());

  EXPECT_FALSE(delegate_.widget_bounds_updated);
}

TEST_F(MakoBubbleEventHandlerTest, TouchReleasedEventStopsDragging) {
  handler_.set_state_for_testing(MakoBubbleEventHandler::DraggingState{});

  std::unique_ptr<ui::TouchEvent> event = MakeTouchEvent(
      /*type=*/ui::EventType::kTouchReleased,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnTouchEvent(event.get());

  EXPECT_THAT(handler_.get_state_for_testing(),
              VariantWith<ash::MakoBubbleEventHandler::InitialState>(_));
}

TEST_F(MakoBubbleEventHandlerTest, MouseReleasedEventStopsDragging) {
  handler_.set_state_for_testing(MakoBubbleEventHandler::DraggingState{});

  std::unique_ptr<ui::MouseEvent> event = MakeMouseEvent(
      /*type=*/ui::EventType::kMouseReleased,
      /*location=*/EventLocation::DRAGGABLE_REGION);

  handler_.OnMouseEvent(event.get());

  EXPECT_THAT(handler_.get_state_for_testing(),
              VariantWith<ash::MakoBubbleEventHandler::InitialState>(_));
}

}  // namespace
}  // namespace ash