Home | History | Annotate | Download | only in tabs
      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.
      4 
      5 #import <Cocoa/Cocoa.h>
      6 
      7 #import "base/memory/scoped_nsobject.h"
      8 #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
      9 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
     10 #import "chrome/browser/ui/cocoa/tabs/tab_controller_target.h"
     11 #include "testing/gtest/include/gtest/gtest.h"
     12 #import "testing/gtest_mac.h"
     13 #include "testing/platform_test.h"
     14 
     15 // Implements the target interface for the tab, which gets sent messages when
     16 // the tab is clicked on by the user and when its close box is clicked.
     17 @interface TabControllerTestTarget : NSObject<TabControllerTarget> {
     18  @private
     19   bool selected_;
     20   bool closed_;
     21 }
     22 - (bool)selected;
     23 - (bool)closed;
     24 @end
     25 
     26 @implementation TabControllerTestTarget
     27 - (bool)selected {
     28   return selected_;
     29 }
     30 - (bool)closed {
     31   return closed_;
     32 }
     33 - (void)selectTab:(id)sender {
     34   selected_ = true;
     35 }
     36 - (void)closeTab:(id)sender {
     37   closed_ = true;
     38 }
     39 - (void)mouseTimer:(NSTimer*)timer {
     40   // Fire the mouseUp to break the TabView drag loop.
     41   NSEvent* current = [NSApp currentEvent];
     42   NSWindow* window = [timer userInfo];
     43   NSEvent* up = [NSEvent mouseEventWithType:NSLeftMouseUp
     44                                    location:[current locationInWindow]
     45                               modifierFlags:0
     46                                   timestamp:[current timestamp]
     47                                windowNumber:[window windowNumber]
     48                                     context:nil
     49                                 eventNumber:0
     50                                  clickCount:1
     51                                    pressure:1.0];
     52   [window postEvent:up atStart:YES];
     53 }
     54 - (void)commandDispatch:(TabStripModel::ContextMenuCommand)command
     55           forController:(TabController*)controller {
     56 }
     57 - (BOOL)isCommandEnabled:(TabStripModel::ContextMenuCommand)command
     58            forController:(TabController*)controller {
     59   return NO;
     60 }
     61 @end
     62 
     63 namespace {
     64 
     65 CGFloat LeftMargin(NSRect superFrame, NSRect subFrame) {
     66   return NSMinX(subFrame) - NSMinX(superFrame);
     67 }
     68 
     69 CGFloat RightMargin(NSRect superFrame, NSRect subFrame) {
     70   return NSMaxX(superFrame) - NSMaxX(subFrame);
     71 }
     72 
     73 // The dragging code in TabView makes heavy use of autorelease pools so
     74 // inherit from CocoaTest to have one created for us.
     75 class TabControllerTest : public CocoaTest {
     76  public:
     77   TabControllerTest() { }
     78 };
     79 
     80 // Tests creating the controller, sticking it in a window, and removing it.
     81 TEST_F(TabControllerTest, Creation) {
     82   NSWindow* window = test_window();
     83   scoped_nsobject<TabController> controller([[TabController alloc] init]);
     84   [[window contentView] addSubview:[controller view]];
     85   EXPECT_TRUE([controller tabView]);
     86   EXPECT_EQ([[controller view] window], window);
     87   [[controller view] display];  // Test drawing to ensure nothing leaks/crashes.
     88   [[controller view] removeFromSuperview];
     89 }
     90 
     91 // Tests sending it a close message and ensuring that the target/action get
     92 // called. Mimics the user clicking on the close button in the tab.
     93 TEST_F(TabControllerTest, Close) {
     94   NSWindow* window = test_window();
     95   scoped_nsobject<TabController> controller([[TabController alloc] init]);
     96   [[window contentView] addSubview:[controller view]];
     97 
     98   scoped_nsobject<TabControllerTestTarget> target(
     99       [[TabControllerTestTarget alloc] init]);
    100   EXPECT_FALSE([target closed]);
    101   [controller setTarget:target];
    102   EXPECT_EQ(target.get(), [controller target]);
    103 
    104   [controller closeTab:nil];
    105   EXPECT_TRUE([target closed]);
    106 
    107   [[controller view] removeFromSuperview];
    108 }
    109 
    110 // Tests setting the |selected| property via code.
    111 TEST_F(TabControllerTest, APISelection) {
    112   NSWindow* window = test_window();
    113   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    114   [[window contentView] addSubview:[controller view]];
    115 
    116   EXPECT_FALSE([controller selected]);
    117   [controller setSelected:YES];
    118   EXPECT_TRUE([controller selected]);
    119 
    120   [[controller view] removeFromSuperview];
    121 }
    122 
    123 // Tests that setting the title of a tab sets the tooltip as well.
    124 TEST_F(TabControllerTest, ToolTip) {
    125   NSWindow* window = test_window();
    126 
    127   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    128   [[window contentView] addSubview:[controller view]];
    129 
    130   EXPECT_TRUE([[controller toolTip] length] == 0);
    131   NSString *tooltip_string = @"Some text to use as a tab title";
    132   [controller setTitle:tooltip_string];
    133   EXPECT_NSEQ(tooltip_string, [controller toolTip]);
    134 }
    135 
    136 // Tests setting the |loading| property via code.
    137 TEST_F(TabControllerTest, Loading) {
    138   NSWindow* window = test_window();
    139   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    140   [[window contentView] addSubview:[controller view]];
    141 
    142   EXPECT_EQ(kTabDone, [controller loadingState]);
    143   [controller setLoadingState:kTabWaiting];
    144   EXPECT_EQ(kTabWaiting, [controller loadingState]);
    145   [controller setLoadingState:kTabLoading];
    146   EXPECT_EQ(kTabLoading, [controller loadingState]);
    147   [controller setLoadingState:kTabDone];
    148   EXPECT_EQ(kTabDone, [controller loadingState]);
    149 
    150   [[controller view] removeFromSuperview];
    151 }
    152 
    153 // Tests selecting the tab with the mouse click and ensuring the target/action
    154 // get called.
    155 TEST_F(TabControllerTest, UserSelection) {
    156   NSWindow* window = test_window();
    157 
    158   // Create a tab at a known location in the window that we can click on
    159   // to activate selection.
    160   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    161   [[window contentView] addSubview:[controller view]];
    162   NSRect frame = [[controller view] frame];
    163   frame.size.width = [TabController minTabWidth];
    164   frame.origin = NSMakePoint(0, 0);
    165   [[controller view] setFrame:frame];
    166 
    167   // Set the target and action.
    168   scoped_nsobject<TabControllerTestTarget> target(
    169       [[TabControllerTestTarget alloc] init]);
    170   EXPECT_FALSE([target selected]);
    171   [controller setTarget:target];
    172   [controller setAction:@selector(selectTab:)];
    173   EXPECT_EQ(target.get(), [controller target]);
    174   EXPECT_EQ(@selector(selectTab:), [controller action]);
    175 
    176   // In order to track a click, we have to fake a mouse down and a mouse
    177   // up, but the down goes into a tight drag loop. To break the loop, we have
    178   // to fire a timer that sends a mouse up event while the "drag" is ongoing.
    179   [NSTimer scheduledTimerWithTimeInterval:0.1
    180                                    target:target.get()
    181                                  selector:@selector(mouseTimer:)
    182                                  userInfo:window
    183                                   repeats:NO];
    184   NSEvent* current = [NSApp currentEvent];
    185   NSPoint click_point = NSMakePoint(frame.size.width / 2,
    186                                     frame.size.height / 2);
    187   NSEvent* down = [NSEvent mouseEventWithType:NSLeftMouseDown
    188                                      location:click_point
    189                                 modifierFlags:0
    190                                     timestamp:[current timestamp]
    191                                  windowNumber:[window windowNumber]
    192                                       context:nil
    193                                   eventNumber:0
    194                                    clickCount:1
    195                                      pressure:1.0];
    196   [[controller view] mouseDown:down];
    197 
    198   // Check our target was told the tab got selected.
    199   EXPECT_TRUE([target selected]);
    200 
    201   [[controller view] removeFromSuperview];
    202 }
    203 
    204 TEST_F(TabControllerTest, IconCapacity) {
    205   NSWindow* window = test_window();
    206   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    207   [[window contentView] addSubview:[controller view]];
    208   int cap = [controller iconCapacity];
    209   EXPECT_GE(cap, 1);
    210 
    211   NSRect frame = [[controller view] frame];
    212   frame.size.width += 500;
    213   [[controller view] setFrame:frame];
    214   int newcap = [controller iconCapacity];
    215   EXPECT_GT(newcap, cap);
    216 }
    217 
    218 TEST_F(TabControllerTest, ShouldShowIcon) {
    219   NSWindow* window = test_window();
    220   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    221   [[window contentView] addSubview:[controller view]];
    222   int cap = [controller iconCapacity];
    223   EXPECT_GT(cap, 0);
    224 
    225   // Tab is minimum width, both icon and close box should be hidden.
    226   NSRect frame = [[controller view] frame];
    227   frame.size.width = [TabController minTabWidth];
    228   [[controller view] setFrame:frame];
    229   EXPECT_FALSE([controller shouldShowIcon]);
    230   EXPECT_FALSE([controller shouldShowCloseButton]);
    231 
    232   // Setting the icon when tab is at min width should not show icon (bug 18359).
    233   scoped_nsobject<NSView> newIcon(
    234       [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 16, 16)]);
    235   [controller setIconView:newIcon.get()];
    236   EXPECT_TRUE([newIcon isHidden]);
    237 
    238   // Tab is at selected minimum width. Since it's selected, the close box
    239   // should be visible.
    240   [controller setSelected:YES];
    241   frame = [[controller view] frame];
    242   frame.size.width = [TabController minSelectedTabWidth];
    243   [[controller view] setFrame:frame];
    244   EXPECT_FALSE([controller shouldShowIcon]);
    245   EXPECT_TRUE([newIcon isHidden]);
    246   EXPECT_TRUE([controller shouldShowCloseButton]);
    247 
    248   // Test expanding the tab to max width and ensure the icon and close box
    249   // get put back, even when de-selected.
    250   frame.size.width = [TabController maxTabWidth];
    251   [[controller view] setFrame:frame];
    252   EXPECT_TRUE([controller shouldShowIcon]);
    253   EXPECT_FALSE([newIcon isHidden]);
    254   EXPECT_TRUE([controller shouldShowCloseButton]);
    255   [controller setSelected:NO];
    256   EXPECT_TRUE([controller shouldShowIcon]);
    257   EXPECT_TRUE([controller shouldShowCloseButton]);
    258 
    259   cap = [controller iconCapacity];
    260   EXPECT_GT(cap, 0);
    261 }
    262 
    263 TEST_F(TabControllerTest, Menu) {
    264   NSWindow* window = test_window();
    265   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    266   [[window contentView] addSubview:[controller view]];
    267   int cap = [controller iconCapacity];
    268   EXPECT_GT(cap, 0);
    269 
    270   // Asking the view for its menu should yield a valid menu.
    271   NSMenu* menu = [[controller view] menu];
    272   EXPECT_TRUE(menu);
    273   EXPECT_GT([menu numberOfItems], 0);
    274 }
    275 
    276 // Tests that the title field is correctly positioned and sized when the
    277 // view is resized.
    278 TEST_F(TabControllerTest, TitleViewLayout) {
    279   NSWindow* window = test_window();
    280 
    281   scoped_nsobject<TabController> controller([[TabController alloc] init]);
    282   [[window contentView] addSubview:[controller view]];
    283   NSRect tabFrame = [[controller view] frame];
    284   tabFrame.size.width = [TabController maxTabWidth];
    285   [[controller view] setFrame:tabFrame];
    286 
    287   const NSRect originalTabFrame = [[controller view] frame];
    288   const NSRect originalIconFrame = [[controller iconView] frame];
    289   const NSRect originalCloseFrame = [[controller closeButton] frame];
    290   const NSRect originalTitleFrame = [[controller titleView] frame];
    291 
    292   // Sanity check the start state.
    293   EXPECT_FALSE([[controller iconView] isHidden]);
    294   EXPECT_FALSE([[controller closeButton] isHidden]);
    295   EXPECT_GT(NSWidth([[controller view] frame]),
    296             NSWidth([[controller titleView] frame]));
    297 
    298   // Resize the tab so that that the it shrinks.
    299   tabFrame.size.width = [TabController minTabWidth];
    300   [[controller view] setFrame:tabFrame];
    301 
    302   // The icon view and close button should be hidden and the title view should
    303   // be resize to take up their space.
    304   EXPECT_TRUE([[controller iconView] isHidden]);
    305   EXPECT_TRUE([[controller closeButton] isHidden]);
    306   EXPECT_GT(NSWidth([[controller view] frame]),
    307             NSWidth([[controller titleView] frame]));
    308   EXPECT_EQ(LeftMargin(originalTabFrame, originalIconFrame),
    309             LeftMargin([[controller view] frame],
    310                        [[controller titleView] frame]));
    311   EXPECT_EQ(RightMargin(originalTabFrame, originalCloseFrame),
    312             RightMargin([[controller view] frame],
    313                         [[controller titleView] frame]));
    314 
    315   // Resize the tab so that that the it grows.
    316   tabFrame.size.width = [TabController maxTabWidth] * 0.75;
    317   [[controller view] setFrame:tabFrame];
    318 
    319   // The icon view and close button should be visible again and the title view
    320   // should be resized to make room for them.
    321   EXPECT_FALSE([[controller iconView] isHidden]);
    322   EXPECT_FALSE([[controller closeButton] isHidden]);
    323   EXPECT_GT(NSWidth([[controller view] frame]),
    324             NSWidth([[controller titleView] frame]));
    325   EXPECT_EQ(LeftMargin(originalTabFrame, originalTitleFrame),
    326             LeftMargin([[controller view] frame],
    327                        [[controller titleView] frame]));
    328   EXPECT_EQ(RightMargin(originalTabFrame, originalTitleFrame),
    329             RightMargin([[controller view] frame],
    330                         [[controller titleView] frame]));
    331 }
    332 
    333 }  // namespace
    334