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 "chrome/browser/ui/cocoa/toolbar/reload_button.h" 8 9 #include "base/mac/scoped_nsobject.h" 10 #include "chrome/app/chrome_command_ids.h" 11 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" 12 #import "chrome/browser/ui/cocoa/image_button_cell.h" 13 #import "testing/gtest_mac.h" 14 #include "testing/platform_test.h" 15 #import "third_party/ocmock/OCMock/OCMock.h" 16 #import "ui/base/test/cocoa_test_event_utils.h" 17 18 @interface ReloadButton (Testing) 19 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds; 20 @end 21 22 @protocol TargetActionMock <NSObject> 23 - (void)anAction:(id)sender; 24 @end 25 26 namespace { 27 28 class ReloadButtonTest : public CocoaTest { 29 public: 30 ReloadButtonTest() { 31 NSRect frame = NSMakeRect(0, 0, 20, 20); 32 base::scoped_nsobject<ReloadButton> button( 33 [[ReloadButton alloc] initWithFrame:frame]); 34 button_ = button.get(); 35 36 // Set things up so unit tests have a reliable baseline. 37 [button_ setTag:IDC_RELOAD]; 38 [button_ awakeFromNib]; 39 40 [[test_window() contentView] addSubview:button_]; 41 } 42 43 bool IsMouseInside() { 44 return [[button_ cell] isMouseInside]; 45 } 46 47 void MouseEnter() { 48 [[button_ cell] mouseEntered:nil]; 49 } 50 51 void MouseExit() { 52 [[button_ cell] mouseExited:nil]; 53 } 54 55 ReloadButton* button_; 56 }; 57 58 TEST_VIEW(ReloadButtonTest, button_) 59 60 // Test that mouse-tracking is setup and does the right thing. 61 TEST_F(ReloadButtonTest, IsMouseInside) { 62 EXPECT_FALSE(IsMouseInside()); 63 MouseEnter(); 64 EXPECT_TRUE(IsMouseInside()); 65 MouseExit(); 66 } 67 68 // Verify that multiple clicks do not result in multiple messages to 69 // the target. 70 TEST_F(ReloadButtonTest, IgnoredMultiClick) { 71 id mock_target = [OCMockObject mockForProtocol:@protocol(TargetActionMock)]; 72 [button_ setTarget:mock_target]; 73 [button_ setAction:@selector(anAction:)]; 74 75 // Expect the action once. 76 [[mock_target expect] anAction:button_]; 77 78 const std::pair<NSEvent*,NSEvent*> click_one = 79 cocoa_test_event_utils::MouseClickInView(button_, 1); 80 const std::pair<NSEvent*,NSEvent*> click_two = 81 cocoa_test_event_utils::MouseClickInView(button_, 2); 82 [NSApp postEvent:click_one.second atStart:YES]; 83 [button_ mouseDown:click_one.first]; 84 [NSApp postEvent:click_two.second atStart:YES]; 85 [button_ mouseDown:click_two.first]; 86 87 [button_ setTarget:nil]; 88 } 89 90 TEST_F(ReloadButtonTest, UpdateTag) { 91 [button_ setTag:IDC_STOP]; 92 93 [button_ updateTag:IDC_RELOAD]; 94 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 95 NSString* const reloadToolTip = [button_ toolTip]; 96 97 [button_ updateTag:IDC_STOP]; 98 EXPECT_EQ(IDC_STOP, [button_ tag]); 99 NSString* const stopToolTip = [button_ toolTip]; 100 EXPECT_NSNE(reloadToolTip, stopToolTip); 101 102 [button_ updateTag:IDC_RELOAD]; 103 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 104 EXPECT_NSEQ(reloadToolTip, [button_ toolTip]); 105 } 106 107 // Test that when forcing the mode, it takes effect immediately, 108 // regardless of whether the mouse is hovering. 109 TEST_F(ReloadButtonTest, SetIsLoadingForce) { 110 EXPECT_FALSE(IsMouseInside()); 111 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 112 113 // Changes to stop immediately. 114 [button_ setIsLoading:YES force:YES]; 115 EXPECT_EQ(IDC_STOP, [button_ tag]); 116 117 // Changes to reload immediately. 118 [button_ setIsLoading:NO force:YES]; 119 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 120 121 // Changes to stop immediately when the mouse is hovered, and 122 // doesn't change when the mouse exits. 123 MouseEnter(); 124 EXPECT_TRUE(IsMouseInside()); 125 [button_ setIsLoading:YES force:YES]; 126 EXPECT_EQ(IDC_STOP, [button_ tag]); 127 MouseExit(); 128 EXPECT_FALSE(IsMouseInside()); 129 EXPECT_EQ(IDC_STOP, [button_ tag]); 130 131 // Changes to reload immediately when the mouse is hovered, and 132 // doesn't change when the mouse exits. 133 MouseEnter(); 134 EXPECT_TRUE(IsMouseInside()); 135 [button_ setIsLoading:NO force:YES]; 136 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 137 MouseExit(); 138 EXPECT_FALSE(IsMouseInside()); 139 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 140 } 141 142 // Test that without force, stop mode is set immediately, but reload 143 // is affected by the hover status. 144 TEST_F(ReloadButtonTest, SetIsLoadingNoForceUnHover) { 145 EXPECT_FALSE(IsMouseInside()); 146 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 147 148 // Changes to stop immediately when the mouse is not hovering. 149 [button_ setIsLoading:YES force:NO]; 150 EXPECT_EQ(IDC_STOP, [button_ tag]); 151 152 // Changes to reload immediately when the mouse is not hovering. 153 [button_ setIsLoading:NO force:NO]; 154 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 155 156 // Changes to stop immediately when the mouse is hovered, and 157 // doesn't change when the mouse exits. 158 MouseEnter(); 159 EXPECT_TRUE(IsMouseInside()); 160 [button_ setIsLoading:YES force:NO]; 161 EXPECT_EQ(IDC_STOP, [button_ tag]); 162 MouseExit(); 163 EXPECT_FALSE(IsMouseInside()); 164 EXPECT_EQ(IDC_STOP, [button_ tag]); 165 166 // Does not change to reload immediately when the mouse is hovered, 167 // changes when the mouse exits. 168 MouseEnter(); 169 EXPECT_TRUE(IsMouseInside()); 170 [button_ setIsLoading:NO force:NO]; 171 EXPECT_EQ(IDC_STOP, [button_ tag]); 172 MouseExit(); 173 EXPECT_FALSE(IsMouseInside()); 174 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 175 } 176 177 // Test that without force, stop mode is set immediately, and reload 178 // will be set after a timeout. 179 // TODO(shess): Reenable, http://crbug.com/61485 180 TEST_F(ReloadButtonTest, DISABLED_SetIsLoadingNoForceTimeout) { 181 // When the event loop first spins, some delayed tracking-area setup 182 // is done, which causes -mouseExited: to be called. Spin it at 183 // least once, and dequeue any pending events. 184 // TODO(shess): It would be more reasonable to have an MockNSTimer 185 // factory for the class to use, which this code could fire 186 // directly. 187 while ([NSApp nextEventMatchingMask:NSAnyEventMask 188 untilDate:nil 189 inMode:NSDefaultRunLoopMode 190 dequeue:YES]) { 191 } 192 193 const NSTimeInterval kShortTimeout = 0.1; 194 [ReloadButton setPendingReloadTimeout:kShortTimeout]; 195 196 EXPECT_FALSE(IsMouseInside()); 197 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 198 199 // Move the mouse into the button and press it. 200 MouseEnter(); 201 EXPECT_TRUE(IsMouseInside()); 202 [button_ setIsLoading:YES force:NO]; 203 EXPECT_EQ(IDC_STOP, [button_ tag]); 204 205 // Does not change to reload immediately when the mouse is hovered. 206 EXPECT_TRUE(IsMouseInside()); 207 [button_ setIsLoading:NO force:NO]; 208 EXPECT_TRUE(IsMouseInside()); 209 EXPECT_EQ(IDC_STOP, [button_ tag]); 210 EXPECT_TRUE(IsMouseInside()); 211 212 // Spin event loop until the timeout passes. 213 NSDate* pastTimeout = [NSDate dateWithTimeIntervalSinceNow:2 * kShortTimeout]; 214 [NSApp nextEventMatchingMask:NSAnyEventMask 215 untilDate:pastTimeout 216 inMode:NSDefaultRunLoopMode 217 dequeue:NO]; 218 219 // Mouse is still hovered, button is in reload mode. If the mouse 220 // is no longer hovered, see comment at top of function. 221 EXPECT_TRUE(IsMouseInside()); 222 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 223 } 224 225 // Test that pressing stop after reload mode has been requested 226 // doesn't forward the stop message. 227 TEST_F(ReloadButtonTest, StopAfterReloadSet) { 228 id mock_target = [OCMockObject mockForProtocol:@protocol(TargetActionMock)]; 229 [button_ setTarget:mock_target]; 230 [button_ setAction:@selector(anAction:)]; 231 232 EXPECT_FALSE(IsMouseInside()); 233 234 // Get to stop mode. 235 [button_ setIsLoading:YES force:YES]; 236 EXPECT_EQ(IDC_STOP, [button_ tag]); 237 EXPECT_TRUE([button_ isEnabled]); 238 239 // Expect the action once. 240 [[mock_target expect] anAction:button_]; 241 242 // Clicking in stop mode should send the action and transition to 243 // reload mode. 244 const std::pair<NSEvent*,NSEvent*> click = 245 cocoa_test_event_utils::MouseClickInView(button_, 1); 246 [NSApp postEvent:click.second atStart:YES]; 247 [button_ mouseDown:click.first]; 248 EXPECT_EQ(IDC_RELOAD, [button_ tag]); 249 EXPECT_TRUE([button_ isEnabled]); 250 251 // Get back to stop mode. 252 [button_ setIsLoading:YES force:YES]; 253 EXPECT_EQ(IDC_STOP, [button_ tag]); 254 EXPECT_TRUE([button_ isEnabled]); 255 256 // If hover prevented reload mode immediately taking effect, clicks should do 257 // nothing, because the button should be disabled. 258 MouseEnter(); 259 EXPECT_TRUE(IsMouseInside()); 260 [button_ setIsLoading:NO force:NO]; 261 EXPECT_EQ(IDC_STOP, [button_ tag]); 262 EXPECT_FALSE([button_ isEnabled]); 263 [NSApp postEvent:click.second atStart:YES]; 264 [button_ mouseDown:click.first]; 265 EXPECT_EQ(IDC_STOP, [button_ tag]); 266 267 [button_ setTarget:nil]; 268 } 269 270 } // namespace 271