Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / views / tabs / tab_strip_unittest.cc
blobf4901b841787636f123e31919b78d5e691de8b42
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_strip.h"
7 #include "base/message_loop/message_loop.h"
8 #include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
9 #include "chrome/browser/ui/views/tabs/tab.h"
10 #include "chrome/browser/ui/views/tabs/tab_strip.h"
11 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
12 #include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
13 #include "chrome/test/base/testing_profile.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/gfx/path.h"
16 #include "ui/gfx/rect_conversions.h"
17 #include "ui/gfx/skia_util.h"
18 #include "ui/views/view.h"
20 namespace {
22 // Walks up the views hierarchy until it finds a tab view. It returns the
23 // found tab view, on NULL if none is found.
24 views::View* FindTabView(views::View* view) {
25 views::View* current = view;
26 while (current && strcmp(current->GetClassName(), Tab::kViewClassName)) {
27 current = current->parent();
29 return current;
32 } // namespace
34 class TestTabStripObserver : public TabStripObserver {
35 public:
36 explicit TestTabStripObserver(TabStrip* tab_strip)
37 : tab_strip_(tab_strip),
38 last_tab_added_(-1),
39 last_tab_removed_(-1),
40 last_tab_moved_from_(-1),
41 last_tab_moved_to_(-1),
42 tabstrip_deleted_(false) {
43 tab_strip_->AddObserver(this);
46 virtual ~TestTabStripObserver() {
47 if (tab_strip_)
48 tab_strip_->RemoveObserver(this);
51 int last_tab_added() const { return last_tab_added_; }
52 int last_tab_removed() const { return last_tab_removed_; }
53 int last_tab_moved_from() const { return last_tab_moved_from_; }
54 int last_tab_moved_to() const { return last_tab_moved_to_; }
55 bool tabstrip_deleted() const { return tabstrip_deleted_; }
57 private:
58 // TabStripObserver overrides.
59 virtual void TabStripAddedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
60 last_tab_added_ = index;
63 virtual void TabStripMovedTab(TabStrip* tab_strip,
64 int from_index,
65 int to_index) OVERRIDE {
66 last_tab_moved_from_ = from_index;
67 last_tab_moved_to_ = to_index;
70 virtual void TabStripRemovedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
71 last_tab_removed_ = index;
74 virtual void TabStripDeleted(TabStrip* tab_strip) OVERRIDE {
75 tabstrip_deleted_ = true;
76 tab_strip_ = NULL;
79 TabStrip* tab_strip_;
80 int last_tab_added_;
81 int last_tab_removed_;
82 int last_tab_moved_from_;
83 int last_tab_moved_to_;
84 bool tabstrip_deleted_;
86 DISALLOW_COPY_AND_ASSIGN(TestTabStripObserver);
89 class TabStripTest : public testing::Test {
90 public:
91 TabStripTest()
92 : controller_(new FakeBaseTabStripController) {
93 tab_strip_ = new TabStrip(controller_);
94 controller_->set_tab_strip(tab_strip_);
95 // Do this to force TabStrip to create the buttons.
96 parent_.AddChildView(tab_strip_);
99 protected:
100 // Returns the rectangular hit test region of |tab| in |tab|'s local
101 // coordinate space.
102 gfx::Rect GetTabHitTestMask(Tab* tab) {
103 gfx::Path mask;
104 tab->GetHitTestMask(views::View::HIT_TEST_SOURCE_TOUCH, &mask);
105 return gfx::ToEnclosingRect((gfx::SkRectToRectF(mask.getBounds())));
108 // Returns the rectangular hit test region of the tab close button of
109 // |tab| in |tab|'s coordinate space (including padding if |padding|
110 // is true).
111 gfx::Rect GetTabCloseHitTestMask(Tab* tab, bool padding) {
112 gfx::RectF bounds_f = tab->close_button_->GetContentsBounds();
113 if (padding)
114 bounds_f = tab->close_button_->GetLocalBounds();
115 views::View::ConvertRectToTarget(tab->close_button_, tab, &bounds_f);
116 return gfx::ToEnclosingRect(bounds_f);
119 // Checks whether |tab| contains |point_in_tabstrip_coords|, where the point
120 // is in |tab_strip_| coordinates.
121 bool IsPointInTab(Tab* tab, const gfx::Point& point_in_tabstrip_coords) {
122 gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
123 views::View::ConvertPointToTarget(tab_strip_, tab, &point_in_tab_coords);
124 return tab->HitTestPoint(point_in_tab_coords);
127 base::MessageLoopForUI ui_loop_;
128 // Owned by TabStrip.
129 FakeBaseTabStripController* controller_;
130 // Owns |tab_strip_|.
131 views::View parent_;
132 TabStrip* tab_strip_;
134 private:
135 DISALLOW_COPY_AND_ASSIGN(TabStripTest);
138 TEST_F(TabStripTest, GetModelCount) {
139 EXPECT_EQ(0, tab_strip_->GetModelCount());
142 TEST_F(TabStripTest, IsValidModelIndex) {
143 EXPECT_FALSE(tab_strip_->IsValidModelIndex(0));
146 TEST_F(TabStripTest, tab_count) {
147 EXPECT_EQ(0, tab_strip_->tab_count());
150 TEST_F(TabStripTest, CreateTabForDragging) {
151 // Any result is good, as long as it doesn't crash.
152 scoped_ptr<Tab> tab(tab_strip_->CreateTabForDragging());
155 TEST_F(TabStripTest, AddTabAt) {
156 TestTabStripObserver observer(tab_strip_);
157 tab_strip_->AddTabAt(0, TabRendererData(), false);
158 ASSERT_EQ(1, tab_strip_->tab_count());
159 EXPECT_EQ(0, observer.last_tab_added());
160 Tab* tab = tab_strip_->tab_at(0);
161 EXPECT_FALSE(tab == NULL);
164 // Confirms that TabStripObserver::TabStripDeleted() is sent.
165 TEST_F(TabStripTest, TabStripDeleted) {
166 FakeBaseTabStripController* controller = new FakeBaseTabStripController;
167 TabStrip* tab_strip = new TabStrip(controller);
168 controller->set_tab_strip(tab_strip);
169 TestTabStripObserver observer(tab_strip);
170 delete tab_strip;
171 EXPECT_TRUE(observer.tabstrip_deleted());
174 TEST_F(TabStripTest, MoveTab) {
175 TestTabStripObserver observer(tab_strip_);
176 tab_strip_->AddTabAt(0, TabRendererData(), false);
177 tab_strip_->AddTabAt(1, TabRendererData(), false);
178 tab_strip_->AddTabAt(2, TabRendererData(), false);
179 ASSERT_EQ(3, tab_strip_->tab_count());
180 EXPECT_EQ(2, observer.last_tab_added());
181 Tab* tab = tab_strip_->tab_at(0);
182 tab_strip_->MoveTab(0, 1, TabRendererData());
183 EXPECT_EQ(0, observer.last_tab_moved_from());
184 EXPECT_EQ(1, observer.last_tab_moved_to());
185 EXPECT_EQ(tab, tab_strip_->tab_at(1));
188 // Verifies child views are deleted after an animation completes.
189 TEST_F(TabStripTest, RemoveTab) {
190 TestTabStripObserver observer(tab_strip_);
191 controller_->AddTab(0, false);
192 controller_->AddTab(1, false);
193 const int child_view_count = tab_strip_->child_count();
194 EXPECT_EQ(2, tab_strip_->tab_count());
195 controller_->RemoveTab(0);
196 EXPECT_EQ(0, observer.last_tab_removed());
197 // When removing a tab the tabcount should immediately decrement.
198 EXPECT_EQ(1, tab_strip_->tab_count());
199 // But the number of views should remain the same (it's animatining closed).
200 EXPECT_EQ(child_view_count, tab_strip_->child_count());
201 tab_strip_->SetBounds(0, 0, 200, 20);
202 // Layout at a different size should force the animation to end and delete
203 // the tab that was removed.
204 tab_strip_->Layout();
205 EXPECT_EQ(child_view_count - 1, tab_strip_->child_count());
207 // Remove the last tab to make sure things are cleaned up correctly when
208 // the TabStrip is destroyed and an animation is ongoing.
209 controller_->RemoveTab(0);
210 EXPECT_EQ(0, observer.last_tab_removed());
213 TEST_F(TabStripTest, ImmersiveMode) {
214 // Immersive mode defaults to off.
215 EXPECT_FALSE(tab_strip_->IsImmersiveStyle());
217 // Tab strip defaults to normal tab height.
218 int normal_height = Tab::GetMinimumUnselectedSize().height();
219 EXPECT_EQ(normal_height, tab_strip_->GetPreferredSize().height());
221 // Tab strip can toggle immersive mode.
222 tab_strip_->SetImmersiveStyle(true);
223 EXPECT_TRUE(tab_strip_->IsImmersiveStyle());
225 // Now tabs have the immersive height.
226 int immersive_height = Tab::GetImmersiveHeight();
227 EXPECT_EQ(immersive_height, tab_strip_->GetPreferredSize().height());
229 // Sanity-check immersive tabs are shorter than normal tabs.
230 EXPECT_LT(immersive_height, normal_height);
233 // Creates a tab strip in stacked layout mode and verifies the correctness
234 // of hit tests against the visible/occluded regions of a tab and
235 // visible/occluded tab close buttons.
236 TEST_F(TabStripTest, TabHitTestMaskWhenStacked) {
237 tab_strip_->SetBounds(0, 0, 300, 20);
239 controller_->AddTab(0, false);
240 controller_->AddTab(1, true);
241 controller_->AddTab(2, false);
242 controller_->AddTab(3, false);
243 ASSERT_EQ(4, tab_strip_->tab_count());
245 Tab* left_tab = tab_strip_->tab_at(0);
246 left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
248 Tab* active_tab = tab_strip_->tab_at(1);
249 active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
250 ASSERT_TRUE(active_tab->IsActive());
252 Tab* right_tab = tab_strip_->tab_at(2);
253 right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
255 Tab* most_right_tab = tab_strip_->tab_at(3);
256 most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
257 gfx::Size(200, 20)));
259 // Switch to stacked layout mode and force a layout to ensure tabs stack.
260 tab_strip_->SetLayoutType(TAB_STRIP_LAYOUT_STACKED, false);
261 tab_strip_->DoLayout();
264 // Tests involving |left_tab|, which has part of its bounds and its tab
265 // close button occluded by |active_tab|.
267 // Bounds of the tab's hit test mask.
268 gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
269 EXPECT_EQ(gfx::Rect(6, 2, 61, 27).ToString(), tab_bounds.ToString());
271 // Bounds of the tab close button (without padding) in the tab's
272 // coordinate space.
273 gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
274 // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
275 //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
277 // Verify that the tab close button is occluded.
278 EXPECT_FALSE(tab_bounds.Contains(contents_bounds));
280 // Hit tests in the non-occuluded region of the tab.
281 EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 2, 2)));
282 EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 1, 1)));
283 EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
284 EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 25, 35)));
285 EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(-10, -5, 20, 30)));
287 // Hit tests in the occluded region of the tab.
288 EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, 15, 2, 2)));
289 EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, -15, 30, 40)));
290 EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(87, 20, 5, 3)));
292 // Hit tests completely outside of the tab.
293 EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 1, 1)));
294 EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 3, 19)));
296 // All hit tests against the tab close button should fail because
297 // it is occluded by |active_tab|.
298 views::ImageButton* left_close = left_tab->close_button_;
299 EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
300 EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 5, 10)));
301 EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
302 EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 3, 4)));
305 // Tests involving |active_tab|, which is completely visible.
307 tab_bounds = GetTabHitTestMask(active_tab);
308 EXPECT_EQ(gfx::Rect(6, 2, 108, 27).ToString(), tab_bounds.ToString());
309 contents_bounds = GetTabCloseHitTestMask(active_tab, false);
310 // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
311 //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
313 // Verify that the tab close button is not occluded.
314 EXPECT_TRUE(tab_bounds.Contains(contents_bounds));
316 // Bounds of the tab close button (without padding) in the tab's
317 // coordinate space.
318 gfx::Rect local_bounds = GetTabCloseHitTestMask(active_tab, true);
319 EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());
321 // Hit tests within the tab.
322 EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
323 EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 2, 2)));
325 // Hit tests against the tab close button. Note that if the hit test
326 // source is a mouse, a hit test within the button's padding should fail.
327 views::ImageButton* active_close = active_tab->close_button_;
328 EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
329 EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
330 EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
331 EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
334 // Tests involving |most_right_tab|, which has part of its bounds occluded
335 // by |right_tab| but has its tab close button completely visible.
337 tab_bounds = GetTabHitTestMask(most_right_tab);
338 EXPECT_EQ(gfx::Rect(84, 2, 30, 27).ToString(), tab_bounds.ToString());
339 contents_bounds = GetTabCloseHitTestMask(active_tab, false);
340 // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
341 //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
342 local_bounds = GetTabCloseHitTestMask(active_tab, true);
343 EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());
345 // Verify that the tab close button is not occluded.
346 EXPECT_TRUE(tab_bounds.Contains(contents_bounds));
348 // Hit tests in the occluded region of the tab.
349 EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 1, 1)));
350 EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 5, 6)));
352 // Hit tests in the non-occluded region of the tab.
353 EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 1, 1)));
354 EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 2, 2)));
356 // Hit tests against the tab close button. Note that if the hit test
357 // source is a mouse, a hit test within the button's padding should fail.
358 views::ImageButton* most_right_close = most_right_tab->close_button_;
359 EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
360 EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
361 EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
362 EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
363 EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(-10, 10, 25, 35)));
366 TEST_F(TabStripTest, GetEventHandlerForOverlappingArea) {
367 tab_strip_->SetBounds(0, 0, 1000, 20);
369 controller_->AddTab(0, false);
370 controller_->AddTab(1, true);
371 controller_->AddTab(2, false);
372 controller_->AddTab(3, false);
373 ASSERT_EQ(4, tab_strip_->tab_count());
375 // Verify that the active tab will be a tooltip handler for points that hit
376 // it.
377 Tab* left_tab = tab_strip_->tab_at(0);
378 left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
380 Tab* active_tab = tab_strip_->tab_at(1);
381 active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
382 ASSERT_TRUE(active_tab->IsActive());
384 Tab* right_tab = tab_strip_->tab_at(2);
385 right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
387 Tab* most_right_tab = tab_strip_->tab_at(3);
388 most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
389 gfx::Size(200, 20)));
391 // Test that active tabs gets events from area in which it overlaps with its
392 // left neighbour.
393 gfx::Point left_overlap(
394 (active_tab->x() + left_tab->bounds().right() + 1) / 2,
395 active_tab->bounds().bottom() - 1);
397 // Sanity check that the point is in both active and left tab.
398 ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
399 ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
401 EXPECT_EQ(active_tab,
402 FindTabView(tab_strip_->GetEventHandlerForPoint(left_overlap)));
404 // Test that active tabs gets events from area in which it overlaps with its
405 // right neighbour.
406 gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
407 active_tab->bounds().bottom() - 1);
409 // Sanity check that the point is in both active and right tab.
410 ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
411 ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
413 EXPECT_EQ(active_tab,
414 FindTabView(tab_strip_->GetEventHandlerForPoint(right_overlap)));
416 // Test that if neither of tabs is active, the left one is selected.
417 gfx::Point unactive_overlap(
418 (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
419 right_tab->bounds().bottom() - 1);
421 // Sanity check that the point is in both active and left tab.
422 ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
423 ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
425 EXPECT_EQ(right_tab,
426 FindTabView(tab_strip_->GetEventHandlerForPoint(unactive_overlap)));
429 TEST_F(TabStripTest, GetTooltipHandler) {
430 tab_strip_->SetBounds(0, 0, 1000, 20);
432 controller_->AddTab(0, false);
433 controller_->AddTab(1, true);
434 controller_->AddTab(2, false);
435 controller_->AddTab(3, false);
436 ASSERT_EQ(4, tab_strip_->tab_count());
438 // Verify that the active tab will be a tooltip handler for points that hit
439 // it.
440 Tab* left_tab = tab_strip_->tab_at(0);
441 left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
443 Tab* active_tab = tab_strip_->tab_at(1);
444 active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
445 ASSERT_TRUE(active_tab->IsActive());
447 Tab* right_tab = tab_strip_->tab_at(2);
448 right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
450 Tab* most_right_tab = tab_strip_->tab_at(3);
451 most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
452 gfx::Size(200, 20)));
454 // Test that active_tab handles tooltips from area in which it overlaps with
455 // its left neighbour.
456 gfx::Point left_overlap(
457 (active_tab->x() + left_tab->bounds().right() + 1) / 2,
458 active_tab->bounds().bottom() - 1);
460 // Sanity check that the point is in both active and left tab.
461 ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
462 ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
464 EXPECT_EQ(active_tab,
465 FindTabView(tab_strip_->GetTooltipHandlerForPoint(left_overlap)));
467 // Test that active_tab handles tooltips from area in which it overlaps with
468 // its right neighbour.
469 gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
470 active_tab->bounds().bottom() - 1);
472 // Sanity check that the point is in both active and right tab.
473 ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
474 ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
476 EXPECT_EQ(active_tab,
477 FindTabView(tab_strip_->GetTooltipHandlerForPoint(right_overlap)));
479 // Test that if neither of tabs is active, the left one is selected.
480 gfx::Point unactive_overlap(
481 (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
482 right_tab->bounds().bottom() - 1);
484 // Sanity check that the point is in both active and left tab.
485 ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
486 ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
488 EXPECT_EQ(
489 right_tab,
490 FindTabView(tab_strip_->GetTooltipHandlerForPoint(unactive_overlap)));
492 // Confirm that tab strip doe not return tooltip handler for points that
493 // don't hit it.
494 EXPECT_FALSE(tab_strip_->GetTooltipHandlerForPoint(gfx::Point(-1, 2)));