MacViews: Use Mac's "Constrained Window Button" style for Button::STYLE_BUTTON LabelB...
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_unittest.cc
bloba54c2b1fff351f0af887649260ea61907bac6d81
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.
5 #include "chrome/browser/ui/views/tabs/tab.h"
7 #include "base/i18n/rtl.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/ui/tabs/tab_utils.h"
10 #include "chrome/browser/ui/views/tabs/media_indicator_button.h"
11 #include "chrome/browser/ui/views/tabs/tab_controller.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "ui/base/models/list_selection_model.h"
14 #include "ui/views/controls/button/image_button.h"
15 #include "ui/views/controls/label.h"
16 #include "ui/views/test/views_test_base.h"
17 #include "ui/views/widget/widget.h"
19 using views::Widget;
21 class FakeTabController : public TabController {
22 public:
23 FakeTabController() : immersive_style_(false), active_tab_(false) {
25 ~FakeTabController() override {}
27 void set_immersive_style(bool value) { immersive_style_ = value; }
28 void set_active_tab(bool value) { active_tab_ = value; }
30 const ui::ListSelectionModel& GetSelectionModel() override {
31 return selection_model_;
33 bool SupportsMultipleSelection() override { return false; }
34 bool ShouldHideCloseButtonForInactiveTabs() override {
35 return false;
37 void SelectTab(Tab* tab) override {}
38 void ExtendSelectionTo(Tab* tab) override {}
39 void ToggleSelected(Tab* tab) override {}
40 void AddSelectionFromAnchorTo(Tab* tab) override {}
41 void CloseTab(Tab* tab, CloseTabSource source) override {}
42 void ToggleTabAudioMute(Tab* tab) override {}
43 void ShowContextMenuForTab(Tab* tab,
44 const gfx::Point& p,
45 ui::MenuSourceType source_type) override {}
46 bool IsActiveTab(const Tab* tab) const override { return active_tab_; }
47 bool IsTabSelected(const Tab* tab) const override { return false; }
48 bool IsTabPinned(const Tab* tab) const override { return false; }
49 void MaybeStartDrag(
50 Tab* tab,
51 const ui::LocatedEvent& event,
52 const ui::ListSelectionModel& original_selection) override {}
53 void ContinueDrag(views::View* view, const ui::LocatedEvent& event) override {
55 bool EndDrag(EndDragReason reason) override { return false; }
56 Tab* GetTabAt(Tab* tab, const gfx::Point& tab_in_tab_coordinates) override {
57 return NULL;
59 void OnMouseEventInTab(views::View* source,
60 const ui::MouseEvent& event) override {}
61 bool ShouldPaintTab(const Tab* tab, gfx::Rect* clip) override { return true; }
62 bool IsImmersiveStyle() const override { return immersive_style_; }
63 void UpdateTabAccessibilityState(const Tab* tab,
64 ui::AXViewState* state) override{};
66 private:
67 ui::ListSelectionModel selection_model_;
68 bool immersive_style_;
69 bool active_tab_;
71 DISALLOW_COPY_AND_ASSIGN(FakeTabController);
74 class TabTest : public views::ViewsTestBase,
75 public ::testing::WithParamInterface<bool> {
76 public:
77 TabTest() {}
78 virtual ~TabTest() {}
80 bool testing_for_rtl_locale() const { return GetParam(); }
82 void SetUp() override {
83 if (testing_for_rtl_locale()) {
84 original_locale_ = base::i18n::GetConfiguredLocale();
85 base::i18n::SetICUDefaultLocale("he");
87 views::ViewsTestBase::SetUp();
90 void TearDown() override {
91 views::ViewsTestBase::TearDown();
92 if (testing_for_rtl_locale())
93 base::i18n::SetICUDefaultLocale(original_locale_);
96 static void CheckForExpectedLayoutAndVisibilityOfElements(const Tab& tab) {
97 // Check whether elements are visible when they are supposed to be, given
98 // Tab size and TabRendererData state.
99 if (tab.data_.pinned) {
100 EXPECT_EQ(1, tab.IconCapacity());
101 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
102 EXPECT_FALSE(tab.ShouldShowIcon());
103 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
104 } else {
105 EXPECT_TRUE(tab.ShouldShowIcon());
106 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
108 EXPECT_FALSE(tab.ShouldShowCloseBox());
109 } else if (tab.IsActive()) {
110 EXPECT_TRUE(tab.ShouldShowCloseBox());
111 switch (tab.IconCapacity()) {
112 case 0:
113 case 1:
114 EXPECT_FALSE(tab.ShouldShowIcon());
115 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
116 break;
117 case 2:
118 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
119 EXPECT_FALSE(tab.ShouldShowIcon());
120 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
121 } else {
122 EXPECT_TRUE(tab.ShouldShowIcon());
123 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
125 break;
126 default:
127 EXPECT_LE(3, tab.IconCapacity());
128 EXPECT_TRUE(tab.ShouldShowIcon());
129 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
130 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
131 else
132 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
133 break;
135 } else { // Tab not active and not pinned tab.
136 switch (tab.IconCapacity()) {
137 case 0:
138 EXPECT_FALSE(tab.ShouldShowCloseBox());
139 EXPECT_FALSE(tab.ShouldShowIcon());
140 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
141 break;
142 case 1:
143 EXPECT_FALSE(tab.ShouldShowCloseBox());
144 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE) {
145 EXPECT_FALSE(tab.ShouldShowIcon());
146 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
147 } else {
148 EXPECT_TRUE(tab.ShouldShowIcon());
149 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
151 break;
152 default:
153 EXPECT_LE(2, tab.IconCapacity());
154 EXPECT_TRUE(tab.ShouldShowIcon());
155 if (tab.data_.media_state != TAB_MEDIA_STATE_NONE)
156 EXPECT_TRUE(tab.ShouldShowMediaIndicator());
157 else
158 EXPECT_FALSE(tab.ShouldShowMediaIndicator());
159 break;
163 // Check positioning of elements with respect to each other, and that they
164 // are fully within the contents bounds.
165 const gfx::Rect contents_bounds = tab.GetContentsBounds();
166 if (tab.ShouldShowIcon()) {
167 EXPECT_LE(contents_bounds.x(), tab.favicon_bounds_.x());
168 if (tab.title_->width() > 0)
169 EXPECT_LE(tab.favicon_bounds_.right(), tab.title_->x());
170 EXPECT_LE(contents_bounds.y(), tab.favicon_bounds_.y());
171 EXPECT_LE(tab.favicon_bounds_.bottom(), contents_bounds.bottom());
173 if (tab.ShouldShowIcon() && tab.ShouldShowMediaIndicator())
174 EXPECT_LE(tab.favicon_bounds_.right(), GetMediaIndicatorBounds(tab).x());
175 if (tab.ShouldShowMediaIndicator()) {
176 if (tab.title_->width() > 0) {
177 EXPECT_LE(tab.title_->bounds().right(),
178 GetMediaIndicatorBounds(tab).x());
180 EXPECT_LE(GetMediaIndicatorBounds(tab).right(), contents_bounds.right());
181 EXPECT_LE(contents_bounds.y(), GetMediaIndicatorBounds(tab).y());
182 EXPECT_LE(GetMediaIndicatorBounds(tab).bottom(),
183 contents_bounds.bottom());
185 if (tab.ShouldShowMediaIndicator() && tab.ShouldShowCloseBox()) {
186 // Note: The media indicator can overlap the left-insets of the close box,
187 // but should otherwise be to the left of the close button.
188 EXPECT_LE(GetMediaIndicatorBounds(tab).right(),
189 tab.close_button_->bounds().x() +
190 tab.close_button_->GetInsets().left());
192 if (tab.ShouldShowCloseBox()) {
193 // Note: The title bounds can overlap the left-insets of the close box,
194 // but should otherwise be to the left of the close button.
195 if (tab.title_->width() > 0) {
196 EXPECT_LE(tab.title_->bounds().right(),
197 tab.close_button_->bounds().x() +
198 tab.close_button_->GetInsets().left());
200 EXPECT_LE(tab.close_button_->bounds().right(), contents_bounds.right());
201 EXPECT_LE(contents_bounds.y(), tab.close_button_->bounds().y());
202 EXPECT_LE(tab.close_button_->bounds().bottom(), contents_bounds.bottom());
206 private:
207 static gfx::Rect GetMediaIndicatorBounds(const Tab& tab) {
208 if (!tab.media_indicator_button_) {
209 ADD_FAILURE();
210 return gfx::Rect();
212 return tab.media_indicator_button_->bounds();
215 std::string original_locale_;
218 TEST_P(TabTest, HitTestTopPixel) {
219 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
220 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
221 return;
224 Widget widget;
225 Widget::InitParams params(CreateParams(Widget::InitParams::TYPE_WINDOW));
226 params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
227 params.bounds.SetRect(10, 20, 300, 400);
228 widget.Init(params);
230 FakeTabController tab_controller;
231 Tab tab(&tab_controller);
232 widget.GetContentsView()->AddChildView(&tab);
233 tab.SetBoundsRect(gfx::Rect(gfx::Point(0, 0), Tab::GetStandardSize()));
235 // Tabs have some shadow in the top, so by default we don't hit the tab there.
236 int middle_x = tab.width() / 2;
237 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
239 // Tabs are slanted, so a click halfway down the left edge won't hit it.
240 int middle_y = tab.height() / 2;
241 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, middle_y)));
243 // If the window is maximized, however, we want clicks in the top edge to
244 // select the tab.
245 widget.Maximize();
246 EXPECT_TRUE(tab.HitTestPoint(gfx::Point(middle_x, 0)));
248 // But clicks in the area above the slanted sides should still miss.
249 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(0, 0)));
250 EXPECT_FALSE(tab.HitTestPoint(gfx::Point(tab.width() - 1, 0)));
253 TEST_P(TabTest, LayoutAndVisibilityOfElements) {
254 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
255 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
256 return;
259 static const TabMediaState kMediaStatesToTest[] = {
260 TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
261 TAB_MEDIA_STATE_AUDIO_PLAYING, TAB_MEDIA_STATE_AUDIO_MUTING
264 FakeTabController controller;
265 Tab tab(&controller);
267 SkBitmap bitmap;
268 bitmap.allocN32Pixels(16, 16);
269 TabRendererData data;
270 data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
272 // Perform layout over all possible combinations, checking for correct
273 // results.
274 for (int is_pinned_tab = 0; is_pinned_tab < 2; ++is_pinned_tab) {
275 for (int is_active_tab = 0; is_active_tab < 2; ++is_active_tab) {
276 for (size_t media_state_index = 0;
277 media_state_index < arraysize(kMediaStatesToTest);
278 ++media_state_index) {
279 const TabMediaState media_state = kMediaStatesToTest[media_state_index];
280 SCOPED_TRACE(::testing::Message()
281 << (is_active_tab ? "Active" : "Inactive") << ' '
282 << (is_pinned_tab ? "Pinned " : "")
283 << "Tab with media indicator state " << media_state);
285 data.pinned = !!is_pinned_tab;
286 controller.set_active_tab(!!is_active_tab);
287 data.media_state = media_state;
288 tab.SetData(data);
290 // Test layout for every width from standard to minimum.
291 gfx::Rect bounds(gfx::Point(0, 0), Tab::GetStandardSize());
292 int min_width;
293 if (is_pinned_tab) {
294 bounds.set_width(Tab::GetPinnedWidth());
295 min_width = Tab::GetPinnedWidth();
296 } else {
297 min_width = is_active_tab ? Tab::GetMinimumSelectedSize().width() :
298 Tab::GetMinimumUnselectedSize().width();
300 while (bounds.width() >= min_width) {
301 SCOPED_TRACE(::testing::Message() << "bounds=" << bounds.ToString());
302 tab.SetBoundsRect(bounds); // Invokes Tab::Layout().
303 CheckForExpectedLayoutAndVisibilityOfElements(tab);
304 bounds.set_width(bounds.width() - 1);
311 // Regression test for http://crbug.com/420313: Confirms that any child Views of
312 // Tab do not attempt to provide their own tooltip behavior/text. It also tests
313 // that Tab provides the expected tooltip text (according to tab_utils).
314 TEST_P(TabTest, TooltipProvidedByTab) {
315 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
316 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
317 return;
320 FakeTabController controller;
321 Tab tab(&controller);
322 tab.SetBoundsRect(gfx::Rect(Tab::GetStandardSize()));
324 SkBitmap bitmap;
325 bitmap.allocN32Pixels(16, 16);
326 TabRendererData data;
327 data.favicon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
329 data.title = base::UTF8ToUTF16(
330 "This is a really long tab title that would case views::Label to provide "
331 "its own tooltip; but Tab should disable that feature so it can provide "
332 "the tooltip instead.");
334 // Test both with and without an indicator showing since the tab tooltip text
335 // should include a description of the media state when the indicator is
336 // present.
337 for (int i = 0; i < 2; ++i) {
338 data.media_state =
339 (i == 0 ? TAB_MEDIA_STATE_NONE : TAB_MEDIA_STATE_AUDIO_PLAYING);
340 SCOPED_TRACE(::testing::Message()
341 << "Tab with media indicator state " << data.media_state);
342 tab.SetData(data);
344 for (int j = 0; j < tab.child_count(); ++j) {
345 views::View& child = *tab.child_at(j);
346 if (!strcmp(child.GetClassName(), "TabCloseButton"))
347 continue; // Close button is excepted.
348 if (!child.visible())
349 continue;
350 SCOPED_TRACE(::testing::Message() << "child_at(" << j << "): "
351 << child.GetClassName());
353 const gfx::Point midpoint(child.width() / 2, child.height() / 2);
354 EXPECT_FALSE(child.GetTooltipHandlerForPoint(midpoint));
355 const gfx::Point mouse_hover_point =
356 midpoint + child.GetMirroredPosition().OffsetFromOrigin();
357 base::string16 tooltip;
358 EXPECT_TRUE(static_cast<views::View&>(tab).GetTooltipText(
359 mouse_hover_point, &tooltip));
360 EXPECT_EQ(chrome::AssembleTabTooltipText(data.title, data.media_state),
361 tooltip);
366 // Regression test for http://crbug.com/226253. Calling Layout() more than once
367 // shouldn't change the insets of the close button.
368 TEST_P(TabTest, CloseButtonLayout) {
369 if (testing_for_rtl_locale() && !base::i18n::IsRTL()) {
370 LOG(WARNING) << "Testing of RTL locale not supported on current platform.";
371 return;
374 FakeTabController tab_controller;
375 Tab tab(&tab_controller);
376 tab.SetBounds(0, 0, 100, 50);
377 tab.Layout();
378 gfx::Insets close_button_insets = tab.close_button_->GetInsets();
379 tab.Layout();
380 gfx::Insets close_button_insets_2 = tab.close_button_->GetInsets();
381 EXPECT_EQ(close_button_insets.top(), close_button_insets_2.top());
382 EXPECT_EQ(close_button_insets.left(), close_button_insets_2.left());
383 EXPECT_EQ(close_button_insets.bottom(), close_button_insets_2.bottom());
384 EXPECT_EQ(close_button_insets.right(), close_button_insets_2.right());
386 // Also make sure the close button is sized as large as the tab.
387 EXPECT_EQ(50, tab.close_button_->bounds().height());
390 // Test in both a LTR and a RTL locale. Note: The fact that the UI code is
391 // configured for an RTL locale does *not* change how the coordinates are
392 // examined in the tests above because views::View and friends are supposed to
393 // auto-mirror the widgets when painting. Thus, what we're testing here is that
394 // there's no code in Tab that will erroneously subvert this automatic
395 // coordinate translation. http://crbug.com/384179
396 INSTANTIATE_TEST_CASE_P(, TabTest, ::testing::Values(false, true));