[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / tabs / tab_controller_unittest.mm
blob59776db5e9c4740f0d3d86795c94205988c2d9af
1 // Copyright (c) 2011 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.
5 #import <Cocoa/Cocoa.h>
7 #import "base/mac/scoped_nsobject.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
10 #import "chrome/browser/ui/cocoa/tabs/media_indicator_view.h"
11 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
12 #import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h"
13 #import "chrome/browser/ui/cocoa/tabs/tab_strip_drag_controller.h"
14 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
15 #include "grit/theme_resources.h"
16 #include "grit/ui_resources.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18 #import "testing/gtest_mac.h"
19 #include "testing/platform_test.h"
20 #include "ui/base/resource/resource_bundle.h"
22 // Implements the target interface for the tab, which gets sent messages when
23 // the tab is clicked on by the user and when its close box is clicked.
24 @interface TabControllerTestTarget : NSObject<TabControllerTarget> {
25  @private
26   bool selected_;
27   bool closed_;
28   base::scoped_nsobject<TabStripDragController> dragController_;
30 - (bool)selected;
31 - (bool)closed;
32 @end
34 @implementation TabControllerTestTarget
35 - (id)init {
36   if ((self = [super init])) {
37     dragController_.reset(
38         [[TabStripDragController alloc] initWithTabStripController:nil]);
39   }
40   return self;
42 - (bool)selected {
43   return selected_;
45 - (bool)closed {
46   return closed_;
48 - (void)selectTab:(id)sender {
49   selected_ = true;
51 - (void)closeTab:(id)sender {
52   closed_ = true;
54 - (void)mouseTimer:(NSTimer*)timer {
55   // Fire the mouseUp to break the TabView drag loop.
56   NSEvent* current = [NSApp currentEvent];
57   NSWindow* window = [timer userInfo];
58   NSEvent* up = [NSEvent mouseEventWithType:NSLeftMouseUp
59                                    location:[current locationInWindow]
60                               modifierFlags:0
61                                   timestamp:[current timestamp]
62                                windowNumber:[window windowNumber]
63                                     context:nil
64                                 eventNumber:0
65                                  clickCount:1
66                                    pressure:1.0];
67   [window postEvent:up atStart:YES];
69 - (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
70           forController:(TabController*)controller {
72 - (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
73            forController:(TabController*)controller {
74   return NO;
76 - (ui::SimpleMenuModel*)contextMenuModelForController:(TabController*)controller
77     menuDelegate:(ui::SimpleMenuModel::Delegate*)delegate {
78   ui::SimpleMenuModel* model = new ui::SimpleMenuModel(delegate);
79   model->AddItem(1, base::ASCIIToUTF16("Hello World"));
80   model->AddItem(2, base::ASCIIToUTF16("Allays"));
81   model->AddItem(3, base::ASCIIToUTF16("Chromium"));
82   return model;
84 - (id<TabDraggingEventTarget>)dragController {
85   return dragController_.get();
87 @end
89 namespace {
91 CGFloat LeftMargin(NSRect superFrame, NSRect subFrame) {
92   return NSMinX(subFrame) - NSMinX(superFrame);
95 CGFloat RightMargin(NSRect superFrame, NSRect subFrame) {
96   return NSMaxX(superFrame) - NSMaxX(subFrame);
99 // The dragging code in TabView makes heavy use of autorelease pools so
100 // inherit from CocoaTest to have one created for us.
101 class TabControllerTest : public CocoaTest {
102  public:
103   TabControllerTest() { }
105   static void CheckForExpectedLayoutAndVisibilityOfSubviews(
106       const TabController* controller) {
107     // Check whether subviews should be visible when they are supposed to be,
108     // given Tab size and TabRendererData state.
109     const TabMediaState indicatorState =
110         [[controller mediaIndicatorView] mediaState];
111     if ([controller mini]) {
112       EXPECT_EQ(1, [controller iconCapacity]);
113       if (indicatorState != TAB_MEDIA_STATE_NONE) {
114         EXPECT_FALSE([controller shouldShowIcon]);
115         EXPECT_TRUE([controller shouldShowMediaIndicator]);
116       } else {
117         EXPECT_TRUE([controller shouldShowIcon]);
118         EXPECT_FALSE([controller shouldShowMediaIndicator]);
119       }
120       EXPECT_FALSE([controller shouldShowCloseButton]);
121     } else if ([controller selected]) {
122       EXPECT_TRUE([controller shouldShowCloseButton]);
123       switch ([controller iconCapacity]) {
124         case 0:
125         case 1:
126           EXPECT_FALSE([controller shouldShowIcon]);
127           EXPECT_FALSE([controller shouldShowMediaIndicator]);
128           break;
129         case 2:
130           if (indicatorState != TAB_MEDIA_STATE_NONE) {
131             EXPECT_FALSE([controller shouldShowIcon]);
132             EXPECT_TRUE([controller shouldShowMediaIndicator]);
133           } else {
134             EXPECT_TRUE([controller shouldShowIcon]);
135             EXPECT_FALSE([controller shouldShowMediaIndicator]);
136           }
137           break;
138         default:
139           EXPECT_LE(3, [controller iconCapacity]);
140           EXPECT_TRUE([controller shouldShowIcon]);
141           if (indicatorState != TAB_MEDIA_STATE_NONE)
142             EXPECT_TRUE([controller shouldShowMediaIndicator]);
143           else
144             EXPECT_FALSE([controller shouldShowMediaIndicator]);
145           break;
146       }
147     } else {  // Tab not selected/active and not mini tab.
148       switch ([controller iconCapacity]) {
149         case 0:
150           EXPECT_FALSE([controller shouldShowCloseButton]);
151           EXPECT_FALSE([controller shouldShowIcon]);
152           EXPECT_FALSE([controller shouldShowMediaIndicator]);
153           break;
154         case 1:
155           EXPECT_FALSE([controller shouldShowCloseButton]);
156           if (indicatorState != TAB_MEDIA_STATE_NONE) {
157             EXPECT_FALSE([controller shouldShowIcon]);
158             EXPECT_TRUE([controller shouldShowMediaIndicator]);
159           } else {
160             EXPECT_TRUE([controller shouldShowIcon]);
161             EXPECT_FALSE([controller shouldShowMediaIndicator]);
162           }
163           break;
164         default:
165           EXPECT_LE(2, [controller iconCapacity]);
166           EXPECT_TRUE([controller shouldShowIcon]);
167           if (indicatorState != TAB_MEDIA_STATE_NONE)
168             EXPECT_TRUE([controller shouldShowMediaIndicator]);
169           else
170             EXPECT_FALSE([controller shouldShowMediaIndicator]);
171           break;
172       }
173     }
175     // Make sure the NSView's "isHidden" state jives with the "shouldShowXXX."
176     EXPECT_TRUE([controller shouldShowIcon] ==
177                 (!![controller iconView] && ![[controller iconView] isHidden]));
178     EXPECT_TRUE([controller mini] == [[controller tabView] titleHidden]);
179     EXPECT_TRUE([controller shouldShowMediaIndicator] ==
180                     ![[controller mediaIndicatorView] isHidden]);
181     EXPECT_TRUE([controller shouldShowCloseButton] !=
182                     [[controller closeButton] isHidden]);
184     // Check positioning of elements with respect to each other, and that they
185     // are fully within the tab frame.
186     const NSRect tabFrame = [[controller view] frame];
187     const NSRect titleFrame = [[controller tabView] titleFrame];
188     if ([controller shouldShowIcon]) {
189       const NSRect iconFrame = [[controller iconView] frame];
190       EXPECT_LE(NSMinX(tabFrame), NSMinX(iconFrame));
191       if (NSWidth(titleFrame) > 0)
192         EXPECT_LE(NSMaxX(iconFrame), NSMinX(titleFrame));
193       EXPECT_LE(NSMinY(tabFrame), NSMinY(iconFrame));
194       EXPECT_LE(NSMaxY(iconFrame), NSMaxY(tabFrame));
195     }
196     if ([controller shouldShowIcon] && [controller shouldShowMediaIndicator]) {
197       EXPECT_LE(NSMaxX([[controller iconView] frame]),
198                 NSMinX([[controller mediaIndicatorView] frame]));
199     }
200     if ([controller shouldShowMediaIndicator]) {
201       const NSRect mediaIndicatorFrame =
202           [[controller mediaIndicatorView] frame];
203       if (NSWidth(titleFrame) > 0)
204         EXPECT_LE(NSMaxX(titleFrame), NSMinX(mediaIndicatorFrame));
205       EXPECT_LE(NSMaxX(mediaIndicatorFrame), NSMaxX(tabFrame));
206       EXPECT_LE(NSMinY(tabFrame), NSMinY(mediaIndicatorFrame));
207       EXPECT_LE(NSMaxY(mediaIndicatorFrame), NSMaxY(tabFrame));
208     }
209     if ([controller shouldShowMediaIndicator] &&
210         [controller shouldShowCloseButton]) {
211       EXPECT_LE(NSMaxX([[controller mediaIndicatorView] frame]),
212                 NSMinX([[controller closeButton] frame]));
213     }
214     if ([controller shouldShowCloseButton]) {
215       const NSRect closeButtonFrame = [[controller closeButton] frame];
216       if (NSWidth(titleFrame) > 0)
217         EXPECT_LE(NSMaxX(titleFrame), NSMinX(closeButtonFrame));
218       EXPECT_LE(NSMaxX(closeButtonFrame), NSMaxX(tabFrame));
219       EXPECT_LE(NSMinY(tabFrame), NSMinY(closeButtonFrame));
220       EXPECT_LE(NSMaxY(closeButtonFrame), NSMaxY(tabFrame));
221     }
222   }
225 // Tests creating the controller, sticking it in a window, and removing it.
226 TEST_F(TabControllerTest, Creation) {
227   NSWindow* window = test_window();
228   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
229   [[window contentView] addSubview:[controller view]];
230   EXPECT_TRUE([controller tabView]);
231   EXPECT_EQ([[controller view] window], window);
232   [[controller view] display];  // Test drawing to ensure nothing leaks/crashes.
233   [[controller view] removeFromSuperview];
236 // Tests sending it a close message and ensuring that the target/action get
237 // called. Mimics the user clicking on the close button in the tab.
238 TEST_F(TabControllerTest, Close) {
239   NSWindow* window = test_window();
240   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
241   [[window contentView] addSubview:[controller view]];
243   base::scoped_nsobject<TabControllerTestTarget> target(
244       [[TabControllerTestTarget alloc] init]);
245   EXPECT_FALSE([target closed]);
246   [controller setTarget:target];
247   EXPECT_EQ(target.get(), [controller target]);
249   [controller closeTab:nil];
250   EXPECT_TRUE([target closed]);
252   [[controller view] removeFromSuperview];
255 // Tests setting the |selected| property via code.
256 TEST_F(TabControllerTest, APISelection) {
257   NSWindow* window = test_window();
258   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
259   [[window contentView] addSubview:[controller view]];
261   EXPECT_FALSE([controller selected]);
262   [controller setSelected:YES];
263   EXPECT_TRUE([controller selected]);
265   [[controller view] removeFromSuperview];
268 // Tests setting the |loading| property via code.
269 TEST_F(TabControllerTest, Loading) {
270   NSWindow* window = test_window();
271   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
272   [[window contentView] addSubview:[controller view]];
274   EXPECT_EQ(kTabDone, [controller loadingState]);
275   [controller setLoadingState:kTabWaiting];
276   EXPECT_EQ(kTabWaiting, [controller loadingState]);
277   [controller setLoadingState:kTabLoading];
278   EXPECT_EQ(kTabLoading, [controller loadingState]);
279   [controller setLoadingState:kTabDone];
280   EXPECT_EQ(kTabDone, [controller loadingState]);
282   [[controller view] removeFromSuperview];
285 // Tests selecting the tab with the mouse click and ensuring the target/action
286 // get called.
287 TEST_F(TabControllerTest, UserSelection) {
288   NSWindow* window = test_window();
290   // Create a tab at a known location in the window that we can click on
291   // to activate selection.
292   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
293   [[window contentView] addSubview:[controller view]];
294   NSRect frame = [[controller view] frame];
295   frame.size.width = [TabController minTabWidth];
296   frame.origin = NSZeroPoint;
297   [[controller view] setFrame:frame];
299   // Set the target and action.
300   base::scoped_nsobject<TabControllerTestTarget> target(
301       [[TabControllerTestTarget alloc] init]);
302   EXPECT_FALSE([target selected]);
303   [controller setTarget:target];
304   [controller setAction:@selector(selectTab:)];
305   EXPECT_EQ(target.get(), [controller target]);
306   EXPECT_EQ(@selector(selectTab:), [controller action]);
308   // In order to track a click, we have to fake a mouse down and a mouse
309   // up, but the down goes into a tight drag loop. To break the loop, we have
310   // to fire a timer that sends a mouse up event while the "drag" is ongoing.
311   [NSTimer scheduledTimerWithTimeInterval:0.1
312                                    target:target.get()
313                                  selector:@selector(mouseTimer:)
314                                  userInfo:window
315                                   repeats:NO];
316   NSEvent* current = [NSApp currentEvent];
317   NSPoint click_point = NSMakePoint(frame.size.width / 2,
318                                     frame.size.height / 2);
319   NSEvent* down = [NSEvent mouseEventWithType:NSLeftMouseDown
320                                      location:click_point
321                                 modifierFlags:0
322                                     timestamp:[current timestamp]
323                                  windowNumber:[window windowNumber]
324                                       context:nil
325                                   eventNumber:0
326                                    clickCount:1
327                                      pressure:1.0];
328   [[controller view] mouseDown:down];
330   // Check our target was told the tab got selected.
331   EXPECT_TRUE([target selected]);
333   [[controller view] removeFromSuperview];
336 TEST_F(TabControllerTest, IconCapacity) {
337   NSWindow* window = test_window();
338   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
339   [[window contentView] addSubview:[controller view]];
340   int cap = [controller iconCapacity];
341   EXPECT_GE(cap, 1);
343   NSRect frame = [[controller view] frame];
344   frame.size.width += 500;
345   [[controller view] setFrame:frame];
346   int newcap = [controller iconCapacity];
347   EXPECT_GT(newcap, cap);
350 TEST_F(TabControllerTest, ShouldShowIcon) {
351   NSWindow* window = test_window();
352   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
353   [[window contentView] addSubview:[controller view]];
354   int cap = [controller iconCapacity];
355   EXPECT_GT(cap, 0);
357   // Tab is minimum width, both icon and close box should be hidden.
358   NSRect frame = [[controller view] frame];
359   frame.size.width = [TabController minTabWidth];
360   [[controller view] setFrame:frame];
361   EXPECT_FALSE([controller shouldShowIcon]);
362   EXPECT_FALSE([controller shouldShowCloseButton]);
364   // Setting the icon when tab is at min width should not show icon (bug 18359).
365   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
366   base::scoped_nsobject<NSImage> image(
367       rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
368   [controller setIconImage:image];
369   NSView* newIcon = [controller iconView];
370   EXPECT_TRUE([newIcon isHidden]);
372   // Tab is at selected minimum width. Since it's selected, the close box
373   // should be visible.
374   [controller setSelected:YES];
375   frame = [[controller view] frame];
376   frame.size.width = [TabController minSelectedTabWidth];
377   [[controller view] setFrame:frame];
378   EXPECT_FALSE([controller shouldShowIcon]);
379   EXPECT_TRUE([newIcon isHidden]);
380   EXPECT_TRUE([controller shouldShowCloseButton]);
382   // Test expanding the tab to max width and ensure the icon and close box
383   // get put back, even when de-selected.
384   frame.size.width = [TabController maxTabWidth];
385   [[controller view] setFrame:frame];
386   EXPECT_TRUE([controller shouldShowIcon]);
387   EXPECT_FALSE([newIcon isHidden]);
388   EXPECT_TRUE([controller shouldShowCloseButton]);
389   [controller setSelected:NO];
390   EXPECT_TRUE([controller shouldShowIcon]);
391   EXPECT_TRUE([controller shouldShowCloseButton]);
393   cap = [controller iconCapacity];
394   EXPECT_GT(cap, 0);
397 TEST_F(TabControllerTest, Menu) {
398   NSWindow* window = test_window();
399   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
400   base::scoped_nsobject<TabControllerTestTarget> target(
401       [[TabControllerTestTarget alloc] init]);
402   [controller setTarget:target];
404   [[window contentView] addSubview:[controller view]];
405   int cap = [controller iconCapacity];
406   EXPECT_GT(cap, 0);
408   // Asking the view for its menu should yield a valid menu.
409   NSMenu* menu = [[controller view] menu];
410   EXPECT_TRUE(menu);
411   EXPECT_EQ(3, [menu numberOfItems]);
414 // Tests that the title field is correctly positioned and sized when the
415 // view is resized.
416 TEST_F(TabControllerTest, TitleViewLayout) {
417   NSWindow* window = test_window();
419   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
420   [[window contentView] addSubview:[controller view]];
421   NSRect tabFrame = [[controller view] frame];
422   tabFrame.size.width = [TabController maxTabWidth];
423   [[controller view] setFrame:tabFrame];
425   const NSRect originalTabFrame = [[controller view] frame];
426   const NSRect originalIconFrame = [[controller iconView] frame];
427   const NSRect originalCloseFrame = [[controller closeButton] frame];
428   const NSRect originalTitleFrame = [[controller tabView] titleFrame];
430   // Sanity check the start state.
431   EXPECT_FALSE([[controller iconView] isHidden]);
432   EXPECT_FALSE([[controller closeButton] isHidden]);
433   EXPECT_GT(NSWidth([[controller view] frame]),
434             NSWidth([[controller tabView] titleFrame]));
436   // Resize the tab so that that the it shrinks.
437   tabFrame.size.width = [TabController minTabWidth];
438   [[controller view] setFrame:tabFrame];
440   // The icon view and close button should be hidden and the title view should
441   // be resize to take up their space.
442   EXPECT_TRUE([[controller iconView] isHidden]);
443   EXPECT_TRUE([[controller closeButton] isHidden]);
444   EXPECT_GT(NSWidth([[controller view] frame]),
445             NSWidth([[controller tabView] titleFrame]));
446   EXPECT_EQ(LeftMargin(originalTabFrame, originalIconFrame),
447             LeftMargin([[controller view] frame],
448                        [[controller tabView] titleFrame]));
449   EXPECT_EQ(RightMargin(originalTabFrame, originalCloseFrame),
450             RightMargin([[controller view] frame],
451                         [[controller tabView] titleFrame]));
453   // Resize the tab so that that the it grows.
454   tabFrame.size.width = static_cast<int>([TabController maxTabWidth] * 0.75);
455   [[controller view] setFrame:tabFrame];
457   // The icon view and close button should be visible again and the title view
458   // should be resized to make room for them.
459   EXPECT_FALSE([[controller iconView] isHidden]);
460   EXPECT_FALSE([[controller closeButton] isHidden]);
461   EXPECT_GT(NSWidth([[controller view] frame]),
462             NSWidth([[controller tabView] titleFrame]));
463   EXPECT_EQ(LeftMargin(originalTabFrame, originalTitleFrame),
464             LeftMargin([[controller view] frame],
465                        [[controller tabView] titleFrame]));
466   EXPECT_EQ(RightMargin(originalTabFrame, originalTitleFrame),
467             RightMargin([[controller view] frame],
468                         [[controller tabView] titleFrame]));
471 // A comprehensive test of the layout and visibility of all elements (favicon,
472 // throbber indicators, titile text, audio indicator, and close button) over all
473 // relevant combinations of tab state.  This test overlaps with parts of the
474 // other tests above.
475 // Flaky: https://code.google.com/p/chromium/issues/detail?id=311668
476 TEST_F(TabControllerTest, DISABLED_LayoutAndVisibilityOfSubviews) {
477   static const TabMediaState kMediaStatesToTest[] = {
478     TAB_MEDIA_STATE_NONE, TAB_MEDIA_STATE_CAPTURING,
479     TAB_MEDIA_STATE_AUDIO_PLAYING
480   };
482   NSWindow* const window = test_window();
484   // Create TabController instance and place its view into the test window.
485   base::scoped_nsobject<TabController> controller([[TabController alloc] init]);
486   [[window contentView] addSubview:[controller view]];
488   // Create favicon and media indicator views.  Disable animation in the media
489   // indicator view so that TabController's "what should be shown" logic can be
490   // tested effectively.  If animations were left enabled, the
491   // shouldShowMediaIndicator method would return true during fade-out
492   // transitions.
493   ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
494   base::scoped_nsobject<NSImage> favicon(
495       rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).CopyNSImage());
496   base::scoped_nsobject<MediaIndicatorView> mediaIndicatorView(
497       [[MediaIndicatorView alloc] init]);
498   [mediaIndicatorView disableAnimations];
499   [controller setMediaIndicatorView:mediaIndicatorView];
501   // Perform layout over all possible combinations, checking for correct
502   // results.
503   for (int isMiniTab = 0; isMiniTab < 2; ++isMiniTab) {
504     for (int isActiveTab = 0; isActiveTab < 2; ++isActiveTab) {
505       for (size_t mediaStateIndex = 0;
506            mediaStateIndex < arraysize(kMediaStatesToTest);
507            ++mediaStateIndex) {
508         const TabMediaState mediaState = kMediaStatesToTest[mediaStateIndex];
509         SCOPED_TRACE(::testing::Message()
510                      << (isActiveTab ? "Active" : "Inactive") << ' '
511                      << (isMiniTab ? "Mini " : "")
512                      << "Tab with media indicator state " << mediaState);
514         // Simulate what tab_strip_controller would do to set up the
515         // TabController state.
516         [controller setMini:(isMiniTab ? YES : NO)];
517         [controller setActive:(isActiveTab ? YES : NO)];
518         [[controller mediaIndicatorView] updateIndicator:mediaState];
519         [controller setIconImage:favicon];
521         // Test layout for every width from maximum to minimum.
522         NSRect tabFrame = [[controller view] frame];
523         int minWidth;
524         if (isMiniTab) {
525           tabFrame.size.width = minWidth = [TabController miniTabWidth];
526         } else {
527           tabFrame.size.width = [TabController maxTabWidth];
528           minWidth = isActiveTab ? [TabController minSelectedTabWidth] :
529               [TabController minTabWidth];
530         }
531         while (NSWidth(tabFrame) >= minWidth) {
532           SCOPED_TRACE(::testing::Message() << "width=" << tabFrame.size.width);
533           [[controller view] setFrame:tabFrame];
534           CheckForExpectedLayoutAndVisibilityOfSubviews(controller);
535           --tabFrame.size.width;
536         }
537       }
538     }
539   }
542 }  // namespace