root/chrome/browser/ui/views/tabs/tab_strip_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. FindTabView
  2. tabstrip_deleted_
  3. last_tab_added
  4. last_tab_removed
  5. last_tab_moved_from
  6. last_tab_moved_to
  7. tabstrip_deleted
  8. TabStripAddedTabAt
  9. TabStripMovedTab
  10. TabStripRemovedTabAt
  11. TabStripDeleted
  12. GetTabHitTestMask
  13. GetTabCloseHitTestMask
  14. IsPointInTab
  15. TEST_F
  16. TEST_F
  17. TEST_F
  18. TEST_F
  19. TEST_F
  20. TEST_F
  21. TEST_F
  22. TEST_F
  23. TEST_F
  24. TEST_F
  25. TEST_F
  26. TEST_F

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/views/tabs/tab_strip.h"

#include "base/message_loop/message_loop.h"
#include "chrome/browser/ui/views/tabs/fake_base_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
#include "chrome/test/base/testing_profile.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/path.h"
#include "ui/gfx/rect_conversions.h"
#include "ui/gfx/skia_util.h"
#include "ui/views/view.h"

namespace {

// Walks up the views hierarchy until it finds a tab view. It returns the
// found tab view, on NULL if none is found.
views::View* FindTabView(views::View* view) {
  views::View* current = view;
  while (current && strcmp(current->GetClassName(), Tab::kViewClassName)) {
    current = current->parent();
  }
  return current;
}

}  // namespace

class TestTabStripObserver : public TabStripObserver {
 public:
  explicit TestTabStripObserver(TabStrip* tab_strip)
      : tab_strip_(tab_strip),
        last_tab_added_(-1),
        last_tab_removed_(-1),
        last_tab_moved_from_(-1),
        last_tab_moved_to_(-1),
        tabstrip_deleted_(false) {
    tab_strip_->AddObserver(this);
  }

  virtual ~TestTabStripObserver() {
    if (tab_strip_)
      tab_strip_->RemoveObserver(this);
  }

  int last_tab_added() const { return last_tab_added_; }
  int last_tab_removed() const { return last_tab_removed_; }
  int last_tab_moved_from() const { return last_tab_moved_from_; }
  int last_tab_moved_to() const { return last_tab_moved_to_; }
  bool tabstrip_deleted() const { return tabstrip_deleted_; }

 private:
  // TabStripObserver overrides.
  virtual void TabStripAddedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
    last_tab_added_ = index;
  }

  virtual void TabStripMovedTab(TabStrip* tab_strip,
                                int from_index,
                                int to_index) OVERRIDE {
    last_tab_moved_from_ = from_index;
    last_tab_moved_to_ = to_index;
  }

  virtual void TabStripRemovedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
    last_tab_removed_ = index;
  }

  virtual void TabStripDeleted(TabStrip* tab_strip) OVERRIDE {
    tabstrip_deleted_ = true;
    tab_strip_ = NULL;
  }

  TabStrip* tab_strip_;
  int last_tab_added_;
  int last_tab_removed_;
  int last_tab_moved_from_;
  int last_tab_moved_to_;
  bool tabstrip_deleted_;

  DISALLOW_COPY_AND_ASSIGN(TestTabStripObserver);
};

class TabStripTest : public testing::Test {
 public:
  TabStripTest()
      : controller_(new FakeBaseTabStripController) {
    tab_strip_ = new TabStrip(controller_);
    controller_->set_tab_strip(tab_strip_);
    // Do this to force TabStrip to create the buttons.
    parent_.AddChildView(tab_strip_);
  }

 protected:
  // Returns the rectangular hit test region of |tab| in |tab|'s local
  // coordinate space.
  gfx::Rect GetTabHitTestMask(Tab* tab) {
    gfx::Path mask;
    tab->GetHitTestMask(views::View::HIT_TEST_SOURCE_TOUCH, &mask);
    return gfx::ToEnclosingRect((gfx::SkRectToRectF(mask.getBounds())));
  }

  // Returns the rectangular hit test region of the tab close button of
  // |tab| in |tab|'s coordinate space (including padding if |padding|
  // is true).
  gfx::Rect GetTabCloseHitTestMask(Tab* tab, bool padding) {
    gfx::RectF bounds_f = tab->close_button_->GetContentsBounds();
    if (padding)
      bounds_f = tab->close_button_->GetLocalBounds();
    views::View::ConvertRectToTarget(tab->close_button_, tab, &bounds_f);
    return gfx::ToEnclosingRect(bounds_f);
  }

  // Checks whether |tab| contains |point_in_tabstrip_coords|, where the point
  // is in |tab_strip_| coordinates.
  bool IsPointInTab(Tab* tab, const gfx::Point& point_in_tabstrip_coords) {
    gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
    views::View::ConvertPointToTarget(tab_strip_, tab, &point_in_tab_coords);
    return tab->HitTestPoint(point_in_tab_coords);
  }

  base::MessageLoopForUI ui_loop_;
  // Owned by TabStrip.
  FakeBaseTabStripController* controller_;
  // Owns |tab_strip_|.
  views::View parent_;
  TabStrip* tab_strip_;

 private:
  DISALLOW_COPY_AND_ASSIGN(TabStripTest);
};

TEST_F(TabStripTest, GetModelCount) {
  EXPECT_EQ(0, tab_strip_->GetModelCount());
}

TEST_F(TabStripTest, IsValidModelIndex) {
  EXPECT_FALSE(tab_strip_->IsValidModelIndex(0));
}

TEST_F(TabStripTest, tab_count) {
  EXPECT_EQ(0, tab_strip_->tab_count());
}

TEST_F(TabStripTest, AddTabAt) {
  TestTabStripObserver observer(tab_strip_);
  tab_strip_->AddTabAt(0, TabRendererData(), false);
  ASSERT_EQ(1, tab_strip_->tab_count());
  EXPECT_EQ(0, observer.last_tab_added());
  Tab* tab = tab_strip_->tab_at(0);
  EXPECT_FALSE(tab == NULL);
}

// Confirms that TabStripObserver::TabStripDeleted() is sent.
TEST_F(TabStripTest, TabStripDeleted) {
  FakeBaseTabStripController* controller = new FakeBaseTabStripController;
  TabStrip* tab_strip = new TabStrip(controller);
  controller->set_tab_strip(tab_strip);
  TestTabStripObserver observer(tab_strip);
  delete tab_strip;
  EXPECT_TRUE(observer.tabstrip_deleted());
}

TEST_F(TabStripTest, MoveTab) {
  TestTabStripObserver observer(tab_strip_);
  tab_strip_->AddTabAt(0, TabRendererData(), false);
  tab_strip_->AddTabAt(1, TabRendererData(), false);
  tab_strip_->AddTabAt(2, TabRendererData(), false);
  ASSERT_EQ(3, tab_strip_->tab_count());
  EXPECT_EQ(2, observer.last_tab_added());
  Tab* tab = tab_strip_->tab_at(0);
  tab_strip_->MoveTab(0, 1, TabRendererData());
  EXPECT_EQ(0, observer.last_tab_moved_from());
  EXPECT_EQ(1, observer.last_tab_moved_to());
  EXPECT_EQ(tab, tab_strip_->tab_at(1));
}

// Verifies child views are deleted after an animation completes.
TEST_F(TabStripTest, RemoveTab) {
  TestTabStripObserver observer(tab_strip_);
  controller_->AddTab(0, false);
  controller_->AddTab(1, false);
  const int child_view_count = tab_strip_->child_count();
  EXPECT_EQ(2, tab_strip_->tab_count());
  controller_->RemoveTab(0);
  EXPECT_EQ(0, observer.last_tab_removed());
  // When removing a tab the tabcount should immediately decrement.
  EXPECT_EQ(1, tab_strip_->tab_count());
  // But the number of views should remain the same (it's animatining closed).
  EXPECT_EQ(child_view_count, tab_strip_->child_count());
  tab_strip_->SetBounds(0, 0, 200, 20);
  // Layout at a different size should force the animation to end and delete
  // the tab that was removed.
  tab_strip_->Layout();
  EXPECT_EQ(child_view_count - 1, tab_strip_->child_count());

  // Remove the last tab to make sure things are cleaned up correctly when
  // the TabStrip is destroyed and an animation is ongoing.
  controller_->RemoveTab(0);
  EXPECT_EQ(0, observer.last_tab_removed());
}

TEST_F(TabStripTest, ImmersiveMode) {
  // Immersive mode defaults to off.
  EXPECT_FALSE(tab_strip_->IsImmersiveStyle());

  // Tab strip defaults to normal tab height.
  int normal_height = Tab::GetMinimumUnselectedSize().height();
  EXPECT_EQ(normal_height, tab_strip_->GetPreferredSize().height());

  // Tab strip can toggle immersive mode.
  tab_strip_->SetImmersiveStyle(true);
  EXPECT_TRUE(tab_strip_->IsImmersiveStyle());

  // Now tabs have the immersive height.
  int immersive_height = Tab::GetImmersiveHeight();
  EXPECT_EQ(immersive_height, tab_strip_->GetPreferredSize().height());

  // Sanity-check immersive tabs are shorter than normal tabs.
  EXPECT_LT(immersive_height, normal_height);
}

// Creates a tab strip in stacked layout mode and verifies the correctness
// of hit tests against the visible/occluded regions of a tab and
// visible/occluded tab close buttons.
TEST_F(TabStripTest, TabHitTestMaskWhenStacked) {
  tab_strip_->SetBounds(0, 0, 300, 20);

  controller_->AddTab(0, false);
  controller_->AddTab(1, true);
  controller_->AddTab(2, false);
  controller_->AddTab(3, false);
  ASSERT_EQ(4, tab_strip_->tab_count());

  Tab* left_tab = tab_strip_->tab_at(0);
  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));

  Tab* active_tab = tab_strip_->tab_at(1);
  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
  ASSERT_TRUE(active_tab->IsActive());

  Tab* right_tab = tab_strip_->tab_at(2);
  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));

  Tab* most_right_tab = tab_strip_->tab_at(3);
  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
                                          gfx::Size(200, 20)));

  // Switch to stacked layout mode and force a layout to ensure tabs stack.
  tab_strip_->SetLayoutType(TAB_STRIP_LAYOUT_STACKED, false);
  tab_strip_->DoLayout();


  // Tests involving |left_tab|, which has part of its bounds and its tab
  // close button completely occluded by |active_tab|.

  // Bounds of the tab's hit test mask.
  gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
  EXPECT_EQ(gfx::Rect(6, 2, 61, 27).ToString(), tab_bounds.ToString());

  // Bounds of the tab close button (without padding) in the tab's
  // coordinate space.
  gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());

  // Verify that the tab close button is completely occluded.
  EXPECT_FALSE(tab_bounds.Contains(contents_bounds));

  // Hit tests in the non-occuluded region of the tab.
  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 2, 2)));
  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(6, 2, 1, 1)));
  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(30, 15, 25, 35)));
  EXPECT_TRUE(left_tab->HitTestRect(gfx::Rect(-10, -5, 20, 30)));

  // Hit tests in the occluded region of the tab.
  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, 15, 2, 2)));
  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(70, -15, 30, 40)));
  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(87, 20, 5, 3)));

  // Hit tests completely outside of the tab.
  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 1, 1)));
  EXPECT_FALSE(left_tab->HitTestRect(gfx::Rect(-20, -25, 3, 19)));

  // All hit tests against the tab close button should fail because
  // it is occluded by |active_tab|.
  views::ImageButton* left_close = left_tab->close_button_;
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(1, 1, 5, 10)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 3, 4)));


  // Tests involving |active_tab|, which is completely visible.

  tab_bounds = GetTabHitTestMask(active_tab);
  EXPECT_EQ(gfx::Rect(6, 2, 108, 27).ToString(), tab_bounds.ToString());
  contents_bounds = GetTabCloseHitTestMask(active_tab, false);
  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());

  // Verify that the tab close button is not occluded.
  EXPECT_TRUE(tab_bounds.Contains(contents_bounds));

  // Bounds of the tab close button (without padding) in the tab's
  // coordinate space.
  gfx::Rect local_bounds = GetTabCloseHitTestMask(active_tab, true);
  EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());

  // Hit tests within the tab.
  EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 1, 1)));
  EXPECT_TRUE(active_tab->HitTestRect(gfx::Rect(30, 15, 2, 2)));

  // Hit tests against the tab close button. Note that hit tests from either
  // mouse or touch should both fail if they are strictly contained within
  // the button's padding.
  views::ImageButton* active_close = active_tab->close_button_;
  EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
  EXPECT_FALSE(active_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
  EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
  EXPECT_TRUE(active_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));


  // Tests involving |most_right_tab|, which has part of its bounds occluded
  // by |right_tab| but has its tab close button completely visible.

  tab_bounds = GetTabHitTestMask(most_right_tab);
  EXPECT_EQ(gfx::Rect(84, 2, 30, 27).ToString(), tab_bounds.ToString());
  contents_bounds = GetTabCloseHitTestMask(active_tab, false);
  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());
  local_bounds = GetTabCloseHitTestMask(active_tab, true);
  EXPECT_EQ(gfx::Rect(81, 0, 39, 29).ToString(), local_bounds.ToString());

  // Verify that the tab close button is not occluded.
  EXPECT_TRUE(tab_bounds.Contains(contents_bounds));

  // Hit tests in the occluded region of the tab.
  EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 1, 1)));
  EXPECT_FALSE(most_right_tab->HitTestRect(gfx::Rect(20, 15, 5, 6)));

  // Hit tests in the non-occluded region of the tab.
  EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 1, 1)));
  EXPECT_TRUE(most_right_tab->HitTestRect(gfx::Rect(85, 15, 2, 2)));

  // Hit tests against the tab close button. Note that hit tests from either
  // mouse or touch should both fail if they are strictly contained within
  // the button's padding.
  views::ImageButton* most_right_close = most_right_tab->close_button_;
  EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 1, 1)));
  EXPECT_FALSE(most_right_close->HitTestRect(gfx::Rect(1, 1, 2, 2)));
  EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
  EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(10, 10, 25, 35)));
  EXPECT_TRUE(most_right_close->HitTestRect(gfx::Rect(-10, 10, 25, 35)));
}

// Creates a tab strip in stacked layout mode and verifies the correctness
// of hit tests against the visible/occluded region of a partially-occluded
// tab close button.
TEST_F(TabStripTest, ClippedTabCloseButton) {
  tab_strip_->SetBounds(0, 0, 220, 20);

  controller_->AddTab(0, false);
  controller_->AddTab(1, true);
  ASSERT_EQ(2, tab_strip_->tab_count());

  Tab* left_tab = tab_strip_->tab_at(0);
  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));

  Tab* active_tab = tab_strip_->tab_at(1);
  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(180, 0), gfx::Size(200, 20)));
  ASSERT_TRUE(active_tab->IsActive());

  // Switch to stacked layout mode and force a layout to ensure tabs stack.
  tab_strip_->SetLayoutType(TAB_STRIP_LAYOUT_STACKED, false);
  tab_strip_->DoLayout();


  // Tests involving |left_tab|, which has part of its bounds and its tab
  // close button partially occluded by |active_tab|.

  // Bounds of the tab's hit test mask.
  gfx::Rect tab_bounds = GetTabHitTestMask(left_tab);
  EXPECT_EQ(gfx::Rect(6, 2, 91, 27).ToString(), tab_bounds.ToString());

  // Bounds of the tab close button (without padding) in the tab's
  // coordinate space.
  gfx::Rect contents_bounds = GetTabCloseHitTestMask(left_tab, false);
  // TODO(tdanderson): Uncomment this line once crbug.com/311609 is resolved.
  //EXPECT_EQ(gfx::Rect(84, 8, 18, 18).ToString(), contents_bounds.ToString());

  // Verify that the tab close button is only partially occluded.
  EXPECT_FALSE(tab_bounds.Contains(contents_bounds));
  EXPECT_TRUE(tab_bounds.Intersects(contents_bounds));

  views::ImageButton* left_close = left_tab->close_button_;

  // Hit tests from mouse should return true if and only if the location
  // is within a visible region.
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(2, 15, 1, 1)));
  EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(3, 15, 1, 1)));
  EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(10, 10, 1, 1)));
  EXPECT_TRUE(left_close->HitTestRect(gfx::Rect(15, 12, 1, 1)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(16, 10, 1, 1)));

  // All hit tests from touch should return false because the button is
  // not fully visible.
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(2, 15, 2, 2)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(3, 15, 25, 25)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(10, 10, 4, 5)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(15, 12, 2, 2)));
  EXPECT_FALSE(left_close->HitTestRect(gfx::Rect(16, 10, 20, 20)));
}

TEST_F(TabStripTest, GetEventHandlerForOverlappingArea) {
  tab_strip_->SetBounds(0, 0, 1000, 20);

  controller_->AddTab(0, false);
  controller_->AddTab(1, true);
  controller_->AddTab(2, false);
  controller_->AddTab(3, false);
  ASSERT_EQ(4, tab_strip_->tab_count());

  // Verify that the active tab will be a tooltip handler for points that hit
  // it.
  Tab* left_tab = tab_strip_->tab_at(0);
  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));

  Tab* active_tab = tab_strip_->tab_at(1);
  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
  ASSERT_TRUE(active_tab->IsActive());

  Tab* right_tab = tab_strip_->tab_at(2);
  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));

  Tab* most_right_tab = tab_strip_->tab_at(3);
  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
                                          gfx::Size(200, 20)));

  // Test that active tabs gets events from area in which it overlaps with its
  // left neighbour.
  gfx::Point left_overlap(
      (active_tab->x() + left_tab->bounds().right() + 1) / 2,
      active_tab->bounds().bottom() - 1);

  // Sanity check that the point is in both active and left tab.
  ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
  ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));

  EXPECT_EQ(active_tab,
            FindTabView(tab_strip_->GetEventHandlerForPoint(left_overlap)));

  // Test that active tabs gets events from area in which it overlaps with its
  // right neighbour.
  gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
                           active_tab->bounds().bottom() - 1);

  // Sanity check that the point is in both active and right tab.
  ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
  ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));

  EXPECT_EQ(active_tab,
            FindTabView(tab_strip_->GetEventHandlerForPoint(right_overlap)));

  // Test that if neither of tabs is active, the left one is selected.
  gfx::Point unactive_overlap(
      (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
      right_tab->bounds().bottom() - 1);

  // Sanity check that the point is in both active and left tab.
  ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
  ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));

  EXPECT_EQ(right_tab,
            FindTabView(tab_strip_->GetEventHandlerForPoint(unactive_overlap)));
}

TEST_F(TabStripTest, GetTooltipHandler) {
  tab_strip_->SetBounds(0, 0, 1000, 20);

  controller_->AddTab(0, false);
  controller_->AddTab(1, true);
  controller_->AddTab(2, false);
  controller_->AddTab(3, false);
  ASSERT_EQ(4, tab_strip_->tab_count());

  // Verify that the active tab will be a tooltip handler for points that hit
  // it.
  Tab* left_tab = tab_strip_->tab_at(0);
  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));

  Tab* active_tab = tab_strip_->tab_at(1);
  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
  ASSERT_TRUE(active_tab->IsActive());

  Tab* right_tab = tab_strip_->tab_at(2);
  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));

  Tab* most_right_tab = tab_strip_->tab_at(3);
  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
                                          gfx::Size(200, 20)));

  // Test that active_tab handles tooltips from area in which it overlaps with
  // its left neighbour.
  gfx::Point left_overlap(
      (active_tab->x() + left_tab->bounds().right() + 1) / 2,
      active_tab->bounds().bottom() - 1);

  // Sanity check that the point is in both active and left tab.
  ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
  ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));

  EXPECT_EQ(active_tab,
            FindTabView(tab_strip_->GetTooltipHandlerForPoint(left_overlap)));

  // Test that active_tab handles tooltips from area in which it overlaps with
  // its right neighbour.
  gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
                           active_tab->bounds().bottom() - 1);

  // Sanity check that the point is in both active and right tab.
  ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
  ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));

  EXPECT_EQ(active_tab,
            FindTabView(tab_strip_->GetTooltipHandlerForPoint(right_overlap)));

  // Test that if neither of tabs is active, the left one is selected.
  gfx::Point unactive_overlap(
      (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
      right_tab->bounds().bottom() - 1);

  // Sanity check that the point is in both active and left tab.
  ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
  ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));

  EXPECT_EQ(
      right_tab,
      FindTabView(tab_strip_->GetTooltipHandlerForPoint(unactive_overlap)));

  // Confirm that tab strip doe not return tooltip handler for points that
  // don't hit it.
  EXPECT_FALSE(tab_strip_->GetTooltipHandlerForPoint(gfx::Point(-1, 2)));
}

/* [<][>][^][v][top][bottom][index][help] */