#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "ui/base/mojom/menu_source_type.mojom.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
#include "ui/views/controls/scrollbar/scroll_bar.h"
#include "ui/views/controls/scrollbar/scroll_bar_views.h"
#include "ui/views/test/view_metadata_test_utils.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_utils.h"
namespace views {
namespace {
class TestScrollBarController : public ScrollBarController {
public:
virtual ~TestScrollBarController() = default;
void ScrollToPosition(ScrollBar* source, int position) override {
last_source = source;
last_position = position;
}
int GetScrollIncrement(ScrollBar* source,
bool is_page,
bool is_positive) override {
last_source = source;
last_is_page = is_page;
last_is_positive = is_positive;
if (is_page) {
return 20;
}
return 10;
}
raw_ptr<ScrollBar> last_source = nullptr;
bool last_is_positive = false;
bool last_is_page = false;
int last_position = 0;
};
class TestScrollbarContainer : public View {
public:
TestScrollbarContainer() = default;
~TestScrollbarContainer() override = default;
TestScrollbarContainer(const TestScrollbarContainer&) = delete;
TestScrollbarContainer& operator=(const TestScrollbarContainer&) = delete;
void OnGestureEvent(ui::GestureEvent* event) override {
children()[0]->OnGestureEvent(event);
}
};
}
class ScrollBarViewsTest : public ViewsTestBase {
public:
static constexpr int kScrollBarID = 123;
ScrollBarViewsTest() = default;
void SetUp() override {
ViewsTestBase::SetUp();
controller_ = std::make_unique<TestScrollBarController>();
widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_POPUP);
params.bounds = gfx::Rect(0, 0, 100, 300);
widget_->Init(std::move(params));
widget_->Show();
auto* container =
widget_->SetContentsView(std::make_unique<TestScrollbarContainer>());
auto* bar = container->AddChildView(std::make_unique<ScrollBarViews>());
bar->SetBounds(0, 0, 100, 100);
bar->Update(100, 1000, 0);
bar->set_controller(controller_.get());
bar->SetID(kScrollBarID);
track_size_ = scrollbar()->GetTrackBounds().width();
}
void TearDown() override {
scrollbar()->set_controller(nullptr);
controller_.reset();
widget_.reset();
ViewsTestBase::TearDown();
}
protected:
ScrollBar* scrollbar() {
return static_cast<ScrollBar*>(
widget_->GetContentsView()->GetViewByID(kScrollBarID));
}
UniqueWidgetPtr widget_;
int track_size_;
std::unique_ptr<TestScrollBarController> controller_;
};
TEST_F(ScrollBarViewsTest, MetaDataTest) {
test::TestViewMetadata(scrollbar());
}
TEST_F(ScrollBarViewsTest, Scrolling) {
EXPECT_EQ(0, scrollbar()->GetPosition());
EXPECT_EQ(900, scrollbar()->GetMaxPosition());
EXPECT_EQ(0, scrollbar()->GetMinPosition());
scrollbar()->ScrollToThumbPosition(track_size_ / 2, true);
EXPECT_EQ(450, controller_->last_position);
EXPECT_EQ(scrollbar(), controller_->last_source);
scrollbar()->ScrollToThumbPosition(track_size_, true);
EXPECT_EQ(900, controller_->last_position);
scrollbar()->ScrollToThumbPosition(track_size_ + 100, true);
EXPECT_EQ(900, controller_->last_position);
scrollbar()->ScrollToThumbPosition(-10, false);
EXPECT_EQ(0, controller_->last_position);
scrollbar()->ScrollToThumbPosition(0, false);
scrollbar()->ScrollByAmount(ScrollBar::ScrollAmount::kNextLine);
EXPECT_EQ(10, controller_->last_position);
scrollbar()->ScrollByAmount(ScrollBar::ScrollAmount::kPrevLine);
EXPECT_EQ(0, controller_->last_position);
scrollbar()->ScrollByAmount(ScrollBar::ScrollAmount::kNextPage);
EXPECT_EQ(20, controller_->last_position);
scrollbar()->ScrollByAmount(ScrollBar::ScrollAmount::kPrevPage);
EXPECT_EQ(0, controller_->last_position);
}
TEST_F(ScrollBarViewsTest, ScrollBarFitsToBottom) {
scrollbar()->Update(100, 1999, 0);
EXPECT_EQ(0, scrollbar()->GetPosition());
EXPECT_EQ(1899, scrollbar()->GetMaxPosition());
EXPECT_EQ(0, scrollbar()->GetMinPosition());
scrollbar()->Update(100, 1999, 950);
EXPECT_EQ((scrollbar()->GetTrackBounds().width() -
scrollbar()->GetThumbLengthForTesting()) /
2,
scrollbar()->GetPosition());
scrollbar()->Update(100, 1999, 1899);
EXPECT_EQ(scrollbar()->GetTrackBounds().width() -
scrollbar()->GetThumbLengthForTesting(),
scrollbar()->GetPosition());
}
TEST_F(ScrollBarViewsTest, ScrollToEndAfterShrinkAndExpand) {
scrollbar()->Update(100, 1001, 0);
EXPECT_TRUE(scrollbar()->ScrollByContentsOffset(-1));
scrollbar()->Update(100, 1000, 0);
scrollbar()->Update(100, 1001, 0);
EXPECT_TRUE(scrollbar()->ScrollByContentsOffset(-1));
}
TEST_F(ScrollBarViewsTest, ThumbFullLengthOfTrack) {
scrollbar()->Update(100, 10, 0);
EXPECT_EQ(scrollbar()->GetTrackBounds().width(),
scrollbar()->GetThumbLengthForTesting());
scrollbar()->ScrollToThumbPosition(0, false);
EXPECT_EQ(0, scrollbar()->GetPosition());
scrollbar()->ScrollByAmount(ScrollBar::ScrollAmount::kNextLine);
EXPECT_EQ(0, scrollbar()->GetPosition());
scrollbar()->Update(100, 100, 0);
EXPECT_EQ(scrollbar()->GetTrackBounds().width(),
scrollbar()->GetThumbLengthForTesting());
scrollbar()->ScrollToThumbPosition(0, false);
EXPECT_EQ(0, scrollbar()->GetPosition());
scrollbar()->ScrollByAmount(ScrollBar::ScrollAmount::kNextLine);
EXPECT_EQ(0, scrollbar()->GetPosition());
scrollbar()->Update(100, 100, 10);
EXPECT_EQ(scrollbar()->GetTrackBounds().width(),
scrollbar()->GetThumbLengthForTesting());
}
TEST_F(ScrollBarViewsTest, AccessibleRole) {
ui::AXNodeData data;
scrollbar()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.role, ax::mojom::Role::kScrollBar);
EXPECT_EQ(scrollbar()->GetViewAccessibility().GetCachedRole(),
ax::mojom::Role::kScrollBar);
data = ui::AXNodeData();
scrollbar()->GetViewAccessibility().SetRole(ax::mojom::Role::kButton);
scrollbar()->GetViewAccessibility().GetAccessibleNodeData(&data);
EXPECT_EQ(data.role, ax::mojom::Role::kButton);
EXPECT_EQ(scrollbar()->GetViewAccessibility().GetCachedRole(),
ax::mojom::Role::kButton);
}
#if !BUILDFLAG(IS_MAC)
TEST_F(ScrollBarViewsTest, RightClickOpensMenu) {
EXPECT_EQ(nullptr, scrollbar()->menu_model_);
EXPECT_EQ(nullptr, scrollbar()->menu_runner_);
scrollbar()->set_context_menu_controller(scrollbar());
scrollbar()->ShowContextMenu(scrollbar()->GetBoundsInScreen().CenterPoint(),
ui::mojom::MenuSourceType::kMouse);
EXPECT_NE(nullptr, scrollbar()->menu_model_);
EXPECT_NE(nullptr, scrollbar()->menu_runner_);
}
TEST_F(ScrollBarViewsTest, TestPageScrollingByPress) {
ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
EXPECT_EQ(0, scrollbar()->GetPosition());
generator.MoveMouseTo(
scrollbar()->GetThumb()->GetBoundsInScreen().right_center() +
gfx::Vector2d(4, 0));
generator.ClickLeftButton();
generator.ClickLeftButton();
EXPECT_GT(scrollbar()->GetPosition(), 0);
}
TEST_F(ScrollBarViewsTest, DragThumbScrollsContent) {
ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
EXPECT_EQ(0, scrollbar()->GetPosition());
generator.MoveMouseTo(
scrollbar()->GetThumb()->GetBoundsInScreen().CenterPoint());
generator.PressLeftButton();
generator.MoveMouseBy(15, 0);
EXPECT_GE(scrollbar()->GetPosition(), 10);
generator.MoveMouseBy(0, 100);
EXPECT_GE(scrollbar()->GetPosition(), 10);
generator.MoveMouseBy(0, 100);
EXPECT_EQ(0, scrollbar()->GetPosition());
}
TEST_F(ScrollBarViewsTest, DragThumbScrollsContentWhenSnapBackDisabled) {
scrollbar()->GetThumb()->SetSnapBackOnDragOutside(false);
ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
EXPECT_EQ(0, scrollbar()->GetPosition());
generator.MoveMouseTo(
scrollbar()->GetThumb()->GetBoundsInScreen().CenterPoint());
generator.PressLeftButton();
generator.MoveMouseBy(10, 0);
EXPECT_GT(scrollbar()->GetPosition(), 0);
generator.MoveMouseBy(0, 200);
EXPECT_GT(scrollbar()->GetPosition(), 0);
}
TEST_F(ScrollBarViewsTest, FlingGestureScrollsView) {
constexpr int kNumScrollSteps = 100;
constexpr int kScrollVelocity = 10;
ui::test::EventGenerator generator(GetRootWindow(widget_.get()));
EXPECT_EQ(0, scrollbar()->GetPosition());
const gfx::Point start_pos =
widget_->GetContentsView()->GetBoundsInScreen().CenterPoint();
const gfx::Point end_pos = start_pos + gfx::Vector2d(-100, 0);
generator.GestureScrollSequence(
start_pos, end_pos,
generator.CalculateScrollDurationForFlingVelocity(
start_pos, end_pos, kScrollVelocity, kNumScrollSteps),
kNumScrollSteps);
EXPECT_GT(scrollbar()->GetPosition(), 0);
}
#endif
}