tab_unittest.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 "chrome/browser/ui/views/tabs/tab.h"
6
7#include "base/strings/utf_string_conversions.h"
8#include "chrome/browser/ui/views/tabs/tab_controller.h"
9#include "testing/gtest/include/gtest/gtest.h"
10#include "ui/base/models/list_selection_model.h"
11#include "ui/views/controls/button/image_button.h"
12#include "ui/views/controls/label.h"
13#include "ui/views/test/views_test_base.h"
14#include "ui/views/widget/widget.h"
15
16using views::Widget;
17
18class FakeTabController : public TabController {
19 public:
20  FakeTabController() : immersive_style_(false), active_tab_(false) {
21  }
22  virtual ~FakeTabController() {}
23
24  void set_immersive_style(bool value) { immersive_style_ = value; }
25  void set_active_tab(bool value) { active_tab_ = value; }
26
27  virtual const ui::ListSelectionModel& GetSelectionModel() OVERRIDE {
28    return selection_model_;
29  }
30  virtual bool SupportsMultipleSelection() OVERRIDE { return false; }
31  virtual void SelectTab(Tab* tab) OVERRIDE {}
32  virtual void ExtendSelectionTo(Tab* tab) OVERRIDE {}
33  virtual void ToggleSelected(Tab* tab) OVERRIDE {}
34  virtual void AddSelectionFromAnchorTo(Tab* tab) OVERRIDE {}
35  virtual void CloseTab(Tab* tab, CloseTabSource source) OVERRIDE {}
36  virtual void ShowContextMenuForTab(Tab* tab,
37                                     const gfx::Point& p,
38                                     ui::MenuSourceType source_type) OVERRIDE {}
39  virtual bool IsActiveTab(const Tab* tab) const OVERRIDE {
40    return active_tab_;
41  }
42  virtual bool IsTabSelected(const Tab* tab) const OVERRIDE {
43    return false;
44  }
45  virtual bool IsTabPinned(const Tab* tab) const OVERRIDE { return false; }
46  virtual void MaybeStartDrag(
47      Tab* tab,
48      const ui::LocatedEvent& event,
49      const ui::ListSelectionModel& original_selection) OVERRIDE {}
50  virtual void ContinueDrag(views::View* view,
51                            const ui::LocatedEvent& event) OVERRIDE {}
52  virtual bool EndDrag(EndDragReason reason) OVERRIDE { return false; }
53  virtual Tab* GetTabAt(Tab* tab,
54                        const gfx::Point& tab_in_tab_coordinates) OVERRIDE {
55    return NULL;
56  }
57  virtual void OnMouseEventInTab(views::View* source,
58                                 const ui::MouseEvent& event) OVERRIDE {}
59  virtual bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) OVERRIDE {
60    return true;
61  }
62  virtual bool IsImmersiveStyle() const OVERRIDE { return immersive_style_; }
63  virtual void UpdateTabAccessibilityState(const Tab* tab,
64                                           ui::AXViewState* state) OVERRIDE{};
65
66 private:
67  ui::ListSelectionModel selection_model_;
68  bool immersive_style_;
69  bool active_tab_;
70
71  DISALLOW_COPY_AND_ASSIGN(FakeTabController);
72};
73
74class TabTest : public views::ViewsTestBase {
75 public:
76  TabTest() {}
77  virtual ~TabTest() {}
78
79  static void DisableMediaIndicatorAnimation(Tab* tab) {
80    tab->media_indicator_animation_.reset();
81    tab->animating_media_state_ = tab->data_.media_state;
82  }
83
84  static void CheckForExpectedLayoutAndVisibilityOfElements(const Tab& tab) {
85    // Check whether elements are visible when they are supposed to be, given
86    // Tab size and TabRendererData state.
87    if (tab.data_.mini) {
88      EXPECT_EQ(1, tab.IconCapacity());
89      if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
90        EXPECT_FALSE(tab.ShouldShowIcon());
91        EXPECT_TRUE(tab.ShouldShowMediaIndicator());
92      } else {
93        EXPECT_TRUE(tab.ShouldShowIcon());
94        EXPECT_FALSE(tab.ShouldShowMediaIndicator());
95      }
96      EXPECT_FALSE(tab.ShouldShowCloseBox());
97    } else if (tab.IsActive()) {
98      EXPECT_TRUE(tab.ShouldShowCloseBox());
99      switch (tab.IconCapacity()) {
100        case 0:
101        case 1:
102          EXPECT_FALSE(tab.ShouldShowIcon());
103          EXPECT_FALSE(tab.ShouldShowMediaIndicator());
104          break;
105        case 2:
106          if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
107            EXPECT_FALSE(tab.ShouldShowIcon());
108            EXPECT_TRUE(tab.ShouldShowMediaIndicator());
109          } else {
110            EXPECT_TRUE(tab.ShouldShowIcon());
111            EXPECT_FALSE(tab.ShouldShowMediaIndicator());
112          }
113          break;
114        default:
115          EXPECT_LE(3, tab.IconCapacity());
116          EXPECT_TRUE(tab.ShouldShowIcon());
117          if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
118            EXPECT_TRUE(tab.ShouldShowMediaIndicator());
119          else
120            EXPECT_FALSE(tab.ShouldShowMediaIndicator());
121          break;
122      }
123    } else {  // Tab not active and not mini tab.
124      switch (tab.IconCapacity()) {
125        case 0:
126          EXPECT_FALSE(tab.ShouldShowCloseBox());
127          EXPECT_FALSE(tab.ShouldShowIcon());
128          EXPECT_FALSE(tab.ShouldShowMediaIndicator());
129          break;
130        case 1:
131          EXPECT_FALSE(tab.ShouldShowCloseBox());
132          if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
133            EXPECT_FALSE(tab.ShouldShowIcon());
134            EXPECT_TRUE(tab.ShouldShowMediaIndicator());
135          } else {
136            EXPECT_TRUE(tab.ShouldShowIcon());
137            EXPECT_FALSE(tab.ShouldShowMediaIndicator());
138          }
139          break;
140        default:
141          EXPECT_LE(2, tab.IconCapacity());
142          EXPECT_TRUE(tab.ShouldShowIcon());
143          if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
144            EXPECT_TRUE(tab.ShouldShowMediaIndicator());
145          else
146            EXPECT_FALSE(tab.ShouldShowMediaIndicator());
147          break;
148      }
149    }
150
151    // Check positioning of elements with respect to each other, and that they
152    // are fully within the contents bounds.
153    const gfx::Rect contents_bounds = tab.GetContentsBounds();
154    if (tab.ShouldShowIcon()) {
155      EXPECT_LE(contents_bounds.x(), tab.favicon_bounds_.x());
156      if (tab.title_->width() > 0)
157        EXPECT_LE(tab.favicon_bounds_.right(), tab.title_->x());
158      EXPECT_LE(contents_bounds.y(), tab.favicon_bounds_.y());
159      EXPECT_LE(tab.favicon_bounds_.bottom(), contents_bounds.bottom());
160    }
161    if (tab.ShouldShowIcon() && tab.ShouldShowMediaIndicator())
162      EXPECT_LE(tab.favicon_bounds_.right(), tab.media_indicator_bounds_.x());
163    if (tab.ShouldShowMediaIndicator()) {
164      if (tab.title_->width() > 0) {
165        EXPECT_LE(tab.title_->bounds().right(),
166                  tab.media_indicator_bounds_.x());
167      }
168      EXPECT_LE(tab.media_indicator_bounds_.right(), contents_bounds.right());
169      EXPECT_LE(contents_bounds.y(), tab.media_indicator_bounds_.y());
170      EXPECT_LE(tab.media_indicator_bounds_.bottom(), contents_bounds.bottom());
171    }
172    if (tab.ShouldShowMediaIndicator() && tab.ShouldShowCloseBox()) {
173      // Note: The media indicator can overlap the left-insets of the close box,
174      // but should otherwise be to the left of the close button.
175      EXPECT_LE(tab.media_indicator_bounds_.right(),
176                tab.close_button_->bounds().x() +
177                    tab.close_button_->GetInsets().left());
178    }
179    if (tab.ShouldShowCloseBox()) {
180      // Note: The title bounds can overlap the left-insets of the close box,
181      // but should otherwise be to the left of the close button.
182      if (tab.title_->width() > 0) {
183        EXPECT_LE(tab.title_->bounds().right(),
184                  tab.close_button_->bounds().x() +
185                      tab.close_button_->GetInsets().left());
186      }
187      EXPECT_LE(tab.close_button_->bounds().right(), contents_bounds.right());
188      EXPECT_LE(contents_bounds.y(), tab.close_button_->bounds().y());
189      EXPECT_LE(tab.close_button_->bounds().bottom(), contents_bounds.bottom());
190    }
191  }
192};
193
194TEST_F(TabTest, HitTestTopPixel) {
195  Widget widget;
196  Widget::InitParams params(CreateParams(Widget::InitParams::TYPE_WINDOW));
197  params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
198  params.bounds.SetRect(10, 20, 300, 400);
199  widget.Init(params);
200
201  FakeTabController tab_controller;
202  Tab tab(&tab_controller);
203  widget.GetContentsView()->AddChildView(&tab);
204  tab.SetBoundsRect(gfx::Rect(gfx::Point(0, 0), Tab::GetStandardSize()));
205
206  // Tabs have some shadow in the top, so by default we don't hit the tab there.
207  int middle_x = tab.width() / 2;
208  EXPECT_FALSE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
209
210  // Tabs are slanted, so a click halfway down the left edge won't hit it.
211  int middle_y = tab.height() / 2;
212  EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, middle_y)));
213
214  // If the window is maximized, however, we want clicks in the top edge to
215  // select the tab.
216  widget.Maximize();
217  EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
218
219  // But clicks in the area above the slanted sides should still miss.
220  EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, 0)));
221  EXPECT_FALSE(tab.HitTestPoint(gfx::Point(tab.width() - 1, 0)));
222}
223
224TEST_F(TabTest, LayoutAndVisibilityOfElements) {
225  static const TabMediaState kMediaStatesToTest[] = {
226    TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
227    TAB_MEDIA_STATE_AUDIO_PLAYING
228  };
229
230  FakeTabController controller;
231  Tab tab(&controller);
232
233  SkBitmap bitmap;
234  bitmap.allocN32Pixels(16, 16);
235  TabRendererData data;
236  data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
237
238  // Perform layout over all possible combinations, checking for correct
239  // results.
240  for (int is_mini_tab = 0; is_mini_tab < 2; ++is_mini_tab) {
241    for (int is_active_tab = 0; is_active_tab < 2; ++is_active_tab) {
242      for (size_t media_state_index = 0;
243           media_state_index < arraysize(kMediaStatesToTest);
244           ++media_state_index) {
245        const TabMediaState media_state = kMediaStatesToTest[media_state_index];
246        SCOPED_TRACE(::testing::Message()
247                     << (is_active_tab ? "Active" : "Inactive") << ' '
248                     << (is_mini_tab ? "Mini " : "")
249                     << "Tab with media indicator state " << media_state);
250
251        data.mini = !!is_mini_tab;
252        controller.set_active_tab(!!is_active_tab);
253        data.media_state = media_state;
254        tab.SetData(data);
255
256        // Disable the media indicator animation so that the layout/visibility
257        // logic can be tested effectively.  If the animation was left enabled,
258        // the ShouldShowMediaIndicator() method would return true during
259        // fade-out transitions.
260        DisableMediaIndicatorAnimation(&tab);
261
262        // Test layout for every width from standard to minimum.
263        gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize());
264        int min_width;
265        if (is_mini_tab) {
266          bounds.set_width(Tab::GetMiniWidth());
267          min_width = Tab::GetMiniWidth();
268        } else {
269          min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() :
270              Tab::GetMinimumUnselectedSize().width();
271        }
272        while (bounds.width() >= min_width) {
273          SCOPED_TRACE(::testing::Message() << "bounds=" << bounds.ToString());
274          tab.SetBoundsRect(bounds);  // Invokes Tab::Layout().
275          CheckForExpectedLayoutAndVisibilityOfElements(tab);
276          bounds.set_width(bounds.width() - 1);
277        }
278      }
279    }
280  }
281}
282
283// Regression test for http://crbug.com/226253. Calling Layout() more than once
284// shouldn't change the insets of the close button.
285TEST_F(TabTest, CloseButtonLayout) {
286  FakeTabController tab_controller;
287  Tab tab(&tab_controller);
288  tab.SetBounds(0, 0, 100, 50);
289  tab.Layout();
290  gfx::Insets close_button_insets = tab.close_button_->GetInsets();
291  tab.Layout();
292  gfx::Insets close_button_insets_2 = tab.close_button_->GetInsets();
293  EXPECT_EQ(close_button_insets.top(), close_button_insets_2.top());
294  EXPECT_EQ(close_button_insets.left(), close_button_insets_2.left());
295  EXPECT_EQ(close_button_insets.bottom(), close_button_insets_2.bottom());
296  EXPECT_EQ(close_button_insets.right(), close_button_insets_2.right());
297
298  // Also make sure the close button is sized as large as the tab.
299  EXPECT_EQ(50, tab.close_button_->bounds().height());
300}
301