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
20 namespace {
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.
FindTabView(views::View * view)24 views::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
34 class TestTabStripObserver : public TabStripObserver {
35 public:
TestTabStripObserver(TabStrip * tab_strip)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
~TestTabStripObserver()46 virtual ~TestTabStripObserver() {
47 if (tab_strip_)
48 tab_strip_->RemoveObserver(this);
49 }
50
last_tab_added() const51 int last_tab_added() const { return last_tab_added_; }
last_tab_removed() const52 int last_tab_removed() const { return last_tab_removed_; }
last_tab_moved_from() const53 int last_tab_moved_from() const { return last_tab_moved_from_; }
last_tab_moved_to() const54 int last_tab_moved_to() const { return last_tab_moved_to_; }
tabstrip_deleted() const55 bool tabstrip_deleted() const { return tabstrip_deleted_; }
56
57 private:
58 // TabStripObserver overrides.
TabStripAddedTabAt(TabStrip * tab_strip,int index)59 virtual void TabStripAddedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
60 last_tab_added_ = index;
61 }
62
TabStripMovedTab(TabStrip * tab_strip,int from_index,int to_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;
68 }
69
TabStripRemovedTabAt(TabStrip * tab_strip,int index)70 virtual void TabStripRemovedTabAt(TabStrip* tab_strip, int index) OVERRIDE {
71 last_tab_removed_ = index;
72 }
73
TabStripDeleted(TabStrip * tab_strip)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
89 class TabStripTest : public testing::Test {
90 public:
TabStripTest()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.
GetTabHitTestMask(Tab * tab)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).
GetTabCloseHitTestMask(Tab * tab,bool padding)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.
IsPointInTab(Tab * tab,const gfx::Point & point_in_tabstrip_coords)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
TEST_F(TabStripTest,GetModelCount)138 TEST_F(TabStripTest, GetModelCount) {
139 EXPECT_EQ(0, tab_strip_->GetModelCount());
140 }
141
TEST_F(TabStripTest,IsValidModelIndex)142 TEST_F(TabStripTest, IsValidModelIndex) {
143 EXPECT_FALSE(tab_strip_->IsValidModelIndex(0));
144 }
145
TEST_F(TabStripTest,tab_count)146 TEST_F(TabStripTest, tab_count) {
147 EXPECT_EQ(0, tab_strip_->tab_count());
148 }
149
TEST_F(TabStripTest,AddTabAt)150 TEST_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.
TEST_F(TabStripTest,TabStripDeleted)160 TEST_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
TEST_F(TabStripTest,MoveTab)169 TEST_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.
TEST_F(TabStripTest,RemoveTab)184 TEST_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
TEST_F(TabStripTest,VisibilityInOverflow)208 TEST_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
TEST_F(TabStripTest,ImmersiveMode)264 TEST_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.
TEST_F(TabStripTest,TabHitTestMaskWhenStacked)287 TEST_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.
TEST_F(TabStripTest,ClippedTabCloseButton)422 TEST_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
TEST_F(TabStripTest,GetEventHandlerForOverlappingArea)477 TEST_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
TEST_F(TabStripTest,GetTooltipHandler)540 TEST_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