tab_strip_unittest.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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_strip.h"
6
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"
19
20namespace {
21
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.
24views::View* FindTabView(views::View* view) {
25  views::View* current = view;
26  while (current && strcmp(current->GetClassName(), Tab::kViewClassName)) {
27    current = current->parent();
28  }
29  return current;
30}
31
32}  // namespace
33
34class 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);
44  }
45
46  virtual ~TestTabStripObserver() {
47    if (tab_strip_)
48      tab_strip_->RemoveObserver(this);
49  }
50
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_; }
56
57 private:
58  // TabStripObserver overrides.
59  virtual void TabStripAddedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
60    last_tab_added_ = index;
61  }
62
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;
68  }
69
70  virtual void TabStripRemovedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
71    last_tab_removed_ = index;
72  }
73
74  virtual void TabStripDeleted(TabStrip* tab_strip) OVERRIDE {
75    tabstrip_deleted_ = true;
76    tab_strip_ = NULL;
77  }
78
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_;
85
86  DISALLOW_COPY_AND_ASSIGN(TestTabStripObserver);
87};
88
89class 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_);
97  }
98
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())));
106  }
107
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);
117  }
118
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);
125  }
126
127  base::MessageLoopForUI ui_loop_;
128  // Owned by TabStrip.
129  FakeBaseTabStripController* controller_;
130  // Owns |tab_strip_|.
131  views::View parent_;
132  TabStrip* tab_strip_;
133
134 private:
135  DISALLOW_COPY_AND_ASSIGN(TabStripTest);
136};
137
138TEST_F(TabStripTest, GetModelCount) {
139  EXPECT_EQ(0, tab_strip_->GetModelCount());
140}
141
142TEST_F(TabStripTest, IsValidModelIndex) {
143  EXPECT_FALSE(tab_strip_->IsValidModelIndex(0));
144}
145
146TEST_F(TabStripTest, tab_count) {
147  EXPECT_EQ(0, tab_strip_->tab_count());
148}
149
150TEST_F(TabStripTest, AddTabAt) {
151  TestTabStripObserver observer(tab_strip_);
152  tab_strip_->AddTabAt(0, TabRendererData(), false);
153  ASSERT_EQ(1, tab_strip_->tab_count());
154  EXPECT_EQ(0, observer.last_tab_added());
155  Tab* tab = tab_strip_->tab_at(0);
156  EXPECT_FALSE(tab == NULL);
157}
158
159// Confirms that TabStripObserver::TabStripDeleted() is sent.
160TEST_F(TabStripTest, TabStripDeleted) {
161  FakeBaseTabStripController* controller = new FakeBaseTabStripController;
162  TabStrip* tab_strip = new TabStrip(controller);
163  controller->set_tab_strip(tab_strip);
164  TestTabStripObserver observer(tab_strip);
165  delete tab_strip;
166  EXPECT_TRUE(observer.tabstrip_deleted());
167}
168
169TEST_F(TabStripTest, MoveTab) {
170  TestTabStripObserver observer(tab_strip_);
171  tab_strip_->AddTabAt(0, TabRendererData(), false);
172  tab_strip_->AddTabAt(1, TabRendererData(), false);
173  tab_strip_->AddTabAt(2, TabRendererData(), false);
174  ASSERT_EQ(3, tab_strip_->tab_count());
175  EXPECT_EQ(2, observer.last_tab_added());
176  Tab* tab = tab_strip_->tab_at(0);
177  tab_strip_->MoveTab(0, 1, TabRendererData());
178  EXPECT_EQ(0, observer.last_tab_moved_from());
179  EXPECT_EQ(1, observer.last_tab_moved_to());
180  EXPECT_EQ(tab, tab_strip_->tab_at(1));
181}
182
183// Verifies child views are deleted after an animation completes.
184TEST_F(TabStripTest, RemoveTab) {
185  TestTabStripObserver observer(tab_strip_);
186  controller_->AddTab(0, false);
187  controller_->AddTab(1, false);
188  const int child_view_count = tab_strip_->child_count();
189  EXPECT_EQ(2, tab_strip_->tab_count());
190  controller_->RemoveTab(0);
191  EXPECT_EQ(0, observer.last_tab_removed());
192  // When removing a tab the tabcount should immediately decrement.
193  EXPECT_EQ(1, tab_strip_->tab_count());
194  // But the number of views should remain the same (it's animatining closed).
195  EXPECT_EQ(child_view_count, tab_strip_->child_count());
196  tab_strip_->SetBounds(0, 0, 200, 20);
197  // Layout at a different size should force the animation to end and delete
198  // the tab that was removed.
199  tab_strip_->Layout();
200  EXPECT_EQ(child_view_count - 1, tab_strip_->child_count());
201
202  // Remove the last tab to make sure things are cleaned up correctly when
203  // the TabStrip is destroyed and an animation is ongoing.
204  controller_->RemoveTab(0);
205  EXPECT_EQ(0, observer.last_tab_removed());
206}
207
208TEST_F(TabStripTest, VisibilityInOverflow) {
209  tab_strip_->SetBounds(0, 0, 200, 20);
210
211  // The first tab added to a reasonable-width strip should be visible.  If we
212  // add enough additional tabs, eventually one should be invisible due to
213  // overflow.
214  int invisible_tab_index = 0;
215  for (; invisible_tab_index < 100; ++invisible_tab_index) {
216    controller_->AddTab(invisible_tab_index, false);
217    if (!tab_strip_->tab_at(invisible_tab_index)->visible())
218      break;
219  }
220  EXPECT_GT(invisible_tab_index, 0);
221  EXPECT_LT(invisible_tab_index, 100);
222
223  // The tabs before the invisible tab should still be visible.
224  for (int i = 0; i < invisible_tab_index; ++i)
225    EXPECT_TRUE(tab_strip_->tab_at(i)->visible());
226
227  // Enlarging the strip should result in the last tab becoming visible.
228  tab_strip_->SetBounds(0, 0, 400, 20);
229  EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index)->visible());
230
231  // Shrinking it again should re-hide the last tab.
232  tab_strip_->SetBounds(0, 0, 200, 20);
233  EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->visible());
234
235  // Shrinking it still more should make more tabs invisible, though not all.
236  // All the invisible tabs should be at the end of the strip.
237  tab_strip_->SetBounds(0, 0, 100, 20);
238  int i = 0;
239  for (; i < invisible_tab_index; ++i) {
240    if (!tab_strip_->tab_at(i)->visible())
241      break;
242  }
243  ASSERT_GT(i, 0);
244  EXPECT_LT(i, invisible_tab_index);
245  invisible_tab_index = i;
246  for (int i = invisible_tab_index + 1; i < tab_strip_->tab_count(); ++i)
247    EXPECT_FALSE(tab_strip_->tab_at(i)->visible());
248
249  // When we're already in overflow, adding tabs at the beginning or end of
250  // the strip should not change how many tabs are visible.
251  controller_->AddTab(tab_strip_->tab_count(), false);
252  EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index - 1)->visible());
253  EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->visible());
254  controller_->AddTab(0, false);
255  EXPECT_TRUE(tab_strip_->tab_at(invisible_tab_index - 1)->visible());
256  EXPECT_FALSE(tab_strip_->tab_at(invisible_tab_index)->visible());
257
258  // If we remove enough tabs, all the tabs should be visible.
259  for (int i = tab_strip_->tab_count() - 1; i >= invisible_tab_index; --i)
260    controller_->RemoveTab(i);
261  EXPECT_TRUE(tab_strip_->tab_at(tab_strip_->tab_count() - 1)->visible());
262}
263
264TEST_F(TabStripTest, ImmersiveMode) {
265  // Immersive mode defaults to off.
266  EXPECT_FALSE(tab_strip_->IsImmersiveStyle());
267
268  // Tab strip defaults to normal tab height.
269  int normal_height = Tab::GetMinimumUnselectedSize().height();
270  EXPECT_EQ(normal_height, tab_strip_->GetPreferredSize().height());
271
272  // Tab strip can toggle immersive mode.
273  tab_strip_->SetImmersiveStyle(true);
274  EXPECT_TRUE(tab_strip_->IsImmersiveStyle());
275
276  // Now tabs have the immersive height.
277  int immersive_height = Tab::GetImmersiveHeight();
278  EXPECT_EQ(immersive_height, tab_strip_->GetPreferredSize().height());
279
280  // Sanity-check immersive tabs are shorter than normal tabs.
281  EXPECT_LT(immersive_height, normal_height);
282}
283
284// Creates a tab strip in stacked layout mode and verifies the correctness
285// of hit tests against the visible/occluded regions of a tab and
286// visible/occluded tab close buttons.
287TEST_F(TabStripTest, TabHitTestMaskWhenStacked) {
288  tab_strip_->SetBounds(0, 0, 300, 20);
289
290  controller_->AddTab(0, false);
291  controller_->AddTab(1, true);
292  controller_->AddTab(2, false);
293  controller_->AddTab(3, false);
294  ASSERT_EQ(4, tab_strip_->tab_count());
295
296  Tab* left_tab = tab_strip_->tab_at(0);
297  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
298
299  Tab* active_tab = tab_strip_->tab_at(1);
300  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
301  ASSERT_TRUE(active_tab->IsActive());
302
303  Tab* right_tab = tab_strip_->tab_at(2);
304  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
305
306  Tab* most_right_tab = tab_strip_->tab_at(3);
307  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
308                                          gfx::Size(200, 20)));
309
310  // Switch to stacked layout mode and force a layout to ensure tabs stack.
311  tab_strip_->SetStackedLayout(true);
312  tab_strip_->DoLayout();
313
314
315  // Tests involving |left_tab|, which has part of its bounds and its tab
316  // close button completely occluded by |active_tab|.
317
318  // Bounds of the tab's hit test mask.
319  gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
320  EXPECT_EQ(gfx::Rect(6, 2, 61, 27).ToString(), tab_bounds.ToString());
321
322  // Bounds of the tab close button (without padding) in the tab's
323  // coordinate space.
324  gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
325  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
326  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
327
328  // Verify that the tab close button is completely occluded.
329  EXPECT_FALSE(tab_bounds.Contains(contents_bounds));
330
331  // Hit tests in the non-occuluded region of the tab.
332  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 2, 2)));
333  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 1, 1)));
334  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
335  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 25, 35)));
336  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(-10, -5, 20, 30)));
337
338  // Hit tests in the occluded region of the tab.
339  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, 15, 2, 2)));
340  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, -15, 30, 40)));
341  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(87, 20, 5, 3)));
342
343  // Hit tests completely outside of the tab.
344  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 1, 1)));
345  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 3, 19)));
346
347  // All hit tests against the tab close button should fail because
348  // it is occluded by |active_tab|.
349  views::ImageButton* left_close = left_tab->close_button_;
350  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
351  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 5, 10)));
352  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
353  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 3, 4)));
354
355
356  // Tests involving |active_tab|, which is completely visible.
357
358  tab_bounds = GetTabHitTestMask(active_tab);
359  EXPECT_EQ(gfx::Rect(6, 2, 108, 27).ToString(), tab_bounds.ToString());
360  contents_bounds = GetTabCloseHitTestMask(active_tab, false);
361  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
362  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
363
364  // Verify that the tab close button is not occluded.
365  EXPECT_TRUE(tab_bounds.Contains(contents_bounds));
366
367  // Bounds of the tab close button (without padding) in the tab's
368  // coordinate space.
369  gfx::Rect local_bounds = GetTabCloseHitTestMask(active_tab, true);
370  EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());
371
372  // Hit tests within the tab.
373  EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
374  EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 2, 2)));
375
376  // Hit tests against the tab close button. Note that hit tests from either
377  // mouse or touch should both fail if they are strictly contained within
378  // the button's padding.
379  views::ImageButton* active_close = active_tab->close_button_;
380  EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
381  EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
382  EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
383  EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
384
385
386  // Tests involving |most_right_tab|, which has part of its bounds occluded
387  // by |right_tab| but has its tab close button completely visible.
388
389  tab_bounds = GetTabHitTestMask(most_right_tab);
390  EXPECT_EQ(gfx::Rect(84, 2, 30, 27).ToString(), tab_bounds.ToString());
391  contents_bounds = GetTabCloseHitTestMask(active_tab, false);
392  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
393  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
394  local_bounds = GetTabCloseHitTestMask(active_tab, true);
395  EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());
396
397  // Verify that the tab close button is not occluded.
398  EXPECT_TRUE(tab_bounds.Contains(contents_bounds));
399
400  // Hit tests in the occluded region of the tab.
401  EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 1, 1)));
402  EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 5, 6)));
403
404  // Hit tests in the non-occluded region of the tab.
405  EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 1, 1)));
406  EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 2, 2)));
407
408  // Hit tests against the tab close button. Note that hit tests from either
409  // mouse or touch should both fail if they are strictly contained within
410  // the button's padding.
411  views::ImageButton* most_right_close = most_right_tab->close_button_;
412  EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
413  EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
414  EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
415  EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
416  EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(-10, 10, 25, 35)));
417}
418
419// Creates a tab strip in stacked layout mode and verifies the correctness
420// of hit tests against the visible/occluded region of a partially-occluded
421// tab close button.
422TEST_F(TabStripTest, ClippedTabCloseButton) {
423  tab_strip_->SetBounds(0, 0, 220, 20);
424
425  controller_->AddTab(0, false);
426  controller_->AddTab(1, true);
427  ASSERT_EQ(2, tab_strip_->tab_count());
428
429  Tab* left_tab = tab_strip_->tab_at(0);
430  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
431
432  Tab* active_tab = tab_strip_->tab_at(1);
433  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(180, 0), gfx::Size(200, 20)));
434  ASSERT_TRUE(active_tab->IsActive());
435
436  // Switch to stacked layout mode and force a layout to ensure tabs stack.
437  tab_strip_->SetStackedLayout(true);
438  tab_strip_->DoLayout();
439
440
441  // Tests involving |left_tab|, which has part of its bounds and its tab
442  // close button partially occluded by |active_tab|.
443
444  // Bounds of the tab's hit test mask.
445  gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
446  EXPECT_EQ(gfx::Rect(6, 2, 91, 27).ToString(), tab_bounds.ToString());
447
448  // Bounds of the tab close button (without padding) in the tab's
449  // coordinate space.
450  gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
451  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
452  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
453
454  // Verify that the tab close button is only partially occluded.
455  EXPECT_FALSE(tab_bounds.Contains(contents_bounds));
456  EXPECT_TRUE(tab_bounds.Intersects(contents_bounds));
457
458  views::ImageButton* left_close = left_tab->close_button_;
459
460  // Hit tests from mouse should return true if and only if the location
461  // is within a visible region.
462  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(2, 15, 1, 1)));
463  EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(3, 15, 1, 1)));
464  EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
465  EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(15, 12, 1, 1)));
466  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(16, 10, 1, 1)));
467
468  // All hit tests from touch should return false because the button is
469  // not fully visible.
470  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(2, 15, 2, 2)));
471  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(3, 15, 25, 25)));
472  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 4, 5)));
473  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(15, 12, 2, 2)));
474  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(16, 10, 20, 20)));
475}
476
477TEST_F(TabStripTest, GetEventHandlerForOverlappingArea) {
478  tab_strip_->SetBounds(0, 0, 1000, 20);
479
480  controller_->AddTab(0, false);
481  controller_->AddTab(1, true);
482  controller_->AddTab(2, false);
483  controller_->AddTab(3, false);
484  ASSERT_EQ(4, tab_strip_->tab_count());
485
486  // Verify that the active tab will be a tooltip handler for points that hit
487  // it.
488  Tab* left_tab = tab_strip_->tab_at(0);
489  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
490
491  Tab* active_tab = tab_strip_->tab_at(1);
492  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
493  ASSERT_TRUE(active_tab->IsActive());
494
495  Tab* right_tab = tab_strip_->tab_at(2);
496  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
497
498  Tab* most_right_tab = tab_strip_->tab_at(3);
499  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
500                                          gfx::Size(200, 20)));
501
502  // Test that active tabs gets events from area in which it overlaps with its
503  // left neighbour.
504  gfx::Point left_overlap(
505      (active_tab->x() + left_tab->bounds().right() + 1) / 2,
506      active_tab->bounds().bottom() - 1);
507
508  // Sanity check that the point is in both active and left tab.
509  ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
510  ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
511
512  EXPECT_EQ(active_tab,
513            FindTabView(tab_strip_->GetEventHandlerForPoint(left_overlap)));
514
515  // Test that active tabs gets events from area in which it overlaps with its
516  // right neighbour.
517  gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
518                           active_tab->bounds().bottom() - 1);
519
520  // Sanity check that the point is in both active and right tab.
521  ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
522  ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
523
524  EXPECT_EQ(active_tab,
525            FindTabView(tab_strip_->GetEventHandlerForPoint(right_overlap)));
526
527  // Test that if neither of tabs is active, the left one is selected.
528  gfx::Point unactive_overlap(
529      (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
530      right_tab->bounds().bottom() - 1);
531
532  // Sanity check that the point is in both active and left tab.
533  ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
534  ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
535
536  EXPECT_EQ(right_tab,
537            FindTabView(tab_strip_->GetEventHandlerForPoint(unactive_overlap)));
538}
539
540TEST_F(TabStripTest, GetTooltipHandler) {
541  tab_strip_->SetBounds(0, 0, 1000, 20);
542
543  controller_->AddTab(0, false);
544  controller_->AddTab(1, true);
545  controller_->AddTab(2, false);
546  controller_->AddTab(3, false);
547  ASSERT_EQ(4, tab_strip_->tab_count());
548
549  // Verify that the active tab will be a tooltip handler for points that hit
550  // it.
551  Tab* left_tab = tab_strip_->tab_at(0);
552  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
553
554  Tab* active_tab = tab_strip_->tab_at(1);
555  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
556  ASSERT_TRUE(active_tab->IsActive());
557
558  Tab* right_tab = tab_strip_->tab_at(2);
559  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
560
561  Tab* most_right_tab = tab_strip_->tab_at(3);
562  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
563                                          gfx::Size(200, 20)));
564
565  // Test that active_tab handles tooltips from area in which it overlaps with
566  // its left neighbour.
567  gfx::Point left_overlap(
568      (active_tab->x() + left_tab->bounds().right() + 1) / 2,
569      active_tab->bounds().bottom() - 1);
570
571  // Sanity check that the point is in both active and left tab.
572  ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
573  ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
574
575  EXPECT_EQ(active_tab,
576            FindTabView(tab_strip_->GetTooltipHandlerForPoint(left_overlap)));
577
578  // Test that active_tab handles tooltips from area in which it overlaps with
579  // its right neighbour.
580  gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
581                           active_tab->bounds().bottom() - 1);
582
583  // Sanity check that the point is in both active and right tab.
584  ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
585  ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
586
587  EXPECT_EQ(active_tab,
588            FindTabView(tab_strip_->GetTooltipHandlerForPoint(right_overlap)));
589
590  // Test that if neither of tabs is active, the left one is selected.
591  gfx::Point unactive_overlap(
592      (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
593      right_tab->bounds().bottom() - 1);
594
595  // Sanity check that the point is in both active and left tab.
596  ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
597  ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
598
599  EXPECT_EQ(
600      right_tab,
601      FindTabView(tab_strip_->GetTooltipHandlerForPoint(unactive_overlap)));
602
603  // Confirm that tab strip doe not return tooltip handler for points that
604  // don't hit it.
605  EXPECT_FALSE(tab_strip_->GetTooltipHandlerForPoint(gfx::Point(-1, 2)));
606}
607