tab_strip_controller_unittest.mm revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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#import <Cocoa/Cocoa.h> 6 7#include "base/mac/scoped_nsautorelease_pool.h" 8#include "chrome/browser/ui/browser_window.h" 9#include "chrome/browser/ui/cocoa/cocoa_profile_test.h" 10#import "chrome/browser/ui/cocoa/new_tab_button.h" 11#import "chrome/browser/ui/cocoa/tabs/tab_controller.h" 12#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 13#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" 14#import "chrome/browser/ui/cocoa/tabs/tab_view.h" 15#include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" 16#include "chrome/test/base/testing_profile.h" 17#include "content/public/browser/site_instance.h" 18#include "content/public/browser/web_contents.h" 19#include "testing/gtest/include/gtest/gtest.h" 20#include "testing/platform_test.h" 21#include "ui/base/test/cocoa_test_event_utils.h" 22 23using content::SiteInstance; 24using content::WebContents; 25 26@interface TestTabStripControllerDelegate 27 : NSObject<TabStripControllerDelegate> { 28} 29@end 30 31@implementation TestTabStripControllerDelegate 32- (void)onActivateTabWithContents:(WebContents*)contents { 33} 34- (void)onTabChanged:(TabStripModelObserver::TabChangeType)change 35 withContents:(WebContents*)contents { 36} 37- (void)onTabDetachedWithContents:(WebContents*)contents { 38} 39@end 40 41 42// Helper class for invoking a base::Closure via 43// -performSelector:withObject:afterDelay:. 44@interface TestClosureRunner : NSObject { 45 @private 46 base::Closure closure_; 47} 48- (id)initWithClosure:(const base::Closure&)closure; 49- (void)scheduleDelayedRun; 50- (void)run; 51@end 52 53@implementation TestClosureRunner 54- (id)initWithClosure:(const base::Closure&)closure { 55 if (self) { 56 closure_ = closure; 57 } 58 return self; 59} 60- (void)scheduleDelayedRun { 61 [self performSelector:@selector(run) withObject:nil afterDelay:0]; 62} 63- (void)run { 64 closure_.Run(); 65} 66@end 67 68@interface TabStripController (Test) 69 70- (void)mouseMoved:(NSEvent*)event; 71 72@end 73 74@implementation TabView (Test) 75 76- (TabController*)controller { 77 return controller_; 78} 79 80@end 81 82namespace { 83 84class TabStripControllerTest : public CocoaProfileTest { 85 public: 86 virtual void SetUp() OVERRIDE { 87 CocoaProfileTest::SetUp(); 88 ASSERT_TRUE(browser()); 89 90 NSWindow* window = browser()->window()->GetNativeWindow(); 91 NSView* parent = [window contentView]; 92 NSRect content_frame = [parent frame]; 93 94 // Create the "switch view" (view that gets changed out when a tab 95 // switches). 96 NSRect switch_frame = NSMakeRect(0, 0, content_frame.size.width, 500); 97 base::scoped_nsobject<NSView> switch_view( 98 [[NSView alloc] initWithFrame:switch_frame]); 99 [parent addSubview:switch_view.get()]; 100 101 // Create the tab strip view. It's expected to have a child button in it 102 // already as the "new tab" button so create that too. 103 NSRect strip_frame = NSMakeRect(0, NSMaxY(switch_frame), 104 content_frame.size.width, 30); 105 tab_strip_.reset( 106 [[TabStripView alloc] initWithFrame:strip_frame]); 107 [parent addSubview:tab_strip_.get()]; 108 NSRect button_frame = NSMakeRect(0, 0, 15, 15); 109 base::scoped_nsobject<NewTabButton> new_tab_button( 110 [[NewTabButton alloc] initWithFrame:button_frame]); 111 [tab_strip_ addSubview:new_tab_button.get()]; 112 [tab_strip_ setNewTabButton:new_tab_button.get()]; 113 114 delegate_.reset(new TestTabStripModelDelegate()); 115 model_ = browser()->tab_strip_model(); 116 controller_delegate_.reset([TestTabStripControllerDelegate alloc]); 117 controller_.reset([[TabStripController alloc] 118 initWithView:static_cast<TabStripView*>(tab_strip_.get()) 119 switchView:switch_view.get() 120 browser:browser() 121 delegate:controller_delegate_.get()]); 122 } 123 124 virtual void TearDown() OVERRIDE { 125 // The call to CocoaTest::TearDown() deletes the Browser and TabStripModel 126 // objects, so we first have to delete the controller, which refers to them. 127 controller_.reset(); 128 model_ = NULL; 129 CocoaProfileTest::TearDown(); 130 } 131 132 TabView* CreateTab() { 133 SiteInstance* instance = SiteInstance::Create(profile()); 134 WebContents* web_contents = WebContents::Create( 135 content::WebContents::CreateParams(profile(), instance)); 136 model_->AppendWebContents(web_contents, true); 137 const NSUInteger tab_count = [controller_.get() viewsCount]; 138 return static_cast<TabView*>([controller_.get() viewAtIndex:tab_count - 1]); 139 } 140 141 // Closes all tabs and unrefs the tabstrip and then posts a NSLeftMouseUp 142 // event which should end the nested drag event loop. 143 void CloseTabsAndEndDrag() { 144 // Simulate a close of the browser window. 145 model_->CloseAllTabs(); 146 controller_.reset(); 147 tab_strip_.reset(); 148 // Schedule a NSLeftMouseUp to end the nested drag event loop. 149 NSEvent* event = 150 cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0); 151 [NSApp postEvent:event atStart:NO]; 152 } 153 154 scoped_ptr<TestTabStripModelDelegate> delegate_; 155 TabStripModel* model_; 156 base::scoped_nsobject<TestTabStripControllerDelegate> controller_delegate_; 157 base::scoped_nsobject<TabStripController> controller_; 158 base::scoped_nsobject<TabStripView> tab_strip_; 159}; 160 161// Test adding and removing tabs and making sure that views get added to 162// the tab strip. 163TEST_F(TabStripControllerTest, AddRemoveTabs) { 164 EXPECT_TRUE(model_->empty()); 165 CreateTab(); 166 EXPECT_EQ(model_->count(), 1); 167} 168 169TEST_F(TabStripControllerTest, SelectTab) { 170 // TODO(pinkerton): Implement http://crbug.com/10899 171} 172 173TEST_F(TabStripControllerTest, RearrangeTabs) { 174 // TODO(pinkerton): Implement http://crbug.com/10899 175} 176 177TEST_F(TabStripControllerTest, CorrectToolTipText) { 178 // Set tab 1 tooltip. 179 TabView* tab1 = CreateTab(); 180 [tab1 setToolTip:@"Tab1"]; 181 182 // Set tab 2 tooltip. 183 TabView* tab2 = CreateTab(); 184 [tab2 setToolTip:@"Tab2"]; 185 186 EXPECT_FALSE([tab1 controller].selected); 187 EXPECT_TRUE([tab2 controller].selected); 188 189 // Check that there's no tooltip yet. 190 EXPECT_FALSE([controller_ view:nil 191 stringForToolTip:nil 192 point:NSZeroPoint 193 userData:nil]); 194 195 // Set up mouse event on overlap of tab1 + tab2. 196 const CGFloat min_y = NSMinY([tab_strip_.get() frame]) + 1; 197 198 // Hover over overlap between tab 1 and 2. 199 NSEvent* event = 200 cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(280, min_y), 201 NSMouseMoved, 0); 202 [controller_.get() mouseMoved:event]; 203 EXPECT_STREQ("Tab2", 204 [[controller_ view:nil 205 stringForToolTip:nil 206 point:NSZeroPoint 207 userData:nil] cStringUsingEncoding:NSASCIIStringEncoding]); 208 209 210 // Hover over tab 1. 211 event = cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(260, min_y), 212 NSMouseMoved, 0); 213 [controller_.get() mouseMoved:event]; 214 EXPECT_STREQ("Tab1", 215 [[controller_ view:nil 216 stringForToolTip:nil 217 point:NSZeroPoint 218 userData:nil] cStringUsingEncoding:NSASCIIStringEncoding]); 219 220 // Hover over tab 2. 221 event = cocoa_test_event_utils::MouseEventAtPoint(NSMakePoint(290, min_y), 222 NSMouseMoved, 0); 223 [controller_.get() mouseMoved:event]; 224 EXPECT_STREQ("Tab2", 225 [[controller_ view:nil 226 stringForToolTip:nil 227 point:NSZeroPoint 228 userData:nil] cStringUsingEncoding:NSASCIIStringEncoding]); 229} 230 231TEST_F(TabStripControllerTest, TabCloseDuringDrag) { 232 TabController* tab; 233 // The TabController gets autoreleased when created, but is owned by the 234 // tab strip model. Use a ScopedNSAutoreleasePool to get a truly weak ref 235 // to it to test that -maybeStartDrag:forTab: can handle that properly. 236 { 237 base::mac::ScopedNSAutoreleasePool pool; 238 tab = [CreateTab() controller]; 239 } 240 241 // Schedule a task to close all the tabs and stop the drag, before the call to 242 // -maybeStartDrag:forTab:, which starts a nested event loop. This task will 243 // run in that nested event loop, which shouldn't crash. 244 base::scoped_nsobject<TestClosureRunner> runner([[TestClosureRunner alloc] 245 initWithClosure:base::Bind(&TabStripControllerTest::CloseTabsAndEndDrag, 246 base::Unretained(this))]); 247 [runner scheduleDelayedRun]; 248 249 NSEvent* event = 250 cocoa_test_event_utils::LeftMouseDownAtPoint(NSZeroPoint); 251 [[controller_ dragController] maybeStartDrag:event forTab:tab]; 252} 253 254TEST_F(TabStripControllerTest, ViewAccessibility_Contents) { 255 NSArray* attrs = [tab_strip_ accessibilityAttributeNames]; 256 ASSERT_TRUE([attrs containsObject:NSAccessibilityContentsAttribute]); 257 258 // Create two tabs and ensure they exist in the contents array. 259 TabView* tab1 = CreateTab(); 260 TabView* tab2 = CreateTab(); 261 NSObject* contents = 262 [tab_strip_ accessibilityAttributeValue:NSAccessibilityContentsAttribute]; 263 DCHECK([contents isKindOfClass:[NSArray class]]); 264 NSArray* contentsArray = static_cast<NSArray*>(contents); 265 ASSERT_TRUE([contentsArray containsObject:tab1]); 266 ASSERT_TRUE([contentsArray containsObject:tab2]); 267} 268 269TEST_F(TabStripControllerTest, ViewAccessibility_Value) { 270 NSArray* attrs = [tab_strip_ accessibilityAttributeNames]; 271 ASSERT_TRUE([attrs containsObject:NSAccessibilityValueAttribute]); 272 273 // Create two tabs and ensure the active one gets returned. 274 TabView* tab1 = CreateTab(); 275 TabView* tab2 = CreateTab(); 276 EXPECT_FALSE([tab1 controller].selected); 277 EXPECT_TRUE([tab2 controller].selected); 278 NSObject* value = 279 [tab_strip_ accessibilityAttributeValue:NSAccessibilityValueAttribute]; 280 EXPECT_EQ(tab2, value); 281 282 model_->ActivateTabAt(0, false); 283 EXPECT_TRUE([tab1 controller].selected); 284 EXPECT_FALSE([tab2 controller].selected); 285 value = 286 [tab_strip_ accessibilityAttributeValue:NSAccessibilityValueAttribute]; 287 EXPECT_EQ(tab1, value); 288} 289 290} // namespace 291