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