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 63namespace { 64 65CGFloat LeftMargin(NSRect superFrame, NSRect subFrame) { 66 return NSMinX(subFrame) - NSMinX(superFrame); 67} 68 69CGFloat 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. 75class TabControllerTest : public CocoaTest { 76 public: 77 TabControllerTest() { } 78}; 79 80// Tests creating the controller, sticking it in a window, and removing it. 81TEST_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. 93TEST_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. 111TEST_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. 124TEST_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. 137TEST_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. 155TEST_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 204TEST_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 218TEST_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 263TEST_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. 278TEST_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