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