// Copyright 2013 The Flutter 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 "flutter/shell/platform/linux/fl_scrolling_manager.h"

#include <cstring>
#include <vector>

#include "gtest/gtest.h"
namespace {
typedef std::function<void(FlutterPointerPhase phase,
                           size_t timestamp,
                           double x,
                           double y,
                           double scroll_delta_x,
                           double scroll_delta_y,
                           int64_t buttons)>
    MousePointerCallHandler;
typedef std::function<void(size_t timestamp,
                           double x,
                           double y,
                           FlutterPointerPhase phase,
                           double pan_x,
                           double pan_y,
                           double scale,
                           double rotation)>
    PointerPanZoomCallHandler;

typedef struct {
  FlutterPointerPhase phase;
  size_t timestamp;
  double x;
  double y;
  double scroll_delta_x;
  double scroll_delta_y;
  int64_t buttons;
} MousePointerEventRecord;

typedef struct {
  size_t timestamp;
  double x;
  double y;
  FlutterPointerPhase phase;
  double pan_x;
  double pan_y;
  double scale;
  double rotation;
} PointerPanZoomEventRecord;

G_BEGIN_DECLS

G_DECLARE_FINAL_TYPE(FlMockScrollingViewDelegate,
                     fl_mock_scrolling_view_delegate,
                     FL,
                     MOCK_SCROLLING_VIEW_DELEGATE,
                     GObject)

G_END_DECLS

/***** FlMockScrollingViewDelegate *****/

struct _FlMockScrollingViewDelegate {
  GObject parent_instance;

  MousePointerCallHandler mouse_handler;
  PointerPanZoomCallHandler pan_zoom_handler;
};

static void fl_mock_view_scroll_delegate_iface_init(
    FlScrollingViewDelegateInterface* iface);

G_DEFINE_TYPE_WITH_CODE(
    FlMockScrollingViewDelegate,
    fl_mock_scrolling_view_delegate,
    G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE(fl_scrolling_view_delegate_get_type(),
                          fl_mock_view_scroll_delegate_iface_init))

static void fl_mock_scrolling_view_delegate_init(
    FlMockScrollingViewDelegate* self) {}

static void fl_mock_scrolling_view_delegate_dispose(GObject* object) {
  G_OBJECT_CLASS(fl_mock_scrolling_view_delegate_parent_class)->dispose(object);
}

static void fl_mock_scrolling_view_delegate_class_init(
    FlMockScrollingViewDelegateClass* klass) {
  G_OBJECT_CLASS(klass)->dispose = fl_mock_scrolling_view_delegate_dispose;
}

static void fl_mock_view_send_mouse_pointer_event(
    FlScrollingViewDelegate* delegate,
    FlutterPointerPhase phase,
    size_t timestamp,
    double x,
    double y,
    double scroll_delta_x,
    double scroll_delta_y,
    int64_t buttons) {
  FlMockScrollingViewDelegate* self = FL_MOCK_SCROLLING_VIEW_DELEGATE(delegate);
  self->mouse_handler(phase, timestamp, x, y, scroll_delta_x, scroll_delta_y,
                      buttons);
}

static void fl_mock_view_send_pointer_pan_zoom_event(
    FlScrollingViewDelegate* delegate,
    size_t timestamp,
    double x,
    double y,
    FlutterPointerPhase phase,
    double pan_x,
    double pan_y,
    double scale,
    double rotation) {
  FlMockScrollingViewDelegate* self = FL_MOCK_SCROLLING_VIEW_DELEGATE(delegate);
  self->pan_zoom_handler(timestamp, x, y, phase, pan_x, pan_y, scale, rotation);
}

static void fl_mock_view_scroll_delegate_iface_init(
    FlScrollingViewDelegateInterface* iface) {
  iface->send_mouse_pointer_event = fl_mock_view_send_mouse_pointer_event;
  iface->send_pointer_pan_zoom_event = fl_mock_view_send_pointer_pan_zoom_event;
}

static FlMockScrollingViewDelegate* fl_mock_scrolling_view_delegate_new() {
  FlMockScrollingViewDelegate* self = FL_MOCK_SCROLLING_VIEW_DELEGATE(
      g_object_new(fl_mock_scrolling_view_delegate_get_type(), nullptr));

  // Added to stop compiler complaining about an unused function.
  FL_IS_MOCK_SCROLLING_VIEW_DELEGATE(self);

  return self;
}

static void fl_mock_scrolling_view_set_mouse_handler(
    FlMockScrollingViewDelegate* self,
    MousePointerCallHandler handler) {
  self->mouse_handler = std::move(handler);
}

static void fl_mock_scrolling_view_set_pan_zoom_handler(
    FlMockScrollingViewDelegate* self,
    PointerPanZoomCallHandler handler) {
  self->pan_zoom_handler = std::move(handler);
}

/***** End FlMockScrollingViewDelegate *****/

class ScrollingTester {
 public:
  ScrollingTester() {
    view_ = fl_mock_scrolling_view_delegate_new();
    manager_ = fl_scrolling_manager_new(FL_SCROLLING_VIEW_DELEGATE(view_));
    fl_mock_scrolling_view_set_mouse_handler(
        view_,
        [](FlutterPointerPhase phase, size_t timestamp, double x, double y,
           double scroll_delta_x, double scroll_delta_y, int64_t buttons) {
          // do nothing
        });
    fl_mock_scrolling_view_set_pan_zoom_handler(
        view_,
        [](size_t timestamp, double x, double y, FlutterPointerPhase phase,
           double pan_x, double pan_y, double scale, double rotation) {
          // do nothing
        });
  }

  ~ScrollingTester() {
    g_clear_object(&view_);
    g_clear_object(&manager_);
  }

  FlScrollingManager* manager() { return manager_; }

  void recordMousePointerCallsTo(
      std::vector<MousePointerEventRecord>& storage) {
    fl_mock_scrolling_view_set_mouse_handler(
        view_, [&storage](FlutterPointerPhase phase, size_t timestamp, double x,
                          double y, double scroll_delta_x,
                          double scroll_delta_y, int64_t buttons) {
          storage.push_back(MousePointerEventRecord{
              .phase = phase,
              .timestamp = timestamp,
              .x = x,
              .y = y,
              .scroll_delta_x = scroll_delta_x,
              .scroll_delta_y = scroll_delta_y,
              .buttons = buttons,
          });
        });
  }

  void recordPointerPanZoomCallsTo(
      std::vector<PointerPanZoomEventRecord>& storage) {
    fl_mock_scrolling_view_set_pan_zoom_handler(
        view_, [&storage](size_t timestamp, double x, double y,
                          FlutterPointerPhase phase, double pan_x, double pan_y,
                          double scale, double rotation) {
          storage.push_back(PointerPanZoomEventRecord{
              .timestamp = timestamp,
              .x = x,
              .y = y,
              .phase = phase,
              .pan_x = pan_x,
              .pan_y = pan_y,
              .scale = scale,
              .rotation = rotation,
          });
        });
  }

 private:
  FlMockScrollingViewDelegate* view_;
  FlScrollingManager* manager_;
};

// Disgusting hack but could not find any way to create a GdkDevice
struct _FakeGdkDevice {
  GObject parent_instance;
  gchar* name;
  GdkInputSource source;
};
GdkDevice* makeFakeDevice(GdkInputSource source) {
  _FakeGdkDevice* device =
      static_cast<_FakeGdkDevice*>(g_malloc0(sizeof(_FakeGdkDevice)));
  device->source = source;
  // Bully the type checker
  (reinterpret_cast<GTypeInstance*>(device))->g_class =
      static_cast<GTypeClass*>(g_malloc0(sizeof(GTypeClass)));
  (reinterpret_cast<GTypeInstance*>(device))->g_class->g_type = GDK_TYPE_DEVICE;
  return reinterpret_cast<GdkDevice*>(device);
}

TEST(FlScrollingManagerTest, DiscreteDirectionional) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  GdkDevice* mouse = makeFakeDevice(GDK_SOURCE_MOUSE);
  GdkEventScroll* event =
      reinterpret_cast<GdkEventScroll*>(gdk_event_new(GDK_SCROLL));
  event->time = 1;
  event->x = 4.0;
  event->y = 8.0;
  event->device = mouse;
  event->direction = GDK_SCROLL_UP;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 0u);
  EXPECT_EQ(mouse_records.size(), 1u);
  EXPECT_EQ(mouse_records[0].x, 4.0);
  EXPECT_EQ(mouse_records[0].y, 8.0);
  EXPECT_EQ(mouse_records[0].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(mouse_records[0].scroll_delta_x, 0);
  EXPECT_EQ(mouse_records[0].scroll_delta_y, 53 * -1.0);
  event->direction = GDK_SCROLL_DOWN;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 0u);
  EXPECT_EQ(mouse_records.size(), 2u);
  EXPECT_EQ(mouse_records[1].x, 4.0);
  EXPECT_EQ(mouse_records[1].y, 8.0);
  EXPECT_EQ(mouse_records[1].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(mouse_records[1].scroll_delta_x, 0);
  EXPECT_EQ(mouse_records[1].scroll_delta_y, 53 * 1.0);
  event->direction = GDK_SCROLL_LEFT;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 0u);
  EXPECT_EQ(mouse_records.size(), 3u);
  EXPECT_EQ(mouse_records[2].x, 4.0);
  EXPECT_EQ(mouse_records[2].y, 8.0);
  EXPECT_EQ(mouse_records[2].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(mouse_records[2].scroll_delta_x, 53 * -1.0);
  EXPECT_EQ(mouse_records[2].scroll_delta_y, 0);
  event->direction = GDK_SCROLL_RIGHT;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 0u);
  EXPECT_EQ(mouse_records.size(), 4u);
  EXPECT_EQ(mouse_records[3].x, 4.0);
  EXPECT_EQ(mouse_records[3].y, 8.0);
  EXPECT_EQ(mouse_records[3].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(mouse_records[3].scroll_delta_x, 53 * 1.0);
  EXPECT_EQ(mouse_records[3].scroll_delta_y, 0);
}

TEST(FlScrollingManagerTest, DiscreteScrolling) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  GdkDevice* mouse = makeFakeDevice(GDK_SOURCE_MOUSE);
  GdkEventScroll* event =
      reinterpret_cast<GdkEventScroll*>(gdk_event_new(GDK_SCROLL));
  event->time = 1;
  event->x = 4.0;
  event->y = 8.0;
  event->delta_x = 1.0;
  event->delta_y = 2.0;
  event->device = mouse;
  event->direction = GDK_SCROLL_SMOOTH;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 0u);
  EXPECT_EQ(mouse_records.size(), 1u);
  EXPECT_EQ(mouse_records[0].x, 4.0);
  EXPECT_EQ(mouse_records[0].y, 8.0);
  EXPECT_EQ(mouse_records[0].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(mouse_records[0].scroll_delta_x, 53 * 1.0);
  EXPECT_EQ(mouse_records[0].scroll_delta_y, 53 * 2.0);
}

TEST(FlScrollingManagerTest, Panning) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  GdkDevice* touchpad = makeFakeDevice(GDK_SOURCE_TOUCHPAD);
  GdkEventScroll* event =
      reinterpret_cast<GdkEventScroll*>(gdk_event_new(GDK_SCROLL));
  event->time = 1;
  event->x = 4.0;
  event->y = 8.0;
  event->delta_x = 1.0;
  event->delta_y = 2.0;
  event->device = touchpad;
  event->direction = GDK_SCROLL_SMOOTH;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[0].x, 4.0);
  EXPECT_EQ(pan_zoom_records[0].y, 8.0);
  EXPECT_EQ(pan_zoom_records[0].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart);
  EXPECT_EQ(pan_zoom_records[1].x, 4.0);
  EXPECT_EQ(pan_zoom_records[1].y, 8.0);
  EXPECT_EQ(pan_zoom_records[1].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate);
  EXPECT_EQ(pan_zoom_records[1].pan_x, 53 * -1.0);  // directions get swapped
  EXPECT_EQ(pan_zoom_records[1].pan_y, 53 * -2.0);
  EXPECT_EQ(pan_zoom_records[1].scale, 1.0);
  EXPECT_EQ(pan_zoom_records[1].rotation, 0.0);
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[2].x, 4.0);
  EXPECT_EQ(pan_zoom_records[2].y, 8.0);
  EXPECT_EQ(pan_zoom_records[2].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomUpdate);
  EXPECT_EQ(pan_zoom_records[2].pan_x, 53 * -2.0);  // directions get swapped
  EXPECT_EQ(pan_zoom_records[2].pan_y, 53 * -4.0);
  EXPECT_EQ(pan_zoom_records[2].scale, 1.0);
  EXPECT_EQ(pan_zoom_records[2].rotation, 0.0);
  event->is_stop = true;
  fl_scrolling_manager_handle_scroll_event(tester.manager(), event, 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 4u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[3].x, 4.0);
  EXPECT_EQ(pan_zoom_records[3].y, 8.0);
  EXPECT_EQ(pan_zoom_records[3].timestamp,
            1000lu);  // Milliseconds -> Microseconds
  EXPECT_EQ(pan_zoom_records[3].phase, kPanZoomEnd);
}

TEST(FlScrollingManagerTest, Zooming) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  size_t time_start = g_get_real_time();
  fl_scrolling_manager_handle_zoom_begin(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 1u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[0].x, 0);
  EXPECT_EQ(pan_zoom_records[0].y, 0);
  EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart);
  EXPECT_GE(pan_zoom_records[0].timestamp, time_start);
  fl_scrolling_manager_handle_zoom_update(tester.manager(), 1.1);
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[1].x, 0);
  EXPECT_EQ(pan_zoom_records[1].y, 0);
  EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp);
  EXPECT_EQ(pan_zoom_records[1].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[1].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[1].scale, 1.1);
  EXPECT_EQ(pan_zoom_records[1].rotation, 0);
  fl_scrolling_manager_handle_zoom_end(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[2].x, 0);
  EXPECT_EQ(pan_zoom_records[2].y, 0);
  EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomEnd);
  EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp);
}

TEST(FlScrollingManagerTest, Rotating) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  size_t time_start = g_get_real_time();
  fl_scrolling_manager_handle_rotation_begin(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 1u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[0].x, 0);
  EXPECT_EQ(pan_zoom_records[0].y, 0);
  EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart);
  EXPECT_GE(pan_zoom_records[0].timestamp, time_start);
  fl_scrolling_manager_handle_rotation_update(tester.manager(), 0.5);
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[1].x, 0);
  EXPECT_EQ(pan_zoom_records[1].y, 0);
  EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp);
  EXPECT_EQ(pan_zoom_records[1].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[1].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[1].scale, 1.0);
  EXPECT_EQ(pan_zoom_records[1].rotation, 0.5);
  fl_scrolling_manager_handle_rotation_end(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[2].x, 0);
  EXPECT_EQ(pan_zoom_records[2].y, 0);
  EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomEnd);
  EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp);
}

TEST(FlScrollingManagerTest, SynchronizedZoomingAndRotating) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  size_t time_start = g_get_real_time();
  fl_scrolling_manager_handle_zoom_begin(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 1u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[0].x, 0);
  EXPECT_EQ(pan_zoom_records[0].y, 0);
  EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart);
  EXPECT_GE(pan_zoom_records[0].timestamp, time_start);
  fl_scrolling_manager_handle_zoom_update(tester.manager(), 1.1);
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[1].x, 0);
  EXPECT_EQ(pan_zoom_records[1].y, 0);
  EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp);
  EXPECT_EQ(pan_zoom_records[1].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[1].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[1].scale, 1.1);
  EXPECT_EQ(pan_zoom_records[1].rotation, 0);
  fl_scrolling_manager_handle_rotation_begin(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  fl_scrolling_manager_handle_rotation_update(tester.manager(), 0.5);
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  EXPECT_EQ(pan_zoom_records[2].x, 0);
  EXPECT_EQ(pan_zoom_records[2].y, 0);
  EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp);
  EXPECT_EQ(pan_zoom_records[2].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[2].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[2].scale, 1.1);
  EXPECT_EQ(pan_zoom_records[2].rotation, 0.5);
  fl_scrolling_manager_handle_zoom_end(tester.manager());
  // End event should only be sent after both zoom and rotate complete.
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  fl_scrolling_manager_handle_rotation_end(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 4u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[3].x, 0);
  EXPECT_EQ(pan_zoom_records[3].y, 0);
  EXPECT_EQ(pan_zoom_records[3].phase, kPanZoomEnd);
  EXPECT_GE(pan_zoom_records[3].timestamp, pan_zoom_records[2].timestamp);
}

// Make sure that zoom and rotate sequences which don't end at the same time
// don't cause any problems.
TEST(FlScrollingManagerTest, UnsynchronizedZoomingAndRotating) {
  ScrollingTester tester;
  std::vector<MousePointerEventRecord> mouse_records;
  std::vector<PointerPanZoomEventRecord> pan_zoom_records;
  tester.recordMousePointerCallsTo(mouse_records);
  tester.recordPointerPanZoomCallsTo(pan_zoom_records);
  size_t time_start = g_get_real_time();
  fl_scrolling_manager_handle_zoom_begin(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 1u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[0].x, 0);
  EXPECT_EQ(pan_zoom_records[0].y, 0);
  EXPECT_EQ(pan_zoom_records[0].phase, kPanZoomStart);
  EXPECT_GE(pan_zoom_records[0].timestamp, time_start);
  fl_scrolling_manager_handle_zoom_update(tester.manager(), 1.1);
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[1].x, 0);
  EXPECT_EQ(pan_zoom_records[1].y, 0);
  EXPECT_EQ(pan_zoom_records[1].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[1].timestamp, pan_zoom_records[0].timestamp);
  EXPECT_EQ(pan_zoom_records[1].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[1].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[1].scale, 1.1);
  EXPECT_EQ(pan_zoom_records[1].rotation, 0);
  fl_scrolling_manager_handle_rotation_begin(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 2u);
  EXPECT_EQ(mouse_records.size(), 0u);
  fl_scrolling_manager_handle_rotation_update(tester.manager(), 0.5);
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  EXPECT_EQ(pan_zoom_records[2].x, 0);
  EXPECT_EQ(pan_zoom_records[2].y, 0);
  EXPECT_EQ(pan_zoom_records[2].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[2].timestamp, pan_zoom_records[1].timestamp);
  EXPECT_EQ(pan_zoom_records[2].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[2].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[2].scale, 1.1);
  EXPECT_EQ(pan_zoom_records[2].rotation, 0.5);
  fl_scrolling_manager_handle_zoom_end(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 3u);
  fl_scrolling_manager_handle_rotation_update(tester.manager(), 1.0);
  EXPECT_EQ(pan_zoom_records.size(), 4u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[3].x, 0);
  EXPECT_EQ(pan_zoom_records[3].y, 0);
  EXPECT_EQ(pan_zoom_records[3].phase, kPanZoomUpdate);
  EXPECT_GE(pan_zoom_records[3].timestamp, pan_zoom_records[2].timestamp);
  EXPECT_EQ(pan_zoom_records[3].pan_x, 0);
  EXPECT_EQ(pan_zoom_records[3].pan_y, 0);
  EXPECT_EQ(pan_zoom_records[3].scale, 1.1);
  EXPECT_EQ(pan_zoom_records[3].rotation, 1.0);
  fl_scrolling_manager_handle_rotation_end(tester.manager());
  EXPECT_EQ(pan_zoom_records.size(), 5u);
  EXPECT_EQ(mouse_records.size(), 0u);
  EXPECT_EQ(pan_zoom_records[4].x, 0);
  EXPECT_EQ(pan_zoom_records[4].y, 0);
  EXPECT_EQ(pan_zoom_records[4].phase, kPanZoomEnd);
  EXPECT_GE(pan_zoom_records[4].timestamp, pan_zoom_records[3].timestamp);
}

}  // namespace
