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

#include "ui/views/animation/slide_out_controller.h"

#include <memory>
#include <utility>

#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "ui/base/ui_base_features.h"
#include "ui/compositor/layer.h"
#include "ui/events/event.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/views/animation/slide_out_controller_delegate.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/view.h"

namespace views {

namespace {
constexpr int kSwipeControlWidth = 30;  // px
constexpr int kTargetWidth = 200;       // px
}  // namespace

class TestSlideOutControllerDelegate : public SlideOutControllerDelegate {
 public:
  explicit TestSlideOutControllerDelegate(View* target) : target_(target) {}
  ~TestSlideOutControllerDelegate() override = default;

  ui::Layer* GetSlideOutLayer() override { return target_->layer(); }

  void OnSlideStarted() override { ++slide_started_count_; }

  void OnSlideChanged(bool in_progress) override {
    slide_changed_last_value_ = in_progress;
    ++slide_changed_count_;
  }

  bool IsOnSlideChangedCalled() const { return (slide_changed_count_ > 0); }

  void OnSlideOut() override { ++slide_out_count_; }

  void reset() {
    slide_started_count_ = 0;
    slide_changed_count_ = 0;
    slide_out_count_ = 0;
    slide_changed_last_value_ = std::nullopt;
  }

  std::optional<bool> slide_changed_last_value_;
  int slide_started_count_ = 0;
  int slide_changed_count_ = 0;
  int slide_out_count_ = 0;

 private:
  const raw_ptr<View> target_;
};

class SlideOutControllerTest : public ViewsTestBase {
 public:
  SlideOutControllerTest() = default;
  ~SlideOutControllerTest() override = default;

  void SetUp() override {
    ViewsTestBase::SetUp();

    widget_ = std::make_unique<Widget>();

    Widget::InitParams params = CreateParams(
        Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_POPUP);
    params.bounds = gfx::Rect(50, 50, 650, 650);
    widget_->Init(std::move(params));
    View* root = widget_->GetRootView();

    View* target = root->AddChildView(std::make_unique<View>());
    target->SetPaintToLayer(ui::LAYER_TEXTURED);
    target->SetSize(gfx::Size(kTargetWidth, 50));

    widget_->Show();

    delegate_ = std::make_unique<TestSlideOutControllerDelegate>(target);
    slide_out_controller_ =
        std::make_unique<SlideOutController>(target, delegate_.get());
  }

  void TearDown() override {
    slide_out_controller_.reset();
    delegate_.reset();
    widget_.reset();

    ViewsTestBase::TearDown();
  }

 protected:
  SlideOutController* slide_out_controller() {
    return slide_out_controller_.get();
  }

  TestSlideOutControllerDelegate* delegate() { return delegate_.get(); }

  void PostSequentialGestureEvent(const ui::GestureEventDetails& details) {
    // Set the timestamp ahead one microsecond.
    sequential_event_timestamp_ += base::Microseconds(1);

    ui::GestureEvent gesture_event(
        0, 0, ui::EF_NONE, base::TimeTicks() + sequential_event_timestamp_,
        details);
    slide_out_controller()->OnGestureEvent(&gesture_event);
  }

  void PostSequentialSwipeEvent(int swipe_amount) {
    PostSequentialGestureEvent(
        ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));
    PostSequentialGestureEvent(ui::GestureEventDetails(
        ui::EventType::kGestureScrollUpdate, swipe_amount, 0));
    PostSequentialGestureEvent(
        ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));
  }

  void PostTrackPadSwipeEvent(ui::EventType type,
                              int swipe_amount,
                              int finger_count) {
    auto scroll_event =
        ui::ScrollEvent(type, gfx::PointF(), gfx::PointF(), base::TimeTicks(),
                        0, swipe_amount, 0, 0, 0, finger_count);
    slide_out_controller()->OnScrollEvent(&scroll_event);
  }

 private:
  std::unique_ptr<Widget> widget_;
  std::unique_ptr<SlideOutController> slide_out_controller_;
  std::unique_ptr<TestSlideOutControllerDelegate> delegate_;
  base::TimeDelta sequential_event_timestamp_;
};

TEST_F(SlideOutControllerTest, OnGestureEventAndDelegate) {
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));

  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

TEST_F(SlideOutControllerTest, SlideOutAndClose) {
  // Place a finger on notification.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));

  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_EQ(0, delegate()->slide_changed_count_);
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Move the finger horizontally by 101 px. (101 px is more than half of the
  // target width 200 px)
  PostSequentialGestureEvent(ui::GestureEventDetails(
      ui::EventType::kGestureScrollUpdate, kTargetWidth / 2 + 1, 0));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Release the finger.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  // The target has been scrolled out and the current location is moved by the
  // width (200px).
  EXPECT_EQ(kTargetWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure a deferred OnSlideOut handler is called.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(1, delegate()->slide_out_count_);
}

TEST_F(SlideOutControllerTest, SlideLittleAmountAndNotClose) {
  // Place a finger on notification.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));

  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Move the finger horizontally by 99 px. (99 px is less than half of the
  // target width 200 px)
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, 99, 0));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Release the finger.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  // The target has been moved back to the origin.
  EXPECT_EQ(0.f,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure no deferred SlideOut handler.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

class SwipeControlTest
    : public SlideOutControllerTest,
      public testing::WithParamInterface<SlideOutController::SlideMode> {
 public:
  SwipeControlTest() = default;

  SwipeControlTest(const SwipeControlTest&) = delete;
  SwipeControlTest& operator=(const SwipeControlTest&) = delete;

  ~SwipeControlTest() override = default;

  void SetUp() override {
    SlideOutControllerTest::SetUp();
    slide_out_controller()->set_slide_mode(GetParam());
  }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    SwipeControlTest,
    ::testing::Values(SlideOutController::SlideMode::kFull,
                      SlideOutController::SlideMode::kPartial));

TEST_P(SwipeControlTest, SetSwipeControlWidth_SwipeLessThanControlWidth) {
  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Place a finger on notification.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));

  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Move the finger horizontally by 29 px. (29 px is less than the swipe
  // control width).
  PostSequentialGestureEvent(ui::GestureEventDetails(
      ui::EventType::kGestureScrollUpdate, kSwipeControlWidth - 1, 0));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Release the finger.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  // The target has been moved back to the origin.
  EXPECT_EQ(0.f,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure no deferred SlideOut handler.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

TEST_P(SwipeControlTest, SwipeControlWidth_SwipeMoreThanControlWidth) {
  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Place a finger on notification.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));

  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Move the finger horizontally by 31 px. (31 px is more than the swipe
  // control width).
  PostSequentialGestureEvent(ui::GestureEventDetails(
      ui::EventType::kGestureScrollUpdate, kSwipeControlWidth + 1, 0));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Release the finger.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  // Slide is in progress.
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  // Swipe amount is the swipe control width.
  EXPECT_EQ(kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure no deferred SlideOut handler.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

TEST_P(SwipeControlTest, SetSwipeControlWidth_SwipeOut) {
  const bool swipe_out_supported =
      slide_out_controller()->mode() == SlideOutController::SlideMode::kFull;

  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Place a finger on notification.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));

  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Move the finger horizontally by 101 px. (101 px is more than the half of
  // the target width).
  PostSequentialGestureEvent(ui::GestureEventDetails(
      ui::EventType::kGestureScrollUpdate, kTargetWidth / 2 + 1, 0));

  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);

  delegate()->reset();

  // Release the finger.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  // ... and it is automatically slided out if |swipe_out_supported|.
  EXPECT_EQ(0, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(delegate()->slide_changed_last_value_.value(),
            !swipe_out_supported);
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(swipe_out_supported ? kTargetWidth : kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure a deferred SlideOut handler is called once.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(swipe_out_supported ? 1 : 0, delegate()->slide_out_count_);
}

TEST_P(SwipeControlTest, SwipeControlWidth_SnapAndSwipeOut) {
  const bool swipe_out_supported =
      slide_out_controller()->mode() == SlideOutController::SlideMode::kFull;

  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Snap to the swipe control.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));
  PostSequentialGestureEvent(ui::GestureEventDetails(
      ui::EventType::kGestureScrollUpdate, kSwipeControlWidth, 0));
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Swipe horizontally by 70 px.
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollBegin));
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, 70, 0));
  PostSequentialGestureEvent(
      ui::GestureEventDetails(ui::EventType::kGestureScrollEnd));

  // ... and it is automatically slided out if if |swipe_out_supported|.
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(delegate()->slide_changed_last_value_.value(),
            !swipe_out_supported);
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(swipe_out_supported ? kTargetWidth : kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure a deferred OnSlideOut handler is called.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(swipe_out_supported ? 1 : 0, delegate()->slide_out_count_);
}

TEST_P(SwipeControlTest, SwipeControlWidth_SnapAndSnapToControl) {
  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Snap to the swipe control.
  PostSequentialSwipeEvent(kSwipeControlWidth + 10);
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Swipe horizontally by 40 px for the same direction.
  PostSequentialSwipeEvent(40);

  // Snap automatically back to the swipe control.
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure no deferred OnSlideOut handler.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

TEST_P(SwipeControlTest, SwipeControlWidth_SnapAndBackToOrigin) {
  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Snap to the swipe control.
  PostSequentialSwipeEvent(kSwipeControlWidth + 20);
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_TRUE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(kSwipeControlWidth,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Swipe to the reversed direction by -1 px.
  PostSequentialSwipeEvent(-1);

  // Snap automatically back to the origin.
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(0,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure no deferred OnSlideOut handler.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

TEST_P(SwipeControlTest, SwipeControlWidth_NotSnapAndBackToOrigin) {
  // Set the width of swipe control.
  slide_out_controller()->SetSwipeControlWidth(kSwipeControlWidth);

  // Swipe partially but it's not enough to snap to the swipe control. So it is
  // back to the origin
  PostSequentialSwipeEvent(kSwipeControlWidth - 1);
  EXPECT_EQ(1, delegate()->slide_started_count_);
  EXPECT_TRUE(delegate()->IsOnSlideChangedCalled());
  EXPECT_FALSE(delegate()->slide_changed_last_value_.value());
  EXPECT_EQ(0, delegate()->slide_out_count_);
  EXPECT_EQ(0,
            delegate()->GetSlideOutLayer()->transform().To2dTranslation().x());

  delegate()->reset();

  // Ensure no deferred OnSlideOut handler.
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(delegate()->IsOnSlideChangedCalled());
  EXPECT_EQ(0, delegate()->slide_out_count_);
}

// Test class for making sure trackpad gestures work to trigger slide out
// events.
class TrackPadGestureTest : public SlideOutControllerTest {
 public:
  TrackPadGestureTest() = default;

  explicit TrackPadGestureTest(const SwipeControlTest&) = delete;
  TrackPadGestureTest& operator=(const TrackPadGestureTest&) = delete;

  ~TrackPadGestureTest() override = default;

  void SetUp() override {
    SlideOutControllerTest::SetUp();
    slide_out_controller()->set_trackpad_gestures_enabled(true);
  }
};

TEST_F(TrackPadGestureTest, SlideOut) {
  int width = delegate()->GetSlideOutLayer()->bounds().width();
  // A slide out should not be triggered if the scroll offset isn't less greater
  // than the view's width.
  PostTrackPadSwipeEvent(ui::EventType::kScroll, width,
                         /*finger_count=*/2);
  PostTrackPadSwipeEvent(ui::EventType::kScrollFlingStart, 0, 2);
  EXPECT_EQ(0, delegate()->slide_out_count_);

  // A slide out should not be triggered if the finger count is not equal to 2.
  PostTrackPadSwipeEvent(ui::EventType::kScroll, width + 1,
                         /*finger_count=*/3);
  PostTrackPadSwipeEvent(ui::EventType::kScrollFlingStart, 0,
                         /*finger_count=*/3);
  EXPECT_EQ(0, delegate()->slide_out_count_);

  PostTrackPadSwipeEvent(ui::EventType::kScroll, width + 1,
                         /*finger_count=*/2);
  // A slide out should not be triggered until the
  // `EventType::kScrollFlingStart` is posted.
  EXPECT_EQ(0, delegate()->slide_out_count_);

  PostTrackPadSwipeEvent(ui::EventType::kScrollFlingStart, 0, 2);
  EXPECT_EQ(1, delegate()->slide_out_count_);
}

}  // namespace views
