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 #import "base/mac/scoped_nsobject.h" 8 #include "base/prefs/pref_service.h" 9 #include "chrome/app/chrome_command_ids.h" 10 #include "chrome/browser/command_updater.h" 11 #include "chrome/browser/ui/browser_command_controller.h" 12 #include "chrome/browser/ui/browser_commands.h" 13 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h" 14 #import "chrome/browser/ui/cocoa/gradient_button_cell.h" 15 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 16 #import "chrome/browser/ui/cocoa/view_resizer_pong.h" 17 #include "chrome/common/pref_names.h" 18 #include "testing/gtest/include/gtest/gtest.h" 19 #include "testing/platform_test.h" 20 21 // An NSView that fakes out hitTest:. 22 @interface HitView : NSView { 23 id hitTestReturn_; 24 } 25 @end 26 27 @implementation HitView 28 29 - (void)setHitTestReturn:(id)rtn { 30 hitTestReturn_ = rtn; 31 } 32 33 - (NSView *)hitTest:(NSPoint)aPoint { 34 return hitTestReturn_; 35 } 36 37 @end 38 39 40 namespace { 41 42 class ToolbarControllerTest : public CocoaProfileTest { 43 public: 44 45 // Indexes that match the ordering returned by the private ToolbarController 46 // |-toolbarViews| method. 47 enum { 48 kBackIndex, kForwardIndex, kReloadIndex, kHomeIndex, 49 kWrenchIndex, kLocationIndex, kBrowserActionContainerViewIndex 50 }; 51 52 virtual void SetUp() OVERRIDE { 53 CocoaProfileTest::SetUp(); 54 ASSERT_TRUE(browser()); 55 56 CommandUpdater* updater = 57 browser()->command_controller()->command_updater(); 58 // The default state for the commands is true, set a couple to false to 59 // ensure they get picked up correct on initialization 60 updater->UpdateCommandEnabled(IDC_BACK, false); 61 updater->UpdateCommandEnabled(IDC_FORWARD, false); 62 resizeDelegate_.reset([[ViewResizerPong alloc] init]); 63 bar_.reset( 64 [[ToolbarController alloc] initWithModel:browser()->toolbar_model() 65 commands:browser()->command_controller()->command_updater() 66 profile:profile() 67 browser:browser() 68 resizeDelegate:resizeDelegate_.get()]); 69 EXPECT_TRUE([bar_ view]); 70 NSView* parent = [test_window() contentView]; 71 [parent addSubview:[bar_ view]]; 72 } 73 74 virtual void TearDown() OVERRIDE { 75 bar_.reset(); // browser() must outlive the ToolbarController. 76 CocoaProfileTest::TearDown(); 77 } 78 79 // Make sure the enabled state of the view is the same as the corresponding 80 // command in the updater. The views are in the declaration order of outlets. 81 void CompareState(CommandUpdater* updater, NSArray* views) { 82 EXPECT_EQ(updater->IsCommandEnabled(IDC_BACK), 83 [[views objectAtIndex:kBackIndex] isEnabled] ? true : false); 84 EXPECT_EQ(updater->IsCommandEnabled(IDC_FORWARD), 85 [[views objectAtIndex:kForwardIndex] isEnabled] ? true : false); 86 EXPECT_EQ(updater->IsCommandEnabled(IDC_RELOAD), 87 [[views objectAtIndex:kReloadIndex] isEnabled] ? true : false); 88 EXPECT_EQ(updater->IsCommandEnabled(IDC_HOME), 89 [[views objectAtIndex:kHomeIndex] isEnabled] ? true : false); 90 } 91 92 base::scoped_nsobject<ViewResizerPong> resizeDelegate_; 93 base::scoped_nsobject<ToolbarController> bar_; 94 }; 95 96 TEST_VIEW(ToolbarControllerTest, [bar_ view]) 97 98 // Test the initial state that everything is sync'd up 99 TEST_F(ToolbarControllerTest, InitialState) { 100 CommandUpdater* updater = browser()->command_controller()->command_updater(); 101 CompareState(updater, [bar_ toolbarViews]); 102 } 103 104 // Make sure a "titlebar only" toolbar with location bar works. 105 TEST_F(ToolbarControllerTest, TitlebarOnly) { 106 NSView* view = [bar_ view]; 107 108 [bar_ setHasToolbar:NO hasLocationBar:YES]; 109 EXPECT_NE(view, [bar_ view]); 110 111 // Simulate a popup going fullscreen and back by performing the reparenting 112 // that happens during fullscreen transitions 113 NSView* superview = [view superview]; 114 [view removeFromSuperview]; 115 [superview addSubview:view]; 116 117 [bar_ setHasToolbar:YES hasLocationBar:YES]; 118 EXPECT_EQ(view, [bar_ view]); 119 120 // Leave it off to make sure that's fine 121 [bar_ setHasToolbar:NO hasLocationBar:YES]; 122 } 123 124 // Make sure it works in the completely undecorated case. 125 TEST_F(ToolbarControllerTest, NoLocationBar) { 126 NSView* view = [bar_ view]; 127 128 [bar_ setHasToolbar:NO hasLocationBar:NO]; 129 EXPECT_NE(view, [bar_ view]); 130 EXPECT_TRUE([[bar_ view] isHidden]); 131 132 // Simulate a popup going fullscreen and back by performing the reparenting 133 // that happens during fullscreen transitions 134 NSView* superview = [view superview]; 135 [view removeFromSuperview]; 136 [superview addSubview:view]; 137 } 138 139 // Make some changes to the enabled state of a few of the buttons and ensure 140 // that we're still in sync. 141 TEST_F(ToolbarControllerTest, UpdateEnabledState) { 142 EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_BACK)); 143 EXPECT_FALSE(chrome::IsCommandEnabled(browser(), IDC_FORWARD)); 144 chrome::UpdateCommandEnabled(browser(), IDC_BACK, true); 145 chrome::UpdateCommandEnabled(browser(), IDC_FORWARD, true); 146 CommandUpdater* updater = browser()->command_controller()->command_updater(); 147 CompareState(updater, [bar_ toolbarViews]); 148 } 149 150 // Focus the location bar and make sure that it's the first responder. 151 TEST_F(ToolbarControllerTest, FocusLocation) { 152 NSWindow* window = test_window(); 153 [window makeFirstResponder:[window contentView]]; 154 EXPECT_EQ([window firstResponder], [window contentView]); 155 [bar_ focusLocationBar:YES]; 156 EXPECT_NE([window firstResponder], [window contentView]); 157 NSView* locationBar = [[bar_ toolbarViews] objectAtIndex:kLocationIndex]; 158 EXPECT_EQ([window firstResponder], [(id)locationBar currentEditor]); 159 } 160 161 TEST_F(ToolbarControllerTest, LoadingState) { 162 // In its initial state, the reload button has a tag of 163 // IDC_RELOAD. When loading, it should be IDC_STOP. 164 NSButton* reload = [[bar_ toolbarViews] objectAtIndex:kReloadIndex]; 165 EXPECT_EQ([reload tag], IDC_RELOAD); 166 [bar_ setIsLoading:YES force:YES]; 167 EXPECT_EQ([reload tag], IDC_STOP); 168 [bar_ setIsLoading:NO force:YES]; 169 EXPECT_EQ([reload tag], IDC_RELOAD); 170 } 171 172 // Check that toggling the state of the home button changes the visible 173 // state of the home button and moves the other items accordingly. 174 TEST_F(ToolbarControllerTest, ToggleHome) { 175 PrefService* prefs = profile()->GetPrefs(); 176 bool showHome = prefs->GetBoolean(prefs::kShowHomeButton); 177 NSView* homeButton = [[bar_ toolbarViews] objectAtIndex:kHomeIndex]; 178 EXPECT_EQ(showHome, ![homeButton isHidden]); 179 180 NSView* locationBar = [[bar_ toolbarViews] objectAtIndex:kLocationIndex]; 181 NSRect originalLocationBarFrame = [locationBar frame]; 182 183 // Toggle the pref and make sure the button changed state and the other 184 // views moved. 185 prefs->SetBoolean(prefs::kShowHomeButton, !showHome); 186 EXPECT_EQ(showHome, [homeButton isHidden]); 187 EXPECT_NE(NSMinX(originalLocationBarFrame), NSMinX([locationBar frame])); 188 EXPECT_NE(NSWidth(originalLocationBarFrame), NSWidth([locationBar frame])); 189 } 190 191 // Ensure that we don't toggle the buttons when we have a strip marked as not 192 // having the full toolbar. Also ensure that the location bar doesn't change 193 // size. 194 TEST_F(ToolbarControllerTest, DontToggleWhenNoToolbar) { 195 [bar_ setHasToolbar:NO hasLocationBar:YES]; 196 NSView* homeButton = [[bar_ toolbarViews] objectAtIndex:kHomeIndex]; 197 NSView* locationBar = [[bar_ toolbarViews] objectAtIndex:kLocationIndex]; 198 NSRect locationBarFrame = [locationBar frame]; 199 EXPECT_EQ([homeButton isHidden], YES); 200 [bar_ showOptionalHomeButton]; 201 EXPECT_EQ([homeButton isHidden], YES); 202 NSRect newLocationBarFrame = [locationBar frame]; 203 EXPECT_TRUE(NSEqualRects(locationBarFrame, newLocationBarFrame)); 204 newLocationBarFrame = [locationBar frame]; 205 EXPECT_TRUE(NSEqualRects(locationBarFrame, newLocationBarFrame)); 206 } 207 208 TEST_F(ToolbarControllerTest, BookmarkBubblePoint) { 209 const NSPoint starPoint = [bar_ bookmarkBubblePoint]; 210 const NSRect barFrame = 211 [[bar_ view] convertRect:[[bar_ view] bounds] toView:nil]; 212 213 // Make sure the star is completely inside the location bar. 214 EXPECT_TRUE(NSPointInRect(starPoint, barFrame)); 215 } 216 217 TEST_F(ToolbarControllerTest, HoverButtonForEvent) { 218 base::scoped_nsobject<HitView> view( 219 [[HitView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]); 220 [bar_ setView:view]; 221 NSEvent* event = [NSEvent mouseEventWithType:NSMouseMoved 222 location:NSMakePoint(10,10) 223 modifierFlags:0 224 timestamp:0 225 windowNumber:0 226 context:nil 227 eventNumber:0 228 clickCount:0 229 pressure:0.0]; 230 231 // NOT a match. 232 [view setHitTestReturn:bar_.get()]; 233 EXPECT_FALSE([bar_ hoverButtonForEvent:event]); 234 235 // Not yet... 236 base::scoped_nsobject<NSButton> button([[NSButton alloc] init]); 237 [view setHitTestReturn:button]; 238 EXPECT_FALSE([bar_ hoverButtonForEvent:event]); 239 240 // Now! 241 base::scoped_nsobject<GradientButtonCell> cell( 242 [[GradientButtonCell alloc] init]); 243 [button setCell:cell.get()]; 244 EXPECT_TRUE([bar_ hoverButtonForEvent:nil]); 245 } 246 247 } // namespace 248