tab_strip_unittest.cc revision 9ab5563a3196760eb381d102cbb2bc0f7abc6a50
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_strip.h"
10#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
11#include "chrome/browser/ui/views/tabs/tab_strip_observer.h"
12#include "chrome/test/base/testing_profile.h"
13#include "testing/gtest/include/gtest/gtest.h"
14
15namespace {
16
17// Walks up the views hierarchy until it finds a tab view. It returns the
18// found tab view, on NULL if none is found.
19views::View* FindTabView(views::View* view) {
20  views::View* current = view;
21  while (current && strcmp(current->GetClassName(), Tab::kViewClassName)) {
22    current = current->parent();
23  }
24  return current;
25}
26
27}  // namespace
28
29class TestTabStripObserver : public TabStripObserver {
30 public:
31  explicit TestTabStripObserver(TabStrip* tab_strip)
32      : tab_strip_(tab_strip),
33        last_tab_added_(-1),
34        last_tab_removed_(-1),
35        last_tab_moved_from_(-1),
36        last_tab_moved_to_(-1),
37        tabstrip_deleted_(false) {
38    tab_strip_->AddObserver(this);
39  }
40
41  virtual ~TestTabStripObserver() {
42    if (tab_strip_)
43      tab_strip_->RemoveObserver(this);
44  }
45
46  int last_tab_added() const { return last_tab_added_; }
47  int last_tab_removed() const { return last_tab_removed_; }
48  int last_tab_moved_from() const { return last_tab_moved_from_; }
49  int last_tab_moved_to() const { return last_tab_moved_to_; }
50  bool tabstrip_deleted() const { return tabstrip_deleted_; }
51
52 private:
53  // TabStripObserver overrides.
54  virtual void TabStripAddedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
55    last_tab_added_ = index;
56  }
57
58  virtual void TabStripMovedTab(TabStrip* tab_strip,
59                                int from_index,
60                                int to_index) OVERRIDE {
61    last_tab_moved_from_ = from_index;
62    last_tab_moved_to_ = to_index;
63  }
64
65  virtual void TabStripRemovedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
66    last_tab_removed_ = index;
67  }
68
69  virtual void TabStripDeleted(TabStrip* tab_strip) OVERRIDE {
70    tabstrip_deleted_ = true;
71    tab_strip_ = NULL;
72  }
73
74  TabStrip* tab_strip_;
75  int last_tab_added_;
76  int last_tab_removed_;
77  int last_tab_moved_from_;
78  int last_tab_moved_to_;
79  bool tabstrip_deleted_;
80
81  DISALLOW_COPY_AND_ASSIGN(TestTabStripObserver);
82};
83
84class TabStripTest : public testing::Test {
85 public:
86  TabStripTest()
87      : controller_(new FakeBaseTabStripController) {
88    tab_strip_ = new TabStrip(controller_);
89    controller_->set_tab_strip(tab_strip_);
90    // Do this to force TabStrip to create the buttons.
91    parent_.AddChildView(tab_strip_);
92  }
93
94 protected:
95  // Checks whether |tab| contains |point_in_tabstrip_coords|, where the point
96  // is in |tab_strip_| coordinates.
97  bool IsPointInTab(Tab* tab, const gfx::Point& point_in_tabstrip_coords) {
98    gfx::Point point_in_tab_coords(point_in_tabstrip_coords);
99    views::View::ConvertPointToTarget(tab_strip_, tab, &point_in_tab_coords);
100    return tab->HitTestPoint(point_in_tab_coords);
101  }
102
103  base::MessageLoopForUI ui_loop_;
104  // Owned by TabStrip.
105  FakeBaseTabStripController* controller_;
106  // Owns |tab_strip_|.
107  views::View parent_;
108  TabStrip* tab_strip_;
109
110 private:
111  DISALLOW_COPY_AND_ASSIGN(TabStripTest);
112};
113
114TEST_F(TabStripTest, GetModelCount) {
115  EXPECT_EQ(0, tab_strip_->GetModelCount());
116}
117
118TEST_F(TabStripTest, IsValidModelIndex) {
119  EXPECT_FALSE(tab_strip_->IsValidModelIndex(0));
120}
121
122TEST_F(TabStripTest, tab_count) {
123  EXPECT_EQ(0, tab_strip_->tab_count());
124}
125
126TEST_F(TabStripTest, CreateTabForDragging) {
127  // Any result is good, as long as it doesn't crash.
128  scoped_ptr<Tab> tab(tab_strip_->CreateTabForDragging());
129}
130
131TEST_F(TabStripTest, AddTabAt) {
132  TestTabStripObserver observer(tab_strip_);
133  tab_strip_->AddTabAt(0, TabRendererData(), false);
134  ASSERT_EQ(1, tab_strip_->tab_count());
135  EXPECT_EQ(0, observer.last_tab_added());
136  Tab* tab = tab_strip_->tab_at(0);
137  EXPECT_FALSE(tab == NULL);
138}
139
140// Confirms that TabStripObserver::TabStripDeleted() is sent.
141TEST_F(TabStripTest, TabStripDeleted) {
142  FakeBaseTabStripController* controller = new FakeBaseTabStripController;
143  TabStrip* tab_strip = new TabStrip(controller);
144  controller->set_tab_strip(tab_strip);
145  TestTabStripObserver observer(tab_strip);
146  delete tab_strip;
147  EXPECT_TRUE(observer.tabstrip_deleted());
148}
149
150TEST_F(TabStripTest, MoveTab) {
151  TestTabStripObserver observer(tab_strip_);
152  tab_strip_->AddTabAt(0, TabRendererData(), false);
153  tab_strip_->AddTabAt(1, TabRendererData(), false);
154  tab_strip_->AddTabAt(2, TabRendererData(), false);
155  ASSERT_EQ(3, tab_strip_->tab_count());
156  EXPECT_EQ(2, observer.last_tab_added());
157  Tab* tab = tab_strip_->tab_at(0);
158  tab_strip_->MoveTab(0, 1, TabRendererData());
159  EXPECT_EQ(0, observer.last_tab_moved_from());
160  EXPECT_EQ(1, observer.last_tab_moved_to());
161  EXPECT_EQ(tab, tab_strip_->tab_at(1));
162}
163
164// Verifies child views are deleted after an animation completes.
165TEST_F(TabStripTest, RemoveTab) {
166  TestTabStripObserver observer(tab_strip_);
167  controller_->AddTab(0, false);
168  controller_->AddTab(1, false);
169  const int child_view_count = tab_strip_->child_count();
170  EXPECT_EQ(2, tab_strip_->tab_count());
171  controller_->RemoveTab(0);
172  EXPECT_EQ(0, observer.last_tab_removed());
173  // When removing a tab the tabcount should immediately decrement.
174  EXPECT_EQ(1, tab_strip_->tab_count());
175  // But the number of views should remain the same (it's animatining closed).
176  EXPECT_EQ(child_view_count, tab_strip_->child_count());
177  tab_strip_->SetBounds(0, 0, 200, 20);
178  // Layout at a different size should force the animation to end and delete
179  // the tab that was removed.
180  tab_strip_->Layout();
181  EXPECT_EQ(child_view_count - 1, tab_strip_->child_count());
182
183  // Remove the last tab to make sure things are cleaned up correctly when
184  // the TabStrip is destroyed and an animation is ongoing.
185  controller_->RemoveTab(0);
186  EXPECT_EQ(0, observer.last_tab_removed());
187}
188
189TEST_F(TabStripTest, ImmersiveMode) {
190  // Immersive mode defaults to off.
191  EXPECT_FALSE(tab_strip_->IsImmersiveStyle());
192
193  // Tab strip defaults to normal tab height.
194  int normal_height = Tab::GetMinimumUnselectedSize().height();
195  EXPECT_EQ(normal_height, tab_strip_->GetPreferredSize().height());
196
197  // Tab strip can toggle immersive mode.
198  tab_strip_->SetImmersiveStyle(true);
199  EXPECT_TRUE(tab_strip_->IsImmersiveStyle());
200
201  // Now tabs have the immersive height.
202  int immersive_height = Tab::GetImmersiveHeight();
203  EXPECT_EQ(immersive_height, tab_strip_->GetPreferredSize().height());
204
205  // Sanity-check immersive tabs are shorter than normal tabs.
206  EXPECT_LT(immersive_height, normal_height);
207}
208
209TEST_F(TabStripTest, GetEventHandlerForOverlappingArea) {
210  tab_strip_->SetBounds(0, 0, 1000, 20);
211
212  controller_->AddTab(0, false);
213  controller_->AddTab(1, true);
214  controller_->AddTab(2, false);
215  controller_->AddTab(3, false);
216  ASSERT_EQ(4, tab_strip_->tab_count());
217
218  // Verify that the active tab will be a tooltip handler for points that hit
219  // it.
220  Tab* left_tab = tab_strip_->tab_at(0);
221  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
222
223  Tab* active_tab = tab_strip_->tab_at(1);
224  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
225  ASSERT_TRUE(active_tab->IsActive());
226
227  Tab* right_tab = tab_strip_->tab_at(2);
228  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
229
230  Tab* most_right_tab = tab_strip_->tab_at(3);
231  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
232                                          gfx::Size(200, 20)));
233
234  // Test that active tabs gets events from area in which it overlaps with its
235  // left neighbour.
236  gfx::Point left_overlap(
237      (active_tab->x() + left_tab->bounds().right() + 1) / 2,
238      active_tab->bounds().bottom() - 1);
239
240  // Sanity check that the point is in both active and left tab.
241  ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
242  ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
243
244  EXPECT_EQ(active_tab,
245            FindTabView(tab_strip_->GetEventHandlerForPoint(left_overlap)));
246
247  // Test that active tabs gets events from area in which it overlaps with its
248  // right neighbour.
249  gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
250                           active_tab->bounds().bottom() - 1);
251
252  // Sanity check that the point is in both active and right tab.
253  ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
254  ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
255
256  EXPECT_EQ(active_tab,
257            FindTabView(tab_strip_->GetEventHandlerForPoint(right_overlap)));
258
259  // Test that if neither of tabs is active, the left one is selected.
260  gfx::Point unactive_overlap(
261      (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
262      right_tab->bounds().bottom() - 1);
263
264  // Sanity check that the point is in both active and left tab.
265  ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
266  ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
267
268  EXPECT_EQ(right_tab,
269            FindTabView(tab_strip_->GetEventHandlerForPoint(unactive_overlap)));
270}
271
272TEST_F(TabStripTest, GetTooltipHandler) {
273  tab_strip_->SetBounds(0, 0, 1000, 20);
274
275  controller_->AddTab(0, false);
276  controller_->AddTab(1, true);
277  controller_->AddTab(2, false);
278  controller_->AddTab(3, false);
279  ASSERT_EQ(4, tab_strip_->tab_count());
280
281  // Verify that the active tab will be a tooltip handler for points that hit
282  // it.
283  Tab* left_tab = tab_strip_->tab_at(0);
284  left_tab->SetBoundsRect(gfx::Rect(gfx::Point(0, 0), gfx::Size(200, 20)));
285
286  Tab* active_tab = tab_strip_->tab_at(1);
287  active_tab->SetBoundsRect(gfx::Rect(gfx::Point(150, 0), gfx::Size(200, 20)));
288  ASSERT_TRUE(active_tab->IsActive());
289
290  Tab* right_tab = tab_strip_->tab_at(2);
291  right_tab->SetBoundsRect(gfx::Rect(gfx::Point(300, 0), gfx::Size(200, 20)));
292
293  Tab* most_right_tab = tab_strip_->tab_at(3);
294  most_right_tab->SetBoundsRect(gfx::Rect(gfx::Point(450, 0),
295                                          gfx::Size(200, 20)));
296
297  // Test that active_tab handles tooltips from area in which it overlaps with
298  // its left neighbour.
299  gfx::Point left_overlap(
300      (active_tab->x() + left_tab->bounds().right() + 1) / 2,
301      active_tab->bounds().bottom() - 1);
302
303  // Sanity check that the point is in both active and left tab.
304  ASSERT_TRUE(IsPointInTab(active_tab, left_overlap));
305  ASSERT_TRUE(IsPointInTab(left_tab, left_overlap));
306
307  EXPECT_EQ(active_tab,
308            FindTabView(tab_strip_->GetTooltipHandlerForPoint(left_overlap)));
309
310  // Test that active_tab handles tooltips from area in which it overlaps with
311  // its right neighbour.
312  gfx::Point right_overlap((active_tab->bounds().right() + right_tab->x()) / 2,
313                           active_tab->bounds().bottom() - 1);
314
315  // Sanity check that the point is in both active and right tab.
316  ASSERT_TRUE(IsPointInTab(active_tab, right_overlap));
317  ASSERT_TRUE(IsPointInTab(right_tab, right_overlap));
318
319  EXPECT_EQ(active_tab,
320            FindTabView(tab_strip_->GetTooltipHandlerForPoint(right_overlap)));
321
322  // Test that if neither of tabs is active, the left one is selected.
323  gfx::Point unactive_overlap(
324      (right_tab->x() + most_right_tab->bounds().right() + 1) / 2,
325      right_tab->bounds().bottom() - 1);
326
327  // Sanity check that the point is in both active and left tab.
328  ASSERT_TRUE(IsPointInTab(right_tab, unactive_overlap));
329  ASSERT_TRUE(IsPointInTab(most_right_tab, unactive_overlap));
330
331  EXPECT_EQ(
332      right_tab,
333      FindTabView(tab_strip_->GetTooltipHandlerForPoint(unactive_overlap)));
334
335  // Confirm that tab strip doe not return tooltip handler for points that
336  // don't hit it.
337  EXPECT_FALSE(tab_strip_->GetTooltipHandlerForPoint(gfx::Point(-1, 2)));
338}
339