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