1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/views/controls/scroll_view.h"
6
7#include "testing/gtest/include/gtest/gtest.h"
8#include "ui/views/controls/scrollbar/overlay_scroll_bar.h"
9#include "ui/views/test/test_views.h"
10
11namespace views {
12
13namespace {
14
15const int kWidth = 100;
16const int kMinHeight = 50;
17const int kMaxHeight = 100;
18
19// View implementation that allows setting the preferred size.
20class CustomView : public View {
21 public:
22  CustomView() {}
23
24  void SetPreferredSize(const gfx::Size& size) {
25    preferred_size_ = size;
26    PreferredSizeChanged();
27  }
28
29  virtual gfx::Size GetPreferredSize() const OVERRIDE {
30    return preferred_size_;
31  }
32
33  virtual void Layout() OVERRIDE {
34    gfx::Size pref = GetPreferredSize();
35    int width = pref.width();
36    int height = pref.height();
37    if (parent()) {
38      width = std::max(parent()->width(), width);
39      height = std::max(parent()->height(), height);
40    }
41    SetBounds(x(), y(), width, height);
42  }
43
44 private:
45  gfx::Size preferred_size_;
46
47  DISALLOW_COPY_AND_ASSIGN(CustomView);
48};
49
50}  // namespace
51
52// Verifies the viewport is sized to fit the available space.
53TEST(ScrollViewTest, ViewportSizedToFit) {
54  ScrollView scroll_view;
55  View* contents = new View;
56  scroll_view.SetContents(contents);
57  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
58  scroll_view.Layout();
59  EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
60}
61
62// Verifies the scrollbars are added as necessary.
63TEST(ScrollViewTest, ScrollBars) {
64  ScrollView scroll_view;
65  View* contents = new View;
66  scroll_view.SetContents(contents);
67  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
68
69  // Size the contents such that vertical scrollbar is needed.
70  contents->SetBounds(0, 0, 50, 400);
71  scroll_view.Layout();
72  EXPECT_EQ(100 - scroll_view.GetScrollBarWidth(), contents->parent()->width());
73  EXPECT_EQ(100, contents->parent()->height());
74  EXPECT_TRUE(!scroll_view.horizontal_scroll_bar() ||
75              !scroll_view.horizontal_scroll_bar()->visible());
76  ASSERT_TRUE(scroll_view.vertical_scroll_bar() != NULL);
77  EXPECT_TRUE(scroll_view.vertical_scroll_bar()->visible());
78
79  // Size the contents such that horizontal scrollbar is needed.
80  contents->SetBounds(0, 0, 400, 50);
81  scroll_view.Layout();
82  EXPECT_EQ(100, contents->parent()->width());
83  EXPECT_EQ(100 - scroll_view.GetScrollBarHeight(),
84            contents->parent()->height());
85  ASSERT_TRUE(scroll_view.horizontal_scroll_bar() != NULL);
86  EXPECT_TRUE(scroll_view.horizontal_scroll_bar()->visible());
87  EXPECT_TRUE(!scroll_view.vertical_scroll_bar() ||
88              !scroll_view.vertical_scroll_bar()->visible());
89
90  // Both horizontal and vertical.
91  contents->SetBounds(0, 0, 300, 400);
92  scroll_view.Layout();
93  EXPECT_EQ(100 - scroll_view.GetScrollBarWidth(), contents->parent()->width());
94  EXPECT_EQ(100 - scroll_view.GetScrollBarHeight(),
95            contents->parent()->height());
96  ASSERT_TRUE(scroll_view.horizontal_scroll_bar() != NULL);
97  EXPECT_TRUE(scroll_view.horizontal_scroll_bar()->visible());
98  ASSERT_TRUE(scroll_view.vertical_scroll_bar() != NULL);
99  EXPECT_TRUE(scroll_view.vertical_scroll_bar()->visible());
100}
101
102// Assertions around adding a header.
103TEST(ScrollViewTest, Header) {
104  ScrollView scroll_view;
105  View* contents = new View;
106  CustomView* header = new CustomView;
107  scroll_view.SetHeader(header);
108  View* header_parent = header->parent();
109  scroll_view.SetContents(contents);
110  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
111  scroll_view.Layout();
112  // |header|s preferred size is empty, which should result in all space going
113  // to contents.
114  EXPECT_EQ("0,0 100x0", header->parent()->bounds().ToString());
115  EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
116
117  // Get the header a height of 20.
118  header->SetPreferredSize(gfx::Size(10, 20));
119  EXPECT_EQ("0,0 100x20", header->parent()->bounds().ToString());
120  EXPECT_EQ("0,20 100x80", contents->parent()->bounds().ToString());
121
122  // Remove the header.
123  scroll_view.SetHeader(NULL);
124  // SetHeader(NULL) deletes header.
125  header = NULL;
126  EXPECT_EQ("0,0 100x0", header_parent->bounds().ToString());
127  EXPECT_EQ("0,0 100x100", contents->parent()->bounds().ToString());
128}
129
130// Verifies the scrollbars are added as necessary when a header is present.
131TEST(ScrollViewTest, ScrollBarsWithHeader) {
132  ScrollView scroll_view;
133  View* contents = new View;
134  scroll_view.SetContents(contents);
135  CustomView* header = new CustomView;
136  scroll_view.SetHeader(header);
137  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
138
139  header->SetPreferredSize(gfx::Size(10, 20));
140
141  // Size the contents such that vertical scrollbar is needed.
142  contents->SetBounds(0, 0, 50, 400);
143  scroll_view.Layout();
144  EXPECT_EQ(0, contents->parent()->x());
145  EXPECT_EQ(20, contents->parent()->y());
146  EXPECT_EQ(100 - scroll_view.GetScrollBarWidth(), contents->parent()->width());
147  EXPECT_EQ(80, contents->parent()->height());
148  EXPECT_EQ(0, header->parent()->x());
149  EXPECT_EQ(0, header->parent()->y());
150  EXPECT_EQ(100 - scroll_view.GetScrollBarWidth(), header->parent()->width());
151  EXPECT_EQ(20, header->parent()->height());
152  EXPECT_TRUE(!scroll_view.horizontal_scroll_bar() ||
153              !scroll_view.horizontal_scroll_bar()->visible());
154  ASSERT_TRUE(scroll_view.vertical_scroll_bar() != NULL);
155  EXPECT_TRUE(scroll_view.vertical_scroll_bar()->visible());
156
157  // Size the contents such that horizontal scrollbar is needed.
158  contents->SetBounds(0, 0, 400, 50);
159  scroll_view.Layout();
160  EXPECT_EQ(0, contents->parent()->x());
161  EXPECT_EQ(20, contents->parent()->y());
162  EXPECT_EQ(100, contents->parent()->width());
163  EXPECT_EQ(100 - scroll_view.GetScrollBarHeight() - 20,
164            contents->parent()->height());
165  EXPECT_EQ(0, header->parent()->x());
166  EXPECT_EQ(0, header->parent()->y());
167  EXPECT_EQ(100, header->parent()->width());
168  EXPECT_EQ(20, header->parent()->height());
169  ASSERT_TRUE(scroll_view.horizontal_scroll_bar() != NULL);
170  EXPECT_TRUE(scroll_view.horizontal_scroll_bar()->visible());
171  EXPECT_TRUE(!scroll_view.vertical_scroll_bar() ||
172              !scroll_view.vertical_scroll_bar()->visible());
173
174  // Both horizontal and vertical.
175  contents->SetBounds(0, 0, 300, 400);
176  scroll_view.Layout();
177  EXPECT_EQ(0, contents->parent()->x());
178  EXPECT_EQ(20, contents->parent()->y());
179  EXPECT_EQ(100 - scroll_view.GetScrollBarWidth(), contents->parent()->width());
180  EXPECT_EQ(100 - scroll_view.GetScrollBarHeight() - 20,
181            contents->parent()->height());
182  EXPECT_EQ(0, header->parent()->x());
183  EXPECT_EQ(0, header->parent()->y());
184  EXPECT_EQ(100 - scroll_view.GetScrollBarWidth(), header->parent()->width());
185  EXPECT_EQ(20, header->parent()->height());
186  ASSERT_TRUE(scroll_view.horizontal_scroll_bar() != NULL);
187  EXPECT_TRUE(scroll_view.horizontal_scroll_bar()->visible());
188  ASSERT_TRUE(scroll_view.vertical_scroll_bar() != NULL);
189  EXPECT_TRUE(scroll_view.vertical_scroll_bar()->visible());
190}
191
192// Verifies the header scrolls horizontally with the content.
193TEST(ScrollViewTest, HeaderScrollsWithContent) {
194  ScrollView scroll_view;
195  CustomView* contents = new CustomView;
196  scroll_view.SetContents(contents);
197  contents->SetPreferredSize(gfx::Size(500, 500));
198
199  CustomView* header = new CustomView;
200  scroll_view.SetHeader(header);
201  header->SetPreferredSize(gfx::Size(500, 20));
202
203  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
204  EXPECT_EQ("0,0", contents->bounds().origin().ToString());
205  EXPECT_EQ("0,0", header->bounds().origin().ToString());
206
207  // Scroll the horizontal scrollbar.
208  ASSERT_TRUE(scroll_view.horizontal_scroll_bar());
209  scroll_view.ScrollToPosition(
210      const_cast<ScrollBar*>(scroll_view.horizontal_scroll_bar()), 1);
211  EXPECT_EQ("-1,0", contents->bounds().origin().ToString());
212  EXPECT_EQ("-1,0", header->bounds().origin().ToString());
213
214  // Scrolling the vertical scrollbar shouldn't effect the header.
215  ASSERT_TRUE(scroll_view.vertical_scroll_bar());
216  scroll_view.ScrollToPosition(
217      const_cast<ScrollBar*>(scroll_view.vertical_scroll_bar()), 1);
218  EXPECT_EQ("-1,-1", contents->bounds().origin().ToString());
219  EXPECT_EQ("-1,0", header->bounds().origin().ToString());
220}
221
222// Verifies ScrollRectToVisible() on the child works.
223TEST(ScrollViewTest, ScrollRectToVisible) {
224  ScrollView scroll_view;
225  CustomView* contents = new CustomView;
226  scroll_view.SetContents(contents);
227  contents->SetPreferredSize(gfx::Size(500, 1000));
228
229  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
230  scroll_view.Layout();
231  EXPECT_EQ("0,0", contents->bounds().origin().ToString());
232
233  // Scroll to y=405 height=10, this should make the y position of the content
234  // at (405 + 10) - viewport_height (scroll region bottom aligned).
235  contents->ScrollRectToVisible(gfx::Rect(0, 405, 10, 10));
236  const int viewport_height = contents->parent()->height();
237  EXPECT_EQ(-(415 - viewport_height), contents->y());
238
239  // Scroll to the current y-location and 10x10; should do nothing.
240  contents->ScrollRectToVisible(gfx::Rect(0, -contents->y(), 10, 10));
241  EXPECT_EQ(-(415 - viewport_height), contents->y());
242}
243
244// Verifies ClipHeightTo() uses the height of the content when it is between the
245// minimum and maximum height values.
246TEST(ScrollViewTest, ClipHeightToNormalContentHeight) {
247  ScrollView scroll_view;
248
249  scroll_view.ClipHeightTo(kMinHeight, kMaxHeight);
250
251  const int kNormalContentHeight = 75;
252  scroll_view.SetContents(
253      new views::StaticSizedView(gfx::Size(kWidth, kNormalContentHeight)));
254
255  EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight),
256            scroll_view.GetPreferredSize());
257
258  scroll_view.SizeToPreferredSize();
259  scroll_view.Layout();
260
261  EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight),
262            scroll_view.contents()->size());
263  EXPECT_EQ(gfx::Size(kWidth, kNormalContentHeight), scroll_view.size());
264}
265
266// Verifies ClipHeightTo() uses the minimum height when the content is shorter
267// thamn the minimum height value.
268TEST(ScrollViewTest, ClipHeightToShortContentHeight) {
269  ScrollView scroll_view;
270
271  scroll_view.ClipHeightTo(kMinHeight, kMaxHeight);
272
273  const int kShortContentHeight = 10;
274  scroll_view.SetContents(
275      new views::StaticSizedView(gfx::Size(kWidth, kShortContentHeight)));
276
277  EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view.GetPreferredSize());
278
279  scroll_view.SizeToPreferredSize();
280  scroll_view.Layout();
281
282  EXPECT_EQ(gfx::Size(kWidth, kShortContentHeight),
283            scroll_view.contents()->size());
284  EXPECT_EQ(gfx::Size(kWidth, kMinHeight), scroll_view.size());
285}
286
287// Verifies ClipHeightTo() uses the maximum height when the content is longer
288// thamn the maximum height value.
289TEST(ScrollViewTest, ClipHeightToTallContentHeight) {
290  ScrollView scroll_view;
291
292  // Use a scrollbar that is disabled by default, so the width of the content is
293  // not affected.
294  scroll_view.SetVerticalScrollBar(new views::OverlayScrollBar(false));
295
296  scroll_view.ClipHeightTo(kMinHeight, kMaxHeight);
297
298  const int kTallContentHeight = 1000;
299  scroll_view.SetContents(
300      new views::StaticSizedView(gfx::Size(kWidth, kTallContentHeight)));
301
302  EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view.GetPreferredSize());
303
304  scroll_view.SizeToPreferredSize();
305  scroll_view.Layout();
306
307  EXPECT_EQ(gfx::Size(kWidth, kTallContentHeight),
308            scroll_view.contents()->size());
309  EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view.size());
310}
311
312// Verifies that when ClipHeightTo() produces a scrollbar, it reduces the width
313// of the inner content of the ScrollView.
314TEST(ScrollViewTest, ClipHeightToScrollbarUsesWidth) {
315  ScrollView scroll_view;
316
317  scroll_view.ClipHeightTo(kMinHeight, kMaxHeight);
318
319  // Create a view that will be much taller than it is wide.
320  scroll_view.SetContents(new views::ProportionallySizedView(1000));
321
322  // Without any width, it will default to 0,0 but be overridden by min height.
323  scroll_view.SizeToPreferredSize();
324  EXPECT_EQ(gfx::Size(0, kMinHeight), scroll_view.GetPreferredSize());
325
326  gfx::Size new_size(kWidth, scroll_view.GetHeightForWidth(kWidth));
327  scroll_view.SetSize(new_size);
328  scroll_view.Layout();
329
330  int scroll_bar_width = scroll_view.GetScrollBarWidth();
331  int expected_width = kWidth - scroll_bar_width;
332  EXPECT_EQ(scroll_view.contents()->size().width(), expected_width);
333  EXPECT_EQ(scroll_view.contents()->size().height(), 1000 * expected_width);
334  EXPECT_EQ(gfx::Size(kWidth, kMaxHeight), scroll_view.size());
335}
336
337TEST(ScrollViewTest, CornerViewVisibility) {
338  ScrollView scroll_view;
339  View* contents = new View;
340  scroll_view.SetContents(contents);
341  scroll_view.SetBoundsRect(gfx::Rect(0, 0, 100, 100));
342  View* corner_view = scroll_view.corner_view_;
343
344  // Corner view should be visible when both scrollbars are visible.
345  contents->SetBounds(0, 0, 200, 200);
346  scroll_view.Layout();
347  EXPECT_EQ(&scroll_view, corner_view->parent());
348  EXPECT_TRUE(corner_view->visible());
349
350  // Corner view should be aligned to the scrollbars.
351  EXPECT_EQ(scroll_view.vertical_scroll_bar()->x(), corner_view->x());
352  EXPECT_EQ(scroll_view.horizontal_scroll_bar()->y(), corner_view->y());
353  EXPECT_EQ(scroll_view.GetScrollBarWidth(), corner_view->width());
354  EXPECT_EQ(scroll_view.GetScrollBarHeight(), corner_view->height());
355
356  // Corner view should be removed when only the vertical scrollbar is visible.
357  contents->SetBounds(0, 0, 50, 200);
358  scroll_view.Layout();
359  EXPECT_FALSE(corner_view->parent());
360
361  // ... or when only the horizontal scrollbar is visible.
362  contents->SetBounds(0, 0, 200, 50);
363  scroll_view.Layout();
364  EXPECT_FALSE(corner_view->parent());
365
366  // ... or when no scrollbar is visible.
367  contents->SetBounds(0, 0, 50, 50);
368  scroll_view.Layout();
369  EXPECT_FALSE(corner_view->parent());
370
371  // Corner view should reappear when both scrollbars reappear.
372  contents->SetBounds(0, 0, 200, 200);
373  scroll_view.Layout();
374  EXPECT_EQ(&scroll_view, corner_view->parent());
375  EXPECT_TRUE(corner_view->visible());
376}
377
378}  // namespace views
379