blob: 05a8b3743acc1cecf1bb0554effce79ab51ba976 [file] [log] [blame]
// Copyright (c) 2012 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/views/controls/scroll_view.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/border.h"
#include "ui/views/controls/scrollbar/base_scroll_bar_thumb.h"
#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
#include "ui/views/controls/scrollbar/scroll_bar_views.h"
#include "ui/views/controls/separator.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/test/widget_test.h"
#if defined(OS_MACOSX)
#include "ui/base/test/scoped_preferred_scroller_style_mac.h"
#endif
enum ScrollBarOrientation { HORIZONTAL, VERTICAL };
namespace views {
namespace test {
class ScrollViewTestApi {
public:
explicit ScrollViewTestApi(ScrollView* scroll_view)
: scroll_view_(scroll_view) {}
BaseScrollBar* GetBaseScrollBar(ScrollBarOrientation orientation) {
ScrollBar* scroll_bar = orientation == VERTICAL ? scroll_view_->vert_sb_
: scroll_view_->horiz_sb_;
return static_cast<BaseScrollBar*>(scroll_bar);
}
const base::Timer& GetScrollBarTimer(ScrollBarOrientation orientation) {
return GetBaseScrollBar(orientation)->repeater_.timer_for_testing();
}
BaseScrollBarThumb* GetScrollBarThumb(ScrollBarOrientation orientation) {
return GetBaseScrollBar(orientation)->thumb_;
}
gfx::Point IntegralViewOffset() {
return gfx::Point() - gfx::ScrollOffsetToFlooredVector2d(CurrentOffset());
}
gfx::ScrollOffset CurrentOffset() { return scroll_view_->CurrentOffset(); }
base::Timer* GetScrollBarHideTimer(ScrollBarOrientation orientation) {
return BaseScrollBar::GetHideTimerForTest(GetBaseScrollBar(orientation));
}
View* corner_view() { return scroll_view_->corner_view_; }
View* contents_viewport() { return scroll_view_->contents_viewport_; }
Separator* more_content_left() {
return scroll_view_->more_content_left_.get();
}
Separator* more_content_top() {
return scroll_view_->more_content_top_.get();
}
Separator* more_content_right() {
return scroll_view_->more_content_right_.get();
}
Separator* more_content_bottom() {
return scroll_view_->more_content_bottom_.get();
}
private:
ScrollView* scroll_view_;
DISALLOW_COPY_AND_ASSIGN(ScrollViewTestApi);
};
} // namespace test
namespace {
const int kWidth = 100;
const int kMinHeight = 50;
const int kMaxHeight = 100;
class FixedView : public View {
public:
FixedView() {}
~FixedView() override {}
void Layout() override {
gfx::Size pref = GetPreferredSize();
SetBounds(x(), y(), pref.width(), pref.height());
}
private:
DISALLOW_COPY_AND_ASSIGN(FixedView);
};
class CustomView : public View {
public:
CustomView() {}
~CustomView() override {}
const gfx::Point last_location() const { return last_location_; }
void Layout() override {
gfx::Size pref = GetPreferredSize();
int width = pref.width();
int height = pref.height();
if (parent()) {
width = std::max(parent()->width(), width);
height = std::max(parent()->height(), height);
}
SetBounds(x(), y(), width, height);
}
bool OnMousePressed(const ui::MouseEvent& event) override {
last_location_ = event.location();
return true;
}
private:
gfx::Point last_location_;
DISALLOW_COPY_AND_ASSIGN(CustomView);
};
void CheckScrollbarVisibility(const ScrollView& scroll_view,
ScrollBarOrientation orientation,
bool should_be_visible) {
const ScrollBar* scrollbar = orientation == HORIZONTAL
? scroll_view.horizontal_scroll_bar()
: scroll_view.vertical_scroll_bar();
if (should_be_visible) {
ASSERT_TRUE(scrollbar);
EXPECT_TRUE(scrollbar->visible());
} else {
EXPECT_TRUE(!scrollbar || !scrollbar->visible());
}
}
ui::MouseEvent TestLeftMouseAt(const gfx::Point& location, ui::EventType type) {
return ui::MouseEvent(type, location, location, base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
}
// This view has a large width, but the height always matches the parent's
// height. This is similar to a TableView that has many columns showing, but
// very few rows.
class VerticalResizingView : public View {
public:
VerticalResizingView() = default;
~VerticalResizingView() override = default;
void Layout() override {
int width = 10000;
int height = parent()->height();
SetBounds(x(), y(), width, height);
}
private:
DISALLOW_COPY_AND_ASSIGN(VerticalResizingView);
};
} // namespace
using test::ScrollViewTestApi;
// Simple test harness for testing a ScrollView directly.
class ScrollViewTest : public ViewsTestBase {
public:
ScrollViewTest() {}
View* InstallContents() {
const gfx::Rect default_outer_bounds(0, 0, 100, 100);
View* contents = new View;
scroll_view_.SetContents(contents);
scroll_view_.SetBoundsRect(default_outer_bounds);
return contents;
}
protected:
#if defined(OS_MACOSX)
void SetOverlayScrollersEnabled(bool enabled) {
// Ensure the old scroller override is destroyed before creating a new one.
// Otherwise, the swizzlers are interleaved and restore incorrect methods.
scroller_style_.reset();
scroller_style_ =
std::make_unique<ui::test::ScopedPreferredScrollerStyle>(enabled);
}
private:
// Disable overlay scrollers by default. This needs to be set before
// |scroll_view_| is initialized, otherwise scrollers will try to animate to
// change modes, which requires a MessageLoop to exist. Tests should only
// modify this via SetOverlayScrollersEnabled().
std::unique_ptr<ui::test::ScopedPreferredScrollerStyle> scroller_style_ =
std::make_unique<ui::test::ScopedPreferredScrollerStyle>(false);
protected:
#endif
int VerticalScrollBarWidth() {
return scroll_view_.vertical_scroll_bar()->GetThickness();
}
int HorizontalScrollBarHeight() {
return scroll_view_.horizontal_scroll_bar()->GetThickness();
}
ScrollView scroll_view_;
private:
DISALLOW_COPY_AND_ASSIGN(ScrollViewTest);
};
// Test harness that includes a Widget to help test ui::Event handling.
class WidgetScrollViewTest : public test::WidgetTest,
public ui::CompositorObserver {
public:
static const int kDefaultHeight = 100;
static const int kDefaultWidth = 100;
WidgetScrollViewTest() {}
// Call this before adding the ScrollView to test with overlay scrollbars.
void SetUseOverlayScrollers() {
use_overlay_scrollers_ = true;
}
// Adds a ScrollView with the given |contents_view| and does layout.
ScrollView* AddScrollViewWithContents(View* contents,
bool commit_layers = true) {
#if defined(OS_MACOSX)
scroller_style_.reset(
new ui::test::ScopedPreferredScrollerStyle(use_overlay_scrollers_));
#endif
const gfx::Rect default_bounds(50, 50, kDefaultWidth, kDefaultHeight);
widget_ = CreateTopLevelFramelessPlatformWidget();
ScrollView* scroll_view = new ScrollView();
scroll_view->SetContents(contents);
widget_->SetBounds(default_bounds);
widget_->Show();
widget_->SetContentsView(scroll_view);
scroll_view->Layout();
widget_->GetCompositor()->AddObserver(this);
// Ensure the Compositor has committed layer changes before attempting to
// use them for impl-side scrolling. Note that simply RunUntilIdle() works
// when tests are run in isolation, but compositor scheduling can interact
// between test runs in the general case.
if (commit_layers)
WaitForCommit();
return scroll_view;
}
// Adds a ScrollView with a contents view of the given |size| and does layout.
ScrollView* AddScrollViewWithContentSize(const gfx::Size& contents_size,
bool commit_layers = true) {
View* contents = new View;
contents->SetSize(contents_size);
return AddScrollViewWithContents(contents, commit_layers);
}
// Wait for a commit to be observed on the compositor.
void WaitForCommit() {
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, quit_closure_, TestTimeouts::action_timeout());
run_loop.Run();
EXPECT_TRUE(quit_closure_.is_null()) << "Timed out waiting for a commit.";
}
void TestClickAt(const gfx::Point& location) {
ui::MouseEvent press(TestLeftMouseAt(location, ui::ET_MOUSE_PRESSED));
ui::MouseEvent release(TestLeftMouseAt(location, ui::ET_MOUSE_RELEASED));
widget_->OnMouseEvent(&press);
widget_->OnMouseEvent(&release);
}
// testing::Test:
void TearDown() override {
widget_->GetCompositor()->RemoveObserver(this);
if (widget_)
widget_->CloseNow();
WidgetTest::TearDown();
}
private:
// ui::CompositorObserver:
void OnCompositingDidCommit(ui::Compositor* compositor) override {
quit_closure_.Run();
quit_closure_.Reset();
}
void OnCompositingStarted(ui::Compositor* compositor,
base::TimeTicks start_time) override {}
void OnCompositingEnded(ui::Compositor* compositor) override {}
void OnCompositingLockStateChanged(ui::Compositor* compositor) override {}
void OnCompositingChildResizing(ui::Compositor* compositor) override {}
void OnCompositingShuttingDown(ui::Compositor* compositor) override {}
Widget* widget_ = nullptr;
// Disable scrollbar hiding (i.e. disable overlay scrollbars) by default.
bool use_overlay_scrollers_ = false;
base::Closure quit_closure_;
#if defined(OS_MACOSX)
std::unique_ptr<ui::test::ScopedPreferredScrollerStyle> scroller_style_;
#endif
DISALLOW_COPY_AND_ASSIGN(WidgetScrollViewTest);
};
const int WidgetScrollViewTest::kDefaultHeight;
const int WidgetScrollViewTest::kDefaultWidth;
// Verifies the viewport is sized to fit the available space.
TEST_F(ScrollViewTest, ViewportSizedToFit) {
View* contents = InstallContents();
scroll_view_.Layout();
EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
}
// Verifies the viewport and content is sized to fit the available space for
// bounded scroll view.
TEST_F(ScrollViewTest, BoundedViewportSizedToFit) {
View* contents = InstallContents();
scroll_view_.ClipHeightTo(100, 200);
scroll_view_.SetBorder(CreateSolidBorder(2, 0));
scroll_view_.Layout();
EXPECT_EQ("2,2 96x96", contents->parent()->bounds().ToString());
// Make sure the width of |contents| is set properly not to overflow the
// viewport.
EXPECT_EQ(96, contents->width());
}
// Verifies that the vertical scrollbar does not unnecessarily appear for a
// contents whose height always matches the height of the viewport.
TEST_F(ScrollViewTest, VerticalScrollbarDoesNotAppearUnnecessarily) {
const gfx::Rect default_outer_bounds(0, 0, 100, 100);
View* contents = new VerticalResizingView;
scroll_view_.SetContents(contents);
scroll_view_.SetBoundsRect(default_outer_bounds);
scroll_view_.Layout();
EXPECT_FALSE(scroll_view_.vertical_scroll_bar()->visible());
EXPECT_TRUE(scroll_view_.horizontal_scroll_bar()->visible());
}
// Verifies the scrollbars are added as necessary.
// If on Mac, test the non-overlay scrollbars.
TEST_F(ScrollViewTest, ScrollBars) {
View* contents = InstallContents();
// Size the contents such that vertical scrollbar is needed.
contents->SetBounds(0, 0, 50, 400);
scroll_view_.Layout();
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth(),
contents->parent()->width());
EXPECT_EQ(100, contents->parent()->height());
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, false);
EXPECT_TRUE(!scroll_view_.horizontal_scroll_bar() ||
!scroll_view_.horizontal_scroll_bar()->visible());
ASSERT_TRUE(scroll_view_.vertical_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.vertical_scroll_bar()->visible());
// Size the contents such that horizontal scrollbar is needed.
contents->SetBounds(0, 0, 400, 50);
scroll_view_.Layout();
EXPECT_EQ(100, contents->parent()->width());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight(),
contents->parent()->height());
CheckScrollbarVisibility(scroll_view_, VERTICAL, false);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, true);
// Both horizontal and vertical.
contents->SetBounds(0, 0, 300, 400);
scroll_view_.Layout();
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth(),
contents->parent()->width());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight(),
contents->parent()->height());
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, true);
// Add a border, test vertical scrollbar.
const int kTopPadding = 1;
const int kLeftPadding = 2;
const int kBottomPadding = 3;
const int kRightPadding = 4;
scroll_view_.SetBorder(CreateEmptyBorder(kTopPadding, kLeftPadding,
kBottomPadding, kRightPadding));
contents->SetBounds(0, 0, 50, 400);
scroll_view_.Layout();
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth() - kLeftPadding -
kRightPadding,
contents->parent()->width());
EXPECT_EQ(100 - kTopPadding - kBottomPadding, contents->parent()->height());
EXPECT_TRUE(!scroll_view_.horizontal_scroll_bar() ||
!scroll_view_.horizontal_scroll_bar()->visible());
ASSERT_TRUE(scroll_view_.vertical_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.vertical_scroll_bar()->visible());
gfx::Rect bounds = scroll_view_.vertical_scroll_bar()->bounds();
EXPECT_EQ(100 - VerticalScrollBarWidth() - kRightPadding, bounds.x());
EXPECT_EQ(100 - kRightPadding, bounds.right());
EXPECT_EQ(kTopPadding, bounds.y());
EXPECT_EQ(100 - kBottomPadding, bounds.bottom());
// Horizontal with border.
contents->SetBounds(0, 0, 400, 50);
scroll_view_.Layout();
EXPECT_EQ(100 - kLeftPadding - kRightPadding, contents->parent()->width());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight() - kTopPadding -
kBottomPadding,
contents->parent()->height());
ASSERT_TRUE(scroll_view_.horizontal_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.horizontal_scroll_bar()->visible());
EXPECT_TRUE(!scroll_view_.vertical_scroll_bar() ||
!scroll_view_.vertical_scroll_bar()->visible());
bounds = scroll_view_.horizontal_scroll_bar()->bounds();
EXPECT_EQ(kLeftPadding, bounds.x());
EXPECT_EQ(100 - kRightPadding, bounds.right());
EXPECT_EQ(100 - kBottomPadding - HorizontalScrollBarHeight(), bounds.y());
EXPECT_EQ(100 - kBottomPadding, bounds.bottom());
// Both horizontal and vertical with border.
contents->SetBounds(0, 0, 300, 400);
scroll_view_.Layout();
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth() - kLeftPadding -
kRightPadding,
contents->parent()->width());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight() - kTopPadding -
kBottomPadding,
contents->parent()->height());
bounds = scroll_view_.horizontal_scroll_bar()->bounds();
// Check horiz.
ASSERT_TRUE(scroll_view_.horizontal_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.horizontal_scroll_bar()->visible());
bounds = scroll_view_.horizontal_scroll_bar()->bounds();
EXPECT_EQ(kLeftPadding, bounds.x());
EXPECT_EQ(100 - kRightPadding - VerticalScrollBarWidth(), bounds.right());
EXPECT_EQ(100 - kBottomPadding - HorizontalScrollBarHeight(), bounds.y());
EXPECT_EQ(100 - kBottomPadding, bounds.bottom());
// Check vert.
ASSERT_TRUE(scroll_view_.vertical_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.vertical_scroll_bar()->visible());
bounds = scroll_view_.vertical_scroll_bar()->bounds();
EXPECT_EQ(100 - VerticalScrollBarWidth() - kRightPadding, bounds.x());
EXPECT_EQ(100 - kRightPadding, bounds.right());
EXPECT_EQ(kTopPadding, bounds.y());
EXPECT_EQ(100 - kBottomPadding - HorizontalScrollBarHeight(),
bounds.bottom());
}
// Assertions around adding a header.
TEST_F(ScrollViewTest, Header) {
CustomView* header = new CustomView;
scroll_view_.SetHeader(header);
View* header_parent = header->parent();
View* contents = InstallContents();
scroll_view_.Layout();
// |header|s preferred size is empty, which should result in all space going
// to contents.
EXPECT_EQ("0,0 100x0", header->parent()->bounds().ToString());
EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
// With layered scrolling, ScrollView::Layout() will impose a size on the
// contents that fills the viewport. Since the test view doesn't have its own
// Layout, reset it in this case so that adding a header doesn't shift the
// contents down and require scrollbars.
if (contents->layer()) {
EXPECT_EQ("0,0 100x100", contents->bounds().ToString());
contents->SetBoundsRect(gfx::Rect());
}
EXPECT_EQ("0,0 0x0", contents->bounds().ToString());
// Get the header a height of 20.
header->SetPreferredSize(gfx::Size(10, 20));
EXPECT_EQ("0,0 100x20", header->parent()->bounds().ToString());
EXPECT_EQ("0,20 100x80", contents->parent()->bounds().ToString());
if (contents->layer()) {
EXPECT_EQ("0,0 100x80", contents->bounds().ToString());
contents->SetBoundsRect(gfx::Rect());
}
EXPECT_EQ("0,0 0x0", contents->bounds().ToString());
// Remove the header.
scroll_view_.SetHeader(NULL);
// SetHeader(NULL) deletes header.
header = NULL;
EXPECT_EQ("0,0 100x0", header_parent->bounds().ToString());
EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
}
// Verifies the scrollbars are added as necessary when a header is present.
TEST_F(ScrollViewTest, ScrollBarsWithHeader) {
CustomView* header = new CustomView;
scroll_view_.SetHeader(header);
View* contents = InstallContents();
header->SetPreferredSize(gfx::Size(10, 20));
// Size the contents such that vertical scrollbar is needed.
contents->SetBounds(0, 0, 50, 400);
scroll_view_.Layout();
EXPECT_EQ(0, contents->parent()->x());
EXPECT_EQ(20, contents->parent()->y());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth(),
contents->parent()->width());
EXPECT_EQ(80, contents->parent()->height());
EXPECT_EQ(0, header->parent()->x());
EXPECT_EQ(0, header->parent()->y());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth(),
header->parent()->width());
EXPECT_EQ(20, header->parent()->height());
EXPECT_TRUE(!scroll_view_.horizontal_scroll_bar() ||
!scroll_view_.horizontal_scroll_bar()->visible());
ASSERT_TRUE(scroll_view_.vertical_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.vertical_scroll_bar()->visible());
// Make sure the vertical scrollbar overlaps the header for traditional
// scrollbars and doesn't overlap the header for overlay scrollbars.
const int expected_scrollbar_y =
scroll_view_.vertical_scroll_bar()->OverlapsContent()
? header->bounds().bottom()
: header->y();
EXPECT_EQ(expected_scrollbar_y, scroll_view_.vertical_scroll_bar()->y());
EXPECT_EQ(header->y(), contents->y());
// Size the contents such that horizontal scrollbar is needed.
contents->SetBounds(0, 0, 400, 50);
scroll_view_.Layout();
EXPECT_EQ(0, contents->parent()->x());
EXPECT_EQ(20, contents->parent()->y());
EXPECT_EQ(100, contents->parent()->width());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight() - 20,
contents->parent()->height());
EXPECT_EQ(0, header->parent()->x());
EXPECT_EQ(0, header->parent()->y());
EXPECT_EQ(100, header->parent()->width());
EXPECT_EQ(20, header->parent()->height());
ASSERT_TRUE(scroll_view_.horizontal_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.horizontal_scroll_bar()->visible());
EXPECT_TRUE(!scroll_view_.vertical_scroll_bar() ||
!scroll_view_.vertical_scroll_bar()->visible());
// Both horizontal and vertical.
contents->SetBounds(0, 0, 300, 400);
scroll_view_.Layout();
EXPECT_EQ(0, contents->parent()->x());
EXPECT_EQ(20, contents->parent()->y());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth(),
contents->parent()->width());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight() - 20,
contents->parent()->height());
EXPECT_EQ(0, header->parent()->x());
EXPECT_EQ(0, header->parent()->y());
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutWidth(),
header->parent()->width());
EXPECT_EQ(20, header->parent()->height());
ASSERT_TRUE(scroll_view_.horizontal_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.horizontal_scroll_bar()->visible());
ASSERT_TRUE(scroll_view_.vertical_scroll_bar() != NULL);
EXPECT_TRUE(scroll_view_.vertical_scroll_bar()->visible());
}
// Verifies the header scrolls horizontally with the content.
TEST_F(ScrollViewTest, HeaderScrollsWithContent) {
ScrollViewTestApi test_api(&scroll_view_);
CustomView* contents = new CustomView;
scroll_view_.SetContents(contents);
contents->SetPreferredSize(gfx::Size(500, 500));
CustomView* header = new CustomView;
scroll_view_.SetHeader(header);
header->SetPreferredSize(gfx::Size(500, 20));
scroll_view_.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
EXPECT_EQ("0,0", test_api.IntegralViewOffset().ToString());
EXPECT_EQ("0,0", header->origin().ToString());
// Scroll the horizontal scrollbar.
ASSERT_TRUE(scroll_view_.horizontal_scroll_bar());
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), 1);
EXPECT_EQ("-1,0", test_api.IntegralViewOffset().ToString());
EXPECT_EQ("-1,0", header->origin().ToString());
// Scrolling the vertical scrollbar shouldn't effect the header.
ASSERT_TRUE(scroll_view_.vertical_scroll_bar());
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), 1);
EXPECT_EQ("-1,-1", test_api.IntegralViewOffset().ToString());
EXPECT_EQ("-1,0", header->origin().ToString());
}
// Verifies ScrollRectToVisible() on the child works.
TEST_F(ScrollViewTest, ScrollRectToVisible) {
ScrollViewTestApi test_api(&scroll_view_);
CustomView* contents = new CustomView;
scroll_view_.SetContents(contents);
contents->SetPreferredSize(gfx::Size(500, 1000));
scroll_view_.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
scroll_view_.Layout();
EXPECT_EQ("0,0", test_api.IntegralViewOffset().ToString());
// Scroll to y=405 height=10, this should make the y position of the content
// at (405 + 10) - viewport_height (scroll region bottom aligned).
contents->ScrollRectToVisible(gfx::Rect(0, 405, 10, 10));
const int viewport_height = test_api.contents_viewport()->height();
// Expect there to be a horizontal scrollbar, making the viewport shorter.
EXPECT_EQ(100 - scroll_view_.GetScrollBarLayoutHeight(), viewport_height);
gfx::ScrollOffset offset = test_api.CurrentOffset();
EXPECT_EQ(415 - viewport_height, offset.y());
// Scroll to the current y-location and 10x10; should do nothing.
contents->ScrollRectToVisible(gfx::Rect(0, offset.y(), 10, 10));
EXPECT_EQ(415 - viewport_height, test_api.CurrentOffset().y());
}
// Verifies ClipHeightTo() uses the height of the content when it is between the
// minimum and maximum height values.
TEST_F(ScrollViewTest, ClipHeightToNormalContentHeight) {
scroll_view_.ClipHeightTo(kMinHeight, kMaxHeight);
const int kNormalContentHeight = 75;
scroll_view_.SetContents(
new views::StaticSizedView(gfx::Size(kWidth, kNormalContentHeight)));
EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight),
scroll_view_.GetPreferredSize());
scroll_view_.SizeToPreferredSize();
scroll_view_.Layout();
EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight),
scroll_view_.contents()->size());
EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight), scroll_view_.size());
}
// Verifies ClipHeightTo() uses the minimum height when the content is shorter
// than the minimum height value.
TEST_F(ScrollViewTest, ClipHeightToShortContentHeight) {
scroll_view_.ClipHeightTo(kMinHeight, kMaxHeight);
const int kShortContentHeight = 10;
View* contents =
new views::StaticSizedView(gfx::Size(kWidth, kShortContentHeight));
scroll_view_.SetContents(contents);
EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_.GetPreferredSize());
scroll_view_.SizeToPreferredSize();
scroll_view_.Layout();
// Layered scrolling requires the contents to fill the viewport.
if (contents->layer()) {
EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_.contents()->size());
} else {
EXPECT_EQ(gfx::Size(kWidth, kShortContentHeight),
scroll_view_.contents()->size());
}
EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view_.size());
}
// Verifies ClipHeightTo() uses the maximum height when the content is longer
// thamn the maximum height value.
TEST_F(ScrollViewTest, ClipHeightToTallContentHeight) {
scroll_view_.ClipHeightTo(kMinHeight, kMaxHeight);
const int kTallContentHeight = 1000;
scroll_view_.SetContents(
new views::StaticSizedView(gfx::Size(kWidth, kTallContentHeight)));
EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_.GetPreferredSize());
scroll_view_.SizeToPreferredSize();
scroll_view_.Layout();
// The width may be less than kWidth if the scroll bar takes up some width.
EXPECT_GE(kWidth, scroll_view_.contents()->width());
EXPECT_EQ(kTallContentHeight, scroll_view_.contents()->height());
EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_.size());
}
// Verifies that when ClipHeightTo() produces a scrollbar, it reduces the width
// of the inner content of the ScrollView.
TEST_F(ScrollViewTest, ClipHeightToScrollbarUsesWidth) {
scroll_view_.ClipHeightTo(kMinHeight, kMaxHeight);
// Create a view that will be much taller than it is wide.
scroll_view_.SetContents(new views::ProportionallySizedView(1000));
// Without any width, it will default to 0,0 but be overridden by min height.
scroll_view_.SizeToPreferredSize();
EXPECT_EQ(gfx::Size(0, kMinHeight), scroll_view_.GetPreferredSize());
gfx::Size new_size(kWidth, scroll_view_.GetHeightForWidth(kWidth));
scroll_view_.SetSize(new_size);
scroll_view_.Layout();
int expected_width = kWidth - scroll_view_.GetScrollBarLayoutWidth();
EXPECT_EQ(scroll_view_.contents()->size().width(), expected_width);
EXPECT_EQ(scroll_view_.contents()->size().height(), 1000 * expected_width);
EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view_.size());
}
TEST_F(ScrollViewTest, CornerViewVisibility) {
View* contents = InstallContents();
View* corner_view = ScrollViewTestApi(&scroll_view_).corner_view();
contents->SetBounds(0, 0, 200, 200);
scroll_view_.Layout();
// Corner view should not exist if using overlay scrollbars.
if (scroll_view_.vertical_scroll_bar()->OverlapsContent()) {
EXPECT_FALSE(corner_view->parent());
return;
}
// Corner view should be visible when both scrollbars are visible.
EXPECT_EQ(&scroll_view_, corner_view->parent());
EXPECT_TRUE(corner_view->visible());
// Corner view should be aligned to the scrollbars.
EXPECT_EQ(scroll_view_.vertical_scroll_bar()->x(), corner_view->x());
EXPECT_EQ(scroll_view_.horizontal_scroll_bar()->y(), corner_view->y());
EXPECT_EQ(scroll_view_.GetScrollBarLayoutWidth(), corner_view->width());
EXPECT_EQ(scroll_view_.GetScrollBarLayoutHeight(), corner_view->height());
// Corner view should be removed when only the vertical scrollbar is visible.
contents->SetBounds(0, 0, 50, 200);
scroll_view_.Layout();
EXPECT_FALSE(corner_view->parent());
// ... or when only the horizontal scrollbar is visible.
contents->SetBounds(0, 0, 200, 50);
scroll_view_.Layout();
EXPECT_FALSE(corner_view->parent());
// ... or when no scrollbar is visible.
contents->SetBounds(0, 0, 50, 50);
scroll_view_.Layout();
EXPECT_FALSE(corner_view->parent());
// Corner view should reappear when both scrollbars reappear.
contents->SetBounds(0, 0, 200, 200);
scroll_view_.Layout();
EXPECT_EQ(&scroll_view_, corner_view->parent());
EXPECT_TRUE(corner_view->visible());
}
TEST_F(ScrollViewTest, ChildWithLayerTest) {
View* contents = InstallContents();
ScrollViewTestApi test_api(&scroll_view_);
if (test_api.contents_viewport()->layer())
return;
View* child = new View();
contents->AddChildView(child);
child->SetPaintToLayer(ui::LAYER_TEXTURED);
ASSERT_TRUE(test_api.contents_viewport()->layer());
// The default ScrollView color is opaque, so that fills bounds opaquely
// should be true.
EXPECT_TRUE(test_api.contents_viewport()->layer()->fills_bounds_opaquely());
// Setting a transparent color should make fills opaquely false.
scroll_view_.SetBackgroundColor(SK_ColorTRANSPARENT);
EXPECT_FALSE(test_api.contents_viewport()->layer()->fills_bounds_opaquely());
child->DestroyLayer();
EXPECT_FALSE(test_api.contents_viewport()->layer());
View* child_child = new View();
child->AddChildView(child_child);
EXPECT_FALSE(test_api.contents_viewport()->layer());
child->SetPaintToLayer(ui::LAYER_TEXTURED);
EXPECT_TRUE(test_api.contents_viewport()->layer());
}
// Validates that if a child of a ScrollView adds a layer, then a layer
// is added to the ScrollView's viewport.
TEST_F(ScrollViewTest, DontCreateLayerOnViewportIfLayerOnScrollViewCreated) {
View* contents = InstallContents();
ScrollViewTestApi test_api(&scroll_view_);
if (test_api.contents_viewport()->layer())
return;
scroll_view_.SetPaintToLayer();
View* child = new View();
contents->AddChildView(child);
child->SetPaintToLayer(ui::LAYER_TEXTURED);
EXPECT_FALSE(test_api.contents_viewport()->layer());
}
#if defined(OS_MACOSX)
// Tests the overlay scrollbars on Mac. Ensure that they show up properly and
// do not overlap each other.
TEST_F(ScrollViewTest, CocoaOverlayScrollBars) {
SetOverlayScrollersEnabled(true);
View* contents = InstallContents();
// Size the contents such that vertical scrollbar is needed.
// Since it is overlaid, the ViewPort size should match the ScrollView.
contents->SetBounds(0, 0, 50, 400);
scroll_view_.Layout();
EXPECT_EQ(100, contents->parent()->width());
EXPECT_EQ(100, contents->parent()->height());
EXPECT_EQ(0, scroll_view_.GetScrollBarLayoutWidth());
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, false);
// Size the contents such that horizontal scrollbar is needed.
contents->SetBounds(0, 0, 400, 50);
scroll_view_.Layout();
EXPECT_EQ(100, contents->parent()->width());
EXPECT_EQ(100, contents->parent()->height());
EXPECT_EQ(0, scroll_view_.GetScrollBarLayoutHeight());
CheckScrollbarVisibility(scroll_view_, VERTICAL, false);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, true);
// Both horizontal and vertical scrollbars.
contents->SetBounds(0, 0, 300, 400);
scroll_view_.Layout();
EXPECT_EQ(100, contents->parent()->width());
EXPECT_EQ(100, contents->parent()->height());
EXPECT_EQ(0, scroll_view_.GetScrollBarLayoutWidth());
EXPECT_EQ(0, scroll_view_.GetScrollBarLayoutHeight());
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, true);
// Make sure the horizontal and vertical scrollbars don't overlap each other.
gfx::Rect vert_bounds = scroll_view_.vertical_scroll_bar()->bounds();
gfx::Rect horiz_bounds = scroll_view_.horizontal_scroll_bar()->bounds();
EXPECT_EQ(vert_bounds.x(), horiz_bounds.right());
EXPECT_EQ(horiz_bounds.y(), vert_bounds.bottom());
// Switch to the non-overlay style and check that the ViewPort is now sized
// to be smaller, and ScrollbarWidth and ScrollbarHeight are non-zero.
SetOverlayScrollersEnabled(false);
EXPECT_EQ(100 - VerticalScrollBarWidth(), contents->parent()->width());
EXPECT_EQ(100 - HorizontalScrollBarHeight(), contents->parent()->height());
EXPECT_NE(0, VerticalScrollBarWidth());
EXPECT_NE(0, HorizontalScrollBarHeight());
}
// Test overlay scrollbar behavior when just resting fingers on the trackpad.
TEST_F(WidgetScrollViewTest, ScrollersOnRest) {
// Allow expectations to distinguish between fade outs and immediate changes.
ui::ScopedAnimationDurationScaleMode really_animate(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
const float kMaxOpacity = 0.8f; // Constant from cocoa_scroll_bar.mm.
SetUseOverlayScrollers();
// Set up with both scrollers.
ScrollView* scroll_view = AddScrollViewWithContentSize(
gfx::Size(kDefaultWidth * 5, kDefaultHeight * 5));
ScrollViewTestApi test_api(scroll_view);
BaseScrollBar* bar[]{test_api.GetBaseScrollBar(HORIZONTAL),
test_api.GetBaseScrollBar(VERTICAL)};
base::Timer* hide_timer[]{test_api.GetScrollBarHideTimer(HORIZONTAL),
test_api.GetScrollBarHideTimer(VERTICAL)};
EXPECT_EQ(0, bar[HORIZONTAL]->layer()->opacity());
EXPECT_EQ(0, bar[VERTICAL]->layer()->opacity());
ui::test::EventGenerator generator(
GetContext(), scroll_view->GetWidget()->GetNativeWindow());
generator.GenerateTrackpadRest();
// Scrollers should be max opacity without an animation.
EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->opacity());
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->opacity());
EXPECT_FALSE(hide_timer[HORIZONTAL]->IsRunning());
EXPECT_FALSE(hide_timer[VERTICAL]->IsRunning());
generator.CancelTrackpadRest();
// Scrollers should start fading out, but only after a delay.
for (ScrollBarOrientation orientation : {HORIZONTAL, VERTICAL}) {
EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->GetTargetOpacity());
EXPECT_TRUE(hide_timer[orientation]->IsRunning());
// Trigger the timer. Should then be fading out.
hide_timer[orientation]->user_task().Run();
hide_timer[orientation]->Stop();
EXPECT_EQ(0, bar[orientation]->layer()->GetTargetOpacity());
}
// Rest again.
generator.GenerateTrackpadRest();
EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->GetTargetOpacity());
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->GetTargetOpacity());
// Scroll vertically.
const float y_offset = 3;
const int kSteps = 1;
const int kNnumFingers = 2;
generator.ScrollSequence(generator.current_location(), base::TimeDelta(), 0,
y_offset, kSteps, kNnumFingers);
// Horizontal scroller should start fading out immediately.
EXPECT_EQ(kMaxOpacity, bar[HORIZONTAL]->layer()->opacity());
EXPECT_EQ(0, bar[HORIZONTAL]->layer()->GetTargetOpacity());
EXPECT_FALSE(hide_timer[HORIZONTAL]->IsRunning());
// Vertical should remain visible, but ready to fade out after a delay.
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->opacity());
EXPECT_EQ(kMaxOpacity, bar[VERTICAL]->layer()->GetTargetOpacity());
EXPECT_TRUE(hide_timer[VERTICAL]->IsRunning());
// Scrolling should have occurred.
EXPECT_EQ(gfx::ScrollOffset(0, y_offset), test_api.CurrentOffset());
// Then, scrolling horizontally should show the horizontal scroller. The
// vertical scroller should still be visible, running its hide timer.
const float x_offset = 5;
generator.ScrollSequence(generator.current_location(), base::TimeDelta(),
x_offset, 0, kSteps, kNnumFingers);
for (ScrollBarOrientation orientation : {HORIZONTAL, VERTICAL}) {
EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->opacity());
EXPECT_EQ(kMaxOpacity, bar[orientation]->layer()->GetTargetOpacity());
EXPECT_TRUE(hide_timer[orientation]->IsRunning());
}
// Now scrolling has occurred in both directions.
EXPECT_EQ(gfx::ScrollOffset(x_offset, y_offset), test_api.CurrentOffset());
}
#endif // OS_MACOSX
// Test that increasing the size of the viewport "below" scrolled content causes
// the content to scroll up so that it still fills the viewport.
TEST_F(ScrollViewTest, ConstrainScrollToBounds) {
ScrollViewTestApi test_api(&scroll_view_);
View* contents = InstallContents();
contents->SetBoundsRect(gfx::Rect(0, 0, 300, 300));
scroll_view_.Layout();
EXPECT_EQ(gfx::ScrollOffset(), test_api.CurrentOffset());
// Scroll as far as it goes and query location to discount scroll bars.
contents->ScrollRectToVisible(gfx::Rect(300, 300, 1, 1));
const gfx::ScrollOffset fully_scrolled = test_api.CurrentOffset();
EXPECT_NE(gfx::ScrollOffset(), fully_scrolled);
// Making the viewport 55 pixels taller should scroll up the same amount.
scroll_view_.SetBoundsRect(gfx::Rect(0, 0, 100, 155));
scroll_view_.Layout();
EXPECT_EQ(fully_scrolled.y() - 55, test_api.CurrentOffset().y());
EXPECT_EQ(fully_scrolled.x(), test_api.CurrentOffset().x());
// And 77 pixels wider should scroll left. Also make it short again: the y-
// offset from the last change should remain.
scroll_view_.SetBoundsRect(gfx::Rect(0, 0, 177, 100));
scroll_view_.Layout();
EXPECT_EQ(fully_scrolled.y() - 55, test_api.CurrentOffset().y());
EXPECT_EQ(fully_scrolled.x() - 77, test_api.CurrentOffset().x());
}
// Calling Layout on ScrollView should not reset the scroll location.
TEST_F(ScrollViewTest, ContentScrollNotResetOnLayout) {
ScrollViewTestApi test_api(&scroll_view_);
CustomView* contents = new CustomView;
contents->SetPreferredSize(gfx::Size(300, 300));
scroll_view_.SetContents(contents);
scroll_view_.ClipHeightTo(0, 150);
scroll_view_.SizeToPreferredSize();
// ScrollView preferred width matches that of |contents|, with the height
// capped at the value we clipped to.
EXPECT_EQ(gfx::Size(300, 150), scroll_view_.size());
// Scroll down.
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), 25);
EXPECT_EQ(25, test_api.CurrentOffset().y());
// Call Layout; no change to scroll position.
scroll_view_.Layout();
EXPECT_EQ(25, test_api.CurrentOffset().y());
// Change contents of |contents|, call Layout; still no change to scroll
// position.
contents->SetPreferredSize(gfx::Size(300, 500));
contents->InvalidateLayout();
scroll_view_.Layout();
EXPECT_EQ(25, test_api.CurrentOffset().y());
// Change |contents| to be shorter than the ScrollView's clipped height.
// This /will/ change the scroll location due to ConstrainScrollToBounds.
contents->SetPreferredSize(gfx::Size(300, 50));
scroll_view_.Layout();
EXPECT_EQ(0, test_api.CurrentOffset().y());
}
// Test that overflow indicators turn on appropriately.
TEST_F(ScrollViewTest, VerticalOverflowIndicators) {
const int kWidth = 100;
ScrollViewTestApi test_api(&scroll_view_);
// Set up with vertical scrollbar.
FixedView* contents = new FixedView;
contents->SetPreferredSize(gfx::Size(kWidth, kMaxHeight * 5));
scroll_view_.SetContents(contents);
scroll_view_.ClipHeightTo(0, kMaxHeight);
// Make sure the size is set such that no horizontal scrollbar gets shown.
scroll_view_.SetSize(
gfx::Size(kWidth + test_api.GetBaseScrollBar(VERTICAL)->GetThickness(),
kMaxHeight));
// Make sure the initial origin is 0,0
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// The vertical scroll bar should be visible and the horizontal scroll bar
// should not.
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, false);
// The overflow indicator on the bottom should be visible.
EXPECT_TRUE(test_api.more_content_bottom()->visible());
// The overflow indicator on the top should not be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
// No other overflow indicators should be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// Now scroll the view to someplace in the middle of the scrollable region.
int offset = kMaxHeight * 2;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
// At this point, both overflow indicators on the top and bottom should be
// visible.
EXPECT_TRUE(test_api.more_content_top()->visible());
EXPECT_TRUE(test_api.more_content_bottom()->visible());
// The left and right overflow indicators should still not be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// Finally scroll the view to end of the scrollable region.
offset = kMaxHeight * 4;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
// The overflow indicator on the bottom should not be visible.
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// The overflow indicator on the top should be visible.
EXPECT_TRUE(test_api.more_content_top()->visible());
// As above, no other overflow indicators should be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
}
TEST_F(ScrollViewTest, HorizontalOverflowIndicators) {
const int kWidth = 100;
const int kHeight = 100;
ScrollViewTestApi test_api(&scroll_view_);
// Set up with horizontal scrollbar.
FixedView* contents = new FixedView;
contents->SetPreferredSize(gfx::Size(kWidth * 5, kHeight));
scroll_view_.SetContents(contents);
// Make sure the size is set such that no vertical scrollbar gets shown.
scroll_view_.SetSize(gfx::Size(
kWidth, kHeight + test_api.GetBaseScrollBar(HORIZONTAL)->GetThickness()));
contents->SetBounds(0, 0, kWidth * 5, kHeight);
// Make sure the initial origin is 0,0
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// The horizontal scroll bar should be visible and the vertical scroll bar
// should not.
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, true);
CheckScrollbarVisibility(scroll_view_, VERTICAL, false);
// The overflow indicator on the right should be visible.
EXPECT_TRUE(test_api.more_content_right()->visible());
// The overflow indicator on the left should not be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
// No other overflow indicators should be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// Now scroll the view to someplace in the middle of the scrollable region.
int offset = kWidth * 2;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), offset);
EXPECT_EQ(gfx::ScrollOffset(offset, 0), test_api.CurrentOffset());
// At this point, both overflow indicators on the left and right should be
// visible.
EXPECT_TRUE(test_api.more_content_left()->visible());
EXPECT_TRUE(test_api.more_content_right()->visible());
// The top and bottom overflow indicators should still not be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// Finally scroll the view to end of the scrollable region.
offset = kWidth * 4;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), offset);
EXPECT_EQ(gfx::ScrollOffset(offset, 0), test_api.CurrentOffset());
// The overflow indicator on the right should not be visible.
EXPECT_FALSE(test_api.more_content_right()->visible());
// The overflow indicator on the left should be visible.
EXPECT_TRUE(test_api.more_content_left()->visible());
// As above, no other overflow indicators should be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
}
TEST_F(ScrollViewTest, HorizontalVerticalOverflowIndicators) {
const int kWidth = 100;
const int kHeight = 100;
ScrollViewTestApi test_api(&scroll_view_);
// Set up with both horizontal and vertical scrollbars.
FixedView* contents = new FixedView;
contents->SetPreferredSize(gfx::Size(kWidth * 5, kHeight * 5));
scroll_view_.SetContents(contents);
// Make sure the size is set such that both scrollbars are shown.
scroll_view_.SetSize(gfx::Size(kWidth, kHeight));
// Make sure the initial origin is 0,0
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// The horizontal and vertical scroll bars should be visible.
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, true);
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
// The overflow indicators on the right and bottom should not be visible since
// they are against the scrollbars.
EXPECT_FALSE(test_api.more_content_right()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// The overflow indicators on the left and top should not be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_top()->visible());
// Now scroll the view to someplace in the middle of the horizontal scrollable
// region.
int offset_x = kWidth * 2;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL),
offset_x);
EXPECT_EQ(gfx::ScrollOffset(offset_x, 0), test_api.CurrentOffset());
// Since there is a vertical scrollbar only the overflow indicator on the left
// should be visible and the one on the right should still not be visible.
EXPECT_TRUE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// The top and bottom overflow indicators should still not be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// Next, scroll the view to end of the scrollable region.
offset_x = kWidth * 4;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL),
offset_x);
EXPECT_EQ(gfx::ScrollOffset(offset_x, 0), test_api.CurrentOffset());
// The overflow indicator on the right should still not be visible.
EXPECT_FALSE(test_api.more_content_right()->visible());
// The overflow indicator on the left should be visible.
EXPECT_TRUE(test_api.more_content_left()->visible());
// As above, the other overflow indicators should not be visible because the
// view hasn't scrolled vertically and the bottom indicator is against the
// horizontal scrollbar.
EXPECT_FALSE(test_api.more_content_top()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// Return the view back to the horizontal origin.
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL), 0);
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// The overflow indicators on the right and bottom should not be visible since
// they are against the scrollbars.
EXPECT_FALSE(test_api.more_content_right()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// The overflow indicators on the left and top should not be visible since the
// is at the origin.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_top()->visible());
// Now scroll the view to somplace in the middle of the vertical scrollable
// region.
int offset_y = kHeight * 2;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset_y);
EXPECT_EQ(gfx::ScrollOffset(0, offset_y), test_api.CurrentOffset());
// Similar to the above, since there is a horizontal scrollbar only the
// overflow indicator on the top should be visible and the one on the bottom
// should still not be visible.
EXPECT_TRUE(test_api.more_content_top()->visible());
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// The left and right overflow indicators should still not be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// Finally, for the vertical test scroll the region all the way to the end.
offset_y = kHeight * 4;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset_y);
EXPECT_EQ(gfx::ScrollOffset(0, offset_y), test_api.CurrentOffset());
// The overflow indicator on the bottom should still not be visible.
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// The overflow indicator on the top should still be visible.
EXPECT_TRUE(test_api.more_content_top()->visible());
// As above, the other overflow indicators should not be visible because the
// view hasn't scrolled horizontally and the right indicator is against the
// vertical scrollbar.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// Back to the horizontal. Scroll all the way to the end in the horizontal
// direction.
offset_x = kWidth * 4;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(HORIZONTAL),
offset_x);
EXPECT_EQ(gfx::ScrollOffset(offset_x, offset_y), test_api.CurrentOffset());
// The overflow indicator on the bottom and right should still not be visible.
EXPECT_FALSE(test_api.more_content_bottom()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// The overflow indicators on the top and left should now be visible.
EXPECT_TRUE(test_api.more_content_top()->visible());
EXPECT_TRUE(test_api.more_content_left()->visible());
}
TEST_F(ScrollViewTest, VerticalWithHeaderOverflowIndicators) {
const int kWidth = 100;
ScrollViewTestApi test_api(&scroll_view_);
// Set up with vertical scrollbar and a header.
FixedView* contents = new FixedView;
CustomView* header = new CustomView;
contents->SetPreferredSize(gfx::Size(kWidth, kMaxHeight * 5));
scroll_view_.SetContents(contents);
header->SetPreferredSize(gfx::Size(10, 20));
scroll_view_.SetHeader(header);
scroll_view_.ClipHeightTo(0, kMaxHeight + header->height());
// Make sure the size is set such that no horizontal scrollbar gets shown.
scroll_view_.SetSize(
gfx::Size(kWidth + test_api.GetBaseScrollBar(VERTICAL)->GetThickness(),
kMaxHeight + header->height()));
// Make sure the initial origin is 0,0
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// The vertical scroll bar should be visible and the horizontal scroll bar
// should not.
CheckScrollbarVisibility(scroll_view_, VERTICAL, true);
CheckScrollbarVisibility(scroll_view_, HORIZONTAL, false);
// The overflow indicator on the bottom should be visible.
EXPECT_TRUE(test_api.more_content_bottom()->visible());
// The overflow indicator on the top should not be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
// No other overflow indicators should be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// Now scroll the view to someplace in the middle of the scrollable region.
int offset = kMaxHeight * 2;
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
// At this point, only the overflow indicator on the bottom should be visible
// because the top indicator never comes on because of the presence of the
// header.
EXPECT_FALSE(test_api.more_content_top()->visible());
EXPECT_TRUE(test_api.more_content_bottom()->visible());
// The left and right overflow indicators should still not be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
// Finally scroll the view to end of the scrollable region.
offset = test_api.GetBaseScrollBar(VERTICAL)->GetMaxPosition();
scroll_view_.ScrollToPosition(test_api.GetBaseScrollBar(VERTICAL), offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset), test_api.CurrentOffset());
// The overflow indicator on the bottom should not be visible now.
EXPECT_FALSE(test_api.more_content_bottom()->visible());
// The overflow indicator on the top should still not be visible.
EXPECT_FALSE(test_api.more_content_top()->visible());
// As above, no other overflow indicators should be visible.
EXPECT_FALSE(test_api.more_content_left()->visible());
EXPECT_FALSE(test_api.more_content_right()->visible());
}
// Test scrolling behavior when clicking on the scroll track.
TEST_F(WidgetScrollViewTest, ScrollTrackScrolling) {
// Set up with a vertical scroller.
ScrollView* scroll_view =
AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5));
ScrollViewTestApi test_api(scroll_view);
BaseScrollBar* scroll_bar = test_api.GetBaseScrollBar(VERTICAL);
View* thumb = test_api.GetScrollBarThumb(VERTICAL);
// Click in the middle of the track, ensuring it's below the thumb.
const gfx::Point location = scroll_bar->bounds().CenterPoint();
EXPECT_GT(location.y(), thumb->bounds().bottom());
ui::MouseEvent press(TestLeftMouseAt(location, ui::ET_MOUSE_PRESSED));
ui::MouseEvent release(TestLeftMouseAt(location, ui::ET_MOUSE_RELEASED));
const base::Timer& timer = test_api.GetScrollBarTimer(VERTICAL);
EXPECT_FALSE(timer.IsRunning());
EXPECT_EQ(0, scroll_view->GetVisibleRect().y());
scroll_bar->OnMouseEvent(&press);
// Clicking the scroll track should scroll one "page".
EXPECT_EQ(kDefaultHeight, scroll_view->GetVisibleRect().y());
// While the mouse is pressed, timer should trigger more scroll events.
EXPECT_TRUE(timer.IsRunning());
// Upon release timer should stop (and scroll position should remain).
scroll_bar->OnMouseEvent(&release);
EXPECT_FALSE(timer.IsRunning());
EXPECT_EQ(kDefaultHeight, scroll_view->GetVisibleRect().y());
}
// Test that LocatedEvents are transformed correctly when scrolling.
TEST_F(WidgetScrollViewTest, EventLocation) {
// Set up with both scrollers.
CustomView* contents = new CustomView;
contents->SetPreferredSize(gfx::Size(kDefaultHeight * 5, kDefaultHeight * 5));
AddScrollViewWithContents(contents);
const gfx::Point location_in_widget(10, 10);
// Click without scrolling.
TestClickAt(location_in_widget);
EXPECT_EQ(location_in_widget, contents->last_location());
// Scroll down a page.
contents->ScrollRectToVisible(
gfx::Rect(0, kDefaultHeight, 1, kDefaultHeight));
TestClickAt(location_in_widget);
EXPECT_EQ(gfx::Point(10, 10 + kDefaultHeight), contents->last_location());
// Scroll right a page (and back up).
contents->ScrollRectToVisible(gfx::Rect(kDefaultWidth, 0, kDefaultWidth, 1));
TestClickAt(location_in_widget);
EXPECT_EQ(gfx::Point(10 + kDefaultWidth, 10), contents->last_location());
// Scroll both directions.
contents->ScrollRectToVisible(
gfx::Rect(kDefaultWidth, kDefaultHeight, kDefaultWidth, kDefaultHeight));
TestClickAt(location_in_widget);
EXPECT_EQ(gfx::Point(10 + kDefaultWidth, 10 + kDefaultHeight),
contents->last_location());
}
// Test that views scroll offsets are in sync with the layer scroll offsets.
TEST_F(WidgetScrollViewTest, ScrollOffsetUsingLayers) {
// Set up with a vertical scroller, but don't commit the layer changes yet.
ScrollView* scroll_view =
AddScrollViewWithContentSize(gfx::Size(10, kDefaultHeight * 5), false);
ScrollViewTestApi test_api(scroll_view);
EXPECT_EQ(gfx::ScrollOffset(0, 0), test_api.CurrentOffset());
// UI code may request a scroll before layer changes are committed.
gfx::Rect offset(0, kDefaultHeight * 2, 1, kDefaultHeight);
scroll_view->contents()->ScrollRectToVisible(offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
// The following only makes sense when layered scrolling is enabled.
View* container = scroll_view->contents();
#if defined(OS_MACOSX)
// Sanity check: Mac should always scroll with layers.
EXPECT_TRUE(container->layer());
#endif
if (!container->layer())
return;
// Container and viewport should have layers.
EXPECT_TRUE(container->layer());
EXPECT_TRUE(test_api.contents_viewport()->layer());
// In a Widget, so there should be a compositor.
ui::Compositor* compositor = container->layer()->GetCompositor();
EXPECT_TRUE(compositor);
// But setting on the impl side should fail since the layer isn't committed.
int layer_id = container->layer()->cc_layer_for_testing()->id();
EXPECT_FALSE(compositor->ScrollLayerTo(layer_id, gfx::ScrollOffset(0, 0)));
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
WaitForCommit();
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
// Upon commit, the impl side should report the same value too.
gfx::ScrollOffset impl_offset;
EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset);
// Now impl-side scrolling should work, and also update the ScrollView.
offset.set_y(kDefaultHeight * 3);
EXPECT_TRUE(
compositor->ScrollLayerTo(layer_id, gfx::ScrollOffset(0, offset.y())));
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
// Scroll via ScrollView API. Should be reflected on the impl side.
offset.set_y(kDefaultHeight * 4);
scroll_view->contents()->ScrollRectToVisible(offset);
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), test_api.CurrentOffset());
EXPECT_TRUE(compositor->GetScrollOffsetForLayer(layer_id, &impl_offset));
EXPECT_EQ(gfx::ScrollOffset(0, offset.y()), impl_offset);
}
} // namespace views